From 47977dd34a365ef2788271b788523315a7ebb8cb Mon Sep 17 00:00:00 2001 From: Yaya48 Date: Mon, 31 Aug 2026 02:16:31 +0200 Subject: [PATCH] hw/arm: add experimental iPhone 5 A6 platform --- .gitignore | 7 + A6-STATUS.md | 111 ++ Run-iPhone5-macOS.sh | 47 + Run-iPhone5.ps1 | 171 +++ hw/arm/Kconfig | 8 + hw/arm/meson.build | 1 + hw/arm/s5l8950x.c | 197 ++++ hw/char/meson.build | 1 + hw/char/s5l8950x-uart.c | 239 ++++ hw/gpio/Kconfig | 3 + hw/gpio/meson.build | 1 + hw/gpio/s5l8950x-gpio.c | 119 ++ hw/intc/meson.build | 1 + hw/intc/s5l8950x-aic.c | 249 ++++ hw/misc/meson.build | 1 + hw/misc/s5l8950x-chipid.c | 83 ++ hw/misc/s5l8950x-pmgr.c | 137 +++ hw/timer/meson.build | 1 + hw/timer/s5l8950x-timer.c | 131 +++ hw/usb/Kconfig | 3 + hw/usb/meson.build | 4 + hw/usb/s5l8950x-usb-otg.c | 1372 ++++++++++++++++++++++ hw/usb/s5l8950x-usb-phy.c | 107 ++ include/hw/arm/s5l8950x.h | 43 + windows/QemuA6Ude/QemuA6Ude.inf | 61 + windows/QemuA6Ude/build-driver.ps1 | 88 ++ windows/QemuA6Ude/common.ps1 | 79 ++ windows/QemuA6Ude/driver.c | 769 ++++++++++++ windows/QemuA6Ude/enable-test-mode.ps1 | 29 + windows/QemuA6Ude/install-driver.ps1 | 32 + windows/QemuA6Ude/prepare-package.ps1 | 71 ++ windows/QemuA6UsbBridge/bridge.c | 269 +++++ windows/QemuA6UsbBridge/build-bridge.ps1 | 56 + windows/include/qemu_a6_usb_protocol.h | 33 + 34 files changed, 4524 insertions(+) create mode 100644 A6-STATUS.md create mode 100755 Run-iPhone5-macOS.sh create mode 100644 Run-iPhone5.ps1 create mode 100644 hw/arm/s5l8950x.c create mode 100644 hw/char/s5l8950x-uart.c create mode 100644 hw/gpio/s5l8950x-gpio.c create mode 100644 hw/intc/s5l8950x-aic.c create mode 100644 hw/misc/s5l8950x-chipid.c create mode 100644 hw/misc/s5l8950x-pmgr.c create mode 100644 hw/timer/s5l8950x-timer.c create mode 100644 hw/usb/s5l8950x-usb-otg.c create mode 100644 hw/usb/s5l8950x-usb-phy.c create mode 100644 include/hw/arm/s5l8950x.h create mode 100644 windows/QemuA6Ude/QemuA6Ude.inf create mode 100644 windows/QemuA6Ude/build-driver.ps1 create mode 100644 windows/QemuA6Ude/common.ps1 create mode 100644 windows/QemuA6Ude/driver.c create mode 100644 windows/QemuA6Ude/enable-test-mode.ps1 create mode 100644 windows/QemuA6Ude/install-driver.ps1 create mode 100644 windows/QemuA6Ude/prepare-package.ps1 create mode 100644 windows/QemuA6UsbBridge/bridge.c create mode 100644 windows/QemuA6UsbBridge/build-bridge.ps1 create mode 100644 windows/include/qemu_a6_usb_protocol.h diff --git a/.gitignore b/.gitignore index 61fa399..53019cb 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/A6-STATUS.md b/A6-STATUS.md new file mode 100644 index 0000000..8fb7949 --- /dev/null +++ b/A6-STATUS.md @@ -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 n’est 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`. +- L’UART 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 l’IRQ 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 d’origine 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 d’un 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 l’UEFI. Le script d’installation 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 l’UART 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 l’ancien 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, l’IPSW iOS 10.3.4 sera envoyé par l’`idevicerestore` original. La SecureROM vérifiera l’iBSS ; 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 l’injecteur 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 l’iBSS extrait de l’IPSW sans dépendre d’un 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 n’est donc pas un raccourci par rapport au chemin UDE déjà construit. diff --git a/Run-iPhone5-macOS.sh b/Run-iPhone5-macOS.sh new file mode 100755 index 0000000..19ec3cb --- /dev/null +++ b/Run-iPhone5-macOS.sh @@ -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" "$@" diff --git a/Run-iPhone5.ps1 b/Run-iPhone5.ps1 new file mode 100644 index 0000000..d0fbf84 --- /dev/null +++ b/Run-iPhone5.ps1 @@ -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 diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index 260d2f0..52552b6 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -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 diff --git a/hw/arm/meson.build b/hw/arm/meson.build index 8ee5307..6cb406a 100644 --- a/hw/arm/meson.build +++ b/hw/arm/meson.build @@ -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')) diff --git a/hw/arm/s5l8950x.c b/hw/arm/s5l8950x.c new file mode 100644 index 0000000..7cc9a10 --- /dev/null +++ b/hw/arm/s5l8950x.c @@ -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) diff --git a/hw/char/meson.build b/hw/char/meson.build index fc3d7ee..c78dc01 100644 --- a/hw/char/meson.build +++ b/hw/char/meson.build @@ -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')) diff --git a/hw/char/s5l8950x-uart.c b/hw/char/s5l8950x-uart.c new file mode 100644 index 0000000..eb3fcce --- /dev/null +++ b/hw/char/s5l8950x-uart.c @@ -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) diff --git a/hw/gpio/Kconfig b/hw/gpio/Kconfig index fcc7c70..eeb5f58 100644 --- a/hw/gpio/Kconfig +++ b/hw/gpio/Kconfig @@ -10,6 +10,9 @@ config GPIO_MPC8XXX config GPIO_PWR bool +config S5L8950X_GPIO + bool + config SIFIVE_GPIO bool diff --git a/hw/gpio/meson.build b/hw/gpio/meson.build index 0555f44..9de5513 100644 --- a/hw/gpio/meson.build +++ b/hw/gpio/meson.build @@ -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')) diff --git a/hw/gpio/s5l8950x-gpio.c b/hw/gpio/s5l8950x-gpio.c new file mode 100644 index 0000000..ef766cb --- /dev/null +++ b/hw/gpio/s5l8950x-gpio.c @@ -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) diff --git a/hw/intc/meson.build b/hw/intc/meson.build index 0620b07..4ff538d 100644 --- a/hw/intc/meson.build +++ b/hw/intc/meson.build @@ -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')) diff --git a/hw/intc/s5l8950x-aic.c b/hw/intc/s5l8950x-aic.c new file mode 100644 index 0000000..8b6aa7c --- /dev/null +++ b/hw/intc/s5l8950x-aic.c @@ -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) diff --git a/hw/misc/meson.build b/hw/misc/meson.build index 54e07aa..35d2033 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -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')) diff --git a/hw/misc/s5l8950x-chipid.c b/hw/misc/s5l8950x-chipid.c new file mode 100644 index 0000000..dfef1ab --- /dev/null +++ b/hw/misc/s5l8950x-chipid.c @@ -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) diff --git a/hw/misc/s5l8950x-pmgr.c b/hw/misc/s5l8950x-pmgr.c new file mode 100644 index 0000000..0ae4cc7 --- /dev/null +++ b/hw/misc/s5l8950x-pmgr.c @@ -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) diff --git a/hw/timer/meson.build b/hw/timer/meson.build index 8323efa..eed7af8 100644 --- a/hw/timer/meson.build +++ b/hw/timer/meson.build @@ -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')) diff --git a/hw/timer/s5l8950x-timer.c b/hw/timer/s5l8950x-timer.c new file mode 100644 index 0000000..6617ed9 --- /dev/null +++ b/hw/timer/s5l8950x-timer.c @@ -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) diff --git a/hw/usb/Kconfig b/hw/usb/Kconfig index de95686..5b5fdf7 100644 --- a/hw/usb/Kconfig +++ b/hw/usb/Kconfig @@ -1,6 +1,9 @@ config USB bool +config S5L8950X_USB_PHY + bool + config USB_UHCI bool default y if PCI_DEVICES diff --git a/hw/usb/meson.build b/hw/usb/meson.build index ba55c28..e703235 100644 --- a/hw/usb/meson.build +++ b/hw/usb/meson.build @@ -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')) diff --git a/hw/usb/s5l8950x-usb-otg.c b/hw/usb/s5l8950x-usb-otg.c new file mode 100644 index 0000000..0249e66 --- /dev/null +++ b/hw/usb/s5l8950x-usb-otg.c @@ -0,0 +1,1372 @@ +/* + * Apple S5L8950X Synopsys USB OTG core (minimal SecureROM-facing model) + * + * This starts with the reset handshake used by SecureROM. More endpoint and + * interrupt behavior can be added as the device-mode driver reaches it. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/arm/s5l8950x.h" +#include "hw/core/irq.h" +#include "hw/core/sysbus.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/qdev-properties-system.h" +#include "chardev/char-fe.h" +#include "qapi/error.h" +#include "qom/object.h" +#include "system/address-spaces.h" +#include "system/dma.h" + +#include "../../windows/include/qemu_a6_usb_protocol.h" + +#define S5L8950X_USB_OTG_REGION_SIZE 0x10000 +#define S5L8950X_USB_OTG_NUM_REGS \ + (S5L8950X_USB_OTG_REGION_SIZE / sizeof(uint32_t)) + +#define USB_OTG_GRSTCTL 0x10 +#define USB_OTG_GRSTCTL_CSFTRST BIT(0) +#define USB_OTG_GRSTCTL_RXFFLSH BIT(4) +#define USB_OTG_GRSTCTL_TXFFLSH BIT(5) +#define USB_OTG_GRSTCTL_AHBIDLE BIT(31) +#define USB_OTG_GAHBCFG 0x08 +#define USB_OTG_GAHBCFG_GLBLINTRMSK BIT(0) +#define USB_OTG_GINTSTS 0x14 +#define USB_OTG_GINTMSK 0x18 +#define USB_OTG_GINT_USBRST BIT(12) +#define USB_OTG_GINT_ENUMDONE BIT(13) +#define USB_OTG_GINT_IEPINT BIT(18) +#define USB_OTG_GINT_OEPINT BIT(19) +#define USB_OTG_DIEPMSK 0x810 +#define USB_OTG_DOEPMSK 0x814 +#define USB_OTG_DAINT 0x818 +#define USB_OTG_DAINTMSK 0x81c +#define USB_OTG_DIEPCTL0 0x900 +#define USB_OTG_DIEPINT0 0x908 +#define USB_OTG_DIEPTSIZ0 0x910 +#define USB_OTG_DIEPDMA0 0x914 +#define USB_OTG_DOEPCTL0 0xb00 +#define USB_OTG_DOEPINT0 0xb08 +#define USB_OTG_DOEPTSIZ0 0xb10 +#define USB_OTG_DOEPDMA0 0xb14 +#define USB_OTG_EP_STRIDE 0x20 +#define USB_OTG_EP_COUNT 8 +#define USB_OTG_EPCTL_EPENA BIT(31) +#define USB_OTG_EPCTL_STALL BIT(21) +#define USB_OTG_EPINT_SETUP BIT(3) +#define USB_OTG_EPINT_XFERCOMPL BIT(0) + +#define USB_DFU_TRANSFER_SIZE 0x800 +#define USB_DFU_STATUS_SIZE 6 +#define USB_DFU_STATE_IDLE 2 +#define USB_DFU_STATE_DNLOAD_IDLE 5 +#define USB_DFU_STATE_ERROR 10 +#define USB_DFU_MAX_STATUS_POLLS 20 + +OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XUSBOTGState, S5L8950X_USB_OTG) + +typedef enum S5L8950XUSBHostStage { + USB_HOST_WAIT_DEVICE_SETUP, + USB_HOST_WAIT_DEVICE_IN, + USB_HOST_WAIT_DEVICE_STATUS_OUT, + USB_HOST_WAIT_SET_ADDRESS_SETUP, + USB_HOST_WAIT_SET_ADDRESS_STATUS_IN, + USB_HOST_ADDRESS_ASSIGNED, + USB_HOST_WAIT_SERIAL_IN, + USB_HOST_WAIT_SERIAL_STATUS_OUT, + USB_HOST_WAIT_CONFIG_HEADER_SETUP, + USB_HOST_WAIT_CONFIG_HEADER_IN, + USB_HOST_WAIT_CONFIG_HEADER_STATUS_OUT, + USB_HOST_WAIT_CONFIG_FULL_SETUP, + USB_HOST_WAIT_CONFIG_FULL_IN, + USB_HOST_WAIT_CONFIG_FULL_STATUS_OUT, + USB_HOST_WAIT_SET_CONFIGURATION_SETUP, + USB_HOST_WAIT_SET_CONFIGURATION_STATUS_IN, + USB_HOST_DFU_READY, + USB_HOST_WAIT_DFU_GETSTATE_IN, + USB_HOST_WAIT_DFU_GETSTATE_STATUS_OUT, + USB_HOST_DFU_DNLOAD_READY, + USB_HOST_WAIT_DFU_DNLOAD_DATA_OUT, + USB_HOST_WAIT_DFU_DNLOAD_STATUS_IN, + USB_HOST_WAIT_DFU_GETSTATUS_SETUP, + USB_HOST_WAIT_DFU_GETSTATUS_IN, + USB_HOST_WAIT_DFU_GETSTATUS_STATUS_OUT, + USB_HOST_DFU_DONE, + USB_HOST_DFU_ERROR, + USB_HOST_BRIDGE_WAIT_DATA_IN, + USB_HOST_BRIDGE_WAIT_STATUS_OUT, + USB_HOST_BRIDGE_WAIT_DATA_OUT, + USB_HOST_BRIDGE_WAIT_STATUS_IN, +} S5L8950XUSBHostStage; + +typedef enum S5L8950XDFUPacketKind { + USB_DFU_PACKET_PAYLOAD, + USB_DFU_PACKET_SUFFIX, + USB_DFU_PACKET_NOTIFY, +} S5L8950XDFUPacketKind; + +struct S5L8950XUSBOTGState { + SysBusDevice parent_obj; + MemoryRegion iomem; + qemu_irq irq; + uint32_t regs[S5L8950X_USB_OTG_NUM_REGS]; + bool usb_reset_sent; + bool enum_done_sent; + bool setup_sent; + bool descriptor_received; + uint8_t device_descriptor[18]; + uint32_t device_descriptor_len; + uint8_t serial_descriptor[256]; + uint32_t serial_descriptor_len; + uint8_t config_descriptor[256]; + uint32_t config_descriptor_len; + uint32_t config_total_length; + S5L8950XUSBHostStage host_stage; + char *dfu_image_path; + uint8_t *dfu_image; + size_t dfu_image_size; + size_t dfu_image_offset; + uint8_t dfu_packet[USB_DFU_TRANSFER_SIZE]; + uint32_t dfu_packet_len; + uint32_t dfu_packet_offset; + uint16_t dfu_packet_block; + uint16_t dfu_next_block; + uint16_t dfu_notify_block; + S5L8950XDFUPacketKind dfu_packet_kind; + uint8_t dfu_suffix[16]; + bool dfu_suffix_pending; + uint8_t dfu_status[USB_DFU_STATUS_SIZE]; + uint8_t dfu_state; + uint8_t dfu_status_polls; + uint8_t dfu_finish_statuses; + + CharFrontend bridge_chr; + bool bridge_open; + bool bridge_request_active; + Qa6UsbFrameHeader bridge_request; + uint8_t bridge_rx[QA6_USB_MAX_FRAME_SIZE]; + uint32_t bridge_rx_len; + uint8_t bridge_out_data[QA6_USB_MAX_PAYLOAD]; + uint32_t bridge_out_offset; + uint8_t bridge_in_data[QA6_USB_MAX_PAYLOAD]; + uint32_t bridge_in_len; +}; + +static void s5l8950x_usb_otg_update_ep_irqs(S5L8950XUSBOTGState *s) +{ + uint32_t daint = 0; + int ep; + + for (ep = 0; ep < USB_OTG_EP_COUNT; ep++) { + uint32_t in_int = s->regs[(USB_OTG_DIEPINT0 + + ep * USB_OTG_EP_STRIDE) / 4]; + uint32_t out_int = s->regs[(USB_OTG_DOEPINT0 + + ep * USB_OTG_EP_STRIDE) / 4]; + + if (in_int & s->regs[USB_OTG_DIEPMSK / 4]) { + daint |= BIT(ep); + } + if (out_int & s->regs[USB_OTG_DOEPMSK / 4]) { + daint |= BIT(ep + 16); + } + } + s->regs[USB_OTG_DAINT / 4] = daint; + s->regs[USB_OTG_GINTSTS / 4] &= + ~(USB_OTG_GINT_IEPINT | USB_OTG_GINT_OEPINT); + if (daint & s->regs[USB_OTG_DAINTMSK / 4] & 0xffff) { + s->regs[USB_OTG_GINTSTS / 4] |= USB_OTG_GINT_IEPINT; + } + if (daint & s->regs[USB_OTG_DAINTMSK / 4] & 0xffff0000) { + s->regs[USB_OTG_GINTSTS / 4] |= USB_OTG_GINT_OEPINT; + } +} + +static void s5l8950x_usb_otg_update_irq(S5L8950XUSBOTGState *s) +{ + s5l8950x_usb_otg_update_ep_irqs(s); + bool level = (s->regs[USB_OTG_GAHBCFG / 4] & + USB_OTG_GAHBCFG_GLBLINTRMSK) && + (s->regs[USB_OTG_GINTSTS / 4] & + s->regs[USB_OTG_GINTMSK / 4]); + + qemu_set_irq(s->irq, level); +} + +static bool s5l8950x_usb_otg_is_ep_int(hwaddr offset, hwaddr base) +{ + return offset >= base && + offset < base + USB_OTG_EP_COUNT * USB_OTG_EP_STRIDE && + (offset - base) % USB_OTG_EP_STRIDE == 0; +} + +static void s5l8950x_usb_otg_send_device_descriptor_setup( + S5L8950XUSBOTGState *s) +{ + static const uint8_t setup[] = { + 0x80, 0x06, 0x00, 0x01, 0x00, 0x00, 0x12, 0x00, + }; + hwaddr dma = s->regs[USB_OTG_DOEPDMA0 / 4]; + + if (dma_memory_write(&address_space_memory, dma, setup, sizeof(setup), + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, + "s5l8950x.usb: EP0 SETUP DMA write failed at 0x%08" + HWADDR_PRIx "\n", dma); + return; + } + s->regs[USB_OTG_DOEPINT0 / 4] |= USB_OTG_EPINT_SETUP; + s->regs[USB_OTG_DOEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA; + s->setup_sent = true; + s->host_stage = USB_HOST_WAIT_DEVICE_IN; + qemu_log_mask(LOG_UNIMP, + "s5l8950x.usb-host: GET_DESCRIPTOR(Device) -> EP0 DMA " + "0x%08" HWADDR_PRIx "\n", dma); + s5l8950x_usb_otg_update_irq(s); +} + +static void s5l8950x_usb_otg_send_set_address_setup( + S5L8950XUSBOTGState *s) +{ + static const uint8_t setup[] = { + 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + hwaddr dma = s->regs[USB_OTG_DOEPDMA0 / 4]; + + if (dma_memory_write(&address_space_memory, dma, setup, sizeof(setup), + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, + "s5l8950x.usb: SET_ADDRESS DMA write failed at 0x%08" + HWADDR_PRIx "\n", dma); + return; + } + s->regs[USB_OTG_DOEPINT0 / 4] |= USB_OTG_EPINT_SETUP; + s->regs[USB_OTG_DOEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA; + s->host_stage = USB_HOST_WAIT_SET_ADDRESS_STATUS_IN; + qemu_log_mask(LOG_UNIMP, + "s5l8950x.usb-host: SET_ADDRESS(1) -> EP0 DMA 0x%08" + HWADDR_PRIx "\n", dma); + s5l8950x_usb_otg_update_irq(s); +} + +static bool s5l8950x_usb_otg_send_setup(S5L8950XUSBOTGState *s, + const uint8_t setup[8], + const char *name, + S5L8950XUSBHostStage next_stage) +{ + hwaddr dma = s->regs[USB_OTG_DOEPDMA0 / 4]; + + if (dma_memory_write(&address_space_memory, dma, setup, 8, + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, + "s5l8950x.usb: %s DMA write failed at 0x%08" + HWADDR_PRIx "\n", name, dma); + return false; + } + s->regs[USB_OTG_DOEPINT0 / 4] |= USB_OTG_EPINT_SETUP; + s->regs[USB_OTG_DOEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA; + s->host_stage = next_stage; + qemu_log_mask(LOG_UNIMP, + "s5l8950x.usb-host: %s -> EP0 DMA 0x%08" + HWADDR_PRIx "\n", name, dma); + s5l8950x_usb_otg_update_irq(s); + return true; +} + +static void s5l8950x_usb_bridge_write_response( + S5L8950XUSBOTGState *s, const Qa6UsbFrameHeader *request, + int status, uint32_t transfer_length, const uint8_t *payload, + uint32_t payload_length) +{ + Qa6UsbFrameHeader response = { 0 }; + g_autofree uint8_t *frame = NULL; + uint32_t frame_length; + int written; + + if (payload_length > QA6_USB_MAX_PAYLOAD) { + status = QA6_USB_STATUS_PROTOCOL; + payload_length = 0; + transfer_length = 0; + } + + response.magic = QA6_USB_MAGIC; + response.version = QA6_USB_PROTOCOL_VERSION; + response.type = QA6_USB_MESSAGE_RESPONSE; + response.request_id = request->request_id; + response.status = status; + response.payload_length = payload_length; + response.transfer_length = transfer_length; + memcpy(response.setup, request->setup, sizeof(response.setup)); + + frame_length = QA6_USB_HEADER_SIZE + payload_length; + frame = g_malloc(frame_length); + memcpy(frame, &response, QA6_USB_HEADER_SIZE); + if (payload_length) { + memcpy(frame + QA6_USB_HEADER_SIZE, payload, payload_length); + } + + written = qemu_chr_fe_write_all(&s->bridge_chr, frame, frame_length); + if (written != frame_length) { + qemu_log_mask(LOG_GUEST_ERROR, + "s5l8950x.usb-bridge: response write failed " + "(%d/%u bytes)\n", written, frame_length); + } +} + +static void s5l8950x_usb_bridge_finish(S5L8950XUSBOTGState *s, int status) +{ + bool direction_in = (s->bridge_request.setup[0] & 0x80) != 0; + uint32_t transfer_length = direction_in ? s->bridge_in_len : + s->bridge_out_offset; + + s5l8950x_usb_bridge_write_response( + s, &s->bridge_request, status, transfer_length, + direction_in ? s->bridge_in_data : NULL, + direction_in && status == QA6_USB_STATUS_SUCCESS ? + s->bridge_in_len : 0); + + qemu_log_mask(LOG_UNIMP, + "s5l8950x.usb-bridge: EP0 request %u complete " + "(status=%d, bytes=%u)\n", + s->bridge_request.request_id, status, transfer_length); + s->bridge_request_active = false; + s->bridge_out_offset = 0; + s->bridge_in_len = 0; + s->host_stage = USB_HOST_DFU_READY; +} + +static void s5l8950x_usb_bridge_start_request(S5L8950XUSBOTGState *s) +{ + bool direction_in; + uint16_t length; + S5L8950XUSBHostStage next_stage; + + if (!s->bridge_request_active || s->host_stage != USB_HOST_DFU_READY) { + return; + } + + direction_in = (s->bridge_request.setup[0] & 0x80) != 0; + length = s->bridge_request.setup[6] | + (s->bridge_request.setup[7] << 8); + if (!length) { + next_stage = USB_HOST_BRIDGE_WAIT_STATUS_IN; + } else if (direction_in) { + next_stage = USB_HOST_BRIDGE_WAIT_DATA_IN; + } else { + next_stage = USB_HOST_BRIDGE_WAIT_DATA_OUT; + } + + s5l8950x_usb_otg_send_setup(s, s->bridge_request.setup, + "external EP0 request", next_stage); +} + +static void s5l8950x_usb_bridge_complete_data_out( + S5L8950XUSBOTGState *s) +{ + hwaddr dma = s->regs[USB_OTG_DOEPDMA0 / 4]; + uint32_t remaining = s->bridge_request.payload_length - + s->bridge_out_offset; + uint32_t capacity = s->regs[USB_OTG_DOEPTSIZ0 / 4] & 0x7f; + uint32_t length; + + if (!capacity) { + capacity = 64; + } + length = MIN(remaining, capacity); + if (length && + dma_memory_write(&address_space_memory, dma, + s->bridge_out_data + s->bridge_out_offset, length, + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, + "s5l8950x.usb-bridge: OUT DMA write failed at " + "0x%08" HWADDR_PRIx "\n", dma); + s5l8950x_usb_bridge_finish(s, QA6_USB_STATUS_DISCONNECTED); + return; + } + + s->bridge_out_offset += length; + s->regs[USB_OTG_DOEPTSIZ0 / 4] &= ~0x7f; + s->regs[USB_OTG_DOEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA; + s->regs[USB_OTG_DOEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL; + if (s->bridge_out_offset >= s->bridge_request.payload_length) { + s->host_stage = USB_HOST_BRIDGE_WAIT_STATUS_IN; + } + s5l8950x_usb_otg_update_irq(s); +} + +static void s5l8950x_usb_bridge_complete_data_in( + S5L8950XUSBOTGState *s) +{ + hwaddr dma = s->regs[USB_OTG_DIEPDMA0 / 4]; + uint32_t requested = s->bridge_request.transfer_length; + uint32_t remaining = requested - s->bridge_in_len; + uint32_t length = s->regs[USB_OTG_DIEPTSIZ0 / 4] & 0x7f; + + length = MIN(length, remaining); + if (length && + dma_memory_read(&address_space_memory, dma, + s->bridge_in_data + s->bridge_in_len, length, + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, + "s5l8950x.usb-bridge: IN DMA read failed at " + "0x%08" HWADDR_PRIx "\n", dma); + s5l8950x_usb_bridge_finish(s, QA6_USB_STATUS_DISCONNECTED); + return; + } + + s->bridge_in_len += length; + s->regs[USB_OTG_DIEPTSIZ0 / 4] &= ~0x7f; + s->regs[USB_OTG_DIEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA; + s->regs[USB_OTG_DIEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL; + if (length < 64 || s->bridge_in_len >= requested) { + s->host_stage = USB_HOST_BRIDGE_WAIT_STATUS_OUT; + } + s5l8950x_usb_otg_update_irq(s); +} + +static void s5l8950x_usb_bridge_complete_status_out( + S5L8950XUSBOTGState *s) +{ + s->regs[USB_OTG_DOEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL; + s->regs[USB_OTG_DOEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA; + s5l8950x_usb_bridge_finish(s, QA6_USB_STATUS_SUCCESS); + s5l8950x_usb_otg_update_irq(s); +} + +static void s5l8950x_usb_bridge_complete_status_in( + S5L8950XUSBOTGState *s) +{ + s->regs[USB_OTG_DIEPTSIZ0 / 4] &= ~0x7f; + s->regs[USB_OTG_DIEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA; + s->regs[USB_OTG_DIEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL; + s5l8950x_usb_bridge_finish(s, QA6_USB_STATUS_SUCCESS); + s5l8950x_usb_otg_update_irq(s); +} + +static void s5l8950x_usb_bridge_process_frame(S5L8950XUSBOTGState *s) +{ + Qa6UsbFrameHeader request; + uint32_t expected_payload; + uint16_t setup_length; + bool direction_in; + + memcpy(&request, s->bridge_rx, sizeof(request)); + expected_payload = QA6_USB_HEADER_SIZE + request.payload_length; + direction_in = (request.setup[0] & 0x80) != 0; + setup_length = request.setup[6] | (request.setup[7] << 8); + + if (request.magic != QA6_USB_MAGIC || + request.version != QA6_USB_PROTOCOL_VERSION || + request.type != QA6_USB_MESSAGE_REQUEST || + request.payload_length > QA6_USB_MAX_PAYLOAD || + expected_payload != s->bridge_rx_len || + request.transfer_length != setup_length || + (direction_in && request.payload_length != 0) || + (!direction_in && request.payload_length != setup_length)) { + qemu_log_mask(LOG_GUEST_ERROR, + "s5l8950x.usb-bridge: invalid request frame\n"); + if (request.magic == QA6_USB_MAGIC) { + s5l8950x_usb_bridge_write_response( + s, &request, QA6_USB_STATUS_PROTOCOL, 0, NULL, 0); + } + return; + } + + if (s->bridge_request_active || s->host_stage != USB_HOST_DFU_READY) { + qemu_log_mask(LOG_GUEST_ERROR, + "s5l8950x.usb-bridge: device is busy (stage=%d)\n", + s->host_stage); + s5l8950x_usb_bridge_write_response( + s, &request, QA6_USB_STATUS_PROTOCOL, 0, NULL, 0); + return; + } + + s->bridge_request = request; + if (request.payload_length) { + memcpy(s->bridge_out_data, s->bridge_rx + QA6_USB_HEADER_SIZE, + request.payload_length); + } + s->bridge_out_offset = 0; + s->bridge_in_len = 0; + s->bridge_request_active = true; + qemu_log_mask(LOG_UNIMP, + "s5l8950x.usb-bridge: EP0 request %u " + "bm=%02x req=%02x len=%u\n", + request.request_id, request.setup[0], request.setup[1], + setup_length); + + if (s->regs[USB_OTG_DOEPCTL0 / 4] & USB_OTG_EPCTL_EPENA) { + s5l8950x_usb_bridge_start_request(s); + } +} + +static int s5l8950x_usb_bridge_can_receive(void *opaque) +{ + S5L8950XUSBOTGState *s = opaque; + + return QA6_USB_MAX_FRAME_SIZE - s->bridge_rx_len; +} + +static void s5l8950x_usb_bridge_receive(void *opaque, + const uint8_t *buffer, int size) +{ + S5L8950XUSBOTGState *s = opaque; + + while (size > 0) { + uint32_t target = QA6_USB_HEADER_SIZE; + uint32_t amount; + + if (s->bridge_rx_len >= QA6_USB_HEADER_SIZE) { + Qa6UsbFrameHeader header; + + memcpy(&header, s->bridge_rx, sizeof(header)); + if (header.payload_length > QA6_USB_MAX_PAYLOAD) { + qemu_log_mask(LOG_GUEST_ERROR, + "s5l8950x.usb-bridge: oversized frame\n"); + s->bridge_rx_len = 0; + return; + } + target += header.payload_length; + } + + amount = MIN((uint32_t)size, target - s->bridge_rx_len); + memcpy(s->bridge_rx + s->bridge_rx_len, buffer, amount); + s->bridge_rx_len += amount; + buffer += amount; + size -= amount; + + if (s->bridge_rx_len >= QA6_USB_HEADER_SIZE) { + Qa6UsbFrameHeader header; + + memcpy(&header, s->bridge_rx, sizeof(header)); + if (header.payload_length <= QA6_USB_MAX_PAYLOAD && + s->bridge_rx_len == + QA6_USB_HEADER_SIZE + header.payload_length) { + s5l8950x_usb_bridge_process_frame(s); + s->bridge_rx_len = 0; + } + } + } +} + +static void s5l8950x_usb_bridge_event(void *opaque, QEMUChrEvent event) +{ + S5L8950XUSBOTGState *s = opaque; + + if (event == CHR_EVENT_OPENED) { + s->bridge_open = true; + qemu_log_mask(LOG_UNIMP, + "s5l8950x.usb-bridge: Windows bridge connected\n"); + } else if (event == CHR_EVENT_CLOSED) { + s->bridge_open = false; + s->bridge_rx_len = 0; + s->bridge_request_active = false; + if (s->host_stage >= USB_HOST_BRIDGE_WAIT_DATA_IN && + s->host_stage <= USB_HOST_BRIDGE_WAIT_STATUS_IN) { + s->host_stage = USB_HOST_DFU_READY; + } + qemu_log_mask(LOG_UNIMP, + "s5l8950x.usb-bridge: Windows bridge disconnected\n"); + } +} + +static uint32_t s5l8950x_usb_dfu_crc32(uint32_t crc, const uint8_t *data, + size_t length) +{ + size_t i; + + for (i = 0; i < length; i++) { + int bit; + + crc ^= data[i]; + for (bit = 0; bit < 8; bit++) { + crc = (crc >> 1) ^ (0xedb88320U & -(crc & 1)); + } + } + return crc; +} + +static void s5l8950x_usb_dfu_build_suffix(S5L8950XUSBOTGState *s) +{ + static const uint8_t suffix_header[12] = { + 0xff, 0xff, 0xff, 0xff, 0xac, 0x05, + 0x00, 0x01, 0x55, 0x46, 0x44, 0x10, + }; + uint32_t crc = 0xffffffffU; + + crc = s5l8950x_usb_dfu_crc32(crc, s->dfu_image, s->dfu_image_size); + crc = s5l8950x_usb_dfu_crc32(crc, suffix_header, + sizeof(suffix_header)); + memcpy(s->dfu_suffix, suffix_header, sizeof(suffix_header)); + s->dfu_suffix[12] = crc; + s->dfu_suffix[13] = crc >> 8; + s->dfu_suffix[14] = crc >> 16; + s->dfu_suffix[15] = crc >> 24; +} + +static void s5l8950x_usb_dfu_prepare_packet(S5L8950XUSBOTGState *s) +{ + size_t remaining; + + s->dfu_packet_offset = 0; + if (s->dfu_image_offset < s->dfu_image_size) { + remaining = s->dfu_image_size - s->dfu_image_offset; + s->dfu_packet_len = MIN(remaining, (size_t)USB_DFU_TRANSFER_SIZE); + memcpy(s->dfu_packet, s->dfu_image + s->dfu_image_offset, + s->dfu_packet_len); + s->dfu_image_offset += s->dfu_packet_len; + s->dfu_packet_block = s->dfu_next_block++; + s->dfu_packet_kind = USB_DFU_PACKET_PAYLOAD; + + if (s->dfu_image_offset == s->dfu_image_size) { + if (s->dfu_packet_len + sizeof(s->dfu_suffix) <= + USB_DFU_TRANSFER_SIZE) { + memcpy(s->dfu_packet + s->dfu_packet_len, s->dfu_suffix, + sizeof(s->dfu_suffix)); + s->dfu_packet_len += sizeof(s->dfu_suffix); + } else { + s->dfu_suffix_pending = true; + } + } + return; + } + + if (s->dfu_suffix_pending) { + memcpy(s->dfu_packet, s->dfu_suffix, sizeof(s->dfu_suffix)); + s->dfu_packet_len = sizeof(s->dfu_suffix); + s->dfu_packet_block = s->dfu_next_block - 1; + s->dfu_packet_kind = USB_DFU_PACKET_SUFFIX; + s->dfu_suffix_pending = false; + return; + } + + s->dfu_packet_len = 0; + s->dfu_packet_block = s->dfu_notify_block; + s->dfu_packet_kind = USB_DFU_PACKET_NOTIFY; +} + +static void s5l8950x_usb_dfu_send_getstate(S5L8950XUSBOTGState *s) +{ + static const uint8_t setup[] = { + 0xa1, 0x05, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + }; + + s5l8950x_usb_otg_send_setup(s, setup, "DFU_GETSTATE", + USB_HOST_WAIT_DFU_GETSTATE_IN); +} + +static void s5l8950x_usb_dfu_send_dnload(S5L8950XUSBOTGState *s) +{ + uint8_t setup[] = { + 0x21, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + s5l8950x_usb_dfu_prepare_packet(s); + setup[2] = s->dfu_packet_block; + setup[3] = s->dfu_packet_block >> 8; + setup[6] = s->dfu_packet_len; + setup[7] = s->dfu_packet_len >> 8; + s5l8950x_usb_otg_send_setup( + s, setup, + s->dfu_packet_kind == USB_DFU_PACKET_NOTIFY ? + "DFU_DNLOAD(finish)" : "DFU_DNLOAD", + s->dfu_packet_len ? USB_HOST_WAIT_DFU_DNLOAD_DATA_OUT : + USB_HOST_WAIT_DFU_DNLOAD_STATUS_IN); +} + +static void s5l8950x_usb_dfu_send_getstatus(S5L8950XUSBOTGState *s) +{ + static const uint8_t setup[] = { + 0xa1, 0x03, 0x00, 0x00, 0x00, 0x00, + USB_DFU_STATUS_SIZE, 0x00, + }; + + s5l8950x_usb_otg_send_setup(s, setup, "DFU_GETSTATUS", + USB_HOST_WAIT_DFU_GETSTATUS_IN); +} + +static void s5l8950x_usb_otg_send_config_header_setup( + S5L8950XUSBOTGState *s) +{ + static const uint8_t setup[] = { + 0x80, 0x06, 0x00, 0x02, 0x00, 0x00, 0x09, 0x00, + }; + + s5l8950x_usb_otg_send_setup(s, setup, "GET_DESCRIPTOR(Config, 9)", + USB_HOST_WAIT_CONFIG_HEADER_IN); +} + +static void s5l8950x_usb_otg_send_serial_descriptor_setup( + S5L8950XUSBOTGState *s) +{ + static const uint8_t setup[] = { + 0x80, 0x06, 0x04, 0x03, 0x09, 0x04, 0xff, 0x00, + }; + + s->serial_descriptor_len = 0; + memset(s->serial_descriptor, 0, sizeof(s->serial_descriptor)); + s5l8950x_usb_otg_send_setup(s, setup, "GET_DESCRIPTOR(String 4)", + USB_HOST_WAIT_SERIAL_IN); +} + +static void s5l8950x_usb_otg_send_config_full_setup( + S5L8950XUSBOTGState *s) +{ + uint8_t setup[] = { + 0x80, 0x06, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + }; + + setup[6] = s->config_total_length; + setup[7] = s->config_total_length >> 8; + s5l8950x_usb_otg_send_setup(s, setup, "GET_DESCRIPTOR(Config, full)", + USB_HOST_WAIT_CONFIG_FULL_IN); +} + +static void s5l8950x_usb_otg_send_set_configuration_setup( + S5L8950XUSBOTGState *s) +{ + static const uint8_t setup[] = { + 0x00, 0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + s5l8950x_usb_otg_send_setup(s, setup, "SET_CONFIGURATION(1)", + USB_HOST_WAIT_SET_CONFIGURATION_STATUS_IN); +} + +static void s5l8950x_usb_otg_complete_out_status( + S5L8950XUSBOTGState *s, S5L8950XUSBHostStage next_stage) +{ + s->regs[USB_OTG_DOEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL; + s->regs[USB_OTG_DOEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA; + s->host_stage = next_stage; + qemu_log_mask(LOG_UNIMP, + "s5l8950x.usb-host: EP0 OUT status complete\n"); + s5l8950x_usb_otg_update_irq(s); +} + +static void s5l8950x_usb_otg_complete_in_status(S5L8950XUSBOTGState *s) +{ + s->regs[USB_OTG_DIEPTSIZ0 / 4] &= ~0x7f; + s->regs[USB_OTG_DIEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA; + s->regs[USB_OTG_DIEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL; + s->host_stage = USB_HOST_ADDRESS_ASSIGNED; + qemu_log_mask(LOG_UNIMP, + "s5l8950x.usb-host: SET_ADDRESS status complete, DCFG=" + "0x%08x\n", s->regs[0x800 / 4]); + s5l8950x_usb_otg_update_irq(s); +} + +static void s5l8950x_usb_otg_complete_configuration_status_in( + S5L8950XUSBOTGState *s) +{ + s->regs[USB_OTG_DIEPTSIZ0 / 4] &= ~0x7f; + s->regs[USB_OTG_DIEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA; + s->regs[USB_OTG_DIEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL; + s->host_stage = USB_HOST_DFU_READY; + qemu_log_mask(LOG_UNIMP, + "s5l8950x.usb-host: enumeration complete; DFU control " + "interface ready\n"); + s5l8950x_usb_otg_update_irq(s); +} + +static void s5l8950x_usb_dfu_complete_dnload_data_out( + S5L8950XUSBOTGState *s) +{ + hwaddr dma = s->regs[USB_OTG_DOEPDMA0 / 4]; + uint32_t remaining = s->dfu_packet_len - s->dfu_packet_offset; + uint32_t capacity = s->regs[USB_OTG_DOEPTSIZ0 / 4] & 0x7f; + uint32_t length; + + if (!capacity) { + capacity = 64; + } + length = MIN(remaining, capacity); + if (dma_memory_write(&address_space_memory, dma, + s->dfu_packet + s->dfu_packet_offset, length, + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, + "s5l8950x.usb: DFU data DMA write failed at 0x%08" + HWADDR_PRIx "\n", dma); + s->host_stage = USB_HOST_DFU_ERROR; + return; + } + + s->dfu_packet_offset += length; + s->regs[USB_OTG_DOEPTSIZ0 / 4] &= ~0x7f; + s->regs[USB_OTG_DOEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA; + s->regs[USB_OTG_DOEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL; + if (s->dfu_packet_offset == s->dfu_packet_len) { + s->host_stage = USB_HOST_WAIT_DFU_DNLOAD_STATUS_IN; + qemu_log_mask(LOG_UNIMP, + "s5l8950x.usb-host: DFU block %u data complete " + "(%u bytes)\n", s->dfu_packet_block, + s->dfu_packet_len); + } + s5l8950x_usb_otg_update_irq(s); +} + +static void s5l8950x_usb_dfu_complete_dnload_status_in( + S5L8950XUSBOTGState *s) +{ + s->regs[USB_OTG_DIEPTSIZ0 / 4] &= ~0x7f; + s->regs[USB_OTG_DIEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA; + s->regs[USB_OTG_DIEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL; + s->host_stage = USB_HOST_WAIT_DFU_GETSTATUS_SETUP; + s5l8950x_usb_otg_update_irq(s); +} + +static void s5l8950x_usb_dfu_complete_getstate_in( + S5L8950XUSBOTGState *s) +{ + hwaddr dma = s->regs[USB_OTG_DIEPDMA0 / 4]; + + if (dma_memory_read(&address_space_memory, dma, &s->dfu_state, 1, + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, + "s5l8950x.usb: DFU_GETSTATE DMA read failed at 0x%08" + HWADDR_PRIx "\n", dma); + s->host_stage = USB_HOST_DFU_ERROR; + return; + } + s->regs[USB_OTG_DIEPTSIZ0 / 4] &= ~0x7f; + s->regs[USB_OTG_DIEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA; + s->regs[USB_OTG_DIEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL; + s->host_stage = USB_HOST_WAIT_DFU_GETSTATE_STATUS_OUT; + qemu_log_mask(LOG_UNIMP, + "s5l8950x.usb-host: DFU initial state=%u\n", + s->dfu_state); + s5l8950x_usb_otg_update_irq(s); +} + +static void s5l8950x_usb_dfu_complete_getstatus_in( + S5L8950XUSBOTGState *s) +{ + hwaddr dma = s->regs[USB_OTG_DIEPDMA0 / 4]; + + if (dma_memory_read(&address_space_memory, dma, s->dfu_status, + sizeof(s->dfu_status), + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, + "s5l8950x.usb: DFU_GETSTATUS DMA read failed at " + "0x%08" HWADDR_PRIx "\n", dma); + s->host_stage = USB_HOST_DFU_ERROR; + return; + } + s->dfu_state = s->dfu_status[4]; + s->regs[USB_OTG_DIEPTSIZ0 / 4] &= ~0x7f; + s->regs[USB_OTG_DIEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA; + s->regs[USB_OTG_DIEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL; + s->host_stage = USB_HOST_WAIT_DFU_GETSTATUS_STATUS_OUT; + qemu_log_mask(LOG_UNIMP, + "s5l8950x.usb-host: DFU status=%u state=%u " + "poll=%u ms\n", s->dfu_status[0], s->dfu_state, + s->dfu_status[1] | (s->dfu_status[2] << 8) | + (s->dfu_status[3] << 16)); + s5l8950x_usb_otg_update_irq(s); +} + +static S5L8950XUSBHostStage s5l8950x_usb_dfu_stage_after_status( + S5L8950XUSBOTGState *s) +{ + if (s->dfu_status[0] || s->dfu_state == USB_DFU_STATE_ERROR) { + qemu_log_mask(LOG_GUEST_ERROR, + "s5l8950x.usb-host: DFU rejected block %u " + "(status=%u state=%u)\n", s->dfu_packet_block, + s->dfu_status[0], s->dfu_state); + return USB_HOST_DFU_ERROR; + } + + if (s->dfu_packet_kind == USB_DFU_PACKET_NOTIFY) { + s->dfu_finish_statuses++; + if (s->dfu_finish_statuses < 2) { + return USB_HOST_WAIT_DFU_GETSTATUS_SETUP; + } + qemu_log_mask(LOG_UNIMP, + "s5l8950x.usb-host: DFU image handed to SecureROM " + "(%zu bytes, final state=%u)\n", + s->dfu_image_size, s->dfu_state); + return USB_HOST_DFU_DONE; + } + + if (s->dfu_state == USB_DFU_STATE_DNLOAD_IDLE) { + s->dfu_status_polls = 0; + return USB_HOST_DFU_DNLOAD_READY; + } + if (++s->dfu_status_polls >= USB_DFU_MAX_STATUS_POLLS) { + qemu_log_mask(LOG_GUEST_ERROR, + "s5l8950x.usb-host: DFU state %u did not become " + "DNLOAD-IDLE\n", s->dfu_state); + return USB_HOST_DFU_ERROR; + } + return USB_HOST_WAIT_DFU_GETSTATUS_SETUP; +} + +static void s5l8950x_usb_otg_complete_descriptor_in( + S5L8950XUSBOTGState *s) +{ + hwaddr dma = s->regs[USB_OTG_DIEPDMA0 / 4]; + uint32_t length = s->regs[USB_OTG_DIEPTSIZ0 / 4] & 0x7f; + uint32_t i; + + length = MIN(length, (uint32_t)sizeof(s->device_descriptor)); + if (dma_memory_read(&address_space_memory, dma, s->device_descriptor, + length, MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, + "s5l8950x.usb: EP0 IN DMA read failed at 0x%08" + HWADDR_PRIx "\n", dma); + return; + } + s->device_descriptor_len = length; + s->descriptor_received = true; + s->host_stage = USB_HOST_WAIT_DEVICE_STATUS_OUT; + s->regs[USB_OTG_DIEPTSIZ0 / 4] &= ~0x7f; + s->regs[USB_OTG_DIEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA; + s->regs[USB_OTG_DIEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL; + qemu_log_mask(LOG_UNIMP, + "s5l8950x.usb-host: device descriptor (%u bytes):", + length); + for (i = 0; i < length; i++) { + qemu_log(" %02x", s->device_descriptor[i]); + } + qemu_log("\n"); + s5l8950x_usb_otg_update_irq(s); +} + +static void s5l8950x_usb_otg_complete_serial_in(S5L8950XUSBOTGState *s) +{ + hwaddr dma = s->regs[USB_OTG_DIEPDMA0 / 4]; + uint32_t length = s->regs[USB_OTG_DIEPTSIZ0 / 4] & 0x7f; + uint32_t available = sizeof(s->serial_descriptor) - + s->serial_descriptor_len; + uint32_t descriptor_length; + bool complete; + + length = MIN(length, available); + if (dma_memory_read(&address_space_memory, dma, + s->serial_descriptor + s->serial_descriptor_len, + length, MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, + "s5l8950x.usb: serial descriptor DMA read failed at " + "0x%08" HWADDR_PRIx "\n", dma); + return; + } + s->serial_descriptor_len += length; + descriptor_length = s->serial_descriptor_len ? + s->serial_descriptor[0] : 0; + complete = length < 64 || + (descriptor_length && + s->serial_descriptor_len >= descriptor_length) || + s->serial_descriptor_len == sizeof(s->serial_descriptor); + + s->regs[USB_OTG_DIEPTSIZ0 / 4] &= ~0x7f; + s->regs[USB_OTG_DIEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA; + s->regs[USB_OTG_DIEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL; + if (complete) { + char serial[128]; + uint32_t i; + uint32_t j = 0; + + for (i = 2; i + 1 < s->serial_descriptor_len && + j + 1 < sizeof(serial); i += 2) { + serial[j++] = s->serial_descriptor[i + 1] ? '?' : + s->serial_descriptor[i]; + } + serial[j] = '\0'; + s->host_stage = USB_HOST_WAIT_SERIAL_STATUS_OUT; + qemu_log_mask(LOG_UNIMP, + "s5l8950x.usb-host: DFU serial: %s\n", serial); + } + s5l8950x_usb_otg_update_irq(s); +} + +static void s5l8950x_usb_otg_complete_config_in(S5L8950XUSBOTGState *s, + bool header_only) +{ + hwaddr dma = s->regs[USB_OTG_DIEPDMA0 / 4]; + uint32_t length = s->regs[USB_OTG_DIEPTSIZ0 / 4] & 0x7f; + uint32_t i; + + length = MIN(length, (uint32_t)sizeof(s->config_descriptor)); + if (dma_memory_read(&address_space_memory, dma, s->config_descriptor, + length, MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, + "s5l8950x.usb: config descriptor DMA read failed at " + "0x%08" HWADDR_PRIx "\n", dma); + return; + } + s->config_descriptor_len = length; + if (header_only && length >= 4) { + s->config_total_length = s->config_descriptor[2] | + (s->config_descriptor[3] << 8); + if (s->config_total_length < 9 || + s->config_total_length > sizeof(s->config_descriptor)) { + s->config_total_length = 9; + } + s->host_stage = USB_HOST_WAIT_CONFIG_HEADER_STATUS_OUT; + } else { + s->host_stage = USB_HOST_WAIT_CONFIG_FULL_STATUS_OUT; + } + s->regs[USB_OTG_DIEPTSIZ0 / 4] &= ~0x7f; + s->regs[USB_OTG_DIEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA; + s->regs[USB_OTG_DIEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL; + qemu_log_mask(LOG_UNIMP, + "s5l8950x.usb-host: config descriptor (%u bytes):", + length); + for (i = 0; i < length; i++) { + qemu_log(" %02x", s->config_descriptor[i]); + } + qemu_log("\n"); + s5l8950x_usb_otg_update_irq(s); +} + +static uint64_t s5l8950x_usb_otg_read(void *opaque, hwaddr offset, + unsigned size) +{ + S5L8950XUSBOTGState *s = opaque; + uint32_t value; + + if (offset == USB_OTG_GRSTCTL) { + value = s->regs[offset / sizeof(uint32_t)] | + USB_OTG_GRSTCTL_AHBIDLE; + } else { + value = s->regs[offset / sizeof(uint32_t)]; + } + return value; +} + +static void s5l8950x_usb_otg_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + S5L8950XUSBOTGState *s = opaque; + + if (offset == USB_OTG_GINTSTS) { + uint32_t old_status = s->regs[offset / 4]; + + s->regs[offset / 4] &= ~value; + if ((old_status & value & USB_OTG_GINT_USBRST) && + !s->enum_done_sent) { + s->regs[offset / 4] |= USB_OTG_GINT_ENUMDONE; + s->enum_done_sent = true; + } + s5l8950x_usb_otg_update_irq(s); + return; + } + if (s5l8950x_usb_otg_is_ep_int(offset, USB_OTG_DIEPINT0) || + s5l8950x_usb_otg_is_ep_int(offset, USB_OTG_DOEPINT0)) { + s->regs[offset / 4] &= ~value; + s5l8950x_usb_otg_update_irq(s); + return; + } + if (offset == USB_OTG_GRSTCTL) { + /* Reset/FIFO flush requests complete synchronously in this model. */ + value &= ~(USB_OTG_GRSTCTL_CSFTRST | + USB_OTG_GRSTCTL_RXFFLSH | + USB_OTG_GRSTCTL_TXFFLSH); + value |= USB_OTG_GRSTCTL_AHBIDLE; + } + s->regs[offset / sizeof(uint32_t)] = value; + if (offset == USB_OTG_GINTMSK && + (value & USB_OTG_GINT_USBRST) && !s->usb_reset_sent) { + s->regs[USB_OTG_GINTSTS / 4] |= USB_OTG_GINT_USBRST; + s->usb_reset_sent = true; + } + if (offset == USB_OTG_DOEPCTL0 && + (value & USB_OTG_EPCTL_EPENA) && s->enum_done_sent) { + if ((value & USB_OTG_EPCTL_STALL) && + s->bridge_request_active) { + s5l8950x_usb_bridge_finish(s, QA6_USB_STATUS_STALL); + } else if (s->host_stage == USB_HOST_DFU_READY && + s->bridge_request_active) { + s5l8950x_usb_bridge_start_request(s); + } else if (s->host_stage == USB_HOST_BRIDGE_WAIT_DATA_OUT) { + s5l8950x_usb_bridge_complete_data_out(s); + } else if (s->host_stage == USB_HOST_BRIDGE_WAIT_STATUS_OUT) { + s5l8950x_usb_bridge_complete_status_out(s); + } else if (s->host_stage == USB_HOST_WAIT_DEVICE_SETUP) { + s5l8950x_usb_otg_send_device_descriptor_setup(s); + } else if (s->host_stage == USB_HOST_WAIT_DEVICE_STATUS_OUT) { + s5l8950x_usb_otg_complete_out_status( + s, USB_HOST_WAIT_SET_ADDRESS_SETUP); + } else if (s->host_stage == USB_HOST_WAIT_SET_ADDRESS_SETUP) { + s5l8950x_usb_otg_send_set_address_setup(s); + } else if (s->host_stage == USB_HOST_ADDRESS_ASSIGNED) { + s5l8950x_usb_otg_send_serial_descriptor_setup(s); + } else if (s->host_stage == USB_HOST_WAIT_SERIAL_STATUS_OUT) { + s5l8950x_usb_otg_complete_out_status( + s, USB_HOST_WAIT_CONFIG_HEADER_SETUP); + } else if (s->host_stage == USB_HOST_WAIT_CONFIG_HEADER_SETUP) { + s5l8950x_usb_otg_send_config_header_setup(s); + } else if (s->host_stage == + USB_HOST_WAIT_CONFIG_HEADER_STATUS_OUT) { + s5l8950x_usb_otg_complete_out_status( + s, USB_HOST_WAIT_CONFIG_FULL_SETUP); + } else if (s->host_stage == USB_HOST_WAIT_CONFIG_FULL_SETUP) { + s5l8950x_usb_otg_send_config_full_setup(s); + } else if (s->host_stage == + USB_HOST_WAIT_CONFIG_FULL_STATUS_OUT) { + s5l8950x_usb_otg_complete_out_status( + s, USB_HOST_WAIT_SET_CONFIGURATION_SETUP); + } else if (s->host_stage == + USB_HOST_WAIT_SET_CONFIGURATION_SETUP) { + s5l8950x_usb_otg_send_set_configuration_setup(s); + } else if (s->host_stage == USB_HOST_DFU_READY && s->dfu_image) { + s5l8950x_usb_dfu_send_getstate(s); + } else if (s->host_stage == + USB_HOST_WAIT_DFU_GETSTATE_STATUS_OUT) { + s5l8950x_usb_otg_complete_out_status( + s, s->dfu_state == USB_DFU_STATE_IDLE ? + USB_HOST_DFU_DNLOAD_READY : USB_HOST_DFU_ERROR); + if (s->dfu_state != USB_DFU_STATE_IDLE) { + qemu_log_mask(LOG_GUEST_ERROR, + "s5l8950x.usb-host: expected DFU-IDLE, got " + "state %u\n", s->dfu_state); + } + } else if (s->host_stage == USB_HOST_DFU_DNLOAD_READY) { + s5l8950x_usb_dfu_send_dnload(s); + } else if (s->host_stage == + USB_HOST_WAIT_DFU_DNLOAD_DATA_OUT) { + s5l8950x_usb_dfu_complete_dnload_data_out(s); + } else if (s->host_stage == + USB_HOST_WAIT_DFU_GETSTATUS_SETUP) { + s5l8950x_usb_dfu_send_getstatus(s); + } else if (s->host_stage == + USB_HOST_WAIT_DFU_GETSTATUS_STATUS_OUT) { + s5l8950x_usb_otg_complete_out_status( + s, s5l8950x_usb_dfu_stage_after_status(s)); + } + + /* Device firmware may arm the status IN stage before SETUP/OUT ends. */ + if (s->regs[USB_OTG_DIEPCTL0 / 4] & USB_OTG_EPCTL_EPENA) { + if (s->host_stage == USB_HOST_BRIDGE_WAIT_STATUS_IN) { + s5l8950x_usb_bridge_complete_status_in(s); + } else if (s->host_stage == + USB_HOST_WAIT_SET_ADDRESS_STATUS_IN) { + s5l8950x_usb_otg_complete_in_status(s); + } else if (s->host_stage == + USB_HOST_WAIT_SET_CONFIGURATION_STATUS_IN) { + s5l8950x_usb_otg_complete_configuration_status_in(s); + } else if (s->host_stage == + USB_HOST_WAIT_DFU_DNLOAD_STATUS_IN) { + s5l8950x_usb_dfu_complete_dnload_status_in(s); + } + } + } + if (offset == USB_OTG_DIEPCTL0 && + (value & USB_OTG_EPCTL_EPENA)) { + if ((value & USB_OTG_EPCTL_STALL) && + s->bridge_request_active) { + s5l8950x_usb_bridge_finish(s, QA6_USB_STATUS_STALL); + } else if (s->host_stage == USB_HOST_BRIDGE_WAIT_DATA_IN) { + s5l8950x_usb_bridge_complete_data_in(s); + } else if (s->host_stage == USB_HOST_BRIDGE_WAIT_STATUS_IN) { + s5l8950x_usb_bridge_complete_status_in(s); + } else if ((value & USB_OTG_EPCTL_STALL) && + s->host_stage == USB_HOST_WAIT_DFU_DNLOAD_STATUS_IN) { + s->host_stage = USB_HOST_DFU_ERROR; + qemu_log_mask(LOG_GUEST_ERROR, + "s5l8950x.usb-host: SecureROM stalled EP0; " + "DFU image rejected after block %u\n", + s->dfu_packet_block); + } else if (s->host_stage == USB_HOST_WAIT_DEVICE_IN) { + s5l8950x_usb_otg_complete_descriptor_in(s); + } else if (s->host_stage == USB_HOST_WAIT_SET_ADDRESS_STATUS_IN) { + s5l8950x_usb_otg_complete_in_status(s); + } else if (s->host_stage == USB_HOST_WAIT_SERIAL_IN) { + s5l8950x_usb_otg_complete_serial_in(s); + } else if (s->host_stage == USB_HOST_WAIT_CONFIG_HEADER_IN) { + s5l8950x_usb_otg_complete_config_in(s, true); + } else if (s->host_stage == USB_HOST_WAIT_CONFIG_FULL_IN) { + s5l8950x_usb_otg_complete_config_in(s, false); + } else if (s->host_stage == + USB_HOST_WAIT_SET_CONFIGURATION_STATUS_IN) { + s5l8950x_usb_otg_complete_configuration_status_in(s); + } else if (s->host_stage == USB_HOST_WAIT_DFU_GETSTATE_IN) { + s5l8950x_usb_dfu_complete_getstate_in(s); + } else if (s->host_stage == + USB_HOST_WAIT_DFU_DNLOAD_STATUS_IN) { + s5l8950x_usb_dfu_complete_dnload_status_in(s); + } else if (s->host_stage == USB_HOST_WAIT_DFU_GETSTATUS_IN) { + s5l8950x_usb_dfu_complete_getstatus_in(s); + } + + /* Device firmware may arm status OUT before the final IN packet. */ + if (s->regs[USB_OTG_DOEPCTL0 / 4] & USB_OTG_EPCTL_EPENA) { + if (s->host_stage == USB_HOST_BRIDGE_WAIT_STATUS_OUT) { + s5l8950x_usb_bridge_complete_status_out(s); + } else if (s->host_stage == USB_HOST_WAIT_DEVICE_STATUS_OUT) { + s5l8950x_usb_otg_complete_out_status( + s, USB_HOST_WAIT_SET_ADDRESS_SETUP); + } else if (s->host_stage == USB_HOST_WAIT_SERIAL_STATUS_OUT) { + s5l8950x_usb_otg_complete_out_status( + s, USB_HOST_WAIT_CONFIG_HEADER_SETUP); + } else if (s->host_stage == + USB_HOST_WAIT_CONFIG_HEADER_STATUS_OUT) { + s5l8950x_usb_otg_complete_out_status( + s, USB_HOST_WAIT_CONFIG_FULL_SETUP); + } else if (s->host_stage == + USB_HOST_WAIT_CONFIG_FULL_STATUS_OUT) { + s5l8950x_usb_otg_complete_out_status( + s, USB_HOST_WAIT_SET_CONFIGURATION_SETUP); + } else if (s->host_stage == + USB_HOST_WAIT_DFU_GETSTATE_STATUS_OUT) { + s5l8950x_usb_otg_complete_out_status( + s, s->dfu_state == USB_DFU_STATE_IDLE ? + USB_HOST_DFU_DNLOAD_READY : USB_HOST_DFU_ERROR); + } else if (s->host_stage == + USB_HOST_WAIT_DFU_GETSTATUS_STATUS_OUT) { + s5l8950x_usb_otg_complete_out_status( + s, s5l8950x_usb_dfu_stage_after_status(s)); + } + } + } + s5l8950x_usb_otg_update_irq(s); +} + +static const MemoryRegionOps s5l8950x_usb_otg_ops = { + .read = s5l8950x_usb_otg_read, + .write = s5l8950x_usb_otg_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_usb_otg_reset(DeviceState *dev) +{ + S5L8950XUSBOTGState *s = S5L8950X_USB_OTG(dev); + + QEMU_BUILD_BUG_ON(sizeof(Qa6UsbFrameHeader) != 32); + + memset(s->regs, 0, sizeof(s->regs)); + s->regs[USB_OTG_GRSTCTL / sizeof(uint32_t)] = + USB_OTG_GRSTCTL_AHBIDLE; + s->usb_reset_sent = false; + s->enum_done_sent = false; + s->setup_sent = false; + s->descriptor_received = false; + s->device_descriptor_len = 0; + s->serial_descriptor_len = 0; + s->config_descriptor_len = 0; + s->config_total_length = 0; + s->host_stage = USB_HOST_WAIT_DEVICE_SETUP; + s->dfu_image_offset = 0; + s->dfu_packet_len = 0; + s->dfu_packet_offset = 0; + s->dfu_packet_block = 0; + s->dfu_next_block = 0; + s->dfu_notify_block = DIV_ROUND_UP(s->dfu_image_size, + USB_DFU_TRANSFER_SIZE); + s->dfu_packet_kind = USB_DFU_PACKET_PAYLOAD; + s->dfu_suffix_pending = false; + s->dfu_state = 0; + s->dfu_status_polls = 0; + s->dfu_finish_statuses = 0; + s->bridge_request_active = false; + s->bridge_rx_len = 0; + s->bridge_out_offset = 0; + s->bridge_in_len = 0; + memset(s->device_descriptor, 0, sizeof(s->device_descriptor)); + memset(s->serial_descriptor, 0, sizeof(s->serial_descriptor)); + memset(s->config_descriptor, 0, sizeof(s->config_descriptor)); + memset(s->dfu_packet, 0, sizeof(s->dfu_packet)); + memset(s->dfu_status, 0, sizeof(s->dfu_status)); + memset(&s->bridge_request, 0, sizeof(s->bridge_request)); + memset(s->bridge_out_data, 0, sizeof(s->bridge_out_data)); + memset(s->bridge_in_data, 0, sizeof(s->bridge_in_data)); + s5l8950x_usb_otg_update_irq(s); +} + +static void s5l8950x_usb_otg_realize(DeviceState *dev, Error **errp) +{ + S5L8950XUSBOTGState *s = S5L8950X_USB_OTG(dev); + g_autoptr(GError) gerr = NULL; + + if (qemu_chr_fe_backend_connected(&s->bridge_chr)) { + qemu_chr_fe_set_handlers(&s->bridge_chr, + s5l8950x_usb_bridge_can_receive, + s5l8950x_usb_bridge_receive, + s5l8950x_usb_bridge_event, + NULL, s, NULL, true); + qemu_log_mask(LOG_UNIMP, + "s5l8950x.usb-bridge: external EP0 bridge enabled\n"); + } + + if (!s->dfu_image_path) { + return; + } + if (!g_file_get_contents(s->dfu_image_path, (gchar **)&s->dfu_image, + &s->dfu_image_size, &gerr)) { + error_setg(errp, "could not load DFU image '%s': %s", + s->dfu_image_path, gerr->message); + return; + } + if (!s->dfu_image_size || + s->dfu_image_size > (size_t)UINT16_MAX * USB_DFU_TRANSFER_SIZE) { + error_setg(errp, "DFU image '%s' has unsupported size %zu", + s->dfu_image_path, s->dfu_image_size); + g_clear_pointer(&s->dfu_image, g_free); + s->dfu_image_size = 0; + return; + } + s5l8950x_usb_dfu_build_suffix(s); + qemu_log_mask(LOG_UNIMP, + "s5l8950x.usb-host: loaded DFU image '%s' (%zu bytes)\n", + s->dfu_image_path, s->dfu_image_size); +} + +static void s5l8950x_usb_otg_unrealize(DeviceState *dev) +{ + S5L8950XUSBOTGState *s = S5L8950X_USB_OTG(dev); + + qemu_chr_fe_set_handlers(&s->bridge_chr, NULL, NULL, NULL, NULL, + NULL, NULL, false); + g_clear_pointer(&s->dfu_image, g_free); + s->dfu_image_size = 0; +} + +static void s5l8950x_usb_otg_init(Object *obj) +{ + S5L8950XUSBOTGState *s = S5L8950X_USB_OTG(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + memory_region_init_io(&s->iomem, obj, &s5l8950x_usb_otg_ops, s, + TYPE_S5L8950X_USB_OTG, + S5L8950X_USB_OTG_REGION_SIZE); + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->irq); +} + +static const Property s5l8950x_usb_otg_properties[] = { + DEFINE_PROP_STRING("dfu-image", S5L8950XUSBOTGState, dfu_image_path), + DEFINE_PROP_CHR("usb-bridge", S5L8950XUSBOTGState, bridge_chr), +}; + +static void s5l8950x_usb_otg_class_init(ObjectClass *klass, + const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = s5l8950x_usb_otg_realize; + dc->unrealize = s5l8950x_usb_otg_unrealize; + device_class_set_legacy_reset(dc, s5l8950x_usb_otg_reset); + device_class_set_props(dc, s5l8950x_usb_otg_properties); +} + +static const TypeInfo s5l8950x_usb_otg_info = { + .name = TYPE_S5L8950X_USB_OTG, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(S5L8950XUSBOTGState), + .instance_init = s5l8950x_usb_otg_init, + .class_init = s5l8950x_usb_otg_class_init, +}; + +static void s5l8950x_usb_otg_register_types(void) +{ + type_register_static(&s5l8950x_usb_otg_info); +} + +type_init(s5l8950x_usb_otg_register_types) diff --git a/hw/usb/s5l8950x-usb-phy.c b/hw/usb/s5l8950x-usb-phy.c new file mode 100644 index 0000000..269e0c6 --- /dev/null +++ b/hw/usb/s5l8950x-usb-phy.c @@ -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) diff --git a/include/hw/arm/s5l8950x.h b/include/hw/arm/s5l8950x.h new file mode 100644 index 0000000..2f9f1fa --- /dev/null +++ b/include/hw/arm/s5l8950x.h @@ -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 */ diff --git a/windows/QemuA6Ude/QemuA6Ude.inf b/windows/QemuA6Ude/QemuA6Ude.inf new file mode 100644 index 0000000..40528c5 --- /dev/null +++ b/windows/QemuA6Ude/QemuA6Ude.inf @@ -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" diff --git a/windows/QemuA6Ude/build-driver.ps1 b/windows/QemuA6Ude/build-driver.ps1 new file mode 100644 index 0000000..9dfd566 --- /dev/null +++ b/windows/QemuA6Ude/build-driver.ps1 @@ -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" diff --git a/windows/QemuA6Ude/common.ps1 b/windows/QemuA6Ude/common.ps1 new file mode 100644 index 0000000..76d164a --- /dev/null +++ b/windows/QemuA6Ude/common.ps1 @@ -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." +} diff --git a/windows/QemuA6Ude/driver.c b/windows/QemuA6Ude/driver.c new file mode 100644 index 0000000..b50254e --- /dev/null +++ b/windows/QemuA6Ude/driver.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/windows/QemuA6Ude/enable-test-mode.ps1 b/windows/QemuA6Ude/enable-test-mode.ps1 new file mode 100644 index 0000000..9b48c9a --- /dev/null +++ b/windows/QemuA6Ude/enable-test-mode.ps1 @@ -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 l’UEFI avant de recommencer.' + } + throw "Impossible d’activer 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.' +) diff --git a/windows/QemuA6Ude/install-driver.ps1 b/windows/QemuA6Ude/install-driver.ps1 new file mode 100644 index 0000000..6e1f378 --- /dev/null +++ b/windows/QemuA6Ude/install-driver.ps1 @@ -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.' diff --git a/windows/QemuA6Ude/prepare-package.ps1 b/windows/QemuA6Ude/prepare-package.ps1 new file mode 100644 index 0000000..613a5f8 --- /dev/null +++ b/windows/QemuA6Ude/prepare-package.ps1 @@ -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 l’installation du certificat de test.' +} + +Write-Host "Paquet pilote prêt : $packageDir" +Write-Host "Certificat public : $certificateCer" diff --git a/windows/QemuA6UsbBridge/bridge.c b/windows/QemuA6UsbBridge/bridge.c new file mode 100644 index 0000000..44cd133 --- /dev/null +++ b/windows/QemuA6UsbBridge/bridge.c @@ -0,0 +1,269 @@ +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include + +#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; +} diff --git a/windows/QemuA6UsbBridge/build-bridge.ps1 b/windows/QemuA6UsbBridge/build-bridge.ps1 new file mode 100644 index 0000000..a3bade5 --- /dev/null +++ b/windows/QemuA6UsbBridge/build-bridge.ps1 @@ -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 l’environnement 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" diff --git a/windows/include/qemu_a6_usb_protocol.h b/windows/include/qemu_a6_usb_protocol.h new file mode 100644 index 0000000..efc951a --- /dev/null +++ b/windows/include/qemu_a6_usb_protocol.h @@ -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