hw/arm: add experimental iPhone 5 A6 platform

This commit is contained in:
2026-08-31 02:16:31 +02:00
parent cf256aa081
commit 47977dd34a
34 changed files with 4524 additions and 0 deletions
+7
View File
@@ -1,5 +1,12 @@
/GNUmakefile
/build/
/target/
/firmware/
/windows/QemuA6Ude/build/
/windows/QemuA6Ude/cert/
/windows/QemuA6Ude/package/
/windows/QemuA6UsbBridge/build/
/windows/QemuA6Ude/*.log
/.cache/
/.vscode/
*.pyc
+111
View File
@@ -0,0 +1,111 @@
# iPhone 5 / Apple A6 sur QEMU Windows natif
## État actuel
- QEMU `arm-softmmu` est compilé en PE Win64 natif avec MSYS2 MinGW64. WSL nest pas utilisé.
- La machine `iphone5` exécute la SecureROM A6 `iBoot-1145.3` de 65 536 octets.
- Les fusibles exposent `CPID:8950`, `CPRV:20` et `CPFM:03`.
- LUART A6 est relié à la console QEMU (`Ctrl+A`, puis `C` pour le moniteur ; `Ctrl+A`, puis `X` pour quitter).
- Le contrôleur USB OTG, son EP0, les DMA et lIRQ 11 permettent à la SecureROM de terminer son énumération DFU.
- Le descripteur produit par cette SecureROM annonce `VID=05AC, PID=1229`. La façade Windows le normalise vers le PID DFU public `1227`, attendu par `libirecovery`.
- Le pilote KMDF/UDE `QemuA6Ude.sys` crée un véritable enfant PnP `USB\VID_05AC&PID_1227` sous Windows.
- `QemuA6UsbBridge.exe` relaie les contrôles DFU entre le pilote et QEMU sur `127.0.0.1:26050`.
- Un test externe `DFU_GETSTATE` a traversé le socket, le modèle OTG et la SecureROM, puis a renvoyé `dfuIDLE` (`2`).
- `idevicerestore.exe` et `libirecovery` restent dorigine et ne sont pas patchés.
Le paquet pilote signé en mode test est généré localement dans
`windows\QemuA6Ude\package` ; les clés, certificats et binaires générés ne sont
pas versionnés.
## Construction Windows native
QEMU se configure depuis un terminal MSYS2 MinGW64 :
```sh
./configure --target-list=arm-softmmu
ninja -C build qemu-system-arm.exe
```
Le pilote UDE nécessite Visual Studio 2022 Build Tools et le WDK avec UDE. Si
le WDK n'est pas installé à son emplacement standard, définir
`QA6_WDK_ROOT` vers sa racine (celle qui contient `Include`, `bin` et `tools`).
Depuis PowerShell :
```powershell
.\windows\QemuA6Ude\prepare-package.ps1
.\windows\QemuA6UsbBridge\build-bridge.ps1 -Configuration Release
```
## Première installation du pilote
La signature dun pilote noyau local exige le mode de signature test Windows. Cette opération modifie le BCD et demande un redémarrage. Depuis un PowerShell administrateur :
```powershell
.\windows\QemuA6Ude\enable-test-mode.ps1
Restart-Computer
```
Après le redémarrage, toujours en administrateur :
```powershell
.\windows\QemuA6Ude\install-driver.ps1
```
Si Secure Boot bloque `testsigning`, il faut le désactiver dans lUEFI. Le script dinstallation importe uniquement le certificat public de test puis crée le contrôleur racine `Root\QemuA6Ude` avec `devcon`.
## Lancement normal
Depuis PowerShell à la racine :
```powershell
.\Run-iPhone5.ps1 -SkipBuild
```
Le mode USB Windows est actif par défaut. Le lanceur démarre le pont en arrière-plan, garde lUART dans la console et écrit les échanges USB dans `build\a6-usb-bridge.log`.
Vérification dans un second PowerShell :
```powershell
pnputil /enum-devices /connected /class USB
& 'C:\msys64\mingw64\bin\idevicerestore.exe' -d 'iPhone_4.0_32bit_10.3.4_14G61_Restore.ipsw'
```
Pour conserver lancien injecteur DFU interne, fournir directement une image :
```powershell
.\Run-iPhone5.ps1 -DfuImagePath '.\build\firmware\iBSS.iphone5.RELEASE.dfu' -Trace
```
Pour démarrer sans façade USB Windows :
```powershell
.\Run-iPhone5.ps1 -NoWindowsUsb
```
## Prochain jalon
Une fois l’énumération PnP confirmée, lIPSW iOS 10.3.4 sera envoyé par l`idevicerestore` original. La SecureROM vérifiera liBSS ; après son exécution, il faudra modéliser les périphériques supplémentaires utilisés par iBSS/iBEC et gérer sa réénumération USB Recovery pour atteindre iBoot.
## Reprise sur macOS
Le fork QEMU et linjecteur DFU interne sont portables. Sur macOS, installez les
dépendances QEMU, configurez `arm-softmmu`, puis compilez :
```sh
./configure --target-list=arm-softmmu
ninja -C build qemu-system-arm
```
Placez ensuite le dump dans `firmware/s5l8950x-secure-rom.bin`, puis lancez :
```sh
./Run-iPhone5-macOS.sh
```
Pour charger liBSS extrait de lIPSW sans dépendre dun périphérique USB hôte :
```sh
DFU_IMAGE="$PWD/firmware/iBSS.n41.RELEASE.dfu" ./Run-iPhone5-macOS.sh
```
La façade UDE est propre à Windows. Faire apparaître le téléphone comme un vrai périphérique USB sur macOS demanderait une extension DriverKit signée ; ce nest donc pas un raccourci par rapport au chemin UDE déjà construit.
+47
View File
@@ -0,0 +1,47 @@
#!/bin/sh
set -eu
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
qemu_bin=${QEMU_BIN:-"$script_dir/build/qemu-system-arm"}
rom_path=${ROM_PATH:-"$script_dir/firmware/s5l8950x-secure-rom.bin"}
dfu_image=${DFU_IMAGE:-}
if [ ! -x "$qemu_bin" ]; then
echo "QEMU introuvable: $qemu_bin" >&2
echo "Configurez ce dépôt avec --target-list=arm-softmmu puis compilez-le." >&2
exit 1
fi
if [ ! -f "$rom_path" ]; then
echo "SecureROM introuvable: $rom_path" >&2
echo "Placez votre dump A6 de 65536 octets dans firmware/s5l8950x-secure-rom.bin." >&2
exit 1
fi
set -- \
-machine iphone5 \
-bios "$rom_path" \
-no-reboot \
-no-shutdown \
-display none \
-serial mon:stdio
if [ -n "$dfu_image" ]; then
if [ ! -f "$dfu_image" ]; then
echo "Image DFU introuvable: $dfu_image" >&2
exit 1
fi
set -- "$@" -global "s5l8950x-usb-otg.dfu-image=$dfu_image"
fi
echo "QEMU macOS : $qemu_bin"
echo "SecureROM : $rom_path"
if [ -n "$dfu_image" ]; then
echo "Image DFU : $dfu_image"
echo "Injection DFU interne active."
else
echo "La SecureROM attend en dfuIDLE."
fi
echo "UART A6 : cette console (Ctrl+A C = moniteur, Ctrl+A X = quitter)"
exec "$qemu_bin" "$@"
+171
View File
@@ -0,0 +1,171 @@
[CmdletBinding()]
param(
[string]$RomPath = (Join-Path $PSScriptRoot 'firmware\s5l8950x-secure-rom.bin'),
[Alias('IbootPath')]
[string]$DfuImagePath,
[string]$MsysRoot = 'C:\msys64',
[ValidateRange(1024, 65535)]
[int]$UsbBridgePort = 26050,
[switch]$NoWindowsUsb,
[switch]$SkipBuild,
[switch]$Trace,
[switch]$Paused
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
if ([Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT) {
throw 'Ce lanceur requiert Windows natif.'
}
$qemuSource = $PSScriptRoot
$buildDir = Join-Path $qemuSource 'build'
$mingwBin = Join-Path $MsysRoot 'mingw64\bin'
$msysBin = Join-Path $MsysRoot 'usr\bin'
$ninja = Join-Path $mingwBin 'ninja.exe'
$qemu = Join-Path $buildDir 'qemu-system-arm.exe'
$buildTemp = Join-Path $buildDir 'tmp'
$bridge = Join-Path $PSScriptRoot 'windows\QemuA6UsbBridge\build\Release\x64\QemuA6UsbBridge.exe'
$bridgeBuild = Join-Path $PSScriptRoot 'windows\QemuA6UsbBridge\build-bridge.ps1'
$bridgeStdout = Join-Path $buildDir 'a6-usb-bridge.log'
$bridgeStderr = Join-Path $buildDir 'a6-usb-bridge-error.log'
if (-not (Test-Path -LiteralPath $RomPath -PathType Leaf)) {
throw "SecureROM introuvable : $RomPath"
}
$rom = Get-Item -LiteralPath $RomPath
if ($rom.Length -ne 0x10000) {
throw "La SecureROM doit faire exactement 65536 octets (taille actuelle : $($rom.Length))."
}
$dfuImage = $null
if ($DfuImagePath) {
if (-not (Test-Path -LiteralPath $DfuImagePath -PathType Leaf)) {
throw "Image DFU/iBSS introuvable : $DfuImagePath"
}
$dfuImage = Get-Item -LiteralPath $DfuImagePath
if ($dfuImage.Length -eq 0) {
throw "L'image DFU/iBSS est vide : $($dfuImage.FullName)"
}
}
$windowsUsb = -not $NoWindowsUsb -and $null -eq $dfuImage
$env:Path = "$mingwBin;$msysBin;$env:Path"
if (-not $SkipBuild) {
if (-not (Test-Path -LiteralPath $ninja -PathType Leaf)) {
throw "Ninja MinGW64 introuvable : $ninja"
}
if (-not (Test-Path -LiteralPath (Join-Path $msysBin 'sort.exe') -PathType Leaf)) {
throw "Les outils MSYS2 sont incomplets : $msysBin"
}
if (-not (Test-Path -LiteralPath (Join-Path $buildDir 'build.ninja') -PathType Leaf)) {
throw "Le dossier qemu\build n'est pas configure. Configurez QEMU depuis MSYS2 MinGW64 avec --target-list=arm-softmmu."
}
[IO.Directory]::CreateDirectory($buildTemp) | Out-Null
$env:TMP = $buildTemp
$env:TEMP = $buildTemp
$env:TMPDIR = $buildTemp
& $ninja -C $buildDir qemu-system-arm.exe
if ($LASTEXITCODE -ne 0) {
throw "La compilation QEMU a echoue avec le code $LASTEXITCODE."
}
}
if (-not (Test-Path -LiteralPath $qemu -PathType Leaf)) {
throw "Binaire QEMU introuvable : $qemu"
}
$machines = & $qemu -machine help 2>&1
if ($LASTEXITCODE -ne 0 -or -not ($machines -match '^iphone5\s')) {
throw 'Ce binaire QEMU ne contient pas la machine iphone5.'
}
$qemuArgs = @(
'-machine', 'iphone5',
'-bios', $rom.FullName,
'-no-reboot',
'-no-shutdown'
)
if ($dfuImage) {
$qemuArgs += @(
'-global',
"s5l8950x-usb-otg.dfu-image=$($dfuImage.FullName)"
)
}
if ($windowsUsb) {
$qemuArgs += @(
'-chardev', "socket,id=a6usb,host=127.0.0.1,port=$UsbBridgePort,server=on,wait=off",
'-global', 's5l8950x-usb-otg.usb-bridge=a6usb'
)
}
if ($Paused) {
$qemuArgs += @('-S', '-display', 'none', '-serial', 'null', '-monitor', 'stdio')
} else {
# UART0 is connected explicitly to this native Windows console. The
# monitor is multiplexed on the same chardev (Ctrl+A C toggles it).
$qemuArgs += @('-display', 'none', '-serial', 'mon:stdio')
}
if ($Trace) {
$tracePath = Join-Path $buildDir 'a6-boot.log'
$qemuArgs += @(
'-d', 'in_asm,cpu_reset,guest_errors,unimp',
'-D', $tracePath
)
Write-Host "Trace QEMU : $tracePath"
}
Write-Host "QEMU Windows natif : $qemu"
Write-Host "SecureROM : $($rom.FullName)"
if ($dfuImage) {
Write-Host "Image DFU/iBSS : $($dfuImage.FullName)"
}
if ($windowsUsb) {
if (-not (Test-Path -LiteralPath $bridge -PathType Leaf)) {
& $bridgeBuild -Configuration Release
}
Write-Host 'USB Windows : Apple DFU VID_05AC&PID_1227 via UDE'
Write-Host "Pont QEMU : 127.0.0.1:$UsbBridgePort"
Write-Host "Journal pont USB : $bridgeStdout"
}
if (-not $Paused) {
if ($dfuImage) {
Write-Host 'Le cable USB et le transfert DFU sont simules en interne.'
} elseif ($windowsUsb) {
Write-Host 'Windows voit le téléphone DFU virtuel ; idevicerestore reste inchangé.'
} else {
Write-Host 'Recovery et cable USB sont simulés : la ROM attend en mode DFU.'
}
Write-Host 'UART A6 : cette console (Ctrl+A C = moniteur)'
Write-Host 'Utilisez Ctrl+A puis X pour quitter.'
}
$bridgeProcess = $null
$exitCode = 1
try {
if ($windowsUsb) {
$bridgeProcess = Start-Process -FilePath $bridge `
-ArgumentList @('127.0.0.1', $UsbBridgePort.ToString()) `
-WindowStyle Hidden -PassThru `
-RedirectStandardOutput $bridgeStdout `
-RedirectStandardError $bridgeStderr
}
& $qemu @qemuArgs
$exitCode = $LASTEXITCODE
} finally {
if ($null -ne $bridgeProcess -and -not $bridgeProcess.HasExited) {
Stop-Process -Id $bridgeProcess.Id
$bridgeProcess.WaitForExit()
}
}
exit $exitCode
+8
View File
@@ -51,6 +51,14 @@ config DIGIC
select PTIMER
select PFLASH_CFI02
config S5L8950X
bool
default y
depends on TCG && ARM
select S5L8950X_GPIO
select S5L8950X_USB_PHY
select UNIMP
config EXYNOS4
bool
default y
+1
View File
@@ -21,6 +21,7 @@ arm_common_ss.add(when: 'CONFIG_SABRELITE', if_true: files('sabrelite.c'))
arm_common_ss.add(when: 'CONFIG_ARM_V7M', if_true: files('armv7m.c'))
arm_common_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210.c'))
arm_common_ss.add(when: 'CONFIG_DIGIC', if_true: files('digic.c'))
arm_common_ss.add(when: 'CONFIG_S5L8950X', if_true: files('s5l8950x.c'))
arm_common_ss.add(when: 'CONFIG_OMAP', if_true: files('omap1.c'))
arm_common_ss.add(when: 'CONFIG_ALLWINNER_A10', if_true: files('allwinner-a10.c', 'cubieboard.c'))
arm_common_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-h3.c', 'orangepi.c'))
+197
View File
@@ -0,0 +1,197 @@
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "hw/arm/s5l8950x.h"
#include "hw/arm/machines-qom.h"
#include "hw/core/boards.h"
#include "hw/core/loader.h"
#include "hw/misc/unimp.h"
#include "hw/core/sysbus.h"
#include "system/address-spaces.h"
#include "target/arm/cpu.h"
#include "qemu/units.h"
#include "qom/object.h"
#include "hw/arm/boot.h"
#include "system/system.h"
#include "target/arm/cpregs.h"
/*
* Swift has implementation-defined CP15 controls which the SecureROM uses
* while bringing caches and the memory system up. QEMU does not model
* those microarchitectural details, so the matching control is write-ignore.
*/
static const ARMCPRegInfo s5l8950x_cp_reginfo[] = {
{
.name = "APPLE_SWIFT_CFG",
.cp = 15,
.opc1 = 1,
.crn = 15,
.crm = 2,
.opc2 = 0,
.access = PL1_W,
.type = ARM_CP_NOP,
},
};
/* Addresses */
#define S5L8950X_SECUREROM_BASE 0x00000000
#define S5L8950X_SECUREROM_SIZE 0x10000
#define S5L8950X_SRAM_BASE 0x10000000
#define S5L8950X_SRAM_SIZE 0x80000
#define S5L8950X_SDIO_BASE 0x20000000
#define S5L8950X_USB_PHY_BASE 0x36000000
#define S5L8950X_USB_BASE 0x36100000
#define S5L8950X_SHA1_BASE 0x3C500000
#define S5L8950X_SPI_BASE 0x3D200000
#define S5L8950X_AES_BASE 0x3E000000
#define S5L8950X_IIC_BASE 0x3E100000
#define S5L8950X_PKE_BASE 0x3E200000
#define S5L8950X_WDT_BASE 0x3E300000
#define S5L8950X_CHIPID_BASE 0x3F500000
#define S5L8950X_TIMER_BASE 0x3F200000
#define S5L8950X_PMGR_BASE 0x3F100000
#define S5L8950X_AMC_BASE 0x40000000
#define S5L8950X_AIC_BASE 0x3F200000
#define S5L8950X_UART_BASE 0x44300000
#define S5L8950X_GPIO_BASE 0x3FA00000
#define S5L8950X_DRAM_BASE 0x80000000
static void s5l8950x_init(Object *obj)
{
S5L8950XState *s = S5L8950X(obj);
object_initialize_child(obj, "cpu", &s->cpu, ARM_CPU_TYPE_NAME("cortex-a9"));
define_arm_cp_regs(&s->cpu, s5l8950x_cp_reginfo);
}
static void s5l8950x_realize(DeviceState *dev, Error **errp)
{
S5L8950XState *s = S5L8950X(dev);
Error *err = NULL;
/* Realize CPU */
if (!qdev_realize(DEVICE(&s->cpu), NULL, &err)) {
error_propagate(errp, err);
return;
}
/* SRAM */
memory_region_init_ram(&s->sram, OBJECT(s), "s5l8950x.sram", S5L8950X_SRAM_SIZE, &error_fatal);
memory_region_add_subregion(get_system_memory(), S5L8950X_SRAM_BASE, &s->sram);
/* SecureROM */
memory_region_init_rom(&s->securerom, OBJECT(s), "s5l8950x.securerom", S5L8950X_SECUREROM_SIZE, &error_fatal);
memory_region_add_subregion(get_system_memory(), S5L8950X_SECUREROM_BASE, &s->securerom);
/* Create unimplemented devices */
create_unimplemented_device("s5l8950x.sdio", S5L8950X_SDIO_BASE, 0x10000);
create_unimplemented_device("s5l8950x.sha1", S5L8950X_SHA1_BASE, 0x1000);
create_unimplemented_device("s5l8950x.spi", S5L8950X_SPI_BASE, 0x1000);
create_unimplemented_device("s5l8950x.aes", S5L8950X_AES_BASE, 0x1000);
create_unimplemented_device("s5l8950x.iic", S5L8950X_IIC_BASE, 0x1000);
create_unimplemented_device("s5l8950x.pke", S5L8950X_PKE_BASE, 0x1000);
create_unimplemented_device("s5l8950x.wdt", S5L8950X_WDT_BASE, 0x1000);
create_unimplemented_device("s5l8950x.amc", S5L8950X_AMC_BASE, 0x1000);
/* Realize custom devices (assumes stubs will be created) */
DeviceState *dev_chipid = qdev_new(TYPE_S5L8950X_CHIPID);
s->chipid = SYS_BUS_DEVICE(dev_chipid);
sysbus_realize_and_unref(s->chipid, &error_fatal);
sysbus_mmio_map(s->chipid, 0, S5L8950X_CHIPID_BASE);
DeviceState *dev_aic = qdev_new(TYPE_S5L8950X_AIC);
s->aic = SYS_BUS_DEVICE(dev_aic);
sysbus_realize_and_unref(s->aic, &error_fatal);
sysbus_mmio_map(s->aic, 0, S5L8950X_AIC_BASE);
sysbus_connect_irq(s->aic, 0,
qdev_get_gpio_in(DEVICE(&s->cpu), ARM_CPU_IRQ));
sysbus_connect_irq(s->aic, 1,
qdev_get_gpio_in(DEVICE(&s->cpu), ARM_CPU_FIQ));
DeviceState *dev_uart = qdev_new(TYPE_S5L8950X_UART);
qdev_prop_set_chr(dev_uart, "chardev", serial_hd(0));
s->uart = SYS_BUS_DEVICE(dev_uart);
sysbus_realize_and_unref(s->uart, &error_fatal);
sysbus_mmio_map(s->uart, 0, S5L8950X_UART_BASE);
DeviceState *dev_pmgr = qdev_new(TYPE_S5L8950X_PMGR);
s->pmgr = SYS_BUS_DEVICE(dev_pmgr);
sysbus_realize_and_unref(s->pmgr, &error_fatal);
sysbus_mmio_map(s->pmgr, 0, S5L8950X_PMGR_BASE);
DeviceState *dev_gpio = qdev_new(TYPE_S5L8950X_GPIO);
qdev_prop_set_bit(dev_gpio, "force-dfu", true);
s->gpio = SYS_BUS_DEVICE(dev_gpio);
sysbus_realize_and_unref(s->gpio, &error_fatal);
sysbus_mmio_map(s->gpio, 0, S5L8950X_GPIO_BASE);
DeviceState *dev_usb_phy = qdev_new(TYPE_S5L8950X_USB_PHY);
qdev_prop_set_bit(dev_usb_phy, "cable-connected", true);
s->usb_phy = SYS_BUS_DEVICE(dev_usb_phy);
sysbus_realize_and_unref(s->usb_phy, &error_fatal);
sysbus_mmio_map(s->usb_phy, 0, S5L8950X_USB_PHY_BASE);
DeviceState *dev_usb_otg = qdev_new(TYPE_S5L8950X_USB_OTG);
s->usb_otg = SYS_BUS_DEVICE(dev_usb_otg);
sysbus_realize_and_unref(s->usb_otg, &error_fatal);
sysbus_mmio_map(s->usb_otg, 0, S5L8950X_USB_BASE);
sysbus_connect_irq(s->usb_otg, 0,
qdev_get_gpio_in(dev_aic, 11));
}
static void s5l8950x_class_init(ObjectClass *oc, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(oc);
dc->realize = s5l8950x_realize;
}
static const TypeInfo s5l8950x_type_info = {
.name = TYPE_S5L8950X,
.parent = TYPE_DEVICE,
.instance_size = sizeof(S5L8950XState),
.instance_init = s5l8950x_init,
.class_init = s5l8950x_class_init,
};
static void iphone5_machine_init(MachineState *machine)
{
S5L8950XState *s;
s = S5L8950X(object_new(TYPE_S5L8950X));
object_property_add_child(OBJECT(machine), "soc", OBJECT(s));
qdev_realize(DEVICE(s), NULL, &error_fatal);
/* Map DRAM */
memory_region_add_subregion(get_system_memory(), S5L8950X_DRAM_BASE, machine->ram);
/* Load BIOS (SecureROM) */
if (machine->firmware) {
load_image_targphys(machine->firmware, S5L8950X_SECUREROM_BASE,
S5L8950X_SECUREROM_SIZE, &error_fatal);
}
}
static void iphone5_machine_class_init(ObjectClass *oc, const void *data)
{
MachineClass *mc = MACHINE_CLASS(oc);
mc->desc = "Apple iPhone 5 (S5L8950X / A6)";
mc->init = iphone5_machine_init;
mc->default_ram_size = 1 * GiB;
mc->default_ram_id = "dram";
mc->ignore_memory_transaction_failures = true;
}
static const TypeInfo iphone5_machine_type_info = {
.name = MACHINE_TYPE_NAME("iphone5"),
.parent = TYPE_MACHINE,
.class_init = iphone5_machine_class_init,
.interfaces = arm_machine_interfaces,
};
static void s5l8950x_register_types(void)
{
type_register_static(&s5l8950x_type_info);
type_register_static(&iphone5_machine_type_info);
}
type_init(s5l8950x_register_types)
+1
View File
@@ -26,6 +26,7 @@ system_ss.add(when: 'CONFIG_DIVA_GSP', if_true: files('diva-gsp.c'))
system_ss.add(when: 'CONFIG_AVR_USART', if_true: files('avr_usart.c'))
system_ss.add(when: 'CONFIG_COLDFIRE', if_true: files('mcf_uart.c'))
system_ss.add(when: 'CONFIG_DIGIC', if_true: files('digic-uart.c'))
system_ss.add(when: 'CONFIG_S5L8950X', if_true: files('s5l8950x-uart.c'))
system_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_uart.c'))
system_ss.add(when: 'CONFIG_MAX78000_UART', if_true: files('max78000_uart.c'))
system_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_uart.c'))
+239
View File
@@ -0,0 +1,239 @@
#include "qemu/osdep.h"
#include "hw/core/sysbus.h"
#include "hw/core/irq.h"
#include "hw/core/qdev-properties-system.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "chardev/char-fe.h"
#define TYPE_S5L8950X_UART "s5l8950x-uart"
OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XUartState, S5L8950X_UART)
struct S5L8950XUartState {
SysBusDevice parent_obj;
MemoryRegion iomem;
CharFrontend chr;
uint32_t ulcon;
uint32_t ucon;
uint32_t ufcon;
uint32_t umcon;
uint32_t ubrdiv;
uint32_t ufracval;
uint8_t rx_fifo[16];
int rx_count;
qemu_irq irq;
};
#define UART_ULCON 0x00
#define UART_UCON 0x04
#define UART_UFCON 0x08
#define UART_UMCON 0x0C
#define UART_UTRSTAT 0x10
#define UART_UERSTAT 0x14
#define UART_UFSTAT 0x18
#define UART_UMSTAT 0x1C
#define UART_UTXH 0x20
#define UART_URXH 0x24
#define UART_UBRDIV 0x28
#define UART_UFRACVAL 0x2C
static void s5l8950x_uart_update_irq(S5L8950XUartState *s)
{
if (s->rx_count > 0) {
qemu_irq_raise(s->irq);
} else {
qemu_irq_lower(s->irq);
}
}
static uint64_t s5l8950x_uart_read(void *opaque, hwaddr offset, unsigned size)
{
S5L8950XUartState *s = S5L8950X_UART(opaque);
uint32_t val = 0;
switch (offset) {
case UART_ULCON:
val = s->ulcon;
break;
case UART_UCON:
val = s->ucon;
break;
case UART_UFCON:
val = s->ufcon;
break;
case UART_UMCON:
val = s->umcon;
break;
case UART_UTRSTAT:
/* Bit 0: rx ready, Bit 1: tx buf empty, Bit 2: tx empty */
val = 0x6 | (s->rx_count > 0 ? 1 : 0);
break;
case UART_UERSTAT:
val = 0;
break;
case UART_UFSTAT:
/* Return rx count in low bits, no tx fifo used here */
val = s->rx_count & 0xf;
break;
case UART_UMSTAT:
val = 0;
break;
case UART_UTXH:
break;
case UART_URXH:
if (s->rx_count > 0) {
val = s->rx_fifo[0];
s->rx_count--;
memmove(s->rx_fifo, s->rx_fifo + 1, s->rx_count);
s5l8950x_uart_update_irq(s);
}
break;
case UART_UBRDIV:
val = s->ubrdiv;
break;
case UART_UFRACVAL:
val = s->ufracval;
break;
default:
qemu_log_mask(LOG_UNIMP, "%s: Unimplemented read at offset 0x%" HWADDR_PRIx "\n",
__func__, offset);
break;
}
return val;
}
static void s5l8950x_uart_write(void *opaque, hwaddr offset, uint64_t val, unsigned size)
{
S5L8950XUartState *s = S5L8950X_UART(opaque);
unsigned char c;
switch (offset) {
case UART_ULCON:
s->ulcon = val;
break;
case UART_UCON:
s->ucon = val;
break;
case UART_UFCON:
s->ufcon = val;
break;
case UART_UMCON:
s->umcon = val;
break;
case UART_UTRSTAT:
case UART_UERSTAT:
case UART_UFSTAT:
case UART_UMSTAT:
/* Read-only registers */
break;
case UART_UTXH:
c = val & 0xFF;
qemu_chr_fe_write_all(&s->chr, &c, 1);
break;
case UART_URXH:
break;
case UART_UBRDIV:
s->ubrdiv = val;
break;
case UART_UFRACVAL:
s->ufracval = val;
break;
default:
qemu_log_mask(LOG_UNIMP, "%s: Unimplemented write at offset 0x%" HWADDR_PRIx "\n",
__func__, offset);
break;
}
}
static const MemoryRegionOps s5l8950x_uart_ops = {
.read = s5l8950x_uart_read,
.write = s5l8950x_uart_write,
.endianness = DEVICE_NATIVE_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 4,
},
};
static int s5l8950x_uart_can_receive(void *opaque)
{
S5L8950XUartState *s = S5L8950X_UART(opaque);
return sizeof(s->rx_fifo) - s->rx_count;
}
static void s5l8950x_uart_receive(void *opaque, const uint8_t *buf, int size)
{
S5L8950XUartState *s = S5L8950X_UART(opaque);
int i;
for (i = 0; i < size && s->rx_count < sizeof(s->rx_fifo); i++) {
s->rx_fifo[s->rx_count++] = buf[i];
}
s5l8950x_uart_update_irq(s);
}
static void s5l8950x_uart_reset(DeviceState *dev)
{
S5L8950XUartState *s = S5L8950X_UART(dev);
s->ulcon = 0;
s->ucon = 0;
s->ufcon = 0;
s->umcon = 0;
s->ubrdiv = 0;
s->ufracval = 0;
s->rx_count = 0;
s5l8950x_uart_update_irq(s);
}
static void s5l8950x_uart_realize(DeviceState *dev, Error **errp)
{
S5L8950XUartState *s = S5L8950X_UART(dev);
qemu_chr_fe_set_handlers(&s->chr, s5l8950x_uart_can_receive,
s5l8950x_uart_receive, NULL, NULL,
s, NULL, true);
}
static void s5l8950x_uart_init(Object *obj)
{
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
S5L8950XUartState *s = S5L8950X_UART(obj);
memory_region_init_io(&s->iomem, obj, &s5l8950x_uart_ops, s,
TYPE_S5L8950X_UART, 0x100);
sysbus_init_mmio(sbd, &s->iomem);
sysbus_init_irq(sbd, &s->irq);
}
static const Property s5l8950x_uart_properties[] = {
DEFINE_PROP_CHR("chardev", S5L8950XUartState, chr),
};
static void s5l8950x_uart_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, s5l8950x_uart_reset);
dc->realize = s5l8950x_uart_realize;
device_class_set_props(dc, s5l8950x_uart_properties);
}
static const TypeInfo s5l8950x_uart_info = {
.name = TYPE_S5L8950X_UART,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(S5L8950XUartState),
.instance_init = s5l8950x_uart_init,
.class_init = s5l8950x_uart_class_init,
};
static void s5l8950x_uart_register_types(void)
{
type_register_static(&s5l8950x_uart_info);
}
type_init(s5l8950x_uart_register_types)
+3
View File
@@ -10,6 +10,9 @@ config GPIO_MPC8XXX
config GPIO_PWR
bool
config S5L8950X_GPIO
bool
config SIFIVE_GPIO
bool
+1
View File
@@ -1,6 +1,7 @@
system_ss.add(when: 'CONFIG_GPIO_KEY', if_true: files('gpio_key.c'))
system_ss.add(when: 'CONFIG_GPIO_MPC8XXX', if_true: files('mpc8xxx.c'))
system_ss.add(when: 'CONFIG_GPIO_PWR', if_true: files('gpio_pwr.c'))
system_ss.add(when: 'CONFIG_S5L8950X_GPIO', if_true: files('s5l8950x-gpio.c'))
system_ss.add(when: 'CONFIG_PCA9552', if_true: files('pca9552.c'))
system_ss.add(when: 'CONFIG_PCA9554', if_true: files('pca9554.c'))
system_ss.add(when: 'CONFIG_PL061', if_true: files('pl061.c'))
+119
View File
@@ -0,0 +1,119 @@
/*
* Apple S5L8950X GPIO controller (minimal SecureROM-facing model)
*
* The A6 SecureROM addresses pins as (bank << 8) | pin. Each pin maps to
* one 32-bit register at ((bank * 8) + pin) * 4. Bit 0 is the sampled input
* level; the remaining fields configure muxing and pull state.
*/
#include "qemu/osdep.h"
#include "hw/arm/s5l8950x.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/sysbus.h"
#include "qemu/log.h"
#include "qom/object.h"
#define S5L8950X_GPIO_REGION_SIZE 0x10000
#define S5L8950X_GPIO_NUM_REGS (S5L8950X_GPIO_REGION_SIZE / 4)
/* Recovery/DFU button sampled by SecureROM as GPIO 0x1906. */
#define S5L8950X_GPIO_DFU_OFFSET (((0x19 * 8) + 6) * sizeof(uint32_t))
#define S5L8950X_GPIO_INPUT_LEVEL BIT(0)
OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XGPIOState, S5L8950X_GPIO)
struct S5L8950XGPIOState {
SysBusDevice parent_obj;
MemoryRegion iomem;
uint32_t regs[S5L8950X_GPIO_NUM_REGS];
bool force_dfu;
};
static uint64_t s5l8950x_gpio_read(void *opaque, hwaddr offset,
unsigned size)
{
S5L8950XGPIOState *s = opaque;
uint32_t value;
if (offset >= S5L8950X_GPIO_REGION_SIZE) {
return 0;
}
value = s->regs[offset / sizeof(uint32_t)];
if (s->force_dfu && offset == S5L8950X_GPIO_DFU_OFFSET) {
value |= S5L8950X_GPIO_INPUT_LEVEL;
}
return value;
}
static void s5l8950x_gpio_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
S5L8950XGPIOState *s = opaque;
if (offset < S5L8950X_GPIO_REGION_SIZE) {
s->regs[offset / sizeof(uint32_t)] = value;
} else {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: invalid write at offset 0x%" HWADDR_PRIx "\n",
__func__, offset);
}
}
static const MemoryRegionOps s5l8950x_gpio_ops = {
.read = s5l8950x_gpio_read,
.write = s5l8950x_gpio_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 4,
},
.impl = {
.min_access_size = 4,
.max_access_size = 4,
},
};
static const Property s5l8950x_gpio_properties[] = {
DEFINE_PROP_BOOL("force-dfu", S5L8950XGPIOState, force_dfu, false),
};
static void s5l8950x_gpio_reset(DeviceState *dev)
{
S5L8950XGPIOState *s = S5L8950X_GPIO(dev);
memset(s->regs, 0, sizeof(s->regs));
}
static void s5l8950x_gpio_init(Object *obj)
{
S5L8950XGPIOState *s = S5L8950X_GPIO(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
memory_region_init_io(&s->iomem, obj, &s5l8950x_gpio_ops, s,
TYPE_S5L8950X_GPIO, S5L8950X_GPIO_REGION_SIZE);
sysbus_init_mmio(sbd, &s->iomem);
}
static void s5l8950x_gpio_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, s5l8950x_gpio_reset);
device_class_set_props(dc, s5l8950x_gpio_properties);
}
static const TypeInfo s5l8950x_gpio_info = {
.name = TYPE_S5L8950X_GPIO,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(S5L8950XGPIOState),
.instance_init = s5l8950x_gpio_init,
.class_init = s5l8950x_gpio_class_init,
};
static void s5l8950x_gpio_register_types(void)
{
type_register_static(&s5l8950x_gpio_info);
}
type_init(s5l8950x_gpio_register_types)
+1
View File
@@ -24,6 +24,7 @@ system_ss.add(when: 'CONFIG_GOLDFISH_PIC', if_true: files('goldfish_pic.c'))
system_ss.add(when: 'CONFIG_HEATHROW_PIC', if_true: files('heathrow_pic.c'))
system_ss.add(when: 'CONFIG_I8259', if_true: files('i8259_common.c', 'i8259.c'))
system_ss.add(when: 'CONFIG_IMX', if_true: files('imx_avic.c', 'imx_gpcv2.c'))
system_ss.add(when: 'CONFIG_S5L8950X', if_true: files('s5l8950x-aic.c'))
system_ss.add(when: 'CONFIG_IOAPIC', if_true: files('ioapic_common.c'), if_false: files('ioapic-stub.c'))
system_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_intc.c'))
system_ss.add(when: 'CONFIG_OPENPIC', if_true: files('openpic.c'))
+249
View File
@@ -0,0 +1,249 @@
/*
* Apple Interrupt Controller v2 as used by the S5L8950X.
*
* The A6 SecureROM uses the same register layout later documented for AICv2:
* event acknowledge at 0x2004, per-source destinations at 0x3000 and mask
* set/clear banks at 0x4100/0x4180.
*/
#include "qemu/osdep.h"
#include "qemu/log.h"
#include "qemu/timer.h"
#include "hw/core/sysbus.h"
#include "hw/core/irq.h"
#include "qom/object.h"
#define TYPE_S5L8950X_AIC "s5l8950x-aic"
OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XAicState, S5L8950X_AIC)
#define S5L8950X_AIC_NUM_IRQS 256
#define S5L8950X_AIC_NUM_BANKS (S5L8950X_AIC_NUM_IRQS / 32)
#define AIC_REV 0x0000
#define AIC_CAP0 0x0004
#define AIC_CAP1 0x0008
#define AIC_RST 0x000c
#define AIC_GLB_CFG 0x0010
#define AIC_TIME_LO 0x0020
#define AIC_TIME_HI 0x0028
#define AIC_WHOAMI 0x2000
#define AIC_ACK 0x2004
#define AIC_IPI_SET 0x2008
#define AIC_IPI_CLR 0x200c
#define AIC_SRC_CFG_BASE 0x3000
#define AIC_MASK_SET_BASE 0x4100
#define AIC_MASK_CLR_BASE 0x4180
#define AIC_INT_STATE_BASE 0x4200
#define AIC_EVENT_EXT 0x00010000
struct S5L8950XAicState {
SysBusDevice parent_obj;
MemoryRegion iomem;
uint32_t mask[S5L8950X_AIC_NUM_BANKS];
uint32_t pending[S5L8950X_AIC_NUM_BANKS];
uint32_t src_cfg[S5L8950X_AIC_NUM_IRQS];
uint32_t glb_cfg;
qemu_irq irq;
qemu_irq fiq;
};
static bool s5l8950x_aic_irq_is_active(S5L8950XAicState *s, int irq)
{
int bank = irq / 32;
uint32_t bit = BIT(irq % 32);
return (s->pending[bank] & bit) && !(s->mask[bank] & bit) &&
(s->src_cfg[irq] & 1);
}
static void s5l8950x_aic_update(S5L8950XAicState *s)
{
bool active = false;
int irq;
for (irq = 0; irq < S5L8950X_AIC_NUM_IRQS; irq++) {
if (s5l8950x_aic_irq_is_active(s, irq)) {
active = true;
break;
}
}
qemu_set_irq(s->irq, active);
qemu_irq_lower(s->fiq);
}
static uint32_t s5l8950x_aic_ack(S5L8950XAicState *s)
{
int irq;
for (irq = 0; irq < S5L8950X_AIC_NUM_IRQS; irq++) {
if (s5l8950x_aic_irq_is_active(s, irq)) {
s->mask[irq / 32] |= BIT(irq % 32);
s5l8950x_aic_update(s);
return AIC_EVENT_EXT | irq;
}
}
s5l8950x_aic_update(s);
return 0;
}
static void s5l8950x_aic_set_irq(void *opaque, int irq, int level)
{
S5L8950XAicState *s = opaque;
uint32_t bit = BIT(irq % 32);
if (level) {
s->pending[irq / 32] |= bit;
} else {
s->pending[irq / 32] &= ~bit;
}
s5l8950x_aic_update(s);
}
static uint64_t s5l8950x_aic_read(void *opaque, hwaddr offset, unsigned size)
{
S5L8950XAicState *s = opaque;
uint64_t ticks;
ticks = muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), 24000000,
NANOSECONDS_PER_SECOND);
switch (offset) {
case AIC_REV:
return 2;
case AIC_CAP0:
return S5L8950X_AIC_NUM_IRQS;
case AIC_CAP1:
return 0;
case AIC_RST:
return 0;
case AIC_GLB_CFG:
return s->glb_cfg;
case AIC_TIME_LO:
return (uint32_t)ticks;
case AIC_TIME_HI:
return (uint32_t)(ticks >> 32);
case AIC_WHOAMI:
return 0;
case AIC_ACK:
return s5l8950x_aic_ack(s);
case AIC_SRC_CFG_BASE ... AIC_SRC_CFG_BASE +
S5L8950X_AIC_NUM_IRQS * 4 - 4:
return s->src_cfg[(offset - AIC_SRC_CFG_BASE) / 4];
case AIC_MASK_SET_BASE ... AIC_MASK_SET_BASE +
S5L8950X_AIC_NUM_BANKS * 4 - 4:
return s->mask[(offset - AIC_MASK_SET_BASE) / 4];
case AIC_MASK_CLR_BASE ... AIC_MASK_CLR_BASE +
S5L8950X_AIC_NUM_BANKS * 4 - 4:
return s->mask[(offset - AIC_MASK_CLR_BASE) / 4];
case AIC_INT_STATE_BASE ... AIC_INT_STATE_BASE +
S5L8950X_AIC_NUM_BANKS * 4 - 4:
return s->pending[(offset - AIC_INT_STATE_BASE) / 4];
default:
qemu_log_mask(LOG_UNIMP,
"%s: unimplemented read at 0x%" HWADDR_PRIx "\n",
__func__, offset);
return 0;
}
}
static void s5l8950x_aic_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
S5L8950XAicState *s = opaque;
switch (offset) {
case AIC_RST:
memset(s->mask, 0xff, sizeof(s->mask));
memset(s->pending, 0, sizeof(s->pending));
s5l8950x_aic_update(s);
break;
case AIC_GLB_CFG:
s->glb_cfg = value;
break;
case AIC_IPI_SET:
case AIC_IPI_CLR:
break;
case AIC_SRC_CFG_BASE ... AIC_SRC_CFG_BASE +
S5L8950X_AIC_NUM_IRQS * 4 - 4:
s->src_cfg[(offset - AIC_SRC_CFG_BASE) / 4] = value;
s5l8950x_aic_update(s);
break;
case AIC_MASK_SET_BASE ... AIC_MASK_SET_BASE +
S5L8950X_AIC_NUM_BANKS * 4 - 4:
s->mask[(offset - AIC_MASK_SET_BASE) / 4] |= value;
s5l8950x_aic_update(s);
break;
case AIC_MASK_CLR_BASE ... AIC_MASK_CLR_BASE +
S5L8950X_AIC_NUM_BANKS * 4 - 4:
s->mask[(offset - AIC_MASK_CLR_BASE) / 4] &= ~value;
s5l8950x_aic_update(s);
break;
default:
qemu_log_mask(LOG_UNIMP,
"%s: unimplemented write at 0x%" HWADDR_PRIx
" = 0x%08" PRIx64 "\n",
__func__, offset, value);
break;
}
}
static const MemoryRegionOps s5l8950x_aic_ops = {
.read = s5l8950x_aic_read,
.write = s5l8950x_aic_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 4,
},
.impl = {
.min_access_size = 4,
.max_access_size = 4,
},
};
static void s5l8950x_aic_reset(DeviceState *dev)
{
S5L8950XAicState *s = S5L8950X_AIC(dev);
memset(s->mask, 0xff, sizeof(s->mask));
memset(s->pending, 0, sizeof(s->pending));
memset(s->src_cfg, 0, sizeof(s->src_cfg));
s->glb_cfg = 0;
s5l8950x_aic_update(s);
}
static void s5l8950x_aic_init(Object *obj)
{
S5L8950XAicState *s = S5L8950X_AIC(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
memory_region_init_io(&s->iomem, obj, &s5l8950x_aic_ops, s,
TYPE_S5L8950X_AIC, 0x8000);
sysbus_init_mmio(sbd, &s->iomem);
sysbus_init_irq(sbd, &s->irq);
sysbus_init_irq(sbd, &s->fiq);
qdev_init_gpio_in(DEVICE(obj), s5l8950x_aic_set_irq,
S5L8950X_AIC_NUM_IRQS);
}
static void s5l8950x_aic_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, s5l8950x_aic_reset);
}
static const TypeInfo s5l8950x_aic_info = {
.name = TYPE_S5L8950X_AIC,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(S5L8950XAicState),
.instance_init = s5l8950x_aic_init,
.class_init = s5l8950x_aic_class_init,
};
static void s5l8950x_aic_register_types(void)
{
type_register_static(&s5l8950x_aic_info);
}
type_init(s5l8950x_aic_register_types)
+1
View File
@@ -16,6 +16,7 @@ system_ss.add(when: 'CONFIG_PL310', if_true: files('arm_l2x0.c'))
system_ss.add(when: 'CONFIG_INTEGRATOR_DEBUG', if_true: files('arm_integrator_debug.c'))
system_ss.add(when: 'CONFIG_A9SCU', if_true: files('a9scu.c'))
system_ss.add(when: 'CONFIG_ARM11SCU', if_true: files('arm11scu.c'))
system_ss.add(when: 'CONFIG_S5L8950X', if_true: files('s5l8950x-chipid.c', 's5l8950x-pmgr.c'))
system_ss.add(when: 'CONFIG_ARM_V7M', if_true: files('armv7m_ras.c'))
+83
View File
@@ -0,0 +1,83 @@
#include "qemu/osdep.h"
#include "hw/core/sysbus.h"
#include "qemu/log.h"
#include "qom/object.h"
#define TYPE_S5L8950X_CHIPID "s5l8950x-chipid"
OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XChipIdState, S5L8950X_CHIPID)
struct S5L8950XChipIdState {
SysBusDevice parent_obj;
MemoryRegion iomem;
};
static uint64_t s5l8950x_chipid_read(void *opaque, hwaddr offset, unsigned size)
{
switch (offset) {
case 0x00:
/* n41ap: CPFM 0x03, SCEP 0x10, BDID 0x00, IBFL 0x00. */
return 0x200D;
case 0x04:
return 0x00;
case 0x08:
return 0xDEADBEEF;
case 0x0C:
return 0xCAFEBABE;
case 0x10:
return 0x1;
case 0x14:
return 0x1;
case 0x18:
return 0x1;
case 0x1C:
return 0x0;
case 0x20:
return 0x1;
case 0x24:
/* CPRV 0x20. Also contributes to the synthetic ECID. */
return 0x4000;
default:
return 0;
}
}
static void s5l8950x_chipid_write(void *opaque, hwaddr offset,
uint64_t val, unsigned size)
{
qemu_log_mask(LOG_UNIMP, "%s: Unimplemented write to offset 0x%" HWADDR_PRIx
" with value 0x%" PRIx64 "\n", __func__, offset, val);
}
static const MemoryRegionOps s5l8950x_chipid_ops = {
.read = s5l8950x_chipid_read,
.write = s5l8950x_chipid_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.impl = {
.min_access_size = 4,
.max_access_size = 4,
},
};
static void s5l8950x_chipid_init(Object *obj)
{
S5L8950XChipIdState *s = S5L8950X_CHIPID(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
memory_region_init_io(&s->iomem, obj, &s5l8950x_chipid_ops, s,
TYPE_S5L8950X_CHIPID, 0x100);
sysbus_init_mmio(sbd, &s->iomem);
}
static const TypeInfo s5l8950x_chipid_info = {
.name = TYPE_S5L8950X_CHIPID,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(S5L8950XChipIdState),
.instance_init = s5l8950x_chipid_init,
};
static void s5l8950x_chipid_register_types(void)
{
type_register_static(&s5l8950x_chipid_info);
}
type_init(s5l8950x_chipid_register_types)
+137
View File
@@ -0,0 +1,137 @@
#include "qemu/osdep.h"
#include "hw/core/sysbus.h"
#include "qemu/log.h"
#include "qom/object.h"
#define TYPE_S5L8950X_PMGR "s5l8950x-pmgr"
OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XPmgrState, S5L8950X_PMGR)
#define PMGR_REGION_SIZE 0x10000
#define PMGR_NUM_REGS (PMGR_REGION_SIZE / 4)
#define PMGR_GATE_BASE 0x1000
#define PMGR_GATE_COUNT 0x50
#define PMGR_GATE_END (PMGR_GATE_BASE + PMGR_GATE_COUNT * sizeof(uint32_t))
#define PMGR_DOMAIN_CTRL_STRIDE 0x18
#define PMGR_DOMAIN_COUNT 9
#define PMGR_DOMAIN_STATUS_BASE 0x2010
#define PMGR_SPECIAL_DOMAIN_CTRL 0x100
#define PMGR_SPECIAL_DOMAIN_STATUS 0x2034
#define PMGR_DOMAIN_ACTIVE BIT(30)
#define PMGR_PLL0_CTRL 0x60
#define PMGR_PLL_LOCKED BIT(29)
struct S5L8950XPmgrState {
SysBusDevice parent_obj;
MemoryRegion iomem;
uint32_t regs[PMGR_NUM_REGS];
};
static uint64_t s5l8950x_pmgr_read(void *opaque, hwaddr offset, unsigned size)
{
S5L8950XPmgrState *s = opaque;
uint32_t idx = offset / 4;
if (idx < PMGR_NUM_REGS) {
return s->regs[idx];
}
return 0;
}
static void s5l8950x_pmgr_write(void *opaque, hwaddr offset,
uint64_t val, unsigned size)
{
S5L8950XPmgrState *s = opaque;
uint32_t idx = offset / 4;
if (idx < PMGR_NUM_REGS) {
uint32_t reg = (uint32_t)val;
/*
* Gate registers expose the requested state in bits [3:0] and the
* hardware-acknowledged state in bits [7:4]. Clocks settle
* immediately in this model, so mirror the request on every write.
*/
if (offset >= PMGR_GATE_BASE && offset < PMGR_GATE_END) {
reg = (reg & ~0xf0u) | ((reg & 0xfu) << 4);
}
if (offset == PMGR_PLL0_CTRL) {
reg |= PMGR_PLL_LOCKED;
}
s->regs[idx] = reg;
/* Power-domain transitions also complete immediately. */
if (offset < PMGR_DOMAIN_CTRL_STRIDE * PMGR_DOMAIN_COUNT &&
offset % PMGR_DOMAIN_CTRL_STRIDE == 0) {
uint32_t domain = offset / PMGR_DOMAIN_CTRL_STRIDE;
uint32_t status = (PMGR_DOMAIN_STATUS_BASE / 4) + domain;
s->regs[status] = deposit32(s->regs[status], 30, 1,
!!(reg & PMGR_DOMAIN_ACTIVE));
} else if (offset == PMGR_SPECIAL_DOMAIN_CTRL) {
uint32_t status = PMGR_SPECIAL_DOMAIN_STATUS / 4;
s->regs[status] = deposit32(s->regs[status], 30, 1,
!!(reg & PMGR_DOMAIN_ACTIVE));
}
} else {
qemu_log_mask(LOG_UNIMP, "%s: Out of bounds write to offset 0x%" HWADDR_PRIx
" with value 0x%" PRIx64 "\n", __func__, offset, val);
}
}
static const MemoryRegionOps s5l8950x_pmgr_ops = {
.read = s5l8950x_pmgr_read,
.write = s5l8950x_pmgr_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.impl = {
.min_access_size = 4,
.max_access_size = 4,
},
};
static void s5l8950x_pmgr_reset(DeviceState *dev)
{
S5L8950XPmgrState *s = S5L8950X_PMGR(dev);
int i;
/* Initialize all registers to 0xF (all clocks enabled) as reasonable defaults */
for (i = 0; i < PMGR_NUM_REGS; i++) {
s->regs[i] = 0xF;
}
for (i = PMGR_GATE_BASE / 4; i < PMGR_GATE_END / 4; i++) {
s->regs[i] = 0xFF;
}
}
static void s5l8950x_pmgr_init(Object *obj)
{
S5L8950XPmgrState *s = S5L8950X_PMGR(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
memory_region_init_io(&s->iomem, obj, &s5l8950x_pmgr_ops, s,
TYPE_S5L8950X_PMGR, PMGR_REGION_SIZE);
sysbus_init_mmio(sbd, &s->iomem);
}
static void s5l8950x_pmgr_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, s5l8950x_pmgr_reset);
}
static const TypeInfo s5l8950x_pmgr_info = {
.name = TYPE_S5L8950X_PMGR,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(S5L8950XPmgrState),
.instance_init = s5l8950x_pmgr_init,
.class_init = s5l8950x_pmgr_class_init,
};
static void s5l8950x_pmgr_register_types(void)
{
type_register_static(&s5l8950x_pmgr_info);
}
type_init(s5l8950x_pmgr_register_types)
+1
View File
@@ -10,6 +10,7 @@ system_ss.add(when: 'CONFIG_CMSDK_APB_TIMER', if_true: files('cmsdk-apb-timer.c'
system_ss.add(when: 'CONFIG_RENESAS_TMR', if_true: files('renesas_tmr.c'))
system_ss.add(when: 'CONFIG_RENESAS_CMT', if_true: files('renesas_cmt.c'))
system_ss.add(when: 'CONFIG_DIGIC', if_true: files('digic-timer.c'))
system_ss.add(when: 'CONFIG_S5L8950X', if_true: files('s5l8950x-timer.c'))
system_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_mct.c'))
system_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_pwm.c'))
system_ss.add(when: 'CONFIG_GRLIB', if_true: files('grlib_gptimer.c'))
+131
View File
@@ -0,0 +1,131 @@
#include "qemu/osdep.h"
#include "qemu/log.h"
#include "qemu/timer.h"
#include "hw/core/sysbus.h"
#include "qom/object.h"
#define TYPE_S5L8950X_TIMER "s5l8950x-timer"
OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XTimerState, S5L8950X_TIMER)
struct S5L8950XTimerState {
SysBusDevice parent_obj;
MemoryRegion iomem;
uint32_t tcon[4];
uint32_t tcnt[4];
uint32_t tdata[4];
uint32_t tstat[4];
};
#define TIMER_64BIT_LO 0x20
#define TIMER_64BIT_HI 0x28
static uint64_t s5l8950x_timer_read(void *opaque, hwaddr offset, unsigned size)
{
S5L8950XTimerState *s = opaque;
uint64_t ticks;
ticks = muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), 24000000,
NANOSECONDS_PER_SECOND);
if (offset == TIMER_64BIT_LO) {
return (uint32_t)ticks;
}
if (offset == TIMER_64BIT_HI) {
return (uint32_t)(ticks >> 32);
}
switch (offset) {
case 0x00 ... 0x7C: {
int channel = offset / 0x20;
int reg = offset % 0x20;
if (channel >= 4) {
break;
}
switch (reg) {
case 0x00: return s->tcon[channel];
case 0x08: return s->tdata[channel];
case 0x10: return s->tcnt[channel];
case 0x14: return s->tstat[channel];
}
break;
}
default:
qemu_log_mask(LOG_UNIMP, "%s: Unimplemented read at offset 0x%" HWADDR_PRIx "\n", __func__, offset);
return 0;
}
qemu_log_mask(LOG_UNIMP, "%s: Unimplemented read at offset 0x%" HWADDR_PRIx "\n", __func__, offset);
return 0;
}
static void s5l8950x_timer_write(void *opaque, hwaddr offset, uint64_t val, unsigned size)
{
S5L8950XTimerState *s = opaque;
switch (offset) {
case 0x00 ... 0x7C: {
int channel = offset / 0x20;
int reg = offset % 0x20;
if (channel >= 4) {
break;
}
switch (reg) {
case 0x00: s->tcon[channel] = val; return;
case 0x08: s->tdata[channel] = val; return;
case 0x10: s->tcnt[channel] = val; return;
case 0x14: s->tstat[channel] = val; return;
case 0x04: /* TCMD */
if (val == 1) {
s->tstat[channel] &= ~1; /* Clear stop bit or whatever */
} else if (val == 2) {
/* Stop */
}
return;
}
break;
}
default:
qemu_log_mask(LOG_UNIMP, "%s: Unimplemented write at offset 0x%" HWADDR_PRIx "\n", __func__, offset);
return;
}
qemu_log_mask(LOG_UNIMP, "%s: Unimplemented write at offset 0x%" HWADDR_PRIx "\n", __func__, offset);
}
static const MemoryRegionOps s5l8950x_timer_ops = {
.read = s5l8950x_timer_read,
.write = s5l8950x_timer_write,
.endianness = DEVICE_NATIVE_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 4,
},
};
static void s5l8950x_timer_init(Object *obj)
{
S5L8950XTimerState *s = S5L8950X_TIMER(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
memory_region_init_io(&s->iomem, obj, &s5l8950x_timer_ops, s,
"s5l8950x-timer", 0x1000);
sysbus_init_mmio(sbd, &s->iomem);
}
static void s5l8950x_timer_class_init(ObjectClass *klass, const void *data)
{
}
static const TypeInfo s5l8950x_timer_info = {
.name = TYPE_S5L8950X_TIMER,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(S5L8950XTimerState),
.instance_init = s5l8950x_timer_init,
.class_init = s5l8950x_timer_class_init,
};
static void s5l8950x_timer_register_types(void)
{
type_register_static(&s5l8950x_timer_info);
}
type_init(s5l8950x_timer_register_types)
+3
View File
@@ -1,6 +1,9 @@
config USB
bool
config S5L8950X_USB_PHY
bool
config USB_UHCI
bool
default y if PCI_DEVICES
+4
View File
@@ -27,6 +27,10 @@ system_ss.add(when: 'CONFIG_USB_XHCI_NEC', if_true: files('hcd-xhci-nec.c'))
system_ss.add(when: 'CONFIG_USB_DWC2', if_true: files('hcd-dwc2.c'))
system_ss.add(when: 'CONFIG_USB_DWC3', if_true: files('hcd-dwc3.c'))
system_ss.add(when: 'CONFIG_USB_CHIPIDEA', if_true: files('chipidea.c'))
system_ss.add(when: 'CONFIG_S5L8950X_USB_PHY', if_true: files(
's5l8950x-usb-otg.c',
's5l8950x-usb-phy.c',
))
system_ss.add(when: 'CONFIG_IMX_USBPHY', if_true: files('imx-usb-phy.c'))
system_ss.add(when: 'CONFIG_VT82C686', if_true: files('vt82c686-uhci-pci.c'))
File diff suppressed because it is too large Load Diff
+107
View File
@@ -0,0 +1,107 @@
/*
* Apple S5L8950X USB PHY (minimal SecureROM-facing model)
*
* The SecureROM samples bit 0 of register 0x28 to determine whether a USB
* cable is present before honoring the recovery-button GPIO and entering DFU.
*/
#include "qemu/osdep.h"
#include "hw/arm/s5l8950x.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/sysbus.h"
#include "qom/object.h"
#define S5L8950X_USB_PHY_REGION_SIZE 0x1000
#define S5L8950X_USB_PHY_NUM_REGS \
(S5L8950X_USB_PHY_REGION_SIZE / sizeof(uint32_t))
#define S5L8950X_USB_PHY_CABLE_STATUS 0x28
#define S5L8950X_USB_PHY_CABLE_PRESENT BIT(0)
OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XUSBPHYState, S5L8950X_USB_PHY)
struct S5L8950XUSBPHYState {
SysBusDevice parent_obj;
MemoryRegion iomem;
uint32_t regs[S5L8950X_USB_PHY_NUM_REGS];
bool cable_connected;
};
static uint64_t s5l8950x_usb_phy_read(void *opaque, hwaddr offset,
unsigned size)
{
S5L8950XUSBPHYState *s = opaque;
uint32_t value = s->regs[offset / sizeof(uint32_t)];
if (offset == S5L8950X_USB_PHY_CABLE_STATUS && s->cable_connected) {
value |= S5L8950X_USB_PHY_CABLE_PRESENT;
}
return value;
}
static void s5l8950x_usb_phy_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
S5L8950XUSBPHYState *s = opaque;
s->regs[offset / sizeof(uint32_t)] = value;
}
static const MemoryRegionOps s5l8950x_usb_phy_ops = {
.read = s5l8950x_usb_phy_read,
.write = s5l8950x_usb_phy_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 4,
},
.impl = {
.min_access_size = 4,
.max_access_size = 4,
},
};
static const Property s5l8950x_usb_phy_properties[] = {
DEFINE_PROP_BOOL("cable-connected", S5L8950XUSBPHYState,
cable_connected, false),
};
static void s5l8950x_usb_phy_reset(DeviceState *dev)
{
S5L8950XUSBPHYState *s = S5L8950X_USB_PHY(dev);
memset(s->regs, 0, sizeof(s->regs));
}
static void s5l8950x_usb_phy_init(Object *obj)
{
S5L8950XUSBPHYState *s = S5L8950X_USB_PHY(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
memory_region_init_io(&s->iomem, obj, &s5l8950x_usb_phy_ops, s,
TYPE_S5L8950X_USB_PHY,
S5L8950X_USB_PHY_REGION_SIZE);
sysbus_init_mmio(sbd, &s->iomem);
}
static void s5l8950x_usb_phy_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, s5l8950x_usb_phy_reset);
device_class_set_props(dc, s5l8950x_usb_phy_properties);
}
static const TypeInfo s5l8950x_usb_phy_info = {
.name = TYPE_S5L8950X_USB_PHY,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(S5L8950XUSBPHYState),
.instance_init = s5l8950x_usb_phy_init,
.class_init = s5l8950x_usb_phy_class_init,
};
static void s5l8950x_usb_phy_register_types(void)
{
type_register_static(&s5l8950x_usb_phy_info);
}
type_init(s5l8950x_usb_phy_register_types)
+43
View File
@@ -0,0 +1,43 @@
#ifndef HW_ARM_S5L8950X_H
#define HW_ARM_S5L8950X_H
#include "qemu/osdep.h"
#include "hw/core/sysbus.h"
#include "target/arm/cpu.h"
#include "qom/object.h"
#define TYPE_S5L8950X "s5l8950x"
#define TYPE_S5L8950X_MACHINE "s5l8950x-machine"
#define TYPE_S5L8950X_CHIPID "s5l8950x-chipid"
#define TYPE_S5L8950X_AIC "s5l8950x-aic"
#define TYPE_S5L8950X_UART "s5l8950x-uart"
#define TYPE_S5L8950X_TIMER "s5l8950x-timer"
#define TYPE_S5L8950X_PMGR "s5l8950x-pmgr"
#define TYPE_S5L8950X_GPIO "s5l8950x-gpio"
#define TYPE_S5L8950X_USB_PHY "s5l8950x-usb-phy"
#define TYPE_S5L8950X_USB_OTG "s5l8950x-usb-otg"
OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XState, S5L8950X)
struct S5L8950XState {
/*< private >*/
DeviceState parent_obj;
/*< public >*/
ARMCPU cpu;
MemoryRegion securerom;
MemoryRegion sram;
SysBusDevice *chipid;
SysBusDevice *aic;
SysBusDevice *uart;
SysBusDevice *timer;
SysBusDevice *pmgr;
SysBusDevice *gpio;
SysBusDevice *usb_phy;
SysBusDevice *usb_otg;
};
#endif /* HW_ARM_S5L8950X_H */
+61
View File
@@ -0,0 +1,61 @@
; QEMU A6 USB Device Emulation controller (root-enumerated KMDF/UDE driver)
[Version]
Signature="$WINDOWS NT$"
Class=USB
ClassGuid={36FC9E60-C465-11CF-8056-444553540000}
Provider=%ProviderName%
CatalogFile=QemuA6Ude.cat
DriverVer=08/30/2026,1.0.0.0
PnpLockdown=1
[DestinationDirs]
DefaultDestDir=12
[SourceDisksNames]
1=%DiskName%,,,
[SourceDisksFiles]
QemuA6Ude.sys=1
[Manufacturer]
%ProviderName%=Models,NTamd64
[Models.NTamd64]
%DeviceDescription%=Install,Root\QemuA6Ude
[Install.NT]
CopyFiles=DriverCopyFiles
[Install.NT.HW]
AddReg=Install_AddReg.HW
Include=machine.inf
Needs=PciD3ColdSupported
[Install_AddReg.HW]
[DriverCopyFiles]
QemuA6Ude.sys
[Install.NT.Services]
AddService=QemuA6Ude,0x00000002,ServiceInstall
[ServiceInstall]
DisplayName=%ServiceDescription%
ServiceType=1
StartType=3
ErrorControl=1
ServiceBinary=%12%\QemuA6Ude.sys
Dependencies=ucx01000,udecx
[Install.NT.Wdf]
KmdfService=QemuA6Ude,KmdfSection
[KmdfSection]
KmdfLibraryVersion=1.33
[Strings]
ProviderName="QEMU A6 Project"
DiskName="QEMU A6 UDE Driver"
DeviceDescription="QEMU Apple A6 USB Device Emulator"
ServiceDescription="QEMU A6 USB Device Emulation Controller"
+88
View File
@@ -0,0 +1,88 @@
[CmdletBinding()]
param(
[ValidateSet('Debug', 'Release')]
[string]$Configuration = 'Debug',
[string]$WdkRoot
)
$ErrorActionPreference = 'Stop'
$projectDir = $PSScriptRoot
$repoRoot = (Resolve-Path (Join-Path $projectDir '..\..')).Path
. (Join-Path $projectDir 'common.ps1')
$wdkRoot = Find-Qa6WdkRoot -ExplicitRoot $WdkRoot -RepoRoot $repoRoot
$wdkVersion = Find-Qa6WdkVersion -WdkRoot $wdkRoot
$wdfVersion = '1.33'
$sdkRoot = 'C:\Program Files (x86)\Windows Kits\10'
$msvcRoot = Find-Qa6MsvcRoot
$toolBin = Join-Path $msvcRoot 'bin\Hostx64\x64'
$outputDir = Join-Path $projectDir "build\$Configuration\x64"
$objectPath = Join-Path $outputDir 'driver.obj'
$sysPath = Join-Path $outputDir 'QemuA6Ude.sys'
$pdbPath = Join-Path $outputDir 'QemuA6Ude.pdb'
$compilePdbPath = Join-Path $outputDir 'QemuA6Ude-compile.pdb'
foreach ($required in @(
(Join-Path $toolBin 'cl.exe'),
(Join-Path $toolBin 'link.exe'),
(Join-Path $wdkRoot "Include\$wdkVersion\km\ude\1.0\UdeCx.h")
)) {
if (-not (Test-Path -LiteralPath $required -PathType Leaf)) {
throw "Composant de compilation introuvable : $required"
}
}
New-Item -ItemType Directory -Force -Path $outputDir | Out-Null
$compileArgs = @(
'/nologo', '/c', '/kernel', '/Zp8', '/W4', '/WX-', '/Zi', '/Oi',
'/D_AMD64_', '/DAMD64', '/DWIN64', '/D_WIN64',
'/DNTDDI_VERSION=0x0A00000C', '/D_WIN32_WINNT=0x0A00',
'/DUNICODE', '/D_UNICODE', '/D_WDF_',
'/DKMDF_VERSION_MAJOR=1', '/DKMDF_VERSION_MINOR=33',
"/I$msvcRoot\include",
"/I$wdkRoot\Include\$wdkVersion\km",
"/I$wdkRoot\Include\$wdkVersion\shared",
"/I$sdkRoot\Include\$wdkVersion\shared",
"/I$sdkRoot\Include\$wdkVersion\um",
"/I$sdkRoot\Include\$wdkVersion\ucrt",
"/I$wdkRoot\Include\wdf\kmdf\$wdfVersion",
"/Fo$objectPath", "/Fd$compilePdbPath",
(Join-Path $projectDir 'driver.c')
)
if ($Configuration -eq 'Release') {
$compileArgs += @('/O2', '/GL')
} else {
$compileArgs += @('/Od')
}
& (Join-Path $toolBin 'cl.exe') @compileArgs
if ($LASTEXITCODE -ne 0) {
throw "La compilation du pilote a échoué ($LASTEXITCODE)."
}
$kmLib = Join-Path $wdkRoot "Lib\$wdkVersion\km\x64"
$wdfLib = Join-Path $wdkRoot "Lib\wdf\kmdf\x64\$wdfVersion"
$linkArgs = @(
'/nologo', '/driver', '/subsystem:native,10.00', '/entry:FxDriverEntry',
'/kernel', '/nodefaultlib', '/incremental:no', '/debug',
'/opt:ref', '/opt:icf', '/merge:_TEXT=.text', '/section:INIT,d',
"/out:$sysPath", "/pdb:$pdbPath",
"/libpath:$kmLib", "/libpath:$wdfLib",
(Join-Path $kmLib 'ude\1.0\udecxstub.lib'),
'usbdex.lib', 'WdmSec.lib', 'ntstrsafe.lib',
'BufferOverflowFastFailK.lib', 'ntoskrnl.lib', 'hal.lib', 'wmilib.lib',
'wdfldr.lib', 'wdfdriverentry.lib',
$objectPath
)
if ($Configuration -eq 'Release') {
$linkArgs += '/LTCG'
}
& (Join-Path $toolBin 'link.exe') @linkArgs
if ($LASTEXITCODE -ne 0) {
throw "L'édition de liens du pilote a échoué ($LASTEXITCODE)."
}
Copy-Item -LiteralPath (Join-Path $projectDir 'QemuA6Ude.inf') -Destination $outputDir -Force
Write-Host "Pilote construit : $sysPath"
+79
View File
@@ -0,0 +1,79 @@
function Find-Qa6WdkRoot {
param(
[string]$ExplicitRoot,
[string]$RepoRoot
)
$candidates = @(
$ExplicitRoot,
$env:QA6_WDK_ROOT,
(Join-Path $RepoRoot '.tools\wdk\c'),
(Join-Path (Split-Path $RepoRoot -Parent) '.research\packages\wdk-10.0.26100.6584\c'),
'C:\Program Files (x86)\Windows Kits\10'
) | Where-Object { $_ }
foreach ($candidate in $candidates) {
if (-not (Test-Path -LiteralPath $candidate -PathType Container)) {
continue
}
$udeHeader = Get-ChildItem (Join-Path $candidate 'Include') -Recurse `
-File -Filter UdeCx.h -ErrorAction SilentlyContinue | Select-Object -First 1
if ($null -ne $udeHeader) {
return (Resolve-Path $candidate).Path
}
}
throw 'WDK UDE introuvable. Définissez QA6_WDK_ROOT vers le dossier « c » du paquet Microsoft.Windows.WDK.x64.'
}
function Find-Qa6WdkVersion {
param([string]$WdkRoot)
$version = Get-ChildItem (Join-Path $WdkRoot 'Include') -Directory `
-ErrorAction Stop | Where-Object {
Test-Path -LiteralPath (Join-Path $_.FullName 'km\ude\1.0\UdeCx.h')
} | Sort-Object Name -Descending | Select-Object -First 1
if ($null -eq $version) {
throw "Aucune version UDE dans $WdkRoot"
}
return $version.Name
}
function Find-Qa6MsvcRoot {
$bases = @(
'C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC',
'C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC'
)
foreach ($base in $bases) {
if (Test-Path -LiteralPath $base -PathType Container) {
$toolset = Get-ChildItem $base -Directory | Sort-Object Name -Descending |
Where-Object {
Test-Path -LiteralPath (Join-Path $_.FullName 'bin\Hostx64\x64\cl.exe')
} | Select-Object -First 1
if ($null -ne $toolset) {
return $toolset.FullName
}
}
}
throw 'MSVC x64 de Visual Studio 2022 est introuvable.'
}
function Find-Qa6SignTool {
param(
[string]$WdkRoot,
[string]$WdkVersion
)
$candidates = @(
(Join-Path $WdkRoot "bin\$WdkVersion\x64\signtool.exe"),
(Join-Path 'C:\Program Files (x86)\Windows Kits\10' "bin\$WdkVersion\x64\signtool.exe"),
(Join-Path 'C:\Program Files\Windows Kits\10' "bin\$WdkVersion\x64\signtool.exe")
)
foreach ($candidate in $candidates) {
if (Test-Path -LiteralPath $candidate -PathType Leaf) {
return $candidate
}
}
throw "SignTool x64 $WdkVersion est introuvable."
}
+769
View File
@@ -0,0 +1,769 @@
/*
* QemuA6Ude - Windows USB Device Emulation controller for the QEMU A6 model.
*
* The UDE child is deliberately exposed as Apple's normal DFU PID (0x1227),
* which is what unmodified libirecovery/idevicerestore discovers on Windows.
* Class and vendor control transfers are relayed through \\.\QemuA6Ude0.
*/
#include <ntddk.h>
#include <wdf.h>
#include <usb.h>
#include <wdfusb.h>
#include <usbdlib.h>
#include <ude/1.0/UdeCx.h>
#include <initguid.h>
#include <usbioctl.h>
#include <wdmsec.h>
#include "../include/qemu_a6_usb_protocol.h"
#define QA6_POOL_TAG 'U6AQ'
#define QA6_DEVICE_NAME L"\\Device\\QemuA6Ude0"
#define QA6_SYMBOLIC_LINK L"\\DosDevices\\QemuA6Ude0"
#define QA6_HOST_INTERFACE_REF L"QemuA6UdeHost"
#define QA6_LANGUAGE_ID 0x0409
#define QA6_LOG_ERROR(...) \
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, \
"QemuA6Ude: " __VA_ARGS__)
#define QA6_LOG_INFO(...) \
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, \
"QemuA6Ude: " __VA_ARGS__)
typedef struct _QA6_CONTROLLER_CONTEXT {
WDFQUEUE DefaultQueue;
WDFQUEUE WaitingUserReads;
WDFSPINLOCK BridgeLock;
PUDECXUSBDEVICE_INIT ChildInit;
UDECXUSBDEVICE ChildDevice;
UDECXUSBENDPOINT ControlEndpoint;
WDFQUEUE ControlQueue;
BOOLEAN PluggedIn;
WDFREQUEST PendingUrb;
ULONG PendingRequestId;
ULONG PendingTransferLength;
BOOLEAN PendingDirectionIn;
BOOLEAN RequestFrameReady;
ULONG RequestFrameLength;
UCHAR RequestFrame[QA6_USB_MAX_FRAME_SIZE];
} QA6_CONTROLLER_CONTEXT, *PQA6_CONTROLLER_CONTEXT;
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(QA6_CONTROLLER_CONTEXT,
Qa6GetControllerContext);
DRIVER_INITIALIZE DriverEntry;
EVT_WDF_DRIVER_DEVICE_ADD Qa6EvtDeviceAdd;
EVT_WDF_DEVICE_D0_ENTRY Qa6EvtDeviceD0Entry;
EVT_WDF_DEVICE_D0_EXIT Qa6EvtDeviceD0Exit;
EVT_WDF_OBJECT_CONTEXT_CLEANUP Qa6EvtControllerCleanup;
EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL Qa6EvtControllerIoctl;
EVT_WDF_IO_QUEUE_IO_READ Qa6EvtBridgeRead;
EVT_WDF_IO_QUEUE_IO_WRITE Qa6EvtBridgeWrite;
EVT_WDF_IO_QUEUE_IO_CANCELED_ON_QUEUE Qa6EvtCanceledUserRead;
EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL Qa6EvtControlUrb;
EVT_UDECX_USB_ENDPOINT_RESET Qa6EvtEndpointReset;
EVT_UDECX_WDF_DEVICE_QUERY_USB_CAPABILITY Qa6EvtQueryUsbCapability;
static const USB_DEVICE_DESCRIPTOR Qa6DeviceDescriptor = {
sizeof(USB_DEVICE_DESCRIPTOR),
USB_DEVICE_DESCRIPTOR_TYPE,
0x0200,
0x00,
0x00,
0x00,
0x40,
0x05ac,
0x1227,
0x0000,
2,
3,
4,
1
};
static const UCHAR Qa6ConfigurationDescriptor[] = {
0x09, USB_CONFIGURATION_DESCRIPTOR_TYPE,
0x19, 0x00,
0x01,
0x01,
0x05,
0x80,
0xfa,
0x09, USB_INTERFACE_DESCRIPTOR_TYPE,
0x00,
0x00,
0x00,
0xfe,
0x01,
0x00,
0x00,
0x07, 0x21,
0x01,
0x0a, 0x00,
0x00,
0x08
};
static const UCHAR Qa6LanguageDescriptor[] = { 4, USB_STRING_DESCRIPTOR_TYPE,
0x09, 0x04 };
DECLARE_CONST_UNICODE_STRING(Qa6ManufacturerString, L"Apple Inc.");
DECLARE_CONST_UNICODE_STRING(Qa6ProductString,
L"Apple Mobile Device (DFU Mode)");
DECLARE_CONST_UNICODE_STRING(
Qa6SerialString,
L"CPID:8950 CPRV:20 CPFM:03 SCEP:10 BDID:00 "
L"ECID:0000000000200000 IBFL:00 SRTG:[iBoot-1145.3]");
DECLARE_CONST_UNICODE_STRING(Qa6ConfigurationString, L"Apple Mobile Device");
static VOID
Qa6CompleteUserRead(
_In_ WDFREQUEST Request,
_In_reads_bytes_(Length) const UCHAR *Data,
_In_ ULONG Length
)
{
PVOID buffer;
size_t bufferLength;
NTSTATUS status;
status = WdfRequestRetrieveOutputBuffer(Request, Length, &buffer,
&bufferLength);
if (!NT_SUCCESS(status)) {
WdfRequestComplete(Request, status);
return;
}
if (bufferLength < Length) {
WdfRequestComplete(Request, STATUS_BUFFER_TOO_SMALL);
return;
}
RtlCopyMemory(buffer, Data, Length);
WdfRequestCompleteWithInformation(Request, STATUS_SUCCESS, Length);
}
static VOID
Qa6PublishRequestFrame(
_In_ WDFDEVICE Controller
)
{
PQA6_CONTROLLER_CONTEXT context = Qa6GetControllerContext(Controller);
WDFREQUEST reader = NULL;
NTSTATUS status;
WdfSpinLockAcquire(context->BridgeLock);
status = WdfIoQueueRetrieveNextRequest(context->WaitingUserReads, &reader);
if (NT_SUCCESS(status)) {
context->RequestFrameReady = FALSE;
} else {
context->RequestFrameReady = TRUE;
}
WdfSpinLockRelease(context->BridgeLock);
if (reader != NULL) {
Qa6CompleteUserRead(reader, context->RequestFrame,
context->RequestFrameLength);
}
}
static NTSTATUS
Qa6InitializeUsbDescriptors(
_In_ WDFDEVICE Controller
)
{
PQA6_CONTROLLER_CONTEXT context = Qa6GetControllerContext(Controller);
UDECX_USB_DEVICE_STATE_CHANGE_CALLBACKS callbacks;
NTSTATUS status;
context->ChildInit = UdecxUsbDeviceInitAllocate(Controller);
if (context->ChildInit == NULL) {
return STATUS_INSUFFICIENT_RESOURCES;
}
UDECX_USB_DEVICE_CALLBACKS_INIT(&callbacks);
UdecxUsbDeviceInitSetStateChangeCallbacks(context->ChildInit, &callbacks);
UdecxUsbDeviceInitSetSpeed(context->ChildInit, UdecxUsbHighSpeed);
UdecxUsbDeviceInitSetEndpointsType(context->ChildInit,
UdecxEndpointTypeSimple);
status = UdecxUsbDeviceInitAddDescriptor(
context->ChildInit, (PUCHAR)&Qa6DeviceDescriptor,
(USHORT)sizeof(Qa6DeviceDescriptor));
if (!NT_SUCCESS(status)) {
return status;
}
status = UdecxUsbDeviceInitAddDescriptorWithIndex(
context->ChildInit, (PUCHAR)Qa6LanguageDescriptor,
(USHORT)sizeof(Qa6LanguageDescriptor), 0);
if (!NT_SUCCESS(status)) {
return status;
}
status = UdecxUsbDeviceInitAddStringDescriptor(
context->ChildInit, &Qa6ManufacturerString, 2, QA6_LANGUAGE_ID);
if (!NT_SUCCESS(status)) {
return status;
}
status = UdecxUsbDeviceInitAddStringDescriptor(
context->ChildInit, &Qa6ProductString, 3, QA6_LANGUAGE_ID);
if (!NT_SUCCESS(status)) {
return status;
}
status = UdecxUsbDeviceInitAddStringDescriptor(
context->ChildInit, &Qa6SerialString, 4, QA6_LANGUAGE_ID);
if (!NT_SUCCESS(status)) {
return status;
}
return UdecxUsbDeviceInitAddStringDescriptor(
context->ChildInit, &Qa6ConfigurationString, 5, QA6_LANGUAGE_ID);
}
static NTSTATUS
Qa6CreateControlEndpoint(
_In_ WDFDEVICE Controller
)
{
PQA6_CONTROLLER_CONTEXT context = Qa6GetControllerContext(Controller);
WDF_IO_QUEUE_CONFIG queueConfig;
PUDECXUSBENDPOINT_INIT endpointInit = NULL;
UDECX_USB_ENDPOINT_CALLBACKS callbacks;
NTSTATUS status;
WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, WdfIoQueueDispatchSequential);
queueConfig.EvtIoInternalDeviceControl = Qa6EvtControlUrb;
queueConfig.PowerManaged = WdfFalse;
status = WdfIoQueueCreate(Controller, &queueConfig,
WDF_NO_OBJECT_ATTRIBUTES,
&context->ControlQueue);
if (!NT_SUCCESS(status)) {
return status;
}
endpointInit = UdecxUsbSimpleEndpointInitAllocate(context->ChildDevice);
if (endpointInit == NULL) {
return STATUS_INSUFFICIENT_RESOURCES;
}
UdecxUsbEndpointInitSetEndpointAddress(endpointInit,
USB_DEFAULT_ENDPOINT_ADDRESS);
UDECX_USB_ENDPOINT_CALLBACKS_INIT(&callbacks, Qa6EvtEndpointReset);
UdecxUsbEndpointInitSetCallbacks(endpointInit, &callbacks);
status = UdecxUsbEndpointCreate(&endpointInit,
WDF_NO_OBJECT_ATTRIBUTES,
&context->ControlEndpoint);
if (!NT_SUCCESS(status)) {
if (endpointInit != NULL) {
UdecxUsbEndpointInitFree(endpointInit);
}
return status;
}
UdecxUsbEndpointSetWdfIoQueue(context->ControlEndpoint,
context->ControlQueue);
return STATUS_SUCCESS;
}
static NTSTATUS
Qa6PlugInUsbDevice(
_In_ WDFDEVICE Controller
)
{
PQA6_CONTROLLER_CONTEXT context = Qa6GetControllerContext(Controller);
UDECX_USB_DEVICE_PLUG_IN_OPTIONS plugOptions;
WDF_OBJECT_ATTRIBUTES attributes;
NTSTATUS status;
if (context->PluggedIn) {
return STATUS_SUCCESS;
}
status = UdecxUsbDeviceInitAddDescriptor(
context->ChildInit, (PUCHAR)Qa6ConfigurationDescriptor,
(USHORT)sizeof(Qa6ConfigurationDescriptor));
if (!NT_SUCCESS(status)) {
return status;
}
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
status = UdecxUsbDeviceCreate(&context->ChildInit, &attributes,
&context->ChildDevice);
if (!NT_SUCCESS(status)) {
return status;
}
status = Qa6CreateControlEndpoint(Controller);
if (!NT_SUCCESS(status)) {
return status;
}
UDECX_USB_DEVICE_PLUG_IN_OPTIONS_INIT(&plugOptions);
plugOptions.Usb20PortNumber = 1;
status = UdecxUsbDevicePlugIn(context->ChildDevice, &plugOptions);
if (NT_SUCCESS(status)) {
context->PluggedIn = TRUE;
QA6_LOG_INFO("Apple DFU child plugged in as USB\\VID_05AC&PID_1227\n");
}
return status;
}
NTSTATUS
DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{
WDF_DRIVER_CONFIG config;
WDF_OBJECT_ATTRIBUTES attributes;
WDF_DRIVER_CONFIG_INIT(&config, Qa6EvtDeviceAdd);
config.DriverPoolTag = QA6_POOL_TAG;
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
return WdfDriverCreate(DriverObject, RegistryPath, &attributes, &config,
WDF_NO_HANDLE);
}
NTSTATUS
Qa6EvtDeviceAdd(
_In_ WDFDRIVER Driver,
_Inout_ PWDFDEVICE_INIT DeviceInit
)
{
WDF_PNPPOWER_EVENT_CALLBACKS powerCallbacks;
WDF_FILEOBJECT_CONFIG fileConfig;
WDF_OBJECT_ATTRIBUTES attributes;
UDECX_WDF_DEVICE_CONFIG udeConfig;
WDF_IO_QUEUE_CONFIG queueConfig;
WDF_IO_QUEUE_CONFIG manualQueueConfig;
PQA6_CONTROLLER_CONTEXT context;
WDFDEVICE controller;
UNICODE_STRING deviceName;
UNICODE_STRING symbolicLink;
UNICODE_STRING reference;
NTSTATUS status;
UNREFERENCED_PARAMETER(Driver);
WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&powerCallbacks);
powerCallbacks.EvtDeviceD0Entry = Qa6EvtDeviceD0Entry;
powerCallbacks.EvtDeviceD0Exit = Qa6EvtDeviceD0Exit;
WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &powerCallbacks);
WDF_FILEOBJECT_CONFIG_INIT(&fileConfig, WDF_NO_EVENT_CALLBACK,
WDF_NO_EVENT_CALLBACK,
WDF_NO_EVENT_CALLBACK);
fileConfig.FileObjectClass = WdfFileObjectWdfCannotUseFsContexts;
WdfDeviceInitSetFileObjectConfig(DeviceInit, &fileConfig,
WDF_NO_OBJECT_ATTRIBUTES);
status = WdfDeviceInitAssignSDDLString(
DeviceInit, &SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_RW_RES_R);
if (!NT_SUCCESS(status)) {
return status;
}
status = UdecxInitializeWdfDeviceInit(DeviceInit);
if (!NT_SUCCESS(status)) {
return status;
}
RtlInitUnicodeString(&deviceName, QA6_DEVICE_NAME);
status = WdfDeviceInitAssignName(DeviceInit, &deviceName);
if (!NT_SUCCESS(status)) {
return status;
}
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes,
QA6_CONTROLLER_CONTEXT);
attributes.EvtCleanupCallback = Qa6EvtControllerCleanup;
attributes.ExecutionLevel = WdfExecutionLevelPassive;
status = WdfDeviceCreate(&DeviceInit, &attributes, &controller);
if (!NT_SUCCESS(status)) {
return status;
}
RtlInitUnicodeString(&symbolicLink, QA6_SYMBOLIC_LINK);
status = WdfDeviceCreateSymbolicLink(controller, &symbolicLink);
if (!NT_SUCCESS(status)) {
return status;
}
RtlInitUnicodeString(&reference, QA6_HOST_INTERFACE_REF);
status = WdfDeviceCreateDeviceInterface(
controller, (LPGUID)&GUID_DEVINTERFACE_USB_HOST_CONTROLLER,
&reference);
if (!NT_SUCCESS(status)) {
return status;
}
UDECX_WDF_DEVICE_CONFIG_INIT(&udeConfig, Qa6EvtQueryUsbCapability);
status = UdecxWdfDeviceAddUsbDeviceEmulation(controller, &udeConfig);
if (!NT_SUCCESS(status)) {
return status;
}
context = Qa6GetControllerContext(controller);
RtlZeroMemory(context, sizeof(*context));
status = WdfSpinLockCreate(WDF_NO_OBJECT_ATTRIBUTES,
&context->BridgeLock);
if (!NT_SUCCESS(status)) {
return status;
}
WDF_IO_QUEUE_CONFIG_INIT(&manualQueueConfig, WdfIoQueueDispatchManual);
manualQueueConfig.EvtIoCanceledOnQueue = Qa6EvtCanceledUserRead;
manualQueueConfig.PowerManaged = WdfFalse;
status = WdfIoQueueCreate(controller, &manualQueueConfig,
WDF_NO_OBJECT_ATTRIBUTES,
&context->WaitingUserReads);
if (!NT_SUCCESS(status)) {
return status;
}
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig,
WdfIoQueueDispatchSequential);
queueConfig.EvtIoDeviceControl = Qa6EvtControllerIoctl;
queueConfig.EvtIoRead = Qa6EvtBridgeRead;
queueConfig.EvtIoWrite = Qa6EvtBridgeWrite;
queueConfig.PowerManaged = WdfFalse;
status = WdfIoQueueCreate(controller, &queueConfig,
WDF_NO_OBJECT_ATTRIBUTES,
&context->DefaultQueue);
if (!NT_SUCCESS(status)) {
return status;
}
return Qa6InitializeUsbDescriptors(controller);
}
NTSTATUS
Qa6EvtDeviceD0Entry(
_In_ WDFDEVICE Device,
_In_ WDF_POWER_DEVICE_STATE PreviousState
)
{
UNREFERENCED_PARAMETER(PreviousState);
return Qa6PlugInUsbDevice(Device);
}
NTSTATUS
Qa6EvtDeviceD0Exit(
_In_ WDFDEVICE Device,
_In_ WDF_POWER_DEVICE_STATE TargetState
)
{
PQA6_CONTROLLER_CONTEXT context = Qa6GetControllerContext(Device);
if (TargetState == WdfPowerDeviceD3Final && context->PluggedIn) {
context->PluggedIn = FALSE;
return UdecxUsbDevicePlugOutAndDelete(context->ChildDevice);
}
return STATUS_SUCCESS;
}
VOID
Qa6EvtControllerCleanup(
_In_ WDFOBJECT Object
)
{
PQA6_CONTROLLER_CONTEXT context =
Qa6GetControllerContext((WDFDEVICE)Object);
if (context->ChildInit != NULL) {
UdecxUsbDeviceInitFree(context->ChildInit);
context->ChildInit = NULL;
}
}
VOID
Qa6EvtControllerIoctl(
_In_ WDFQUEUE Queue,
_In_ WDFREQUEST Request,
_In_ size_t OutputBufferLength,
_In_ size_t InputBufferLength,
_In_ ULONG IoControlCode
)
{
WDFDEVICE controller = WdfIoQueueGetDevice(Queue);
UNREFERENCED_PARAMETER(OutputBufferLength);
UNREFERENCED_PARAMETER(InputBufferLength);
UNREFERENCED_PARAMETER(IoControlCode);
if (!UdecxWdfDeviceTryHandleUserIoctl(controller, Request)) {
WdfRequestComplete(Request, STATUS_INVALID_DEVICE_REQUEST);
}
}
VOID
Qa6EvtBridgeRead(
_In_ WDFQUEUE Queue,
_In_ WDFREQUEST Request,
_In_ size_t Length
)
{
WDFDEVICE controller = WdfIoQueueGetDevice(Queue);
PQA6_CONTROLLER_CONTEXT context = Qa6GetControllerContext(controller);
BOOLEAN ready;
ULONG frameLength;
NTSTATUS status;
UNREFERENCED_PARAMETER(Length);
WdfSpinLockAcquire(context->BridgeLock);
ready = context->RequestFrameReady;
frameLength = context->RequestFrameLength;
if (ready) {
context->RequestFrameReady = FALSE;
}
WdfSpinLockRelease(context->BridgeLock);
if (ready) {
Qa6CompleteUserRead(Request, context->RequestFrame, frameLength);
return;
}
status = WdfRequestForwardToIoQueue(Request, context->WaitingUserReads);
if (!NT_SUCCESS(status)) {
WdfRequestComplete(Request, status);
}
}
VOID
Qa6EvtBridgeWrite(
_In_ WDFQUEUE Queue,
_In_ WDFREQUEST Request,
_In_ size_t Length
)
{
WDFDEVICE controller = WdfIoQueueGetDevice(Queue);
PQA6_CONTROLLER_CONTEXT context = Qa6GetControllerContext(controller);
const Qa6UsbFrameHeader *header;
PVOID rawBuffer;
size_t rawLength;
WDFREQUEST urb = NULL;
ULONG originalTransferLength = 0;
BOOLEAN directionIn = FALSE;
NTSTATUS status;
UNREFERENCED_PARAMETER(Length);
status = WdfRequestRetrieveInputBuffer(Request, QA6_USB_HEADER_SIZE,
&rawBuffer, &rawLength);
if (!NT_SUCCESS(status)) {
WdfRequestComplete(Request, status);
return;
}
header = (const Qa6UsbFrameHeader *)rawBuffer;
if (header->magic != QA6_USB_MAGIC ||
header->version != QA6_USB_PROTOCOL_VERSION ||
header->type != QA6_USB_MESSAGE_RESPONSE ||
header->payload_length > QA6_USB_MAX_PAYLOAD ||
rawLength != QA6_USB_HEADER_SIZE + header->payload_length) {
WdfRequestComplete(Request, STATUS_INVALID_PARAMETER);
return;
}
WdfSpinLockAcquire(context->BridgeLock);
if (context->PendingUrb != NULL &&
context->PendingRequestId == header->request_id) {
urb = context->PendingUrb;
originalTransferLength = context->PendingTransferLength;
directionIn = context->PendingDirectionIn;
context->PendingUrb = NULL;
}
WdfSpinLockRelease(context->BridgeLock);
if (urb == NULL) {
WdfRequestComplete(Request, STATUS_NOT_FOUND);
return;
}
if (header->status == QA6_USB_STATUS_SUCCESS) {
ULONG completed;
if (directionIn) {
PUCHAR transferBuffer;
ULONG transferBufferLength;
status = UdecxUrbRetrieveBuffer(urb, &transferBuffer,
&transferBufferLength);
if (NT_SUCCESS(status)) {
completed = header->payload_length;
if (completed > transferBufferLength) {
completed = transferBufferLength;
}
RtlCopyMemory(transferBuffer,
(const UCHAR *)rawBuffer + QA6_USB_HEADER_SIZE,
completed);
} else {
completed = 0;
}
} else {
completed = header->transfer_length;
if (completed > originalTransferLength) {
completed = originalTransferLength;
}
status = STATUS_SUCCESS;
}
UdecxUrbSetBytesCompleted(urb, completed);
UdecxUrbCompleteWithNtStatus(urb, status);
} else if (header->status == QA6_USB_STATUS_STALL) {
UdecxUrbComplete(urb, USBD_STATUS_STALL_PID);
} else {
UdecxUrbCompleteWithNtStatus(urb, STATUS_DEVICE_NOT_CONNECTED);
}
WdfRequestCompleteWithInformation(Request, STATUS_SUCCESS, rawLength);
}
VOID
Qa6EvtCanceledUserRead(
_In_ WDFQUEUE Queue,
_In_ WDFREQUEST Request
)
{
UNREFERENCED_PARAMETER(Queue);
WdfRequestComplete(Request, STATUS_CANCELLED);
}
VOID
Qa6EvtControlUrb(
_In_ WDFQUEUE Queue,
_In_ WDFREQUEST Request,
_In_ size_t OutputBufferLength,
_In_ size_t InputBufferLength,
_In_ ULONG IoControlCode
)
{
WDFDEVICE controller = WdfIoQueueGetDevice(Queue);
PQA6_CONTROLLER_CONTEXT context = Qa6GetControllerContext(controller);
Qa6UsbFrameHeader *header =
(Qa6UsbFrameHeader *)context->RequestFrame;
WDF_USB_CONTROL_SETUP_PACKET setup;
PUCHAR transferBuffer = NULL;
ULONG transferBufferLength = 0;
ULONG payloadLength = 0;
ULONG requestId;
NTSTATUS status;
UNREFERENCED_PARAMETER(OutputBufferLength);
UNREFERENCED_PARAMETER(InputBufferLength);
if (IoControlCode != IOCTL_INTERNAL_USB_SUBMIT_URB) {
UdecxUrbCompleteWithNtStatus(Request, STATUS_INVALID_DEVICE_REQUEST);
return;
}
status = UdecxUrbRetrieveControlSetupPacket(Request, &setup);
if (!NT_SUCCESS(status)) {
UdecxUrbCompleteWithNtStatus(Request, status);
return;
}
/* UDE answers descriptor/enumeration requests from the registered sets. */
if ((setup.Generic.Bytes[0] & 0x60) == 0) {
UdecxUrbSetBytesCompleted(Request, 0);
UdecxUrbCompleteWithNtStatus(Request, STATUS_SUCCESS);
return;
}
if (setup.Packet.wLength != 0) {
status = UdecxUrbRetrieveBuffer(Request, &transferBuffer,
&transferBufferLength);
if (!NT_SUCCESS(status)) {
UdecxUrbCompleteWithNtStatus(Request, status);
return;
}
if (transferBufferLength > QA6_USB_MAX_PAYLOAD) {
UdecxUrbCompleteWithNtStatus(Request,
STATUS_INVALID_BUFFER_SIZE);
return;
}
}
if ((setup.Generic.Bytes[0] & 0x80) == 0) {
payloadLength = transferBufferLength;
}
WdfSpinLockAcquire(context->BridgeLock);
if (context->PendingUrb != NULL) {
WdfSpinLockRelease(context->BridgeLock);
UdecxUrbCompleteWithNtStatus(Request, STATUS_DEVICE_BUSY);
return;
}
requestId = ++context->PendingRequestId;
if (requestId == 0) {
requestId = ++context->PendingRequestId;
}
RtlZeroMemory(header, sizeof(*header));
header->magic = QA6_USB_MAGIC;
header->version = QA6_USB_PROTOCOL_VERSION;
header->type = QA6_USB_MESSAGE_REQUEST;
header->request_id = requestId;
header->status = QA6_USB_STATUS_SUCCESS;
header->payload_length = payloadLength;
header->transfer_length = transferBufferLength;
RtlCopyMemory(header->setup, setup.Generic.Bytes, sizeof(header->setup));
if (payloadLength != 0) {
RtlCopyMemory(context->RequestFrame + QA6_USB_HEADER_SIZE,
transferBuffer, payloadLength);
}
context->RequestFrameLength = QA6_USB_HEADER_SIZE + payloadLength;
context->PendingUrb = Request;
context->PendingTransferLength = transferBufferLength;
context->PendingDirectionIn =
(setup.Generic.Bytes[0] & 0x80) != 0;
WdfSpinLockRelease(context->BridgeLock);
QA6_LOG_INFO("EP0 request %lu bm=%02x req=%02x len=%u\n",
requestId, setup.Generic.Bytes[0], setup.Generic.Bytes[1],
setup.Packet.wLength);
Qa6PublishRequestFrame(controller);
}
VOID
Qa6EvtEndpointReset(
_In_ UDECXUSBENDPOINT Endpoint,
_In_ WDFREQUEST Request
)
{
UNREFERENCED_PARAMETER(Endpoint);
WdfRequestComplete(Request, STATUS_SUCCESS);
}
NTSTATUS
Qa6EvtQueryUsbCapability(
_In_ WDFDEVICE Device,
_In_ PGUID CapabilityType,
_In_ ULONG OutputBufferLength,
_Out_writes_to_opt_(OutputBufferLength, *ResultLength) PVOID OutputBuffer,
_Out_ PULONG ResultLength
)
{
UNREFERENCED_PARAMETER(Device);
UNREFERENCED_PARAMETER(OutputBufferLength);
UNREFERENCED_PARAMETER(OutputBuffer);
*ResultLength = 0;
if (RtlCompareMemory(
CapabilityType,
&GUID_USB_CAPABILITY_DEVICE_CONNECTION_HIGH_SPEED_COMPATIBLE,
sizeof(GUID)) == sizeof(GUID)) {
return STATUS_SUCCESS;
}
return STATUS_NOT_SUPPORTED;
}
+29
View File
@@ -0,0 +1,29 @@
#Requires -RunAsAdministrator
[CmdletBinding()]
param()
$ErrorActionPreference = 'Stop'
$logPath = Join-Path $PSScriptRoot 'enable-test-mode.log'
$secureBoot = $null
try {
$secureBoot = Confirm-SecureBootUEFI
} catch {
Write-Verbose "État Secure Boot non lisible : $($_.Exception.Message)"
}
$bcdOutput = & bcdedit.exe /set testsigning on 2>&1
$bcdOutput | Out-File -LiteralPath $logPath -Encoding utf8 -Force
if ($LASTEXITCODE -ne 0) {
if ($secureBoot -eq $true) {
throw 'Secure Boot bloque le mode de signature test. Il faut le désactiver dans lUEFI avant de recommencer.'
}
throw "Impossible dactiver le mode de signature test ($LASTEXITCODE)."
}
Write-Host 'Mode de signature test activé dans le BCD.'
Write-Host 'Un redémarrage de Windows est requis avant le chargement de QemuA6Ude.sys.'
Add-Content -LiteralPath $logPath -Encoding utf8 -Value @(
'Mode de signature test activé dans le BCD.',
'Un redémarrage de Windows est requis.'
)
+32
View File
@@ -0,0 +1,32 @@
#Requires -RunAsAdministrator
[CmdletBinding()]
param([string]$WdkRoot)
$ErrorActionPreference = 'Stop'
$projectDir = $PSScriptRoot
$repoRoot = (Resolve-Path (Join-Path $projectDir '..\..')).Path
. (Join-Path $projectDir 'common.ps1')
$wdkRoot = Find-Qa6WdkRoot -ExplicitRoot $WdkRoot -RepoRoot $repoRoot
$wdkVersion = Find-Qa6WdkVersion -WdkRoot $wdkRoot
$packageDir = Join-Path $projectDir 'package'
$certificate = Join-Path $packageDir 'QemuA6Ude-Test.cer'
$inf = Join-Path $packageDir 'QemuA6Ude.inf'
$devcon = Join-Path $wdkRoot "tools\$wdkVersion\x64\devcon.exe"
foreach ($required in @($certificate, $inf, $devcon)) {
if (-not (Test-Path -LiteralPath $required -PathType Leaf)) {
throw "Fichier requis introuvable : $required"
}
}
Import-Certificate -FilePath $certificate -CertStoreLocation Cert:\LocalMachine\Root | Out-Null
Import-Certificate -FilePath $certificate -CertStoreLocation Cert:\LocalMachine\TrustedPublisher | Out-Null
& $devcon install $inf 'Root\QemuA6Ude'
if ($LASTEXITCODE -ne 0) {
throw "Installation du pilote échouée ($LASTEXITCODE)."
}
Write-Host 'Pilote QemuA6Ude installé.'
Write-Host 'Le périphérique enfant USB\VID_05AC&PID_1227 doit maintenant apparaître.'
+71
View File
@@ -0,0 +1,71 @@
[CmdletBinding()]
param([string]$WdkRoot)
$ErrorActionPreference = 'Stop'
$projectDir = $PSScriptRoot
$repoRoot = (Resolve-Path (Join-Path $projectDir '..\..')).Path
. (Join-Path $projectDir 'common.ps1')
$wdkRoot = Find-Qa6WdkRoot -ExplicitRoot $WdkRoot -RepoRoot $repoRoot
$wdkVersion = Find-Qa6WdkVersion -WdkRoot $wdkRoot
$buildDir = Join-Path $projectDir 'build\Release\x64'
$packageDir = Join-Path $projectDir 'package'
$certificateDir = Join-Path $projectDir 'cert'
$inf2Cat = Join-Path $wdkRoot "bin\$wdkVersion\x86\Inf2Cat.exe"
$signTool = Find-Qa6SignTool -WdkRoot $wdkRoot -WdkVersion $wdkVersion
$certificateBase = Join-Path $certificateDir 'QemuA6Ude-Test'
$certificateCer = "$certificateBase.cer"
$certificatePfx = "$certificateBase.pfx"
$certificatePasswordText = 'qemu-a6-test'
& (Join-Path $projectDir 'build-driver.ps1') -Configuration Release -WdkRoot $wdkRoot
New-Item -ItemType Directory -Force -Path $packageDir | Out-Null
New-Item -ItemType Directory -Force -Path $certificateDir | Out-Null
Copy-Item -LiteralPath (Join-Path $buildDir 'QemuA6Ude.sys') -Destination $packageDir -Force
Copy-Item -LiteralPath (Join-Path $projectDir 'QemuA6Ude.inf') -Destination $packageDir -Force
$certificatePassword = ConvertTo-SecureString $certificatePasswordText -AsPlainText -Force
$signingCertificate = New-SelfSignedCertificate `
-Type CodeSigningCert `
-Subject 'CN=QEMU A6 UDE Test' `
-CertStoreLocation 'Cert:\CurrentUser\My' `
-KeyAlgorithm RSA `
-KeyLength 3072 `
-HashAlgorithm SHA256 `
-KeyExportPolicy Exportable `
-NotAfter (Get-Date).AddYears(10)
try {
Export-PfxCertificate -Cert $signingCertificate -FilePath $certificatePfx `
-Password $certificatePassword -Force | Out-Null
Export-Certificate -Cert $signingCertificate -FilePath $certificateCer `
-Type CERT -Force | Out-Null
} finally {
Remove-Item -LiteralPath "Cert:\CurrentUser\My\$($signingCertificate.Thumbprint)" -Force
}
& $signTool sign /fd SHA256 /f $certificatePfx /p $certificatePasswordText (Join-Path $packageDir 'QemuA6Ude.sys')
if ($LASTEXITCODE -ne 0) {
throw 'La signature de test du fichier SYS a échoué.'
}
& $inf2Cat "/driver:$packageDir" '/os:10_X64'
if ($LASTEXITCODE -ne 0) {
throw 'La génération du catalogue du pilote a échoué.'
}
& $signTool sign /fd SHA256 /f $certificatePfx /p $certificatePasswordText (Join-Path $packageDir 'QemuA6Ude.cat')
if ($LASTEXITCODE -ne 0) {
throw 'La signature de test du catalogue a échoué.'
}
Copy-Item -LiteralPath $certificateCer -Destination $packageDir -Force
& $signTool verify /pa /v (Join-Path $packageDir 'QemuA6Ude.cat')
if ($LASTEXITCODE -ne 0) {
Write-Warning 'La chaîne est volontairement non approuvée avant linstallation du certificat de test.'
}
Write-Host "Paquet pilote prêt : $packageDir"
Write-Host "Certificat public : $certificateCer"
+269
View File
@@ -0,0 +1,269 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdint.h>
#include "../include/qemu_a6_usb_protocol.h"
#define QA6_DEFAULT_HOST "127.0.0.1"
#define QA6_DEFAULT_PORT "26050"
#define QA6_DRIVER_PATH L"\\\\.\\QemuA6Ude0"
static volatile LONG Qa6StopRequested;
static BOOL WINAPI
Qa6ConsoleHandler(DWORD event)
{
if (event == CTRL_C_EVENT || event == CTRL_BREAK_EVENT ||
event == CTRL_CLOSE_EVENT) {
InterlockedExchange(&Qa6StopRequested, 1);
return TRUE;
}
return FALSE;
}
static int
Qa6SendAll(SOCKET socketHandle, const unsigned char *buffer, int length)
{
int offset = 0;
while (offset < length) {
int sent = send(socketHandle, (const char *)buffer + offset,
length - offset, 0);
if (sent == SOCKET_ERROR || sent == 0) {
return -1;
}
offset += sent;
}
return 0;
}
static int
Qa6ReceiveAll(SOCKET socketHandle, unsigned char *buffer, int length)
{
int offset = 0;
while (offset < length) {
int received = recv(socketHandle, (char *)buffer + offset,
length - offset, 0);
if (received == SOCKET_ERROR || received == 0) {
return -1;
}
offset += received;
}
return 0;
}
static SOCKET
Qa6ConnectQemu(const char *host, const char *port)
{
struct addrinfo hints;
struct addrinfo *addresses = NULL;
struct addrinfo *address;
SOCKET socketHandle = INVALID_SOCKET;
int result;
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
result = getaddrinfo(host, port, &hints, &addresses);
if (result != 0) {
fprintf(stderr, "Resolution de %s:%s impossible: %d\n", host, port,
result);
return INVALID_SOCKET;
}
for (address = addresses; address != NULL; address = address->ai_next) {
socketHandle = socket(address->ai_family, address->ai_socktype,
address->ai_protocol);
if (socketHandle == INVALID_SOCKET) {
continue;
}
if (connect(socketHandle, address->ai_addr,
(int)address->ai_addrlen) == 0) {
break;
}
closesocket(socketHandle);
socketHandle = INVALID_SOCKET;
}
freeaddrinfo(addresses);
return socketHandle;
}
static HANDLE
Qa6OpenDriver(void)
{
return CreateFileW(QA6_DRIVER_PATH, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
}
static int
Qa6ValidateFrame(const unsigned char *frame, DWORD length,
unsigned short expectedType)
{
const Qa6UsbFrameHeader *header;
if (length < QA6_USB_HEADER_SIZE) {
return -1;
}
header = (const Qa6UsbFrameHeader *)frame;
if (header->magic != QA6_USB_MAGIC ||
header->version != QA6_USB_PROTOCOL_VERSION ||
header->type != expectedType ||
header->payload_length > QA6_USB_MAX_PAYLOAD ||
length != QA6_USB_HEADER_SIZE + header->payload_length) {
return -1;
}
return 0;
}
static int
Qa6RelayLoop(HANDLE driver, SOCKET qemu)
{
unsigned char request[QA6_USB_MAX_FRAME_SIZE];
unsigned char response[QA6_USB_MAX_FRAME_SIZE];
while (InterlockedCompareExchange(&Qa6StopRequested, 0, 0) == 0) {
Qa6UsbFrameHeader *requestHeader;
Qa6UsbFrameHeader *responseHeader;
DWORD requestLength = 0;
DWORD responseLength;
DWORD written = 0;
if (!ReadFile(driver, request, sizeof(request), &requestLength,
NULL)) {
DWORD error = GetLastError();
fprintf(stderr, "Lecture du pilote impossible: erreur %lu\n",
error);
return -1;
}
if (Qa6ValidateFrame(request, requestLength,
QA6_USB_MESSAGE_REQUEST) != 0) {
fprintf(stderr, "Trame invalide recue du pilote (%lu octets)\n",
requestLength);
return -1;
}
requestHeader = (Qa6UsbFrameHeader *)request;
printf("USB EP0 #%lu: bm=%02x req=%02x value=%02x%02x "
"index=%02x%02x len=%u\n",
requestHeader->request_id,
requestHeader->setup[0], requestHeader->setup[1],
requestHeader->setup[3], requestHeader->setup[2],
requestHeader->setup[5], requestHeader->setup[4],
(unsigned)(requestHeader->setup[6] |
(requestHeader->setup[7] << 8)));
fflush(stdout);
if (Qa6SendAll(qemu, request, (int)requestLength) != 0) {
fprintf(stderr, "Connexion QEMU interrompue pendant l'envoi.\n");
return -1;
}
if (Qa6ReceiveAll(qemu, response, QA6_USB_HEADER_SIZE) != 0) {
fprintf(stderr, "Connexion QEMU interrompue pendant la reponse.\n");
return -1;
}
responseHeader = (Qa6UsbFrameHeader *)response;
if (responseHeader->payload_length > QA6_USB_MAX_PAYLOAD) {
fprintf(stderr, "Longueur de reponse QEMU invalide.\n");
return -1;
}
responseLength = QA6_USB_HEADER_SIZE +
responseHeader->payload_length;
if (responseHeader->payload_length != 0 &&
Qa6ReceiveAll(qemu, response + QA6_USB_HEADER_SIZE,
(int)responseHeader->payload_length) != 0) {
fprintf(stderr, "Connexion QEMU interrompue dans les donnees.\n");
return -1;
}
if (Qa6ValidateFrame(response, responseLength,
QA6_USB_MESSAGE_RESPONSE) != 0 ||
responseHeader->request_id != requestHeader->request_id) {
fprintf(stderr, "Reponse QEMU invalide pour la requete #%lu.\n",
requestHeader->request_id);
return -1;
}
if (!WriteFile(driver, response, responseLength, &written, NULL) ||
written != responseLength) {
DWORD error = GetLastError();
fprintf(stderr, "Ecriture vers le pilote impossible: erreur %lu\n",
error);
return -1;
}
}
return 0;
}
int
main(int argc, char **argv)
{
const char *host = argc > 1 ? argv[1] : QA6_DEFAULT_HOST;
const char *port = argc > 2 ? argv[2] : QA6_DEFAULT_PORT;
WSADATA winsockData;
HANDLE driver = INVALID_HANDLE_VALUE;
SOCKET qemu = INVALID_SOCKET;
int exitCode = 1;
if (sizeof(Qa6UsbFrameHeader) != 32) {
fprintf(stderr, "Erreur interne: taille du protocole USB=%zu\n",
sizeof(Qa6UsbFrameHeader));
return 2;
}
SetConsoleCtrlHandler(Qa6ConsoleHandler, TRUE);
if (WSAStartup(MAKEWORD(2, 2), &winsockData) != 0) {
fprintf(stderr, "Initialisation Winsock impossible.\n");
return 2;
}
printf("Pont USB QEMU A6 natif Windows\n");
printf("Pilote : \\\\.\\QemuA6Ude0\n");
printf("QEMU : %s:%s\n", host, port);
while (InterlockedCompareExchange(&Qa6StopRequested, 0, 0) == 0) {
driver = Qa6OpenDriver();
if (driver != INVALID_HANDLE_VALUE) {
break;
}
printf("Attente du pilote QemuA6Ude...\n");
Sleep(1000);
}
if (driver == INVALID_HANDLE_VALUE) {
goto cleanup;
}
while (InterlockedCompareExchange(&Qa6StopRequested, 0, 0) == 0) {
qemu = Qa6ConnectQemu(host, port);
if (qemu != INVALID_SOCKET) {
break;
}
printf("Attente de QEMU sur %s:%s...\n", host, port);
Sleep(1000);
}
if (qemu == INVALID_SOCKET) {
goto cleanup;
}
printf("Connecte. idevicerestore peut utiliser l'iPhone DFU virtuel.\n");
exitCode = Qa6RelayLoop(driver, qemu) == 0 ? 0 : 1;
cleanup:
if (qemu != INVALID_SOCKET) {
shutdown(qemu, SD_BOTH);
closesocket(qemu);
}
if (driver != INVALID_HANDLE_VALUE) {
CloseHandle(driver);
}
WSACleanup();
return exitCode;
}
+56
View File
@@ -0,0 +1,56 @@
[CmdletBinding()]
param(
[ValidateSet('Debug', 'Release')]
[string]$Configuration = 'Release'
)
$ErrorActionPreference = 'Stop'
$projectDir = $PSScriptRoot
. (Join-Path $projectDir '..\QemuA6Ude\common.ps1')
$msvcRoot = Find-Qa6MsvcRoot
$vsInstall = (Resolve-Path (Join-Path $msvcRoot '..\..\..\..')).Path
$vsDevCmd = Join-Path $vsInstall 'Common7\Tools\VsDevCmd.bat'
$cl = Join-Path $msvcRoot 'bin\Hostx64\x64\cl.exe'
$outputDir = Join-Path $projectDir "build\$Configuration\x64"
$exePath = Join-Path $outputDir 'QemuA6UsbBridge.exe'
if (-not (Test-Path -LiteralPath $vsDevCmd -PathType Leaf)) {
throw "Environnement Visual Studio introuvable : $vsDevCmd"
}
New-Item -ItemType Directory -Force -Path $outputDir | Out-Null
$environmentCommand = 'call "{0}" -arch=x64 -host_arch=x64 >nul && set' -f $vsDevCmd
$environmentLines = & cmd.exe /d /s /c $environmentCommand
if ($LASTEXITCODE -ne 0) {
throw 'Impossible de charger lenvironnement MSVC x64.'
}
foreach ($line in $environmentLines) {
$separator = $line.IndexOf('=')
if ($separator -gt 0) {
$name = $line.Substring(0, $separator)
$value = $line.Substring($separator + 1)
Set-Item -Path "Env:$name" -Value $value
}
}
$arguments = @('/nologo', '/W4', '/D_CRT_SECURE_NO_WARNINGS', '/MT')
if ($Configuration -eq 'Release') {
$arguments += @('/O2')
} else {
$arguments += @('/Od', '/Zi')
}
$arguments += @(
"/Fo:$outputDir\bridge.obj",
(Join-Path $projectDir 'bridge.c'),
"/Fe:$exePath",
'/link', 'ws2_32.lib'
)
& $cl @arguments
if ($LASTEXITCODE -ne 0) {
throw "La compilation du pont USB a échoué ($LASTEXITCODE)."
}
Write-Host "Pont construit : $exePath"
+33
View File
@@ -0,0 +1,33 @@
#ifndef QEMU_A6_USB_PROTOCOL_H
#define QEMU_A6_USB_PROTOCOL_H
/* Shared by QEMU, the Windows UDE driver, and the user-mode bridge. */
#define QA6_USB_MAGIC 0x55364151u /* "QA6U" */
#define QA6_USB_PROTOCOL_VERSION 1u
#define QA6_USB_MESSAGE_REQUEST 1u
#define QA6_USB_MESSAGE_RESPONSE 2u
#define QA6_USB_STATUS_SUCCESS 0
#define QA6_USB_STATUS_STALL -1
#define QA6_USB_STATUS_DISCONNECTED -2
#define QA6_USB_STATUS_PROTOCOL -3
#define QA6_USB_MAX_PAYLOAD 65535u
#pragma pack(push, 1)
typedef struct Qa6UsbFrameHeader {
unsigned int magic;
unsigned short version;
unsigned short type;
unsigned int request_id;
int status;
unsigned int payload_length;
unsigned int transfer_length;
unsigned char setup[8];
} Qa6UsbFrameHeader;
#pragma pack(pop)
#define QA6_USB_HEADER_SIZE ((unsigned int)sizeof(Qa6UsbFrameHeader))
#define QA6_USB_MAX_FRAME_SIZE (QA6_USB_HEADER_SIZE + QA6_USB_MAX_PAYLOAD)
#endif