hw/arm: add experimental iPhone 5 A6 platform
This commit is contained in:
@@ -1,5 +1,12 @@
|
|||||||
/GNUmakefile
|
/GNUmakefile
|
||||||
/build/
|
/build/
|
||||||
|
/target/
|
||||||
|
/firmware/
|
||||||
|
/windows/QemuA6Ude/build/
|
||||||
|
/windows/QemuA6Ude/cert/
|
||||||
|
/windows/QemuA6Ude/package/
|
||||||
|
/windows/QemuA6UsbBridge/build/
|
||||||
|
/windows/QemuA6Ude/*.log
|
||||||
/.cache/
|
/.cache/
|
||||||
/.vscode/
|
/.vscode/
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|||||||
+111
@@ -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.
|
||||||
Executable
+47
@@ -0,0 +1,47 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
qemu_bin=${QEMU_BIN:-"$script_dir/build/qemu-system-arm"}
|
||||||
|
rom_path=${ROM_PATH:-"$script_dir/firmware/s5l8950x-secure-rom.bin"}
|
||||||
|
dfu_image=${DFU_IMAGE:-}
|
||||||
|
|
||||||
|
if [ ! -x "$qemu_bin" ]; then
|
||||||
|
echo "QEMU introuvable: $qemu_bin" >&2
|
||||||
|
echo "Configurez ce dépôt avec --target-list=arm-softmmu puis compilez-le." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$rom_path" ]; then
|
||||||
|
echo "SecureROM introuvable: $rom_path" >&2
|
||||||
|
echo "Placez votre dump A6 de 65536 octets dans firmware/s5l8950x-secure-rom.bin." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
-machine iphone5 \
|
||||||
|
-bios "$rom_path" \
|
||||||
|
-no-reboot \
|
||||||
|
-no-shutdown \
|
||||||
|
-display none \
|
||||||
|
-serial mon:stdio
|
||||||
|
|
||||||
|
if [ -n "$dfu_image" ]; then
|
||||||
|
if [ ! -f "$dfu_image" ]; then
|
||||||
|
echo "Image DFU introuvable: $dfu_image" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
set -- "$@" -global "s5l8950x-usb-otg.dfu-image=$dfu_image"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "QEMU macOS : $qemu_bin"
|
||||||
|
echo "SecureROM : $rom_path"
|
||||||
|
if [ -n "$dfu_image" ]; then
|
||||||
|
echo "Image DFU : $dfu_image"
|
||||||
|
echo "Injection DFU interne active."
|
||||||
|
else
|
||||||
|
echo "La SecureROM attend en dfuIDLE."
|
||||||
|
fi
|
||||||
|
echo "UART A6 : cette console (Ctrl+A C = moniteur, Ctrl+A X = quitter)"
|
||||||
|
|
||||||
|
exec "$qemu_bin" "$@"
|
||||||
+171
@@ -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
|
||||||
@@ -51,6 +51,14 @@ config DIGIC
|
|||||||
select PTIMER
|
select PTIMER
|
||||||
select PFLASH_CFI02
|
select PFLASH_CFI02
|
||||||
|
|
||||||
|
config S5L8950X
|
||||||
|
bool
|
||||||
|
default y
|
||||||
|
depends on TCG && ARM
|
||||||
|
select S5L8950X_GPIO
|
||||||
|
select S5L8950X_USB_PHY
|
||||||
|
select UNIMP
|
||||||
|
|
||||||
config EXYNOS4
|
config EXYNOS4
|
||||||
bool
|
bool
|
||||||
default y
|
default y
|
||||||
|
|||||||
@@ -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_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_EXYNOS4', if_true: files('exynos4210.c'))
|
||||||
arm_common_ss.add(when: 'CONFIG_DIGIC', if_true: files('digic.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_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_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'))
|
arm_common_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-h3.c', 'orangepi.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)
|
||||||
@@ -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_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_COLDFIRE', if_true: files('mcf_uart.c'))
|
||||||
system_ss.add(when: 'CONFIG_DIGIC', if_true: files('digic-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_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_MAX78000_UART', if_true: files('max78000_uart.c'))
|
||||||
system_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_uart.c'))
|
system_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_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)
|
||||||
@@ -10,6 +10,9 @@ config GPIO_MPC8XXX
|
|||||||
config GPIO_PWR
|
config GPIO_PWR
|
||||||
bool
|
bool
|
||||||
|
|
||||||
|
config S5L8950X_GPIO
|
||||||
|
bool
|
||||||
|
|
||||||
config SIFIVE_GPIO
|
config SIFIVE_GPIO
|
||||||
bool
|
bool
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
system_ss.add(when: 'CONFIG_GPIO_KEY', if_true: files('gpio_key.c'))
|
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_MPC8XXX', if_true: files('mpc8xxx.c'))
|
||||||
system_ss.add(when: 'CONFIG_GPIO_PWR', if_true: files('gpio_pwr.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_PCA9552', if_true: files('pca9552.c'))
|
||||||
system_ss.add(when: 'CONFIG_PCA9554', if_true: files('pca9554.c'))
|
system_ss.add(when: 'CONFIG_PCA9554', if_true: files('pca9554.c'))
|
||||||
system_ss.add(when: 'CONFIG_PL061', if_true: files('pl061.c'))
|
system_ss.add(when: 'CONFIG_PL061', if_true: files('pl061.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)
|
||||||
@@ -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_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_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_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_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_OMAP', if_true: files('omap_intc.c'))
|
||||||
system_ss.add(when: 'CONFIG_OPENPIC', if_true: files('openpic.c'))
|
system_ss.add(when: 'CONFIG_OPENPIC', if_true: files('openpic.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)
|
||||||
@@ -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_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_A9SCU', if_true: files('a9scu.c'))
|
||||||
system_ss.add(when: 'CONFIG_ARM11SCU', if_true: files('arm11scu.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'))
|
system_ss.add(when: 'CONFIG_ARM_V7M', if_true: files('armv7m_ras.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)
|
||||||
@@ -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)
|
||||||
@@ -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_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_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_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_mct.c'))
|
||||||
system_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_pwm.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'))
|
system_ss.add(when: 'CONFIG_GRLIB', if_true: files('grlib_gptimer.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)
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
config USB
|
config USB
|
||||||
bool
|
bool
|
||||||
|
|
||||||
|
config S5L8950X_USB_PHY
|
||||||
|
bool
|
||||||
|
|
||||||
config USB_UHCI
|
config USB_UHCI
|
||||||
bool
|
bool
|
||||||
default y if PCI_DEVICES
|
default y if PCI_DEVICES
|
||||||
|
|||||||
@@ -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_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_DWC3', if_true: files('hcd-dwc3.c'))
|
||||||
system_ss.add(when: 'CONFIG_USB_CHIPIDEA', if_true: files('chipidea.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_IMX_USBPHY', if_true: files('imx-usb-phy.c'))
|
||||||
system_ss.add(when: 'CONFIG_VT82C686', if_true: files('vt82c686-uhci-pci.c'))
|
system_ss.add(when: 'CONFIG_VT82C686', if_true: files('vt82c686-uhci-pci.c'))
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||||
@@ -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 */
|
||||||
@@ -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"
|
||||||
@@ -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"
|
||||||
@@ -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."
|
||||||
|
}
|
||||||
@@ -0,0 +1,769 @@
|
|||||||
|
/*
|
||||||
|
* QemuA6Ude - Windows USB Device Emulation controller for the QEMU A6 model.
|
||||||
|
*
|
||||||
|
* The UDE child is deliberately exposed as Apple's normal DFU PID (0x1227),
|
||||||
|
* which is what unmodified libirecovery/idevicerestore discovers on Windows.
|
||||||
|
* Class and vendor control transfers are relayed through \\.\QemuA6Ude0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <ntddk.h>
|
||||||
|
#include <wdf.h>
|
||||||
|
#include <usb.h>
|
||||||
|
#include <wdfusb.h>
|
||||||
|
#include <usbdlib.h>
|
||||||
|
#include <ude/1.0/UdeCx.h>
|
||||||
|
#include <initguid.h>
|
||||||
|
#include <usbioctl.h>
|
||||||
|
#include <wdmsec.h>
|
||||||
|
|
||||||
|
#include "../include/qemu_a6_usb_protocol.h"
|
||||||
|
|
||||||
|
#define QA6_POOL_TAG 'U6AQ'
|
||||||
|
#define QA6_DEVICE_NAME L"\\Device\\QemuA6Ude0"
|
||||||
|
#define QA6_SYMBOLIC_LINK L"\\DosDevices\\QemuA6Ude0"
|
||||||
|
#define QA6_HOST_INTERFACE_REF L"QemuA6UdeHost"
|
||||||
|
#define QA6_LANGUAGE_ID 0x0409
|
||||||
|
|
||||||
|
#define QA6_LOG_ERROR(...) \
|
||||||
|
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, \
|
||||||
|
"QemuA6Ude: " __VA_ARGS__)
|
||||||
|
#define QA6_LOG_INFO(...) \
|
||||||
|
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, \
|
||||||
|
"QemuA6Ude: " __VA_ARGS__)
|
||||||
|
|
||||||
|
typedef struct _QA6_CONTROLLER_CONTEXT {
|
||||||
|
WDFQUEUE DefaultQueue;
|
||||||
|
WDFQUEUE WaitingUserReads;
|
||||||
|
WDFSPINLOCK BridgeLock;
|
||||||
|
|
||||||
|
PUDECXUSBDEVICE_INIT ChildInit;
|
||||||
|
UDECXUSBDEVICE ChildDevice;
|
||||||
|
UDECXUSBENDPOINT ControlEndpoint;
|
||||||
|
WDFQUEUE ControlQueue;
|
||||||
|
BOOLEAN PluggedIn;
|
||||||
|
|
||||||
|
WDFREQUEST PendingUrb;
|
||||||
|
ULONG PendingRequestId;
|
||||||
|
ULONG PendingTransferLength;
|
||||||
|
BOOLEAN PendingDirectionIn;
|
||||||
|
|
||||||
|
BOOLEAN RequestFrameReady;
|
||||||
|
ULONG RequestFrameLength;
|
||||||
|
UCHAR RequestFrame[QA6_USB_MAX_FRAME_SIZE];
|
||||||
|
} QA6_CONTROLLER_CONTEXT, *PQA6_CONTROLLER_CONTEXT;
|
||||||
|
|
||||||
|
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(QA6_CONTROLLER_CONTEXT,
|
||||||
|
Qa6GetControllerContext);
|
||||||
|
|
||||||
|
DRIVER_INITIALIZE DriverEntry;
|
||||||
|
EVT_WDF_DRIVER_DEVICE_ADD Qa6EvtDeviceAdd;
|
||||||
|
EVT_WDF_DEVICE_D0_ENTRY Qa6EvtDeviceD0Entry;
|
||||||
|
EVT_WDF_DEVICE_D0_EXIT Qa6EvtDeviceD0Exit;
|
||||||
|
EVT_WDF_OBJECT_CONTEXT_CLEANUP Qa6EvtControllerCleanup;
|
||||||
|
EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL Qa6EvtControllerIoctl;
|
||||||
|
EVT_WDF_IO_QUEUE_IO_READ Qa6EvtBridgeRead;
|
||||||
|
EVT_WDF_IO_QUEUE_IO_WRITE Qa6EvtBridgeWrite;
|
||||||
|
EVT_WDF_IO_QUEUE_IO_CANCELED_ON_QUEUE Qa6EvtCanceledUserRead;
|
||||||
|
EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL Qa6EvtControlUrb;
|
||||||
|
EVT_UDECX_USB_ENDPOINT_RESET Qa6EvtEndpointReset;
|
||||||
|
EVT_UDECX_WDF_DEVICE_QUERY_USB_CAPABILITY Qa6EvtQueryUsbCapability;
|
||||||
|
|
||||||
|
static const USB_DEVICE_DESCRIPTOR Qa6DeviceDescriptor = {
|
||||||
|
sizeof(USB_DEVICE_DESCRIPTOR),
|
||||||
|
USB_DEVICE_DESCRIPTOR_TYPE,
|
||||||
|
0x0200,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x40,
|
||||||
|
0x05ac,
|
||||||
|
0x1227,
|
||||||
|
0x0000,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
1
|
||||||
|
};
|
||||||
|
|
||||||
|
static const UCHAR Qa6ConfigurationDescriptor[] = {
|
||||||
|
0x09, USB_CONFIGURATION_DESCRIPTOR_TYPE,
|
||||||
|
0x19, 0x00,
|
||||||
|
0x01,
|
||||||
|
0x01,
|
||||||
|
0x05,
|
||||||
|
0x80,
|
||||||
|
0xfa,
|
||||||
|
|
||||||
|
0x09, USB_INTERFACE_DESCRIPTOR_TYPE,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0xfe,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
|
||||||
|
0x07, 0x21,
|
||||||
|
0x01,
|
||||||
|
0x0a, 0x00,
|
||||||
|
0x00,
|
||||||
|
0x08
|
||||||
|
};
|
||||||
|
|
||||||
|
static const UCHAR Qa6LanguageDescriptor[] = { 4, USB_STRING_DESCRIPTOR_TYPE,
|
||||||
|
0x09, 0x04 };
|
||||||
|
|
||||||
|
DECLARE_CONST_UNICODE_STRING(Qa6ManufacturerString, L"Apple Inc.");
|
||||||
|
DECLARE_CONST_UNICODE_STRING(Qa6ProductString,
|
||||||
|
L"Apple Mobile Device (DFU Mode)");
|
||||||
|
DECLARE_CONST_UNICODE_STRING(
|
||||||
|
Qa6SerialString,
|
||||||
|
L"CPID:8950 CPRV:20 CPFM:03 SCEP:10 BDID:00 "
|
||||||
|
L"ECID:0000000000200000 IBFL:00 SRTG:[iBoot-1145.3]");
|
||||||
|
DECLARE_CONST_UNICODE_STRING(Qa6ConfigurationString, L"Apple Mobile Device");
|
||||||
|
|
||||||
|
static VOID
|
||||||
|
Qa6CompleteUserRead(
|
||||||
|
_In_ WDFREQUEST Request,
|
||||||
|
_In_reads_bytes_(Length) const UCHAR *Data,
|
||||||
|
_In_ ULONG Length
|
||||||
|
)
|
||||||
|
{
|
||||||
|
PVOID buffer;
|
||||||
|
size_t bufferLength;
|
||||||
|
NTSTATUS status;
|
||||||
|
|
||||||
|
status = WdfRequestRetrieveOutputBuffer(Request, Length, &buffer,
|
||||||
|
&bufferLength);
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
WdfRequestComplete(Request, status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bufferLength < Length) {
|
||||||
|
WdfRequestComplete(Request, STATUS_BUFFER_TOO_SMALL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RtlCopyMemory(buffer, Data, Length);
|
||||||
|
WdfRequestCompleteWithInformation(Request, STATUS_SUCCESS, Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
static VOID
|
||||||
|
Qa6PublishRequestFrame(
|
||||||
|
_In_ WDFDEVICE Controller
|
||||||
|
)
|
||||||
|
{
|
||||||
|
PQA6_CONTROLLER_CONTEXT context = Qa6GetControllerContext(Controller);
|
||||||
|
WDFREQUEST reader = NULL;
|
||||||
|
NTSTATUS status;
|
||||||
|
|
||||||
|
WdfSpinLockAcquire(context->BridgeLock);
|
||||||
|
status = WdfIoQueueRetrieveNextRequest(context->WaitingUserReads, &reader);
|
||||||
|
if (NT_SUCCESS(status)) {
|
||||||
|
context->RequestFrameReady = FALSE;
|
||||||
|
} else {
|
||||||
|
context->RequestFrameReady = TRUE;
|
||||||
|
}
|
||||||
|
WdfSpinLockRelease(context->BridgeLock);
|
||||||
|
|
||||||
|
if (reader != NULL) {
|
||||||
|
Qa6CompleteUserRead(reader, context->RequestFrame,
|
||||||
|
context->RequestFrameLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static NTSTATUS
|
||||||
|
Qa6InitializeUsbDescriptors(
|
||||||
|
_In_ WDFDEVICE Controller
|
||||||
|
)
|
||||||
|
{
|
||||||
|
PQA6_CONTROLLER_CONTEXT context = Qa6GetControllerContext(Controller);
|
||||||
|
UDECX_USB_DEVICE_STATE_CHANGE_CALLBACKS callbacks;
|
||||||
|
NTSTATUS status;
|
||||||
|
|
||||||
|
context->ChildInit = UdecxUsbDeviceInitAllocate(Controller);
|
||||||
|
if (context->ChildInit == NULL) {
|
||||||
|
return STATUS_INSUFFICIENT_RESOURCES;
|
||||||
|
}
|
||||||
|
|
||||||
|
UDECX_USB_DEVICE_CALLBACKS_INIT(&callbacks);
|
||||||
|
UdecxUsbDeviceInitSetStateChangeCallbacks(context->ChildInit, &callbacks);
|
||||||
|
UdecxUsbDeviceInitSetSpeed(context->ChildInit, UdecxUsbHighSpeed);
|
||||||
|
UdecxUsbDeviceInitSetEndpointsType(context->ChildInit,
|
||||||
|
UdecxEndpointTypeSimple);
|
||||||
|
|
||||||
|
status = UdecxUsbDeviceInitAddDescriptor(
|
||||||
|
context->ChildInit, (PUCHAR)&Qa6DeviceDescriptor,
|
||||||
|
(USHORT)sizeof(Qa6DeviceDescriptor));
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = UdecxUsbDeviceInitAddDescriptorWithIndex(
|
||||||
|
context->ChildInit, (PUCHAR)Qa6LanguageDescriptor,
|
||||||
|
(USHORT)sizeof(Qa6LanguageDescriptor), 0);
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = UdecxUsbDeviceInitAddStringDescriptor(
|
||||||
|
context->ChildInit, &Qa6ManufacturerString, 2, QA6_LANGUAGE_ID);
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
status = UdecxUsbDeviceInitAddStringDescriptor(
|
||||||
|
context->ChildInit, &Qa6ProductString, 3, QA6_LANGUAGE_ID);
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
status = UdecxUsbDeviceInitAddStringDescriptor(
|
||||||
|
context->ChildInit, &Qa6SerialString, 4, QA6_LANGUAGE_ID);
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
return UdecxUsbDeviceInitAddStringDescriptor(
|
||||||
|
context->ChildInit, &Qa6ConfigurationString, 5, QA6_LANGUAGE_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
static NTSTATUS
|
||||||
|
Qa6CreateControlEndpoint(
|
||||||
|
_In_ WDFDEVICE Controller
|
||||||
|
)
|
||||||
|
{
|
||||||
|
PQA6_CONTROLLER_CONTEXT context = Qa6GetControllerContext(Controller);
|
||||||
|
WDF_IO_QUEUE_CONFIG queueConfig;
|
||||||
|
PUDECXUSBENDPOINT_INIT endpointInit = NULL;
|
||||||
|
UDECX_USB_ENDPOINT_CALLBACKS callbacks;
|
||||||
|
NTSTATUS status;
|
||||||
|
|
||||||
|
WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, WdfIoQueueDispatchSequential);
|
||||||
|
queueConfig.EvtIoInternalDeviceControl = Qa6EvtControlUrb;
|
||||||
|
queueConfig.PowerManaged = WdfFalse;
|
||||||
|
status = WdfIoQueueCreate(Controller, &queueConfig,
|
||||||
|
WDF_NO_OBJECT_ATTRIBUTES,
|
||||||
|
&context->ControlQueue);
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
endpointInit = UdecxUsbSimpleEndpointInitAllocate(context->ChildDevice);
|
||||||
|
if (endpointInit == NULL) {
|
||||||
|
return STATUS_INSUFFICIENT_RESOURCES;
|
||||||
|
}
|
||||||
|
|
||||||
|
UdecxUsbEndpointInitSetEndpointAddress(endpointInit,
|
||||||
|
USB_DEFAULT_ENDPOINT_ADDRESS);
|
||||||
|
UDECX_USB_ENDPOINT_CALLBACKS_INIT(&callbacks, Qa6EvtEndpointReset);
|
||||||
|
UdecxUsbEndpointInitSetCallbacks(endpointInit, &callbacks);
|
||||||
|
status = UdecxUsbEndpointCreate(&endpointInit,
|
||||||
|
WDF_NO_OBJECT_ATTRIBUTES,
|
||||||
|
&context->ControlEndpoint);
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
if (endpointInit != NULL) {
|
||||||
|
UdecxUsbEndpointInitFree(endpointInit);
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
UdecxUsbEndpointSetWdfIoQueue(context->ControlEndpoint,
|
||||||
|
context->ControlQueue);
|
||||||
|
return STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static NTSTATUS
|
||||||
|
Qa6PlugInUsbDevice(
|
||||||
|
_In_ WDFDEVICE Controller
|
||||||
|
)
|
||||||
|
{
|
||||||
|
PQA6_CONTROLLER_CONTEXT context = Qa6GetControllerContext(Controller);
|
||||||
|
UDECX_USB_DEVICE_PLUG_IN_OPTIONS plugOptions;
|
||||||
|
WDF_OBJECT_ATTRIBUTES attributes;
|
||||||
|
NTSTATUS status;
|
||||||
|
|
||||||
|
if (context->PluggedIn) {
|
||||||
|
return STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = UdecxUsbDeviceInitAddDescriptor(
|
||||||
|
context->ChildInit, (PUCHAR)Qa6ConfigurationDescriptor,
|
||||||
|
(USHORT)sizeof(Qa6ConfigurationDescriptor));
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
|
||||||
|
status = UdecxUsbDeviceCreate(&context->ChildInit, &attributes,
|
||||||
|
&context->ChildDevice);
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = Qa6CreateControlEndpoint(Controller);
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
UDECX_USB_DEVICE_PLUG_IN_OPTIONS_INIT(&plugOptions);
|
||||||
|
plugOptions.Usb20PortNumber = 1;
|
||||||
|
status = UdecxUsbDevicePlugIn(context->ChildDevice, &plugOptions);
|
||||||
|
if (NT_SUCCESS(status)) {
|
||||||
|
context->PluggedIn = TRUE;
|
||||||
|
QA6_LOG_INFO("Apple DFU child plugged in as USB\\VID_05AC&PID_1227\n");
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
NTSTATUS
|
||||||
|
DriverEntry(
|
||||||
|
_In_ PDRIVER_OBJECT DriverObject,
|
||||||
|
_In_ PUNICODE_STRING RegistryPath
|
||||||
|
)
|
||||||
|
{
|
||||||
|
WDF_DRIVER_CONFIG config;
|
||||||
|
WDF_OBJECT_ATTRIBUTES attributes;
|
||||||
|
|
||||||
|
WDF_DRIVER_CONFIG_INIT(&config, Qa6EvtDeviceAdd);
|
||||||
|
config.DriverPoolTag = QA6_POOL_TAG;
|
||||||
|
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
|
||||||
|
return WdfDriverCreate(DriverObject, RegistryPath, &attributes, &config,
|
||||||
|
WDF_NO_HANDLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
NTSTATUS
|
||||||
|
Qa6EvtDeviceAdd(
|
||||||
|
_In_ WDFDRIVER Driver,
|
||||||
|
_Inout_ PWDFDEVICE_INIT DeviceInit
|
||||||
|
)
|
||||||
|
{
|
||||||
|
WDF_PNPPOWER_EVENT_CALLBACKS powerCallbacks;
|
||||||
|
WDF_FILEOBJECT_CONFIG fileConfig;
|
||||||
|
WDF_OBJECT_ATTRIBUTES attributes;
|
||||||
|
UDECX_WDF_DEVICE_CONFIG udeConfig;
|
||||||
|
WDF_IO_QUEUE_CONFIG queueConfig;
|
||||||
|
WDF_IO_QUEUE_CONFIG manualQueueConfig;
|
||||||
|
PQA6_CONTROLLER_CONTEXT context;
|
||||||
|
WDFDEVICE controller;
|
||||||
|
UNICODE_STRING deviceName;
|
||||||
|
UNICODE_STRING symbolicLink;
|
||||||
|
UNICODE_STRING reference;
|
||||||
|
NTSTATUS status;
|
||||||
|
|
||||||
|
UNREFERENCED_PARAMETER(Driver);
|
||||||
|
|
||||||
|
WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&powerCallbacks);
|
||||||
|
powerCallbacks.EvtDeviceD0Entry = Qa6EvtDeviceD0Entry;
|
||||||
|
powerCallbacks.EvtDeviceD0Exit = Qa6EvtDeviceD0Exit;
|
||||||
|
WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &powerCallbacks);
|
||||||
|
|
||||||
|
WDF_FILEOBJECT_CONFIG_INIT(&fileConfig, WDF_NO_EVENT_CALLBACK,
|
||||||
|
WDF_NO_EVENT_CALLBACK,
|
||||||
|
WDF_NO_EVENT_CALLBACK);
|
||||||
|
fileConfig.FileObjectClass = WdfFileObjectWdfCannotUseFsContexts;
|
||||||
|
WdfDeviceInitSetFileObjectConfig(DeviceInit, &fileConfig,
|
||||||
|
WDF_NO_OBJECT_ATTRIBUTES);
|
||||||
|
|
||||||
|
status = WdfDeviceInitAssignSDDLString(
|
||||||
|
DeviceInit, &SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_RW_RES_R);
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = UdecxInitializeWdfDeviceInit(DeviceInit);
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
RtlInitUnicodeString(&deviceName, QA6_DEVICE_NAME);
|
||||||
|
status = WdfDeviceInitAssignName(DeviceInit, &deviceName);
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes,
|
||||||
|
QA6_CONTROLLER_CONTEXT);
|
||||||
|
attributes.EvtCleanupCallback = Qa6EvtControllerCleanup;
|
||||||
|
attributes.ExecutionLevel = WdfExecutionLevelPassive;
|
||||||
|
status = WdfDeviceCreate(&DeviceInit, &attributes, &controller);
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
RtlInitUnicodeString(&symbolicLink, QA6_SYMBOLIC_LINK);
|
||||||
|
status = WdfDeviceCreateSymbolicLink(controller, &symbolicLink);
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
RtlInitUnicodeString(&reference, QA6_HOST_INTERFACE_REF);
|
||||||
|
status = WdfDeviceCreateDeviceInterface(
|
||||||
|
controller, (LPGUID)&GUID_DEVINTERFACE_USB_HOST_CONTROLLER,
|
||||||
|
&reference);
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
UDECX_WDF_DEVICE_CONFIG_INIT(&udeConfig, Qa6EvtQueryUsbCapability);
|
||||||
|
status = UdecxWdfDeviceAddUsbDeviceEmulation(controller, &udeConfig);
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
context = Qa6GetControllerContext(controller);
|
||||||
|
RtlZeroMemory(context, sizeof(*context));
|
||||||
|
|
||||||
|
status = WdfSpinLockCreate(WDF_NO_OBJECT_ATTRIBUTES,
|
||||||
|
&context->BridgeLock);
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
WDF_IO_QUEUE_CONFIG_INIT(&manualQueueConfig, WdfIoQueueDispatchManual);
|
||||||
|
manualQueueConfig.EvtIoCanceledOnQueue = Qa6EvtCanceledUserRead;
|
||||||
|
manualQueueConfig.PowerManaged = WdfFalse;
|
||||||
|
status = WdfIoQueueCreate(controller, &manualQueueConfig,
|
||||||
|
WDF_NO_OBJECT_ATTRIBUTES,
|
||||||
|
&context->WaitingUserReads);
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig,
|
||||||
|
WdfIoQueueDispatchSequential);
|
||||||
|
queueConfig.EvtIoDeviceControl = Qa6EvtControllerIoctl;
|
||||||
|
queueConfig.EvtIoRead = Qa6EvtBridgeRead;
|
||||||
|
queueConfig.EvtIoWrite = Qa6EvtBridgeWrite;
|
||||||
|
queueConfig.PowerManaged = WdfFalse;
|
||||||
|
status = WdfIoQueueCreate(controller, &queueConfig,
|
||||||
|
WDF_NO_OBJECT_ATTRIBUTES,
|
||||||
|
&context->DefaultQueue);
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Qa6InitializeUsbDescriptors(controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
NTSTATUS
|
||||||
|
Qa6EvtDeviceD0Entry(
|
||||||
|
_In_ WDFDEVICE Device,
|
||||||
|
_In_ WDF_POWER_DEVICE_STATE PreviousState
|
||||||
|
)
|
||||||
|
{
|
||||||
|
UNREFERENCED_PARAMETER(PreviousState);
|
||||||
|
return Qa6PlugInUsbDevice(Device);
|
||||||
|
}
|
||||||
|
|
||||||
|
NTSTATUS
|
||||||
|
Qa6EvtDeviceD0Exit(
|
||||||
|
_In_ WDFDEVICE Device,
|
||||||
|
_In_ WDF_POWER_DEVICE_STATE TargetState
|
||||||
|
)
|
||||||
|
{
|
||||||
|
PQA6_CONTROLLER_CONTEXT context = Qa6GetControllerContext(Device);
|
||||||
|
|
||||||
|
if (TargetState == WdfPowerDeviceD3Final && context->PluggedIn) {
|
||||||
|
context->PluggedIn = FALSE;
|
||||||
|
return UdecxUsbDevicePlugOutAndDelete(context->ChildDevice);
|
||||||
|
}
|
||||||
|
return STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID
|
||||||
|
Qa6EvtControllerCleanup(
|
||||||
|
_In_ WDFOBJECT Object
|
||||||
|
)
|
||||||
|
{
|
||||||
|
PQA6_CONTROLLER_CONTEXT context =
|
||||||
|
Qa6GetControllerContext((WDFDEVICE)Object);
|
||||||
|
|
||||||
|
if (context->ChildInit != NULL) {
|
||||||
|
UdecxUsbDeviceInitFree(context->ChildInit);
|
||||||
|
context->ChildInit = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID
|
||||||
|
Qa6EvtControllerIoctl(
|
||||||
|
_In_ WDFQUEUE Queue,
|
||||||
|
_In_ WDFREQUEST Request,
|
||||||
|
_In_ size_t OutputBufferLength,
|
||||||
|
_In_ size_t InputBufferLength,
|
||||||
|
_In_ ULONG IoControlCode
|
||||||
|
)
|
||||||
|
{
|
||||||
|
WDFDEVICE controller = WdfIoQueueGetDevice(Queue);
|
||||||
|
|
||||||
|
UNREFERENCED_PARAMETER(OutputBufferLength);
|
||||||
|
UNREFERENCED_PARAMETER(InputBufferLength);
|
||||||
|
UNREFERENCED_PARAMETER(IoControlCode);
|
||||||
|
|
||||||
|
if (!UdecxWdfDeviceTryHandleUserIoctl(controller, Request)) {
|
||||||
|
WdfRequestComplete(Request, STATUS_INVALID_DEVICE_REQUEST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID
|
||||||
|
Qa6EvtBridgeRead(
|
||||||
|
_In_ WDFQUEUE Queue,
|
||||||
|
_In_ WDFREQUEST Request,
|
||||||
|
_In_ size_t Length
|
||||||
|
)
|
||||||
|
{
|
||||||
|
WDFDEVICE controller = WdfIoQueueGetDevice(Queue);
|
||||||
|
PQA6_CONTROLLER_CONTEXT context = Qa6GetControllerContext(controller);
|
||||||
|
BOOLEAN ready;
|
||||||
|
ULONG frameLength;
|
||||||
|
NTSTATUS status;
|
||||||
|
|
||||||
|
UNREFERENCED_PARAMETER(Length);
|
||||||
|
|
||||||
|
WdfSpinLockAcquire(context->BridgeLock);
|
||||||
|
ready = context->RequestFrameReady;
|
||||||
|
frameLength = context->RequestFrameLength;
|
||||||
|
if (ready) {
|
||||||
|
context->RequestFrameReady = FALSE;
|
||||||
|
}
|
||||||
|
WdfSpinLockRelease(context->BridgeLock);
|
||||||
|
|
||||||
|
if (ready) {
|
||||||
|
Qa6CompleteUserRead(Request, context->RequestFrame, frameLength);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = WdfRequestForwardToIoQueue(Request, context->WaitingUserReads);
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
WdfRequestComplete(Request, status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID
|
||||||
|
Qa6EvtBridgeWrite(
|
||||||
|
_In_ WDFQUEUE Queue,
|
||||||
|
_In_ WDFREQUEST Request,
|
||||||
|
_In_ size_t Length
|
||||||
|
)
|
||||||
|
{
|
||||||
|
WDFDEVICE controller = WdfIoQueueGetDevice(Queue);
|
||||||
|
PQA6_CONTROLLER_CONTEXT context = Qa6GetControllerContext(controller);
|
||||||
|
const Qa6UsbFrameHeader *header;
|
||||||
|
PVOID rawBuffer;
|
||||||
|
size_t rawLength;
|
||||||
|
WDFREQUEST urb = NULL;
|
||||||
|
ULONG originalTransferLength = 0;
|
||||||
|
BOOLEAN directionIn = FALSE;
|
||||||
|
NTSTATUS status;
|
||||||
|
|
||||||
|
UNREFERENCED_PARAMETER(Length);
|
||||||
|
|
||||||
|
status = WdfRequestRetrieveInputBuffer(Request, QA6_USB_HEADER_SIZE,
|
||||||
|
&rawBuffer, &rawLength);
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
WdfRequestComplete(Request, status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
header = (const Qa6UsbFrameHeader *)rawBuffer;
|
||||||
|
if (header->magic != QA6_USB_MAGIC ||
|
||||||
|
header->version != QA6_USB_PROTOCOL_VERSION ||
|
||||||
|
header->type != QA6_USB_MESSAGE_RESPONSE ||
|
||||||
|
header->payload_length > QA6_USB_MAX_PAYLOAD ||
|
||||||
|
rawLength != QA6_USB_HEADER_SIZE + header->payload_length) {
|
||||||
|
WdfRequestComplete(Request, STATUS_INVALID_PARAMETER);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WdfSpinLockAcquire(context->BridgeLock);
|
||||||
|
if (context->PendingUrb != NULL &&
|
||||||
|
context->PendingRequestId == header->request_id) {
|
||||||
|
urb = context->PendingUrb;
|
||||||
|
originalTransferLength = context->PendingTransferLength;
|
||||||
|
directionIn = context->PendingDirectionIn;
|
||||||
|
context->PendingUrb = NULL;
|
||||||
|
}
|
||||||
|
WdfSpinLockRelease(context->BridgeLock);
|
||||||
|
|
||||||
|
if (urb == NULL) {
|
||||||
|
WdfRequestComplete(Request, STATUS_NOT_FOUND);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header->status == QA6_USB_STATUS_SUCCESS) {
|
||||||
|
ULONG completed;
|
||||||
|
|
||||||
|
if (directionIn) {
|
||||||
|
PUCHAR transferBuffer;
|
||||||
|
ULONG transferBufferLength;
|
||||||
|
|
||||||
|
status = UdecxUrbRetrieveBuffer(urb, &transferBuffer,
|
||||||
|
&transferBufferLength);
|
||||||
|
if (NT_SUCCESS(status)) {
|
||||||
|
completed = header->payload_length;
|
||||||
|
if (completed > transferBufferLength) {
|
||||||
|
completed = transferBufferLength;
|
||||||
|
}
|
||||||
|
RtlCopyMemory(transferBuffer,
|
||||||
|
(const UCHAR *)rawBuffer + QA6_USB_HEADER_SIZE,
|
||||||
|
completed);
|
||||||
|
} else {
|
||||||
|
completed = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
completed = header->transfer_length;
|
||||||
|
if (completed > originalTransferLength) {
|
||||||
|
completed = originalTransferLength;
|
||||||
|
}
|
||||||
|
status = STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
UdecxUrbSetBytesCompleted(urb, completed);
|
||||||
|
UdecxUrbCompleteWithNtStatus(urb, status);
|
||||||
|
} else if (header->status == QA6_USB_STATUS_STALL) {
|
||||||
|
UdecxUrbComplete(urb, USBD_STATUS_STALL_PID);
|
||||||
|
} else {
|
||||||
|
UdecxUrbCompleteWithNtStatus(urb, STATUS_DEVICE_NOT_CONNECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
WdfRequestCompleteWithInformation(Request, STATUS_SUCCESS, rawLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID
|
||||||
|
Qa6EvtCanceledUserRead(
|
||||||
|
_In_ WDFQUEUE Queue,
|
||||||
|
_In_ WDFREQUEST Request
|
||||||
|
)
|
||||||
|
{
|
||||||
|
UNREFERENCED_PARAMETER(Queue);
|
||||||
|
WdfRequestComplete(Request, STATUS_CANCELLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID
|
||||||
|
Qa6EvtControlUrb(
|
||||||
|
_In_ WDFQUEUE Queue,
|
||||||
|
_In_ WDFREQUEST Request,
|
||||||
|
_In_ size_t OutputBufferLength,
|
||||||
|
_In_ size_t InputBufferLength,
|
||||||
|
_In_ ULONG IoControlCode
|
||||||
|
)
|
||||||
|
{
|
||||||
|
WDFDEVICE controller = WdfIoQueueGetDevice(Queue);
|
||||||
|
PQA6_CONTROLLER_CONTEXT context = Qa6GetControllerContext(controller);
|
||||||
|
Qa6UsbFrameHeader *header =
|
||||||
|
(Qa6UsbFrameHeader *)context->RequestFrame;
|
||||||
|
WDF_USB_CONTROL_SETUP_PACKET setup;
|
||||||
|
PUCHAR transferBuffer = NULL;
|
||||||
|
ULONG transferBufferLength = 0;
|
||||||
|
ULONG payloadLength = 0;
|
||||||
|
ULONG requestId;
|
||||||
|
NTSTATUS status;
|
||||||
|
|
||||||
|
UNREFERENCED_PARAMETER(OutputBufferLength);
|
||||||
|
UNREFERENCED_PARAMETER(InputBufferLength);
|
||||||
|
|
||||||
|
if (IoControlCode != IOCTL_INTERNAL_USB_SUBMIT_URB) {
|
||||||
|
UdecxUrbCompleteWithNtStatus(Request, STATUS_INVALID_DEVICE_REQUEST);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = UdecxUrbRetrieveControlSetupPacket(Request, &setup);
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
UdecxUrbCompleteWithNtStatus(Request, status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* UDE answers descriptor/enumeration requests from the registered sets. */
|
||||||
|
if ((setup.Generic.Bytes[0] & 0x60) == 0) {
|
||||||
|
UdecxUrbSetBytesCompleted(Request, 0);
|
||||||
|
UdecxUrbCompleteWithNtStatus(Request, STATUS_SUCCESS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setup.Packet.wLength != 0) {
|
||||||
|
status = UdecxUrbRetrieveBuffer(Request, &transferBuffer,
|
||||||
|
&transferBufferLength);
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
UdecxUrbCompleteWithNtStatus(Request, status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (transferBufferLength > QA6_USB_MAX_PAYLOAD) {
|
||||||
|
UdecxUrbCompleteWithNtStatus(Request,
|
||||||
|
STATUS_INVALID_BUFFER_SIZE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((setup.Generic.Bytes[0] & 0x80) == 0) {
|
||||||
|
payloadLength = transferBufferLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
WdfSpinLockAcquire(context->BridgeLock);
|
||||||
|
if (context->PendingUrb != NULL) {
|
||||||
|
WdfSpinLockRelease(context->BridgeLock);
|
||||||
|
UdecxUrbCompleteWithNtStatus(Request, STATUS_DEVICE_BUSY);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestId = ++context->PendingRequestId;
|
||||||
|
if (requestId == 0) {
|
||||||
|
requestId = ++context->PendingRequestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
RtlZeroMemory(header, sizeof(*header));
|
||||||
|
header->magic = QA6_USB_MAGIC;
|
||||||
|
header->version = QA6_USB_PROTOCOL_VERSION;
|
||||||
|
header->type = QA6_USB_MESSAGE_REQUEST;
|
||||||
|
header->request_id = requestId;
|
||||||
|
header->status = QA6_USB_STATUS_SUCCESS;
|
||||||
|
header->payload_length = payloadLength;
|
||||||
|
header->transfer_length = transferBufferLength;
|
||||||
|
RtlCopyMemory(header->setup, setup.Generic.Bytes, sizeof(header->setup));
|
||||||
|
if (payloadLength != 0) {
|
||||||
|
RtlCopyMemory(context->RequestFrame + QA6_USB_HEADER_SIZE,
|
||||||
|
transferBuffer, payloadLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
context->RequestFrameLength = QA6_USB_HEADER_SIZE + payloadLength;
|
||||||
|
context->PendingUrb = Request;
|
||||||
|
context->PendingTransferLength = transferBufferLength;
|
||||||
|
context->PendingDirectionIn =
|
||||||
|
(setup.Generic.Bytes[0] & 0x80) != 0;
|
||||||
|
WdfSpinLockRelease(context->BridgeLock);
|
||||||
|
|
||||||
|
QA6_LOG_INFO("EP0 request %lu bm=%02x req=%02x len=%u\n",
|
||||||
|
requestId, setup.Generic.Bytes[0], setup.Generic.Bytes[1],
|
||||||
|
setup.Packet.wLength);
|
||||||
|
Qa6PublishRequestFrame(controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID
|
||||||
|
Qa6EvtEndpointReset(
|
||||||
|
_In_ UDECXUSBENDPOINT Endpoint,
|
||||||
|
_In_ WDFREQUEST Request
|
||||||
|
)
|
||||||
|
{
|
||||||
|
UNREFERENCED_PARAMETER(Endpoint);
|
||||||
|
WdfRequestComplete(Request, STATUS_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
NTSTATUS
|
||||||
|
Qa6EvtQueryUsbCapability(
|
||||||
|
_In_ WDFDEVICE Device,
|
||||||
|
_In_ PGUID CapabilityType,
|
||||||
|
_In_ ULONG OutputBufferLength,
|
||||||
|
_Out_writes_to_opt_(OutputBufferLength, *ResultLength) PVOID OutputBuffer,
|
||||||
|
_Out_ PULONG ResultLength
|
||||||
|
)
|
||||||
|
{
|
||||||
|
UNREFERENCED_PARAMETER(Device);
|
||||||
|
UNREFERENCED_PARAMETER(OutputBufferLength);
|
||||||
|
UNREFERENCED_PARAMETER(OutputBuffer);
|
||||||
|
|
||||||
|
*ResultLength = 0;
|
||||||
|
if (RtlCompareMemory(
|
||||||
|
CapabilityType,
|
||||||
|
&GUID_USB_CAPABILITY_DEVICE_CONNECTION_HIGH_SPEED_COMPATIBLE,
|
||||||
|
sizeof(GUID)) == sizeof(GUID)) {
|
||||||
|
return STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
return STATUS_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
@@ -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.'
|
||||||
|
)
|
||||||
@@ -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.'
|
||||||
@@ -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"
|
||||||
@@ -0,0 +1,269 @@
|
|||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <windows.h>
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "../include/qemu_a6_usb_protocol.h"
|
||||||
|
|
||||||
|
#define QA6_DEFAULT_HOST "127.0.0.1"
|
||||||
|
#define QA6_DEFAULT_PORT "26050"
|
||||||
|
#define QA6_DRIVER_PATH L"\\\\.\\QemuA6Ude0"
|
||||||
|
|
||||||
|
static volatile LONG Qa6StopRequested;
|
||||||
|
|
||||||
|
static BOOL WINAPI
|
||||||
|
Qa6ConsoleHandler(DWORD event)
|
||||||
|
{
|
||||||
|
if (event == CTRL_C_EVENT || event == CTRL_BREAK_EVENT ||
|
||||||
|
event == CTRL_CLOSE_EVENT) {
|
||||||
|
InterlockedExchange(&Qa6StopRequested, 1);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
Qa6SendAll(SOCKET socketHandle, const unsigned char *buffer, int length)
|
||||||
|
{
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
while (offset < length) {
|
||||||
|
int sent = send(socketHandle, (const char *)buffer + offset,
|
||||||
|
length - offset, 0);
|
||||||
|
if (sent == SOCKET_ERROR || sent == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
offset += sent;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
Qa6ReceiveAll(SOCKET socketHandle, unsigned char *buffer, int length)
|
||||||
|
{
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
while (offset < length) {
|
||||||
|
int received = recv(socketHandle, (char *)buffer + offset,
|
||||||
|
length - offset, 0);
|
||||||
|
if (received == SOCKET_ERROR || received == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
offset += received;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SOCKET
|
||||||
|
Qa6ConnectQemu(const char *host, const char *port)
|
||||||
|
{
|
||||||
|
struct addrinfo hints;
|
||||||
|
struct addrinfo *addresses = NULL;
|
||||||
|
struct addrinfo *address;
|
||||||
|
SOCKET socketHandle = INVALID_SOCKET;
|
||||||
|
int result;
|
||||||
|
|
||||||
|
ZeroMemory(&hints, sizeof(hints));
|
||||||
|
hints.ai_family = AF_INET;
|
||||||
|
hints.ai_socktype = SOCK_STREAM;
|
||||||
|
hints.ai_protocol = IPPROTO_TCP;
|
||||||
|
|
||||||
|
result = getaddrinfo(host, port, &hints, &addresses);
|
||||||
|
if (result != 0) {
|
||||||
|
fprintf(stderr, "Resolution de %s:%s impossible: %d\n", host, port,
|
||||||
|
result);
|
||||||
|
return INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (address = addresses; address != NULL; address = address->ai_next) {
|
||||||
|
socketHandle = socket(address->ai_family, address->ai_socktype,
|
||||||
|
address->ai_protocol);
|
||||||
|
if (socketHandle == INVALID_SOCKET) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (connect(socketHandle, address->ai_addr,
|
||||||
|
(int)address->ai_addrlen) == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
closesocket(socketHandle);
|
||||||
|
socketHandle = INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
|
||||||
|
freeaddrinfo(addresses);
|
||||||
|
return socketHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
static HANDLE
|
||||||
|
Qa6OpenDriver(void)
|
||||||
|
{
|
||||||
|
return CreateFileW(QA6_DRIVER_PATH, GENERIC_READ | GENERIC_WRITE,
|
||||||
|
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
|
||||||
|
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
Qa6ValidateFrame(const unsigned char *frame, DWORD length,
|
||||||
|
unsigned short expectedType)
|
||||||
|
{
|
||||||
|
const Qa6UsbFrameHeader *header;
|
||||||
|
|
||||||
|
if (length < QA6_USB_HEADER_SIZE) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
header = (const Qa6UsbFrameHeader *)frame;
|
||||||
|
if (header->magic != QA6_USB_MAGIC ||
|
||||||
|
header->version != QA6_USB_PROTOCOL_VERSION ||
|
||||||
|
header->type != expectedType ||
|
||||||
|
header->payload_length > QA6_USB_MAX_PAYLOAD ||
|
||||||
|
length != QA6_USB_HEADER_SIZE + header->payload_length) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
Qa6RelayLoop(HANDLE driver, SOCKET qemu)
|
||||||
|
{
|
||||||
|
unsigned char request[QA6_USB_MAX_FRAME_SIZE];
|
||||||
|
unsigned char response[QA6_USB_MAX_FRAME_SIZE];
|
||||||
|
|
||||||
|
while (InterlockedCompareExchange(&Qa6StopRequested, 0, 0) == 0) {
|
||||||
|
Qa6UsbFrameHeader *requestHeader;
|
||||||
|
Qa6UsbFrameHeader *responseHeader;
|
||||||
|
DWORD requestLength = 0;
|
||||||
|
DWORD responseLength;
|
||||||
|
DWORD written = 0;
|
||||||
|
|
||||||
|
if (!ReadFile(driver, request, sizeof(request), &requestLength,
|
||||||
|
NULL)) {
|
||||||
|
DWORD error = GetLastError();
|
||||||
|
fprintf(stderr, "Lecture du pilote impossible: erreur %lu\n",
|
||||||
|
error);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (Qa6ValidateFrame(request, requestLength,
|
||||||
|
QA6_USB_MESSAGE_REQUEST) != 0) {
|
||||||
|
fprintf(stderr, "Trame invalide recue du pilote (%lu octets)\n",
|
||||||
|
requestLength);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestHeader = (Qa6UsbFrameHeader *)request;
|
||||||
|
printf("USB EP0 #%lu: bm=%02x req=%02x value=%02x%02x "
|
||||||
|
"index=%02x%02x len=%u\n",
|
||||||
|
requestHeader->request_id,
|
||||||
|
requestHeader->setup[0], requestHeader->setup[1],
|
||||||
|
requestHeader->setup[3], requestHeader->setup[2],
|
||||||
|
requestHeader->setup[5], requestHeader->setup[4],
|
||||||
|
(unsigned)(requestHeader->setup[6] |
|
||||||
|
(requestHeader->setup[7] << 8)));
|
||||||
|
fflush(stdout);
|
||||||
|
|
||||||
|
if (Qa6SendAll(qemu, request, (int)requestLength) != 0) {
|
||||||
|
fprintf(stderr, "Connexion QEMU interrompue pendant l'envoi.\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (Qa6ReceiveAll(qemu, response, QA6_USB_HEADER_SIZE) != 0) {
|
||||||
|
fprintf(stderr, "Connexion QEMU interrompue pendant la reponse.\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
responseHeader = (Qa6UsbFrameHeader *)response;
|
||||||
|
if (responseHeader->payload_length > QA6_USB_MAX_PAYLOAD) {
|
||||||
|
fprintf(stderr, "Longueur de reponse QEMU invalide.\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
responseLength = QA6_USB_HEADER_SIZE +
|
||||||
|
responseHeader->payload_length;
|
||||||
|
if (responseHeader->payload_length != 0 &&
|
||||||
|
Qa6ReceiveAll(qemu, response + QA6_USB_HEADER_SIZE,
|
||||||
|
(int)responseHeader->payload_length) != 0) {
|
||||||
|
fprintf(stderr, "Connexion QEMU interrompue dans les donnees.\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (Qa6ValidateFrame(response, responseLength,
|
||||||
|
QA6_USB_MESSAGE_RESPONSE) != 0 ||
|
||||||
|
responseHeader->request_id != requestHeader->request_id) {
|
||||||
|
fprintf(stderr, "Reponse QEMU invalide pour la requete #%lu.\n",
|
||||||
|
requestHeader->request_id);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!WriteFile(driver, response, responseLength, &written, NULL) ||
|
||||||
|
written != responseLength) {
|
||||||
|
DWORD error = GetLastError();
|
||||||
|
fprintf(stderr, "Ecriture vers le pilote impossible: erreur %lu\n",
|
||||||
|
error);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
const char *host = argc > 1 ? argv[1] : QA6_DEFAULT_HOST;
|
||||||
|
const char *port = argc > 2 ? argv[2] : QA6_DEFAULT_PORT;
|
||||||
|
WSADATA winsockData;
|
||||||
|
HANDLE driver = INVALID_HANDLE_VALUE;
|
||||||
|
SOCKET qemu = INVALID_SOCKET;
|
||||||
|
int exitCode = 1;
|
||||||
|
|
||||||
|
if (sizeof(Qa6UsbFrameHeader) != 32) {
|
||||||
|
fprintf(stderr, "Erreur interne: taille du protocole USB=%zu\n",
|
||||||
|
sizeof(Qa6UsbFrameHeader));
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetConsoleCtrlHandler(Qa6ConsoleHandler, TRUE);
|
||||||
|
if (WSAStartup(MAKEWORD(2, 2), &winsockData) != 0) {
|
||||||
|
fprintf(stderr, "Initialisation Winsock impossible.\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Pont USB QEMU A6 natif Windows\n");
|
||||||
|
printf("Pilote : \\\\.\\QemuA6Ude0\n");
|
||||||
|
printf("QEMU : %s:%s\n", host, port);
|
||||||
|
|
||||||
|
while (InterlockedCompareExchange(&Qa6StopRequested, 0, 0) == 0) {
|
||||||
|
driver = Qa6OpenDriver();
|
||||||
|
if (driver != INVALID_HANDLE_VALUE) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
printf("Attente du pilote QemuA6Ude...\n");
|
||||||
|
Sleep(1000);
|
||||||
|
}
|
||||||
|
if (driver == INVALID_HANDLE_VALUE) {
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (InterlockedCompareExchange(&Qa6StopRequested, 0, 0) == 0) {
|
||||||
|
qemu = Qa6ConnectQemu(host, port);
|
||||||
|
if (qemu != INVALID_SOCKET) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
printf("Attente de QEMU sur %s:%s...\n", host, port);
|
||||||
|
Sleep(1000);
|
||||||
|
}
|
||||||
|
if (qemu == INVALID_SOCKET) {
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Connecte. idevicerestore peut utiliser l'iPhone DFU virtuel.\n");
|
||||||
|
exitCode = Qa6RelayLoop(driver, qemu) == 0 ? 0 : 1;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
if (qemu != INVALID_SOCKET) {
|
||||||
|
shutdown(qemu, SD_BOTH);
|
||||||
|
closesocket(qemu);
|
||||||
|
}
|
||||||
|
if (driver != INVALID_HANDLE_VALUE) {
|
||||||
|
CloseHandle(driver);
|
||||||
|
}
|
||||||
|
WSACleanup();
|
||||||
|
return exitCode;
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user