Compare commits

..
Author SHA1 Message Date
Workbench IA 8037181992 Rétablir le redémarrage à chaud de SpringBoard et de l’affichage 2026-10-01 10:12:48 +00:00
Mahtan 0286ad8380 Merge pull request 'Rétablir PPP après un reset matériel de l’iPod' (#5) from agent/reprise-ppp into ipod_touch_2g 2026-10-01 11:33:12 +02:00
Workbench IA 993cd88488 Rétablir le lien PPP après un reset matériel 2026-10-01 09:31:36 +00:00
Mahtan 6fc3f004cc Merge pull request 'Valider iPhone OS 2.1.1 et rendre les tests Safari reproductibles' (#3) from agent/validation-iphoneos21 into ipod_touch_2g 2026-10-01 09:47:59 +02:00
Workbench IA 53ebaa7e40 Réconcilier la validation 2.1.1 avec le RTC D1759 2026-10-01 07:47:57 +00:00
Mahtan 11a98a7bac Merge pull request 'Émuler le RTC D1759 dès le démarrage' (#4) from agent/rtc-d1759 into ipod_touch_2g 2026-10-01 09:45:44 +02:00
Workbench IA d2c3478fa6 Émuler le RTC D1759 et supprimer le réglage automatique de l'heure 2026-10-01 07:41:33 +00:00
Workbench IA 21a155d35f Valider iPhone OS 2.1.1 et documenter les tests des applications 2026-10-01 07:17:19 +00:00
Mahtan 436bdc536c Valider les boutons, le tactile et les outils du simulateur
Relecture et validations locales terminées : iPhone OS 2.2.1 et 2.1.1, gestes, HTTP, saisie, installation et persistance.
2026-10-01 09:15:58 +02:00
Workbench IA 5f65af21fc Fiabiliser les transferts et automatiser les tests d'applications 2026-09-30 16:27:48 +00:00
Workbench IA 524e9bbb17 Corriger les accès GPIO et fiabiliser le tactile au réveil 2026-09-30 16:25:06 +00:00
Mahtan e0fd5d5eff Fiabiliser le simulateur et déverrouiller SpringBoard après installation
Relecture et validations locales effectuées : démarrage v11, installation persistante et lancement HelloEmu, session déjà lancée, erreurs QEMU et réseau.
2026-09-30 17:13:13 +02:00
39 changed files with 2431 additions and 139 deletions

No files matched your search

+2
View File
@@ -20,3 +20,5 @@ __pycache__/
/qemu-ios-generate-nor/
!/patches/
!/patches/*.patch
/apps/TouchProbe/TouchProbe.app/
+125 -3
View File
@@ -1,6 +1,128 @@
# Passation — simulateur iPod Touch 2G / iPhone OS 2.2.1
État au 2026-09-30. Environnement : conteneur Arch Linux, utilisateur `dev` avec sudo, sans `CAP_NET_ADMIN`, sans `/dev/ppp` ni TUN.
État au 2026-10-01. Environnement : conteneur Arch Linux, utilisateur `dev` avec sudo, sans `CAP_NET_ADMIN`, sans `/dev/ppp` ni TUN.
## Redémarrage à chaud de l'interface
Le blocage de `respring` est reproduit sur 5H11a : le nouveau SpringBoard reste
bloqué dans `IOServiceClose`, après l'ouverture d'AppleM2TVOut. Le modèle de
sortie TV limitait ses interruptions à deux pour toute la vie de QEMU. Il répond
maintenant à chaque soumission, conserve les événements masqués et acquitte les
bits demandés. Le LCD dispose également de registres distincts pour le masque
(0x08) et l'état W1C (0x0c), conformément aux accès du pilote AppleM2CLCD.
Pour le reset matériel, les états LCD, TV et MIPI sont réinitialisés. La machine
réalise désormais le périphérique MIPI : sans cette étape, son callback de reset
n'était jamais appelé, ce qui faisait échouer la lecture de l'identifiant de dalle.
Le tactile libère les transferts/trames en cours, annule ses timers et relâche
son interruption. SPI vide ses FIFO et abaisse aussi sa ligne d'interruption.
`ipod-emu respring -W` et `ipod-emu reset -W` synchronisent la NAND, mémorisent
la position du journal puis attendent uniquement les nouveaux marqueurs. Le
client `unlock` utilise la même position. `ipod-agent respring` et `ipod-qmp reset`
restent des commandes brutes ; utiliser le lanceur pour attendre le nouveau boot.
Régressions : neuf tests tactile/SPI et trois tests d'affichage sous ASan/UBSan,
cinq tests du lanceur et trois resets QMP/QTest des périphériques d'affichage
réels. Le test natif `tests/test-display-session.py` vérifie le déverrouillage,
la calculatrice au premier plan, HTTP local, le fichier témoin en NAND et les PID
QEMU/relais conservés. Diagnostics : `work/interface-20261001/`.
Validation native finale : trois resprings et deux resets complets par firmware
(5H11a et 5F138), avec les mêmes PID QEMU/relais. La calculatrice affiche
`7 × 8 = 56` après les relances sur les deux versions. Les resprings complets
prennent environ 26–28 s, les resets 64–80 s dans cet environnement partagé.
Synthèse : `work/interface-20261001/validation-finale.json`.
La session utilisateur sur VNC 5900 reste sur le binaire précédemment déployé.
## Reprise PPP après reset matériel
L'UART possède maintenant un callback de reset : registres et FIFO remis à
zéro, IRQ baissée, `qemu_chr_fe_disconnect` sur la connexion série. La fermeture
fait repartir la machine LCP/IPCP du relais existant, sans tuer `ipod-netd` ni
QEMU et sans remplacer la NAND. `ipod-qmp reset` expose le reset brut ; faire
`ipod-agent sync` avant son utilisation.
Validation native avec l'agent v12 : deux resets successifs par firmware,
5H11a (48,2 / 51,3 s) et 5F138 (48,4 / 51,6 s), mêmes PID QEMU et relais,
fichier témoin conservé et requête HTTP locale réussie depuis l'iPod.
Le diagnostic attend `ppp0 up` et `configd publié`, puis laisse cinq secondes
au réseau avant les transferts. Des fermetures TCP ponctuelles ont été observées
avec des requêtes immédiatement après les premiers pings ; un ping isolé ne
suffit donc pas à qualifier la stabilité du service.
Les deux régressions UART passent sous ASan/UBSan, dont trois reconnexions avec
émission et réception sur un vrai backend socket QEMU.
Les premiers essais du correctif PPP laissaient encore l'affichage bloqué après
reset. Les causes et les corrections sont décrites dans la section précédente.
Les anciens diagnostics sont conservés dans `work/ppp-20261001` ; ils précèdent
la correction de la sortie TV et ne décrivent plus l'état actuel du code.
## Dernière mise à jour — RTC D1759
Le modèle `d1759` remplace `pcf50633` sur la machine 2G. Il corrige le
pointeur I²C et fournit le compteur LE32 en `0x5c` avec décalage Unix LE32 en
`0x64`. Les notes anciennes attribuant un accès indirect RTC à `0x61/0x62`
étaient incorrectes : ces registres concernent le démarrage.
`start -W` et `wait` ne règlent plus l'heure via l'agent.
Voir [RTC.md](RTC.md) pour le protocole, les preuves et les limites.
Le raccordement GPIO est aussi borné aux cinq IRQ définies, pour éviter
l'accès hors limites révélé à la recompilation.
## Validation du 1er octobre 2026 — iPhone OS 2.1.1
- Image distincte `5F138-validation-20261001`, créée depuis la base 5F138 conservée,
avec agent v12, TouchProbe et HelloEmu. Session : `work/validation-20261001/`.
`system-version.plist` confirme 2.1.1 / 5F138 ; démarrage déverrouillé en 28 s.
- Calibration : sept points exacts de `(20,40)` à `(300,460)` dans
`calibration-complete.log`, après attente du marqueur `TouchProbe ready`.
Les ordonnées du journal sont relatives à la vue, située à y=20.
- Six cycles alternant Power/Home et Power/Power passent, sans perte du tactile.
- Safari : clavier virtuel QWERTY, formulaire envoyé avec `texte=bonjour`,
défilement jusqu'au lien final et requête `/fin` reçue par le serveur HTTP local.
Journal : `http/requests.jsonl`. Une version réutilisable de la page et du serveur
est fournie dans `tests/http-fixture.py` ; aucun site extérieur n'est requis.
- Calculatrice : `7 × 8 = 56`, capture `calculatrice-56.png`.
- Notes : note « Bonjour » créée par le clavier virtuel puis sauvegardée.
Les lignes de `Note` et `note_bodies` sont identiques dans `notes-avant.db` et
`notes-apres.db`, et la note réapparaît dans l'interface après redémarrage.
- Nouvelle app `HelloEmu21.app` (bundle-id `com.ioslegacy.hello21`) installée via
`push-app`, cache reconstruit, redémarrage sans réinitialiser la NAND en 50 s,
lancement au premier plan et journal HelloEmu validés. Captures :
`hello21-apres-push.png` et `note-apres-reboot.png`.
- Synthèse locale : `validation.json`. Session et serveur HTTP de test arrêtés.
Aucun défaut `Killing device`, `Fatal Exception` ou `MACH Reboot` relevé dans
la console finale. Les images et NAND d'origine sont conservées.
- Particularités : l'image 5F138 conserve ses réglages de langue et de fuseau
d'origine ; les captures sont en anglais. Le lancement `--url` d'un Safari
déjà ouvert n'a pas changé la page pendant le test : passer par les liens ou
relancer le navigateur. Le RTC est depuis implémenté (voir ci-dessus) ; la veille profonde reste à faire.
## Historique — fiabilisation GPIO/SPI et agent v12
Les sections de diagnostic plus anciennes ci-dessous décrivent les étapes précédentes.
Le workflow `push-app` puis déverrouillage/lancement a été validé avec l'agent v11.
Cette suite corrige les débordements GPIO/SYSIC, les commandes tactiles invalides,
les trames instables et les transactions SPI abandonnées à la reprise de l'écran.
Huit cycles veille/réveil alternant Home et Power ont passé sur 5H11a sans perte
du tactile. La calibration TouchProbe retrouve les coordonnées écran sur sept
points ; la vitesse verticale et le calcul signé des vitesses sont corrigés.
L'agent v12 conserve le protocole et transfère par blocs de 64 Kio avec fichiers
temporaires distincts. `ipod-test-app` prépare une NAND indépendante, vérifie le
premier plan, récupère journal et capture, puis arrête sa propre session.
Voir le README pour les commandes et les tests reproductibles ASan/UBSan.
Validation finale : huit régressions matérielles avec ASan/UBSan et sept tests
sur les transferts ; HelloEmu réussit le workflow automatique, l'application
absente échoue avec capture et arrêt de la VM. Un fichier de 8 460 608 octets
fait l'aller-retour via l'agent v12 avec SHA-256 identique. Trois cycles de
veille/réveil supplémentaires avec la calibration finale et l'agent v12 passent,
puis HelloEmu se lance au premier plan avec son journal. Diagnostics locaux :
`work/fiabilisation-20260930/` (dont `test-hello`, `test-echec`, `hello-final.png`).
Les sessions de validation sont arrêtées.
Les fichiers générés, images et diagnostics restent hors du dépôt. Les images
5H11a et les NAND d'origine sont conservées ; le RTC, le Wi-Fi matériel, l'audio
et la veille profonde restent à implémenter. Les nouveaux correctifs n’étaient
pas encore validés sur 5F138 à cette étape.
## Objectif
@@ -53,7 +175,7 @@ On veut le comportement le plus fidèle possible à un vrai appareil. La consign
- Ancienne base sauvegardée : `images/5H11a/nand-readonly-backup`, `rootfs-v8-backup.img`,
`machine-opts-readonly-backup`. Session d'origine : `run/nand-before-persistence` et `fw-before-persistence`.
Session de test avec app et fichier de 8 Mio : `run/nand-persistence-verified` (firmware `persistence-test-v10`).
2. **Horloge RTC du PMU** non émulée : le D1759 passe par un accès indirect, index en `0x61` et donnée en `0x62`. Le modèle PCF50633 existant est faux pour le 2G. Contournement en place : `settime`.
2. **Horloge RTC du PMU** : compteur et décalage D1759 implémentés ; IRQ d'alarme et persistance du décalage entre instances QEMU restent absentes. Voir la dernière mise à jour et `RTC.md`.
3. **Réglages > Wi-Fi** affiche « Pas de Wi-Fi », parce que la puce BCM4325 n'est pas émulée. Le réseau passe par une interface ppp0, que l'app peut voir comme un VPN ou une connexion transitoire. Émuler le WiFi pour de vrai (SDIO + firmware FullMAC) serait un gros chantier ; le pont slirp est réutilisable.
4. **Pas encore écrits** :
- un script `ipod-test-app` (construit l'image, boote, lance l'app avec `--log`, fait une capture, affiche les logs) ;
@@ -64,7 +186,7 @@ On veut le comportement le plus fidèle possible à un vrai appareil. La consign
## Démarrer
```bash
ipod-emu start -f -W # 2.2.1 (défaut), NAND vierge, attend SpringBoard puis règle l'heure
ipod-emu start -f -W # 2.2.1 (défaut), NAND vierge, attend SpringBoard
ipod-agent ping # l'agent répond quand PPP est monté (quelques secondes)
ipod-agent launch com.apple.mobilesafari --url http://example.com/
ipod-qmp screenshot /tmp/s.png # tap X Y | swipe | home | hmp "info registers"
+165 -11
View File
@@ -27,7 +27,7 @@ redirection. `stop` nettoie le relais suivi par PID même si QEMU est déjà arr
## Utilisation
```bash
ipod-emu start -W # agent v11 : conserve la NAND, attend SpringBoard et déverrouille (~35–50 s)
ipod-emu start -w 5H11a-agent-v12 -W # attend SpringBoard et déverrouille (~25–50 s)
# Ajouter -f uniquement pour remettre la NAND à zéro.
ipod-qmp screenshot ecran.png # capture 320x480
ipod-qmp tap 160 326 # touche (coordonnées écran), ipod-qmp tap X Y 1.5 = appui long
@@ -37,14 +37,13 @@ ipod-emu log # console du kernel + syslog iOS
ipod-emu stop
```
- Pas de touch avant que `ipod-emu wait` / `start -W` ait rendu la main : le contrôleur tactile plante QEMU s'il est
touché pendant son démarrage.
- Attendre `ipod-emu wait` / `start -W` avant les gestes : le pilote tactile invité doit avoir terminé son démarrage.
- VNC sur `127.0.0.1:5900` pour interagir à la main (tunnel SSH : `ssh -L 5900:127.0.0.1:5900 …`).
- Le lancement d'une app prend 10-20 s.
## Déverrouillage après redémarrage
Avec l'agent **v11**, `start -W` et `wait` vérifient le verrouillage, réveillent
Avec l'agent **v11 ou ultérieur**, `start -W` et `wait` vérifient le verrouillage, réveillent
l'écran et effectuent le glissement normal avant de rendre la main.
`ipod-agent screen` retourne `locked=0 passcode=0` une fois déverrouillé ;
`ipod-agent unlock` permet de refaire ce parcours après démarrage.
@@ -72,6 +71,36 @@ conserver sa session précédente ou utiliser un environnement de test séparé.
Le dossier de diagnostic `work/reprise-20260930` conserve une session avec l'agent
v11 et HelloEmu, indépendante de la session d'origine.
## Redémarrage à chaud
```bash
ipod-emu respring -W # relance SpringBoard, attend puis déverrouille
ipod-emu reset -W # redémarre iOS dans le même processus QEMU
```
Les deux commandes synchronisent les écritures avant la relance. Elles conservent
la NAND de session et les processus QEMU et `ipod-netd`. Sans `-W`, elles rendent
la main après la demande ; `ipod-emu wait` peut attendre la suite.
L'attente ne lit que les nouveaux marqueurs du journal, puis vérifie le
déverrouillage. Aucun glissement automatique n'est effectué si un code est activé.
La sortie TV émulée répond à chaque soumission, sans l'ancien quota de deux
interruptions qui bloquait SpringBoard dans `IOServiceClose`. Le LCD sépare le
masque d'interruptions de leur acquittement. Un reset réinitialise aussi le
contrôleur MIPI, l'état du tactile et les interruptions SPI.
Le reset de l'UART ferme la connexion série. `ipod-netd` se reconnecte au même
socket et renégocie LCP/IPCP avec le nouveau `pppd`. Pour vérifier le réseau :
```bash
ipod-agent ping
ipod-agent net # ppp0 up et configd publié
```
`ipod-agent respring` et `ipod-qmp reset` restent les accès bruts, sans attente
ni mise à jour du point de départ du journal. Avant un reset brut, exécuter
`ipod-agent sync`. Préférer les commandes `ipod-emu` pour un cycle complet.
## Tester nos apps
```bash
@@ -89,6 +118,107 @@ Les `.app`/`.ipa` vont dans `/Applications` (re-signés avec `ldid -S` ; le kern
la toolchain de l'environnement (`arm-apple-darwin9-gcc -isysroot /opt/theos/sdks/iPhoneOS2.2.1.sdk`), voir
`apps/HelloEmu`. Environ 230 Mo libres dans l'image 2.2.1.
## Test automatique et diagnostics
```bash
# Construire d'abord l'agent v12 et une image dédiée depuis la base inchangée :
PATH=/opt/cctools/bin:$PATH make -C agent
ipod-mkimage -b 5H11a -o 5H11a-agent-v12 agent/overlay
# Après avoir arrêté sa session, tester dans une NAND indépendante :
ipod-test-app MonApp.app
ipod-test-app MonApp.ipa --output /chemin/nouveau-dossier
ipod-test-app paquet.deb --bundle-id com.exemple.monapp
```
Les chemins `agent/` ci-dessus sont relatifs au dossier des outils ; dans le dépôt,
utiliser `contrib/ipod-emu/agent`. Choisir un nom d'image absent lors de sa création.
`--base` permet de choisir une autre image contenant l'agent v11 ou ultérieur.
Le bundle-id est lu dans le plist des `.app` et `.ipa` ; il doit être fourni pour
les `.deb` et overlays. Le runner refuse de démarrer si les ports VNC ou agent
sont déjà occupés. Chaque test crée une image et une session dans son propre
répertoire ; la NAND de `run/` et l'image de base restent conservées.
Le code de sortie vaut zéro uniquement si l'app apparaît puis reste au premier
plan pendant `--settle` secondes (3 par défaut), si les diagnostics sont collectés
et si la VM est arrêtée. Ce contrôle ne remplace pas les tests fonctionnels de l'app.
Les résultats vont dans `work/tests/<date>-<identifiant>/`, ou dans un nouveau dossier
`--output` : `result.json`, `workflow.log`, `app.log`, `screen.png` et les logs QEMU,
série et réseau dans `session/run/`. En cas d'échec, les diagnostics disponibles
sont conservés et la session de test est arrêtée.
## Tactile et transferts
Home/Power ne doivent plus écrire hors du tableau GPIO : le port C est inclus.
Les trous des registres SYSIC ne débordent plus dans le groupe voisin.
Le tactile traite séparément les requêtes de longueur et de données, accepte
EA/EB, conserve une trame stable, et abandonne proprement une commande lors de
la réinitialisation SPI. Les commandes et rapports inconnus ne font plus arrêter
QEMU. Le découpage des requêtes est documenté également dans
[OpeniBoot Z2](https://github.com/iDroid-Project/openiBoot/blob/master/plat-s5l8900/multitouch-z2.c).
La vitesse verticale utilise `y`, les vitesses conservent leur signe et sont
bornées à 16 bits. `apps/TouchProbe` permet de mesurer la calibration : compiler
avec son Makefile, lancer avec `--log`, puis comparer les gestes QMP au marqueur
bleu et aux coordonnées du journal (ajouter 20 pixels à l'ordonnée locale de la vue).
La calibration a été vérifiée sur 5H11a et 5F138, sur sept points de `(20,40)` à `(300,460)`.
Agent **v12** : `put` et `get` utilisent des blocs de 64 Kio au lieu d'allouer
le fichier entier dans l'iPod. Un upload incomplet supprime son temporaire et
conserve le fichier précédent ; le client fait de même pour les téléchargements.
Le protocole reste compatible avec l'agent v11. Les téléchargements vers stdout
peuvent naturellement afficher un préfixe avant une erreur de transport.
Depuis le dépôt, les tests sans firmware sont :
```bash
contrib/ipod-emu/tests/run-uart # reset des registres/FIFO/IRQ et trois reconnexions socket, ASan/UBSan
contrib/ipod-emu/tests/run-display # LCD, MIPI, sortie TV et resets, ASan/UBSan
python3 contrib/ipod-emu/tests/test-reset-launcher.py # synchronisation et nouveaux marqueurs
contrib/ipod-emu/tests/run-rtc # protocole I²C et horloge D1759, ASan/UBSan
contrib/ipod-emu/tests/run-multitouch # build QEMU arm existant, Ninja, compilateur avec ASan/UBSan
python3 contrib/ipod-emu/tests/test-transfers.py # Python et compilateur C, sockets locales
```
## Validation sous iPhone OS 2.1.1
Le firmware 5F138 a été vérifié avec l'agent v12 et les mêmes corrections QEMU.
Conserver l'image 5F138 d'origine et produire une image dérivée avec un nom absent :
```bash
ipod-mkimage -b 5F138 -o 5F138-agent-v12 agent/overlay apps/TouchProbe/TouchProbe.app apps/HelloEmu/HelloEmu.app
```
Utiliser ensuite un environnement `IPOD_EMU_HOME` indépendant pour démarrer cette
image ; un changement de firmware remplace la NAND de la session choisie.
`SystemVersion.plist` confirme iPhone OS 2.1.1 / 5F138. Les sept points de
calibration sont exacts et six cycles de veille/réveil alternant Home et Power
passent, sans désactivation du tactile.
Pour tester Safari sans dépendre d'un site extérieur, depuis le dépôt :
```bash
python3 contrib/ipod-emu/tests/http-fixture.py --output /chemin/diagnostic-http
```
Ouvrir l'URL `http://10.0.2.2:<port>/` affichée dans Safari invité. Le serveur
écoute uniquement sur la boucle locale de l'hôte ; il conserve le port et les
requêtes dans le dossier choisi. Saisir `bonjour` avec le clavier virtuel et
soumettre : le journal doit contenir `/submit?texte=bonjour`. Suivre le lien de
défilement, glisser jusqu'au bas, attendre la fin de l'inertie et toucher le lien
final : `/fin` doit apparaître. Arrêter le serveur avec Ctrl-C après le test.
Attendre l'affichage réel d'une application avant d'envoyer des gestes. Pour
TouchProbe, attendre `TouchProbe ready` dans le journal ; une réponse de lancement
ne garantit pas que la vue soit déjà prête. Sous 2.1.1, lancer Safari avec `--url`
alors qu'il est déjà ouvert n'a pas changé la page lors du test ; utiliser les
liens de la page ou relancer Safari pour ouvrir une autre URL.
Notes et la calculatrice complètent ce contrôle : saisir une note, la sauvegarder,
puis comparer son texte dans `Library/Notes/notes.db` avant et après redémarrage ;
vérifier visuellement `7 × 8 = 56` dans la calculatrice. Les diagnostics et le
résultat détaillé de la validation du 1er octobre sont décrits dans `HANDOFF.md`.
## Limites
- **Persistance NAND** : les pages et métadonnées écrites par iOS, ainsi que les effacements de blocs, sont conservés
@@ -98,17 +228,41 @@ la toolchain de l'environnement (`arm-apple-darwin9-gcc -isysroot /opt/theos/sdk
- **Internet** : PPP sur UART1, pont `ipod-netd` vers libslirp ; DNS et HTTP fonctionnent. `ipod-agent fetch
http://example.com/` teste le réseau depuis l'iPod. L'écran Réglages affiche toujours « Pas de Wi-Fi », car la puce
BCM4325 n'est pas émulée.
- **Heure** : le RTC n'est pas émulé ; `start -W` / `wait` règle l'heure depuis l'hôte dès que l'agent répond,
avant la fin du démarrage de SpringBoard.
- **Heure** : le RTC D1759 fournit l'heure dès le démarrage, avec la configuration QEMU `-rtc`
(UTC et horloge hôte par défaut). `start -W` / `wait` ne modifie plus la date.
`ipod-agent settime SECONDES_UNIX` reste disponible pour les tests explicites.
Le décalage écrit par iOS reste en mémoire pendant la session ; une nouvelle instance QEMU repart
de sa base RTC. Les IRQ d'alarme matérielle ne sont pas émulées. Voir [RTC.md](RTC.md).
L'image de base est en français, région France, fuseau Europe/Paris.
- **Relance de SpringBoard** : `respring` peut bloquer la reconstruction du catalogue d'apps. `push-app` redémarre
donc l'émulateur sans reconstruire l'image ni réinitialiser la NAND. Avec l'agent v11, le déverrouillage puis
le lancement de HelloEmu au premier plan sont validés après cette installation. Les gestes Home/Power restent
susceptibles de déclencher un défaut du pilote lors de la reprise de l'écran ; le workflow automatique utilise
le réveil par l'API d'affichage et vérifie le verrouillage.
- **Relance de SpringBoard** : `ipod-emu respring -W` attend le retour de l'interface et son déverrouillage.
`push-app` garde son redémarrage de QEMU, sans reconstruire l'image ni réinitialiser la NAND. Avec l'agent v11, le déverrouillage puis
le lancement de HelloEmu au premier plan sont validés après cette installation. Les corrections GPIO/SPI
ont permis huit cycles veille/réveil alternant Home et Power sur 5H11a. Le déverrouillage automatique
continue à vérifier l'état de l'écran.
- **Veille** : l'agent v10 pose `SleepDisabled` sur `IOPMrootDomain`, le SoC ne sachant pas reprendre une veille profonde.
- Pas de son, pas d'accélération graphique (SpringBoard tourne avec `LK_ENABLE_MBX2D=0`).
## Régression native du redémarrage
Avec un firmware local, le test QMP/QTest vérifie trois resets des vrais
périphériques d'affichage (CPU arrêté, NAND en lecture seule) :
```bash
python3 contrib/ipod-emu/tests/test-display-reset.py --firmware /chemin/images/5H11a
```
Pour vérifier iOS, préparer et démarrer une **session de test séparée**, avec un
port d'agent distinct. Le test suivant relance cette session, ouvre la calculatrice
et conserve les captures et résultats JSON. Il vérifie le déverrouillage, les PID,
la persistance d'un fichier témoin et HTTP avec un serveur local temporaire :
```bash
python3 contrib/ipod-emu/tests/test-display-session.py \
--emu-home /chemin/session-test --agent-port 7789 \
--action respring --cycles 3 --output /chemin/resultats-respring
# Même commande avec --action reset pour le redémarrage complet d'iOS.
```
## Reconstruire les images
`ipod-build-fw 5H11a` télécharge l'IPSW iPod2,1 chez Apple et les clés sur api.ipsw.me, génère la NOR, déchiffre et
+82
View File
@@ -0,0 +1,82 @@
# RTC du D1759 — iPod Touch 2G
Le modèle `d1759` fournit l'heure avant le lancement des services iOS. La
machine 2G l'utilise à l'adresse I²C `0x73` ; le modèle historique `pcf50633`
reste disponible séparément. Les images et l'agent v12 existants conviennent.
Il faut reconstruire QEMU et utiliser le lanceur mis à jour.
## Protocole observé
Le désassemblage local du pilote `AppleD1759PMURTC` de 5H11a montre :
| Registres | Comportement |
| --- | --- |
| `0x5c` à `0x5f` | Compteur de secondes sur 32 bits, petit-boutiste |
| `0x64` à `0x67` | Décalage ajouté au compteur pour obtenir les secondes Unix |
| `0x50` à `0x5a` | Réglages d'alarme conservés en mémoire, sans IRQ |
| `0x60` à `0x63` | Suivi du démarrage, indépendant du RTC |
Sur le kernelcache déchiffré, le segment `__PRELINK` a le décalage adresse
virtuelle/fichier `0xc0029000`. La routine à `0xc03c8f08` lit quatre octets en
`0x5c` et ajoute le décalage chargé au démarrage par `0xc03c8c10` depuis `0x64`.
La routine à `0xc03c8f48` règle l'heure en écrivant la différence en `0x64`.
Les routines PMU à `0xc03c11e8` et `0xc03c1228` transmettent directement le
registre au nub I²C. Ce sont des observations du binaire local, pas des
symboles exportés ni une spécification complète du composant.
Le protocole I²C standard, registre puis données, est aussi visible dans
[OpeniBoot pour le S5L8720](https://github.com/iDroid-Project/openiBoot/blob/master/plat-s5l8720/pmu.c).
Sa fonction RTC est laissée à implémenter et ne constitue pas une preuve du
format du compteur. Les anciennes notes décrivant `0x61/0x62` comme un accès
indirect RTC étaient incorrectes.
Une lecture capture le compteur au début de la réception pour éviter de
mélanger deux secondes entre les octets. Les sélections de registre avec STOP
et avec START répété sont acceptées. Le compteur est en lecture seule ; les
écritures de décalage sont conservées pendant la session.
## Configuration et limites
QEMU initialise le compteur via `qemu_get_timedate`, puis le fait avancer avec
`rtc_clock`. Les options habituelles `-rtc base=...,clock=...` sont respectées.
Par défaut, la base est UTC et le temps suit l'horloge de l'hôte. Le fuseau
horaire d'affichage reste un réglage d'iOS. Une base reproductible peut être
passée au lanceur, par exemple :
```bash
IPOD_QEMU_EXTRA='-rtc base=2024-02-29T23:59:59,clock=vm' ipod-emu start -W
```
`start -W` et `wait` ne lancent plus `settime`. La commande
`ipod-agent settime SECONDES_UNIX` reste un réglage volontaire pour les tests.
Le décalage RTC ne persiste pas entre deux processus QEMU ; une nouvelle
instance repart de sa configuration `-rtc`. Les IRQ d'alarme, la veille
profonde et la migration de l'état RTC ne sont pas implémentées.
Un reset QEMU à chaud peut laisser PPP inaccessible. Pour redémarrer une
session conservée, utiliser `ipod-agent sync`, `ipod-emu stop`, puis
`ipod-emu start -W`, qui conserve la NAND.
## Validation
```bash
contrib/ipod-emu/tests/run-rtc
```
Le test compile le modèle réel avec ASan/UBSan et utilise le bus I²C QEMU :
base UTC avec fuseau hôte différent, date fixe bissextile, cohérence des
lectures au changement de seconde, décalages positifs/négatifs et séparation
des registres de démarrage.
Validation du 1er octobre 2026 sur des copies de NAND :
- 5H11a / iPhone OS 2.2.1 et 5F138 / iPhone OS 2.1.1 démarrent sans `settime` ;
`gettimeofday` dans un programme armv6 retrouve l'heure Unix de l'hôte.
- Un changement volontaire de date continue à avancer. Après un reset à
chaud de 5F138, les journaux précoces d'iOS retrouvent le décalage écrit ;
PPP ne reprend pas correctement après ce reset.
- Les démarrages normaux, le déverrouillage et le lancement d'applications
sont vérifiés avec le lanceur sans synchronisation par l'agent.
Les diagnostics restent hors du dépôt dans
`/srv/homes/iphoneos2/ipod-emu/work/rtc-20261001/`.
+52 -26
View File
@@ -67,7 +67,7 @@ struct rt_msghdr {
#include <notify.h>
#include <unistd.h>
#define VERSION "11"
#define VERSION "12"
#define PORT 7788
#define PPP_TTY "/dev/tty.bluetooth"
#define GUEST_IP "10.0.2.15"
@@ -236,7 +236,7 @@ static int send_all(int fd, const void *b, size_t n)
const char *p = b;
while (n) {
ssize_t w = write(fd, p, n);
if (w < 0) { if (errno == EINTR) continue; return -1; }
if (w <= 0) { if (w < 0 && errno == EINTR) continue; if (!w) errno = EIO; return -1; }
p += w; n -= w;
}
return 0;
@@ -569,49 +569,75 @@ static int read_full(int fd, void *b, size_t n)
char *p = b;
while (n) {
ssize_t r = read(fd, p, n);
if (r <= 0) { if (r < 0 && errno == EINTR) continue; return -1; }
if (r <= 0) { if (r < 0 && errno == EINTR) continue; if (!r) errno = EIO; return -1; }
p += r; n -= r;
}
return 0;
}
/* Transferts par blocs : ne pas allouer la taille du fichier sur l'iPod. */
static void cmd_put(int fd, char **args, int nargs)
{
if (nargs < 3) { reply_str(fd, 0, "usage: put<TAB>chemin<TAB>mode octal<TAB>taille + données"); return; }
size_t size = strtoul(args[2], NULL, 10);
char *data = malloc(size ? size : 1);
if (read_full(fd, data, size)) { free(data); return; }
char *end;
errno = 0;
unsigned long size = strtoul(args[2], &end, 10);
if (errno || !args[2][0] || args[2][0] == '-' || *end) {
reply_str(fd, 0, "taille invalide"); return;
}
char dir[1024];
strncpy(dir, args[0], sizeof dir - 1);
dir[sizeof dir - 1] = 0;
if (strlen(args[0]) >= sizeof dir) { reply_str(fd, 0, "chemin trop long"); return; }
strcpy(dir, args[0]);
char *slash = strrchr(dir, '/');
if (slash && slash != dir) { *slash = 0; mkdirs(dir); }
char tmp[1100];
snprintf(tmp, sizeof tmp, "%s.emuagent-tmp", args[0]);
int f = open(tmp, O_WRONLY | O_CREAT | O_TRUNC, strtoul(args[1], NULL, 8));
if (f < 0 || send_all(f, data, size) || fsync(f)) {
reply_str(fd, 0, "écriture de %s : %s", args[0], strerror(errno));
if (f >= 0) close(f);
free(data);
snprintf(tmp, sizeof tmp, "%s.emuagent-XXXXXX", args[0]);
int f = mkstemp(tmp);
if (f < 0) { reply_str(fd, 0, "création : %s", strerror(errno)); return; }
char data[65536];
size_t remaining = size;
int failed = 0;
while (remaining) {
size_t chunk = remaining < sizeof data ? remaining : sizeof data;
if (read_full(fd, data, chunk) || send_all(f, data, chunk)) { failed = 1; break; }
remaining -= chunk;
}
if (!failed && (fchmod(f, strtoul(args[1], NULL, 8)) || fsync(f))) failed = 1;
int saved = errno;
if (close(f) && !failed) { failed = 1; saved = errno; }
if (!failed && rename(tmp, args[0])) { failed = 1; saved = errno; }
if (failed) {
unlink(tmp);
reply_str(fd, 0, "écriture de %s : %s", args[0], strerror(saved));
return;
}
close(f);
chmod(tmp, strtoul(args[1], NULL, 8));
rename(tmp, args[0]);
free(data);
reply_str(fd, 1, "%zu octets écrits", size);
reply_str(fd, 1, "%lu octets écrits", size);
}
static void cmd_get(int fd, char **args, int nargs)
{
if (nargs < 1) { reply_str(fd, 0, "usage: get<TAB>chemin"); return; }
int f = open(args[0], O_RDONLY | O_NONBLOCK);
struct stat st;
if (nargs < 1 || stat(args[0], &st) || !S_ISREG(st.st_mode)) { reply_str(fd, 0, "%s : %s", nargs ? args[0] : "?", strerror(errno)); return; }
int f = open(args[0], O_RDONLY);
char *data = malloc(st.st_size ? st.st_size : 1);
if (f < 0 || read_full(f, data, st.st_size)) { reply_str(fd, 0, "lecture de %s : %s", args[0], strerror(errno)); free(data); if (f >= 0) close(f); return; }
if (f < 0 || fstat(f, &st) || !S_ISREG(st.st_mode)) {
reply_str(fd, 0, "lecture de %s impossible", args[0]);
if (f >= 0) close(f);
return;
}
char hdr[128], data[65536];
snprintf(hdr, sizeof hdr, "OK %llu\n", (unsigned long long)st.st_size);
if (send_all(fd, hdr, strlen(hdr))) { close(f); return; }
off_t remaining = st.st_size;
while (remaining) {
size_t chunk = remaining < (off_t)sizeof data ? (size_t)remaining : sizeof data;
/* Après l'en-tête, une erreur impose de fermer la connexion :
le client détecte la réponse incomplète et conserve son ancien fichier. */
if (read_full(f, data, chunk) || send_all(fd, data, chunk)) {
close(f); shutdown(fd, SHUT_RDWR); return;
}
remaining -= chunk;
}
close(f);
reply(fd, 1, NULL, data, st.st_size);
free(data);
}
static void cmd_ls(int fd, char **args, int nargs)
@@ -742,7 +768,7 @@ static void handle(int fd, char *line)
else if (!strcmp(cmd, "sc")) cmd_sc(fd, a, na);
else if (!strcmp(cmd, "routes")) cmd_routes(fd);
else if (!strcmp(cmd, "settime")) {
/* le RTC du PMU (D1759) n'est pas émulé : l'hôte donne l'heure (secondes Unix) */
/* Réglage explicite de l'heure pour les tests (secondes Unix). */
struct timeval tv = { na ? strtol(a[0], NULL, 10) : 0, 0 };
if (na && !settimeofday(&tv, NULL)) {
notify_post("com.apple.system.clock_set"); /* SpringBoard et les apps mettent leur horloge à jour */
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>TouchProbe</string>
<key>CFBundleIdentifier</key>
<string>com.ioslegacy.touchprobe</string>
<key>CFBundleName</key>
<string>TouchProbe</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>2.0</string>
</dict>
</plist>
+10
View File
@@ -0,0 +1,10 @@
# Sonde de calibration tactile pour iPhone OS 2.
SDK := /opt/theos/sdks/iPhoneOS2.2.1.sdk
CC := /opt/cctools/bin/arm-apple-darwin9-gcc
all: TouchProbe.app/TouchProbe
TouchProbe.app/TouchProbe: main.m Info.plist
mkdir -p TouchProbe.app
$(CC) -isysroot $(SDK) -arch armv6 -miphoneos-version-min=2.0 -O2 -o $@ main.m -framework UIKit -framework Foundation
cp Info.plist TouchProbe.app/
ldid -S $@
.PHONY: all
+30
View File
@@ -0,0 +1,30 @@
#import <UIKit/UIKit.h>
@interface ProbeView : UIView
@end
@implementation ProbeView
- (void)record:(NSSet *)touches phase:(NSString *)phase {
CGPoint p=[[touches anyObject] locationInView:self];
UIView *marker=[self viewWithTag:99];
if (!marker) {
marker=[[[UIView alloc] initWithFrame:CGRectMake(0,0,0,0)] autorelease];
marker.tag=99; marker.backgroundColor=[UIColor blueColor]; marker.userInteractionEnabled=NO;
[self addSubview:marker];
}
marker.frame=CGRectMake(p.x-2,p.y-2,5,5);
NSLog(@"TOUCH %@ %.1f %.1f",phase,p.x,p.y);
}
- (void)touchesBegan:(NSSet *)t withEvent:(UIEvent *)e { [self record:t phase:@"begin"]; }
- (void)touchesMoved:(NSSet *)t withEvent:(UIEvent *)e { [self record:t phase:@"move"]; }
- (void)touchesEnded:(NSSet *)t withEvent:(UIEvent *)e { [self record:t phase:@"end"]; }
@end
@interface ProbeApp : NSObject <UIApplicationDelegate> { UIWindow *window; }
@end
@implementation ProbeApp
- (void)applicationDidFinishLaunching:(UIApplication *)app {
window=[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
ProbeView *v=[[ProbeView alloc] initWithFrame:CGRectMake(0,20,320,460)];
v.backgroundColor=[UIColor whiteColor];[window addSubview:v];[v release];[window makeKeyAndVisible];
NSLog(@"TouchProbe ready window=%@ view=%@",NSStringFromCGRect(window.frame),NSStringFromCGRect(v.frame));
}
@end
int main(int argc,char **argv) { NSAutoreleasePool *p=[[NSAutoreleasePool alloc] init];int rc=UIApplicationMain(argc,argv,nil,@"ProbeApp");[p release];return rc; }
+61 -21
View File
@@ -20,7 +20,7 @@
Code de sortie : 0 si OK, 1 si erreur, code de sortie du programme pour exec.
"""
import os, socket, subprocess, sys, time
import os, socket, subprocess, sys, tempfile, time
HOST = os.environ.get("IPOD_AGENT_HOST", "127.0.0.1")
PORT = int(os.environ.get("IPOD_AGENT_PORT", "7788"))
@@ -31,19 +31,41 @@ class Agent:
self.s = socket.create_connection((HOST, PORT), timeout=timeout)
self.f = self.s.makefile("rb")
def call(self, *fields, data=None):
def call(self, *fields, data=None, sink=None):
line = "\t".join(str(x) for x in fields) + "\n"
self.s.sendall(line.encode())
if data is not None:
self.s.sendall(data)
hdr = self.f.readline().decode().rstrip("\n")
if hasattr(data, "read"):
remaining = int(fields[-1])
while remaining:
chunk = data.read(min(65536, remaining))
if not chunk:
raise ConnectionError("fichier local tronqué pendant l'upload")
self.s.sendall(chunk)
remaining -= len(chunk)
else:
self.s.sendall(data)
hdr = self.f.readline(4096).decode().rstrip("\n")
if not hdr:
raise ConnectionError("connexion fermée par l'agent")
parts = hdr.split(" ", 2)
if len(parts) < 2:
raise ConnectionError("en-tête invalide")
n = int(parts[1])
body = self.f.read(n)
if len(body) != n:
raise ConnectionError(f"réponse tronquée : {len(body)} octets reçus sur {n}")
if n < 0 or parts[0] not in ("OK", "ERR"):
raise ConnectionError("en-tête invalide")
body = bytearray()
received = 0
while received < n:
chunk = self.f.read(min(65536, n - received))
if not chunk:
raise ConnectionError(f"réponse tronquée : {received} octets reçus sur {n}")
received += len(chunk)
if sink is not None and parts[0] == "OK":
sink.write(chunk)
else:
body.extend(chunk)
body = bytes(body)
return parts[0] == "OK", body, parts[2] if len(parts) > 2 else None
@@ -74,7 +96,13 @@ def unlock(a):
sys.exit("Un code de verrouillage est activé : déverrouiller manuellement.")
# Le pilote tactile doit avoir terminé son démarrage avant tout glissement.
try:
offset_path = os.path.join(emu_home(), "run", "boot-log-offset")
offset = 0
if os.path.exists(offset_path):
with open(offset_path) as f:
offset = int(f.read())
with open(os.path.join(emu_home(), "run", "serial.log"), "rb") as f:
f.seek(offset)
serial = f.read()
except OSError as e:
sys.exit(f"Console de session inaccessible : {e}")
@@ -88,8 +116,7 @@ def unlock(a):
if not ok:
sys.exit(body.decode())
time.sleep(2) # laisser le pilote réactiver l'écran et le tactile
# La surface tactile émulée est légèrement plus petite que l'écran :
# le geste doit atteindre le bord droit pour terminer le déverrouillage.
# Terminer le geste au bord droit du curseur de déverrouillage.
subprocess.run([qmp, "-s", sock, "swipe", "60", "450", "319", "450"], check=True)
time.sleep(2)
state = screen()
@@ -175,20 +202,30 @@ def main():
unlock(a)
return
if cmd == "get":
ok, body, _ = a.call("get", rest[0])
if not ok:
sys.exit(body.decode())
if len(rest) > 1:
with open(rest[1], "wb") as fh:
fh.write(body)
else:
sys.stdout.buffer.write(body)
tmp = None
try:
if len(rest) > 1:
destination = os.path.abspath(rest[1])
fd, tmp = tempfile.mkstemp(prefix=".ipod-get-", dir=os.path.dirname(destination))
with os.fdopen(fd, "wb") as fh:
ok, body, _ = a.call("get", rest[0], sink=fh)
fh.flush()
os.fsync(fh.fileno())
if ok:
os.replace(tmp, destination)
tmp = None
else:
ok, body, _ = a.call("get", rest[0], sink=sys.stdout.buffer)
if not ok:
sys.exit(body.decode())
finally:
if tmp is not None:
os.unlink(tmp)
return
if cmd == "put":
with open(rest[0], "rb") as fh:
data = fh.read()
mode = rest[2] if len(rest) > 2 else ("755" if os.access(rest[0], os.X_OK) else "644")
ok, body, _ = a.call("put", rest[1], mode, len(data), data=data)
with open(rest[0], "rb") as fh:
ok, body, _ = a.call("put", rest[1], mode, os.fstat(fh.fileno()).st_size, data=fh)
elif cmd == "push-app":
push_app(a, rest[0])
return
@@ -207,4 +244,7 @@ def main():
if __name__ == "__main__":
main()
try:
main()
except (OSError, ValueError, ConnectionError) as e:
sys.exit(f"Erreur de transfert ou de protocole : {e}")
+34 -8
View File
@@ -19,28 +19,34 @@ Usage : ipod-emu start [-f] [-W] [-w FW] démarre (headless, VNC $VNC, QMP $RU
-f repart d'une NAND vierge (sinon garde l'état de la session précédente)
-W attend que SpringBoard soit prêt à recevoir des touches
-w FW firmware à utiliser (défaut : $FW ; dispo : $(ls "$EMU_HOME/images" 2>/dev/null | tr '\n' ' '))
ipod-emu respring [-W] relance SpringBoard dans la session courante
ipod-emu reset [-W] synchronise la NAND et redémarre iOS dans le même QEMU
ipod-emu wait [SECONDES] attend SpringBoard (défaut 300 s) — n'envoyez PAS de touch avant :
le contrôleur tactile plante s'il est touché pendant son boot
le pilote tactile doit avoir terminé son démarrage
ipod-emu stop | status | log | console
Pilotage (écran, touch, boutons) : ipod-qmp --help
USAGE
exit "${1:-0}"
}
boot_log() {
local offset
offset=$(cat "$RUN/boot-log-offset" 2>/dev/null || echo 0)
[[ "$offset" =~ ^[0-9]+$ ]] || return 1
tail -c "+$((offset + 1))" "$RUN/serial.log"
}
ready() {
local n; n=$(grep -ac "AppleMultitouchSPIUserClient::attach" "$RUN/serial.log" 2>/dev/null); [ "${n:-0}" -ge 2 ] &&
grep -aq 'SCHelper.*"EXIT"' "$RUN/serial.log"
local n; n=$(boot_log | grep -ac "AppleMultitouchSPIUserClient::attach"); [ "${n:-0}" -ge 2 ] &&
grep -a 'SCHelper.*"EXIT"' <(boot_log) >/dev/null
}
wait_ready() {
local t=${1:-300} s=$SECONDS
# Régler l'heure dès que PPP répond, avant que SpringBoard initialise
# ses timers à partir de préférences persistées datées dans le futur.
"$(dirname "$0")/ipod-agent" wait 60 >/dev/null 2>&1 &&
timeout 15 "$(dirname "$0")/ipod-agent" settime "$(date +%s)" >/dev/null 2>&1 || true
# Le RTC D1759 fournit l'heure avant le démarrage des services iOS.
until ready; do
[ -n "$(pid)" ] || { echo "L'émulateur s'est arrêté (voir $RUN/qemu.log)" >&2; return 1; }
grep -aq 'Fatal Exception\|MACH Reboot' "$RUN/serial.log" && { echo "Kernel panic / reboot (voir $RUN/serial.log)" >&2; return 1; }
grep -a 'Fatal Exception\|MACH Reboot' <(boot_log) >/dev/null && { echo "Kernel panic / reboot (voir $RUN/serial.log)" >&2; return 1; }
[ $((SECONDS - s)) -lt "$t" ] || { echo "Timeout : SpringBoard pas prêt après ${t}s" >&2; return 1; }
sleep 2
done
@@ -85,6 +91,8 @@ start)
fi
stop_netd
rm -f "$RUN/qmp.sock" "$RUN/serial.log"
printf '0\n' > "$RUN/boot-log-offset.tmp"
mv "$RUN/boot-log-offset.tmp" "$RUN/boot-log-offset"
mopts=$(cat "$img/machine-opts" 2>/dev/null || true) # ex. ",legacy-nor=off"
if [[ "$mopts" == *",nand-write=on"* ]] &&
[ "$(cat "$RUN/nand/.ipod-nand-format" 2>/dev/null || true)" != "vsvfl-production-v1" ]; then
@@ -127,6 +135,24 @@ start)
echo "Logs iOS : $RUN/serial.log"
[ $waitr = 0 ] || wait_ready
;;
respring|reset)
waitr=0
while getopts "W" o; do case $o in W) waitr=1 ;; *) usage 1 ;; esac; done
shift $((OPTIND - 1))
[ "$#" = 0 ] || usage 1
pid >/dev/null || { echo "L'émulateur n'est pas lancé" >&2; exit 1; }
timeout 15 "$TOOLS_DIR/ipod-agent" sync
offset=$(stat -c %s "$RUN/serial.log")
printf '%s\n' "$offset" > "$RUN/boot-log-offset.tmp"
mv "$RUN/boot-log-offset.tmp" "$RUN/boot-log-offset"
if [ "$cmd" = reset ]; then
"$TOOLS_DIR/ipod-qmp" -s "$RUN/qmp.sock" reset
echo "Reset demandé ; QEMU et NAND conservés"
else
timeout 15 "$TOOLS_DIR/ipod-agent" respring
fi
[ "$waitr" = 0 ] || wait_ready
;;
wait) wait_ready "${1:-300}" ;;
stop)
p=$(pid) || { stop_netd; echo "Pas lancé"; exit 0; }
+3
View File
@@ -8,6 +8,7 @@
ipod-qmp status état de la VM
ipod-qmp hmp COMMANDE commande du moniteur QEMU (ex. "info registers", "x/8i $pc")
ipod-qmp quit arrête l'émulateur
ipod-qmp reset reset matériel brut (synchroniser la NAND avec ipod-agent sync avant)
Socket : -s SOCK, sinon $IPOD_QMP, sinon ~/ipod-emu/run/qmp.sock
"""
@@ -106,6 +107,8 @@ def main():
print(q.cmd("human-monitor-command", **{"command-line": " ".join(a.args)}))
elif a.action == "quit":
q.cmd("quit")
elif a.action == "reset":
q.cmd("system_reset")
else:
sys.exit(__doc__)
+147
View File
@@ -0,0 +1,147 @@
#!/usr/bin/env python3
"""Teste une application dans une NAND indépendante et conserve les diagnostics."""
import argparse
import json
import os
from pathlib import Path
import plistlib
import shutil
import socket
import subprocess
import tempfile
import time
import zipfile
def bundle_id(package):
if package.is_dir() and package.suffix == '.app':
with (package / 'Info.plist').open('rb') as f:
return plistlib.load(f)['CFBundleIdentifier']
if package.suffix == '.ipa':
with zipfile.ZipFile(package) as z:
plists = [n for n in z.namelist() if n.startswith('Payload/') and
n.endswith('.app/Info.plist') and n.count('/') == 2]
if len(plists) != 1:
raise ValueError('IPA ambiguë : préciser --bundle-id')
return plistlib.loads(z.read(plists[0]))['CFBundleIdentifier']
raise ValueError('Préciser --bundle-id pour un paquet .deb ou un overlay')
def main():
p = argparse.ArgumentParser(description=__doc__)
p.add_argument('package', type=Path, help='.app, .ipa, .deb ou overlay')
p.add_argument('--bundle-id')
p.add_argument('--base', default=os.environ.get('IPOD_TEST_BASE', '5H11a-agent-v12'))
p.add_argument('--output', type=Path, help='nouveau dossier de résultats (doit être absent)')
p.add_argument('--settle', type=float, default=3, help='attente après apparition au premier plan')
args = p.parse_args()
package = args.package.resolve()
tools = Path(__file__).resolve().parent
default_home = tools.parent if (tools.parent / 'images').is_dir() else Path.home() / 'ipod-emu'
home = Path(os.environ.get('IPOD_EMU_HOME', default_home)).resolve()
try:
if not package.exists():
raise ValueError('Application introuvable')
identifier = args.bundle_id or bundle_id(package)
if not isinstance(identifier, str) or not identifier or any(c in identifier for c in '\t\r\n'):
raise ValueError('Bundle-id invalide')
if Path(args.base).name != args.base or args.base in ('.', '..'):
raise ValueError('Nom de firmware invalide')
if args.settle < 0:
raise ValueError('--settle doit être positif')
base = home / 'images' / args.base
if not (base / 'rootfs.img').is_file():
raise ValueError(f'Image de base introuvable : {base}')
# Les ports sont partagés par les sessions : ne jamais arrêter une VM tierce.
vnc = os.environ.get('IPOD_VNC', '127.0.0.1:0')
host, display = vnc.rsplit(':', 1)
for address, port in [(os.environ.get('IPOD_AGENT_HOST', '127.0.0.1'),
int(os.environ.get('IPOD_AGENT_PORT', '7788'))),
(host or '127.0.0.1', 5900 + int(display))]:
try:
connection = socket.create_connection((address, port), timeout=1)
except ConnectionRefusedError:
continue
else:
connection.close()
raise ValueError(f'Port {port} occupé : arrêter votre session avant ce test')
if args.output:
result = args.output.resolve()
result.mkdir(parents=True, exist_ok=False)
else:
parent = home / 'work' / 'tests'
parent.mkdir(parents=True, exist_ok=True)
result = Path(tempfile.mkdtemp(prefix=time.strftime('%Y%m%d-%H%M%S-'), dir=parent))
except (OSError, ValueError, KeyError, plistlib.InvalidFileException, zipfile.BadZipFile) as e:
p.exit(1, f'{e}\n')
print(f'Diagnostics : {result}', flush=True)
session = result / 'session'
(session / 'images').mkdir(parents=True)
(session / 'images' / args.base).symlink_to(base, target_is_directory=True)
for dependency in ('xpwn', 'gen-v1', 'netd'):
(session / dependency).symlink_to(home / dependency, target_is_directory=True)
env = dict(os.environ, IPOD_EMU_HOME=str(session),
IPOD_QEMU=os.environ.get('IPOD_QEMU', str(home.parent / 'qemu-ios/build/qemu-system-arm')))
# Un socket de session explicite ne doit pas viser la VM de l'utilisateur.
env.pop('IPOD_QMP', None)
image_name = 'test-app' if args.base != 'test-app' else 'test-app-result'
started = False
report = {'package': str(package), 'bundle_id': identifier, 'base': args.base, 'ok': False}
with (result / 'workflow.log').open('w') as workflow:
def run(tool, *argv, capture=False, timeout=360):
r = subprocess.run([str(tools / tool), *map(str, argv)], env=env,
stdout=subprocess.PIPE if capture else workflow,
stderr=workflow, text=True, timeout=timeout)
if capture:
workflow.write(r.stdout)
workflow.flush()
if r.returncode:
raise RuntimeError(f'{tool} {argv[0] if argv else ""} : code {r.returncode}')
return r.stdout if capture else None
try:
run('ipod-mkimage', '-b', args.base, '-o', image_name, package, timeout=900)
started = True # nettoie également un démarrage partiel
run('ipod-emu', 'start', '-w', image_name, '-W')
run('ipod-agent', 'launch', identifier, '--log', '/private/var/mobile/ipod-test-app.log')
deadline = time.monotonic() + 30
while run('ipod-agent', 'front', capture=True, timeout=20).strip() != identifier:
if time.monotonic() >= deadline:
raise RuntimeError('Application absente du premier plan après 30 s')
time.sleep(1)
time.sleep(args.settle)
front = run('ipod-agent', 'front', capture=True, timeout=20).strip()
report['front'] = front
if front != identifier:
raise RuntimeError('Application quittée pendant la période de stabilisation')
report['ok'] = True
except (OSError, RuntimeError, subprocess.TimeoutExpired) as e:
report['error'] = str(e)
finally:
if started:
for tool, argv in [('ipod-agent', ['get', '/private/var/mobile/ipod-test-app.log', result / 'app.log']),
('ipod-qmp', ['screenshot', result / 'screen.png'])]:
try:
run(tool, *argv, timeout=30)
except (OSError, RuntimeError, subprocess.TimeoutExpired) as e:
report.setdefault('diagnostic_errors', []).append(str(e))
try:
run('ipod-agent', 'sync', timeout=15)
except (OSError, RuntimeError, subprocess.TimeoutExpired):
pass
try:
run('ipod-emu', 'stop', timeout=30)
except (OSError, RuntimeError, subprocess.TimeoutExpired) as e:
report['ok'] = False
report['stop_error'] = str(e)
if report.get('diagnostic_errors'):
report['ok'] = False
(result / 'result.json').write_text(json.dumps(report, indent=2, ensure_ascii=False) + '\n')
print('Test réussi' if report['ok'] else 'Test échoué : consulter workflow.log et result.json')
return 0 if report['ok'] else 1
if __name__ == '__main__':
raise SystemExit(main())
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env python3
"""Page HTTP compatible Safari mobile 3 pour valider saisie et défilement.
Lancer sur l'hôte avec --output DOSSIER ; ouvrir l'URL 10.0.2.2 affichée dans
Safari invité. Le port et les requêtes reçues sont enregistrés dans ce dossier.
Arrêter avec Ctrl-C après le test. Aucun accès Internet n'est nécessaire.
"""
import argparse
from datetime import datetime, timezone
from html import escape
from http.server import BaseHTTPRequestHandler, HTTPServer
import json
from pathlib import Path
from urllib.parse import parse_qs, urlparse
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--output', type=Path, required=True)
args = parser.parse_args()
args.output.mkdir(parents=True, exist_ok=True)
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
with (args.output / 'requests.jsonl').open('a', encoding='utf-8') as log:
log.write(json.dumps({'utc': datetime.now(timezone.utc).isoformat(),
'path': self.path}, ensure_ascii=False) + '\n')
url = urlparse(self.path)
if url.path == '/submit':
content = 'Recu : ' + escape(parse_qs(url.query).get('texte', [''])[0])
elif url.path == '/scroll':
content = '<h1>Debut</h1>' + ''.join(
'<p style="height:60px">Ligne %02d</p>' % i for i in range(24))
content += '<a href="/fin">Fin du defilement</a>'
elif url.path == '/fin':
content = '<h1>Defilement OK</h1>'
else:
content = ('<h1>Validation HTTP</h1><form action="/submit" method="get">'
'<input type="text" name="texte"><input type="submit" value="Envoyer">'
'</form><p><a href="/scroll">Test de defilement</a></p>')
body = ('<html><head><meta name="viewport" content="width=device-width">'
'<title>Test iPhone OS</title></head><body>' + content + '</body></html>').encode('utf-8')
self.send_response(200)
self.send_header('Content-Type', 'text/html; charset=utf-8')
self.send_header('Content-Length', str(len(body)))
self.end_headers()
self.wfile.write(body)
with HTTPServer(('127.0.0.1', 0), Handler) as server:
(args.output / 'port').write_text(str(server.server_port) + '\n', encoding='utf-8')
print(f'URL depuis l’iPod : http://10.0.2.2:{server.server_port}/', flush=True)
try:
server.serve_forever()
except KeyboardInterrupt:
pass
if __name__ == '__main__':
main()
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env python3
"""Compile et exécute les régressions LCD, MIPI et sortie TV avec ASan/UBSan sur un build QEMU existant."""
import argparse
import json
import os
from pathlib import Path
import shlex
import subprocess
p = argparse.ArgumentParser(description=__doc__)
p.add_argument('--build', type=Path)
a = p.parse_args()
root = Path(__file__).resolve().parents[3]
build = (a.build or root / 'build').resolve()
source = Path(__file__).with_name('test-display.c')
commands = json.loads((build / 'compile_commands.json').read_text())
c = next(c for c in commands if c['file'].endswith('ipod_touch_lcd.c'))
old_root = str(Path(c['directory']).parent)
args = shlex.split(c['command'].replace(old_root, str(root)))
obj = str(build / 'test-ipod-display.o')
args[args.index('-c') + 1] = str(source)
args[args.index('-o') + 1] = obj
args[args.index('-MF') + 1] = obj + '.d'
args += ['-fsanitize=address,undefined', '-fno-omit-frame-pointer']
subprocess.run(args, cwd=build, check=True)
links = json.loads(subprocess.check_output(
['ninja', '-C', str(build), '-t', 'compdb', '-x', 'c_LINKER_RSP', 'c_LINKER'], text=True))
link = next(c for c in links if c.get('output') == 'qemu-system-arm')
args = shlex.split(link['command'].replace(old_root, str(root)))
args = [x for x in args if x not in (
'qemu-system-arm.p/system_main.c.o',
'libqemu-arm-softmmu.fa.p/hw_arm_ipod_touch_lcd.c.o',
'libqemu-arm-softmmu.fa.p/hw_arm_ipod_touch_mipi_dsi.c.o',
'libqemu-arm-softmmu.fa.p/hw_arm_ipod_touch_tvout.c.o')]
out = str(build / 'test-ipod-display')
args[args.index('-o') + 1] = out
args += [obj, '-fsanitize=address,undefined']
subprocess.run(args, cwd=build, check=True)
# QEMU conserve volontairement ses registres de types et sa boucle globale à la sortie.
env = dict(os.environ, ASAN_OPTIONS='detect_leaks=0', UBSAN_OPTIONS='halt_on_error=1')
subprocess.run([out], env=env, check=True)
+42
View File
@@ -0,0 +1,42 @@
#!/usr/bin/env python3
"""Compile et exécute les régressions tactiles avec ASan/UBSan sur un build QEMU existant."""
import argparse
import json
import os
from pathlib import Path
import shlex
import subprocess
p = argparse.ArgumentParser(description=__doc__)
p.add_argument('--build', type=Path)
a = p.parse_args()
root = Path(__file__).resolve().parents[3]
build = (a.build or root / 'build').resolve()
source = Path(__file__).with_name('test-multitouch.c')
commands = json.loads((build / 'compile_commands.json').read_text())
c = next(c for c in commands if c['file'].endswith('ipod_touch_multitouch.c'))
old_root = str(Path(c['directory']).parent)
args = shlex.split(c['command'].replace(old_root, str(root)))
obj = str(build / 'test-ipod-multitouch.o')
args[args.index('-c') + 1] = str(source)
args[args.index('-o') + 1] = obj
args[args.index('-MF') + 1] = obj + '.d'
args += ['-fsanitize=address,undefined', '-fno-omit-frame-pointer']
subprocess.run(args, cwd=build, check=True)
links = json.loads(subprocess.check_output(
['ninja', '-C', str(build), '-t', 'compdb', '-x', 'c_LINKER_RSP', 'c_LINKER'], text=True))
link = next(c for c in links if c.get('output') == 'qemu-system-arm')
args = shlex.split(link['command'].replace(old_root, str(root)))
args = [x for x in args if x not in (
'qemu-system-arm.p/system_main.c.o',
'libqemu-arm-softmmu.fa.p/hw_arm_ipod_touch_multitouch.c.o',
'libqemu-arm-softmmu.fa.p/hw_arm_ipod_touch_gpio.c.o',
'libqemu-arm-softmmu.fa.p/hw_arm_ipod_touch_sysic.c.o',
'libqemu-arm-softmmu.fa.p/hw_arm_ipod_touch_spi.c.o')]
out = str(build / 'test-ipod-multitouch')
args[args.index('-o') + 1] = out
args += [obj, '-fsanitize=address,undefined']
subprocess.run(args, cwd=build, check=True)
# QEMU conserve volontairement ses registres de types et sa boucle globale à la sortie.
env = dict(os.environ, ASAN_OPTIONS='detect_leaks=0', UBSAN_OPTIONS='halt_on_error=1')
subprocess.run([out], env=env, check=True)
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env python3
"""Compile et exécute les régressions RTC avec ASan/UBSan sur un build QEMU existant."""
import argparse
import json
import os
from pathlib import Path
import shlex
import subprocess
p = argparse.ArgumentParser(description=__doc__)
p.add_argument('--build', type=Path)
a = p.parse_args()
root = Path(__file__).resolve().parents[3]
build = (a.build or root / 'build').resolve()
source = Path(__file__).with_name('test-rtc.c')
commands = json.loads((build / 'compile_commands.json').read_text())
c = next(c for c in commands if c['file'].endswith('ipod_touch_pcf50633_pmu.c'))
old_root = str(Path(c['directory']).parent)
args = shlex.split(c['command'].replace(old_root, str(root)))
obj = str(build / 'test-ipod-rtc.o')
args[args.index('-c') + 1] = str(source)
args[args.index('-o') + 1] = obj
args[args.index('-MF') + 1] = obj + '.d'
args += ['-fsanitize=address,undefined', '-fno-omit-frame-pointer']
subprocess.run(args, cwd=build, check=True)
links = json.loads(subprocess.check_output(
['ninja', '-C', str(build), '-t', 'compdb', '-x', 'c_LINKER_RSP', 'c_LINKER'], text=True))
link = next(c for c in links if c.get('output') == 'qemu-system-arm')
args = shlex.split(link['command'].replace(old_root, str(root)))
args = [x for x in args if x not in (
'qemu-system-arm.p/system_main.c.o',
'libqemu-arm-softmmu.fa.p/hw_arm_ipod_touch_pcf50633_pmu.c.o')]
out = str(build / 'test-ipod-rtc')
args[args.index('-o') + 1] = out
args += [obj, '-fsanitize=address,undefined']
subprocess.run(args, cwd=build, check=True)
# QEMU conserve volontairement ses registres de types et sa boucle globale à la sortie.
env = dict(os.environ, ASAN_OPTIONS='detect_leaks=0', UBSAN_OPTIONS='halt_on_error=1')
subprocess.run([out], env=env, check=True)
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env python3
"""Compile et exécute les régressions UART avec ASan/UBSan sur un build QEMU existant."""
import argparse
import json
import os
from pathlib import Path
import shlex
import subprocess
p = argparse.ArgumentParser(description=__doc__)
p.add_argument('--build', type=Path)
a = p.parse_args()
root = Path(__file__).resolve().parents[3]
build = (a.build or root / 'build').resolve()
source = Path(__file__).with_name('test-uart.c')
commands = json.loads((build / 'compile_commands.json').read_text())
c = next(c for c in commands if c['file'].endswith('ipod_touch_uart.c'))
old_root = str(Path(c['directory']).parent)
args = shlex.split(c['command'].replace(old_root, str(root)))
obj = str(build / 'test-ipod-uart.o')
args[args.index('-c') + 1] = str(source)
args[args.index('-o') + 1] = obj
args[args.index('-MF') + 1] = obj + '.d'
args += ['-fsanitize=address,undefined', '-fno-omit-frame-pointer']
subprocess.run(args, cwd=build, check=True)
links = json.loads(subprocess.check_output(
['ninja', '-C', str(build), '-t', 'compdb', '-x', 'c_LINKER_RSP', 'c_LINKER'], text=True))
link = next(c for c in links if c.get('output') == 'qemu-system-arm')
args = shlex.split(link['command'].replace(old_root, str(root)))
args = [x for x in args if x not in (
'qemu-system-arm.p/system_main.c.o',
'libqemu-arm-softmmu.fa.p/hw_arm_ipod_touch_uart.c.o')]
out = str(build / 'test-ipod-uart')
args[args.index('-o') + 1] = out
args += [obj, '-fsanitize=address,undefined']
subprocess.run(args, cwd=build, check=True)
# QEMU conserve volontairement ses registres de types et sa boucle globale à la sortie.
env = dict(os.environ, ASAN_OPTIONS='detect_leaks=0', UBSAN_OPTIONS='halt_on_error=1')
subprocess.run([out], env=env, check=True)
@@ -0,0 +1,96 @@
#!/usr/bin/env python3
"""Vérifie le reset des périphériques d’affichage de la machine, CPU arrêté et NAND en lecture seule."""
import argparse
import json
from pathlib import Path
import socket
import subprocess
import tempfile
import time
def main():
parser = argparse.ArgumentParser(description=__doc__)
root = Path(__file__).resolve().parents[3]
parser.add_argument('--qemu', type=Path, default=root / 'build/qemu-system-arm')
parser.add_argument('--firmware', type=Path, required=True, help='dossier avec bootrom, nor.bin et nand/')
args = parser.parse_args()
fw = args.firmware.resolve()
with tempfile.TemporaryDirectory(prefix='ipod-reset-') as directory:
directory = Path(directory)
paths = [directory / name for name in ['qmp.sock', 'qtest.sock']]
with (directory / 'qemu.log').open('w+') as log:
proc = subprocess.Popen([
str(args.qemu.resolve()), '-M',
f'iPod-Touch,bootrom={fw}/bootrom,nor={fw}/nor.bin,nand={fw}/nand,legacy-nor=off,nand-write=off',
'-cpu', 'max', '-m', '2G', '-S', '-display', 'none', '-serial', 'null', '-serial', 'null',
'-qmp', f'unix:{paths[0]},server=on,wait=off',
'-qtest', f'unix:{paths[1]},server=on,wait=off'], stdout=log, stderr=log)
connections = []
try:
deadline = time.monotonic() + 10
while not all(p.exists() for p in paths):
if proc.poll() is not None or time.monotonic() >= deadline:
log.seek(0)
raise RuntimeError(log.read())
time.sleep(.02)
for path in paths:
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.settimeout(5)
sock.connect(str(path))
connections.append((sock, sock.makefile('rwb')))
qmp, test = [f for _, f in connections]
json.loads(qmp.readline())
def command(name):
qmp.write((json.dumps({'execute': name}) + '\n').encode())
qmp.flush()
while True:
r = json.loads(qmp.readline())
if 'error' in r:
raise RuntimeError(r['error'])
if 'return' in r:
return r['return']
def access(line):
test.write((line + '\n').encode())
test.flush()
r = test.readline().decode().strip()
assert r.startswith('OK'), r
return int(r.split()[1], 0) if len(r.split()) > 1 else None
command('qmp_capabilities')
# Registres d'affichage de la machine réelle : le reset doit atteindre MIPI.
regs = [0x38900004, 0x38900008, 0x38900024, 0x3d800008,
0x39100004, 0x39200004, 0x39300000, 0x39300284]
rom = access('readl 0')
for cycle in range(3):
for address in regs:
access(f'writel {address:#x} 0x1234')
assert access(f'readl {address:#x}') == 0x1234, hex(address)
# Abandonner une lecture au milieu de la réponse d'identification de la dalle.
assert access('readl 0x3d80003c') == 0x31a
command('system_reset')
for address in regs:
assert access(f'readl {address:#x}') == 0, hex(address)
assert access('readl 0x3d80003c') == 0x31a
assert access('readl 0x3d80003c') == 0x00a1d13c
assert access('readl 0') == rom
print('Trois resets d’affichage : registres effacés, réponse MIPI réinitialisée, BootROM conservée.')
command('quit')
proc.wait(timeout=5)
finally:
for sock, stream in connections:
stream.close()
sock.close()
if proc.poll() is None:
proc.terminate()
try:
proc.wait(timeout=5)
except subprocess.TimeoutExpired:
proc.kill()
proc.wait()
if __name__ == '__main__':
main()
@@ -0,0 +1,110 @@
#!/usr/bin/env python3
"""Relances natives dans une session de test déjà démarrée (firmware non distribué)."""
import argparse
import http.server
import json
import os
from pathlib import Path
import runpy
import subprocess
import threading
import time
import uuid
def main():
p = argparse.ArgumentParser(description=__doc__)
p.add_argument('--emu-home', type=Path, required=True)
p.add_argument('--agent-port', type=int, required=True)
p.add_argument('--action', choices=('respring', 'reset'), required=True)
p.add_argument('--cycles', type=int, default=3)
p.add_argument('--output', type=Path, required=True)
args = p.parse_args()
if args.cycles < 1:
p.error('--cycles doit être positif')
home = args.emu_home.resolve()
run = home / 'run'
bindir = Path(__file__).resolve().parents[1] / 'bin'
os.environ.update(IPOD_EMU_HOME=str(home), IPOD_AGENT_PORT=str(args.agent_port))
Agent = runpy.run_path(str(bindir / 'ipod-agent'))['Agent']
def call(*fields, data=None):
a = Agent(timeout=10)
try:
ok, body, detail = a.call(*fields, data=data)
if not ok:
raise RuntimeError(body.decode(errors='replace'))
return body
finally:
a.f.close()
a.s.close()
def springboard():
return next(line.split()[0] for line in call('ps').decode().splitlines()
if line.endswith(' SpringBoard'))
nonce = uuid.uuid4().hex.encode()
marker = '/private/var/mobile/Library/test-display-' + nonce.decode()
class Fixture(http.server.BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-Length', str(len(nonce)))
self.end_headers()
self.wfile.write(nonce)
def log_message(self, *unused):
pass
server = http.server.ThreadingHTTPServer(('127.0.0.1', 0), Fixture)
threading.Thread(target=server.serve_forever, daemon=True).start()
url = f'http://10.0.2.2:{server.server_port}/preuve'
args.output.mkdir(parents=True, exist_ok=True)
qpid = (run / 'qemu.pid').read_text()
npid = (run / 'netd.pid').read_text()
inode = (run / 'nand').stat().st_ino
results = []
try:
call('put', marker, '600', len(nonce), data=nonce)
for cycle in range(args.cycles):
oldpid = springboard()
offset = (run / 'serial.log').stat().st_size
started = time.monotonic()
subprocess.run([str(bindir / 'ipod-emu'), args.action, '-W'],
check=True, timeout=340)
newpid = springboard()
fresh = (run / 'serial.log').read_bytes()[offset:]
if args.action == 'reset':
assert b'Darwin Kernel Version' in fresh
else:
assert oldpid != newpid
assert b'Darwin Kernel Version' not in fresh
assert b'locked=0 passcode=0' in call('screen')
assert (run / 'qemu.pid').read_text() == qpid
assert (run / 'netd.pid').read_text() == npid
assert (run / 'nand').stat().st_ino == inode
assert call('get', marker) == nonce
assert nonce in call('fetch', url)
call('launch', 'com.apple.calculator', '-')
time.sleep(3)
assert call('front').strip() == b'com.apple.calculator'
capture = args.output.resolve() / f'{args.action}-{cycle + 1}.png'
subprocess.run([str(bindir / 'ipod-qmp'), 'screenshot', str(capture)],
check=True, timeout=15)
result = dict(cycle=cycle + 1, seconds=round(time.monotonic() - started, 1),
qemu_pid=int(qpid), netd_pid=int(npid), springboard_pid=newpid,
unlocked=True, nand_preserved=True, http=True,
net=call('net').decode(), front='com.apple.calculator')
results.append(result)
print(json.dumps(result, ensure_ascii=False), flush=True)
(args.output / 'validation.json').write_text(
json.dumps(results, ensure_ascii=False, indent=2) + '\n')
call('rm', marker)
call('sync')
finally:
server.shutdown()
server.server_close()
if __name__ == '__main__':
main()
+119
View File
@@ -0,0 +1,119 @@
/* Le masque LCD et l'acquittement doivent survivre à plusieurs resprings. */
#include "qemu/osdep.h"
#include "qemu/main-loop.h"
#include "sysemu/cpus.h"
#include "hw/arm/ipod_touch_lcd.c"
#define ipod_touch_machine_types test_mipi_register_types
#include "hw/arm/ipod_touch_mipi_dsi.c"
#undef ipod_touch_machine_types
#include "hw/arm/ipod_touch_tvout.c"
static void irq_changed(void *opaque, int n, int level)
{
*(int *)opaque = level;
}
static void test_interrupt_reenable(void)
{
IPodTouchLCDState *s = IPOD_TOUCH_LCD(object_new(TYPE_IPOD_TOUCH_LCD));
int level = 0;
qemu_irq irq = qemu_allocate_irq(irq_changed, &level, 0);
sysbus_connect_irq(SYS_BUS_DEVICE(s), 0, irq);
s->refresh_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, refresh_timer_tick, s);
for (int cycle = 0; cycle < 3; cycle++) {
/* Vblank masqué : état en attente, aucune interruption CPU. */
ipod_touch_lcd_write(s, 0x8, 0, 4);
refresh_timer_tick(s);
g_assert_cmpint(level, ==, 0);
g_assert_cmpuint(ipod_touch_lcd_read(s, 0xc, 4), ==, 1);
ipod_touch_lcd_write(s, 0xc, 0xff, 4);
g_assert_cmpuint(ipod_touch_lcd_read(s, 0xc, 4), ==, 0);
/* Nouveau client : le masque réactive les vblanks après cet ACK. */
ipod_touch_lcd_write(s, 0x8, 1, 4);
g_assert_cmpint(level, ==, 0);
refresh_timer_tick(s);
g_assert_cmpint(level, ==, 1);
ipod_touch_lcd_write(s, 0xc, 2, 4);
g_assert_cmpint(level, ==, 1);
ipod_touch_lcd_write(s, 0xc, 1, 4);
g_assert_cmpint(level, ==, 0);
g_assert_cmpuint(ipod_touch_lcd_read(s, 0x8, 4), ==, 1);
refresh_timer_tick(s);
g_assert_cmpint(level, ==, 1);
ipod_touch_lcd_write(s, 0x8, 0, 4);
g_assert_cmpint(level, ==, 0);
ipod_touch_lcd_write(s, 0x8, 1, 4);
g_assert_cmpint(level, ==, 1);
}
ipod_touch_lcd_write(s, 0x24, 0x12340000, 4);
device_cold_reset(DEVICE(s));
g_assert_cmpint(level, ==, 0);
g_assert_cmpuint(ipod_touch_lcd_read(s, 0x8, 4), ==, 0);
g_assert_cmpuint(ipod_touch_lcd_read(s, 0xc, 4), ==, 0);
g_assert_cmpuint(ipod_touch_lcd_read(s, 0x24, 4), ==, 0);
timer_del(s->refresh_timer);
timer_free(s->refresh_timer);
object_unref(OBJECT(s));
qemu_free_irq(irq);
}
static void test_tvout_submissions(void)
{
IPodTouchTVOutState *s = IPOD_TOUCH_TVOUT(object_new(TYPE_IPOD_TOUCH_TVOUT));
int level = 0;
qemu_irq irq = qemu_allocate_irq(irq_changed, &level, 0);
sysbus_connect_irq(SYS_BUS_DEVICE(s), 0, irq);
/* L'ancien quota global cessait de répondre dès la troisième soumission. */
for (int cycle = 0; cycle < 20; cycle++) {
ipod_touch_tvout_mixer1_write(s, MXR_STATUS, 0x85, 4);
g_assert_cmpint(level, ==, 1);
ipod_touch_tvout_sdo_write(s, SDO_IRQ, 2, 4);
g_assert_cmpint(level, ==, 1);
ipod_touch_tvout_sdo_write(s, SDO_IRQ, 1, 4);
g_assert_cmpint(level, ==, 0);
}
ipod_touch_tvout_sdo_write(s, SDO_IRQMASK, 1, 4);
ipod_touch_tvout_mixer1_write(s, MXR_STATUS, 0x85, 4);
g_assert_cmpint(level, ==, 0);
g_assert_cmpuint(ipod_touch_tvout_sdo_read(s, SDO_IRQ, 4), ==, 1);
ipod_touch_tvout_sdo_write(s, SDO_IRQMASK, 0, 4);
g_assert_cmpint(level, ==, 1);
device_cold_reset(DEVICE(s));
g_assert_cmpint(level, ==, 0);
g_assert_cmpuint(ipod_touch_tvout_sdo_read(s, SDO_IRQ, 4), ==, 0);
ipod_touch_tvout_mixer1_write(s, MXR_STATUS, 0x85, 4);
g_assert_cmpint(level, ==, 1);
object_unref(OBJECT(s));
qemu_free_irq(irq);
}
static void test_panel_reset(void)
{
IPodTouchMIPIDSIState *s = IPOD_TOUCH_MIPI_DSI(object_new(TYPE_IPOD_TOUCH_MIPI_DSI));
for (int cycle = 0; cycle < 3; cycle++) {
ipod_touch_mipi_dsi_write(s, REG_CLKCTRL, DSIM_CLKCTRL_TX_REQUEST_HSCLK, 4);
g_assert_cmpuint(ipod_touch_mipi_dsi_read(s, 0, 4) & rDSIM_STATUS_TxReadyHsClk, !=, 0);
g_assert_cmpuint(ipod_touch_mipi_dsi_read(s, REG_RXFIFO, 4), ==, 0x31a);
/* Un reset pendant une réponse doit recommencer par son en-tête. */
device_cold_reset(DEVICE(s));
g_assert_cmpuint(ipod_touch_mipi_dsi_read(s, 0, 4) & rDSIM_STATUS_TxReadyHsClk, ==, 0);
g_assert_cmpuint(ipod_touch_mipi_dsi_read(s, REG_RXFIFO, 4), ==, 0x31a);
g_assert_cmpuint(ipod_touch_mipi_dsi_read(s, REG_RXFIFO, 4), ==, 0x00a1d13c);
}
object_unref(OBJECT(s));
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
module_call_init(MODULE_INIT_QOM);
qemu_init_main_loop(&error_abort);
qemu_init_cpu_loop();
qemu_mutex_lock_iothread();
g_test_add_func("/ipod-lcd/interrupt-reenable", test_interrupt_reenable);
g_test_add_func("/ipod-tvout/repeated-submissions", test_tvout_submissions);
g_test_add_func("/ipod-mipi/panel-reset", test_panel_reset);
return g_test_run();
}
+242
View File
@@ -0,0 +1,242 @@
/* Régressions du protocole tactile : commandes invalides et trames concurrentes. */
#include "qemu/osdep.h"
#include "qemu/main-loop.h"
#include "sysemu/cpus.h"
#include "hw/arm/ipod_touch_multitouch.c"
#include "hw/arm/ipod_touch_gpio.c"
#include "hw/arm/ipod_touch_sysic.c"
#include "hw/arm/ipod_touch_spi.c"
static IPodTouchMultitouchState *new_sensor(void)
{
IPodTouchMultitouchState *s = IPOD_TOUCH_MULTITOUCH(
object_new(TYPE_IPOD_TOUCH_MULTITOUCH));
ipod_touch_multitouch_realize(SSI_PERIPHERAL(s), &error_abort);
return s;
}
static void destroy_sensor(IPodTouchMultitouchState *s)
{
finish_transfer(s);
timer_free(s->touch_timer);
timer_free(s->touch_end_timer);
g_free(s->next_frame);
g_free(s->read_frame);
object_unref(OBJECT(s));
}
static uint8_t transfer(IPodTouchMultitouchState *s, uint8_t byte)
{
return ipod_touch_multitouch_transfer(SSI_PERIPHERAL(s), byte);
}
static void test_unknown(void)
{
IPodTouchMultitouchState *s = new_sensor();
for (int i = 0; i < 10000; i++) {
g_assert_cmpuint(transfer(s, 0xab), ==, 0xff);
g_assert_cmpuint(s->cur_cmd, ==, 0);
g_assert_null(s->in_buffer);
}
uint8_t out[16];
out[0] = transfer(s, MT_CMD_GET_INTERFACE_VERSION);
for (int i = 1; i < 16; i++) out[i] = transfer(s, 0);
g_assert_cmpuint(out[2], ==, MT_INTERFACE_VERSION);
destroy_sensor(s);
}
static void test_snapshot(void)
{
IPodTouchMultitouchState *s = new_sensor();
MTFrame expected = *s->next_frame;
uint8_t out[sizeof(MTFrame)];
out[0] = transfer(s, MT_CMD_FRAME_READ);
for (int i = 1; i < 16; i++) out[i] = transfer(s, 0);
g_free(s->next_frame);
s->next_frame = get_frame(s, MT_EVENT_TOUCH_START, .4, .6,
100, 660, 580, 150);
g_assert_cmpuint(s->cur_cmd, ==, 0);
out[16] = transfer(s, 0xeb);
out[17] = transfer(s, 0);
out[18] = transfer(s, 1);
for (size_t i = 19; i < sizeof out; i++) out[i] = transfer(s, 0);
expected.frame_packet.unused1 = expected.frame_length.length1;
expected.frame_packet.checksum_pad = -(expected.frame_packet.cmd +
expected.frame_packet.unused1 + expected.frame_packet.length1 +
expected.frame_packet.length2);
g_assert_cmpmem(out, sizeof out, &expected, sizeof expected);
g_assert_cmpuint(s->next_frame->finger_data.event, ==, MT_EVENT_TOUCH_START);
destroy_sensor(s);
}
static void test_reports(void)
{
IPodTouchMultitouchState *s = new_sensor();
uint8_t out[16];
for (int cmd = MT_CMD_GET_REPORT_INFO; cmd <= MT_CMD_SHORT_CONTROL_READ; cmd += 3) {
out[0] = transfer(s, cmd);
out[1] = transfer(s, 0xff);
for (int i = 2; i < 16; i++) out[i] = transfer(s, 0);
g_assert_cmpuint(out[2], ==, 1);
unsigned sum = 0;
for (int i = 0; i < 14; i++) sum += out[i];
g_assert_cmpuint(lduw_le_p(out + 14), ==, sum);
}
transfer(s, MT_CMD_SHORT_CONTROL_READ);
transfer(s, MT_REPORT_SENSOR_DIMENSIONS);
for (int i = 2; i < 16; i++) out[i] = transfer(s, 0);
g_assert_cmpuint(ldl_le_p(out + 3), ==, 5000);
g_assert_cmpuint(ldl_le_p(out + 7), ==, 7500);
destroy_sensor(s);
}
static void test_spi_abort(void)
{
IPodTouchSPIState *spi = IPOD_TOUCH_SPI(object_new(TYPE_IPOD_TOUCH_SPI));
DEVICE(spi)->id = g_strdup("spi-test");
set_spi_base(4);
ipod_touch_spi_realize(DEVICE(spi), &error_abort);
ipod_touch_spi_write(spi, R_TXCNT, 2, 4);
ipod_touch_spi_write(spi, R_RXCNT, 2, 4);
ipod_touch_spi_write(spi, R_TXDATA, MT_CMD_FRAME_READ, 4);
ipod_touch_spi_write(spi, R_TXDATA, 2, 4);
ipod_touch_spi_write(spi, R_CTRL, R_CTRL_RUN, 4);
g_assert_cmpuint(spi->mt->in_buffer_ind, ==, 2);
ipod_touch_spi_write(spi, R_CTRL, R_CTRL_TX_RESET | R_CTRL_RX_RESET, 4);
g_assert_cmpuint(spi->mt->cur_cmd, ==, 0);
g_assert_null(spi->mt->in_buffer);
uint8_t response[16];
response[0] = transfer(spi->mt, MT_CMD_GET_INTERFACE_VERSION);
for (int i = 1; i < 16; i++) response[i] = transfer(spi->mt, 0);
g_assert_cmpuint(response[2], ==, MT_INTERFACE_VERSION);
spi->last_irq = 1;
fifo8_push(&spi->tx_fifo, 0x12);
fifo8_push(&spi->rx_fifo, 0x34);
device_cold_reset(DEVICE(spi));
g_assert_cmpuint(spi->last_irq, ==, 0);
g_assert_true(fifo8_is_empty(&spi->tx_fifo));
g_assert_true(fifo8_is_empty(&spi->rx_fifo));
g_assert_cmpuint(ipod_touch_spi_read(spi, R_TXCNT, 4), ==, 0);
timer_free(spi->mt->touch_timer);
timer_free(spi->mt->touch_end_timer);
g_free(spi->mt->next_frame);
fifo8_destroy(&spi->tx_fifo);
fifo8_destroy(&spi->rx_fifo);
object_unref(OBJECT(spi));
}
static void test_hbpp(void)
{
IPodTouchMultitouchState *s = new_sensor();
uint8_t bad[10] = {MT_CMD_HBPP_DATA_PACKET, 0, 0, 4};
for (int i = 0; i < 10; i++) transfer(s, bad[i]);
g_assert_cmpuint(s->cur_cmd, ==, 0);
uint8_t good[10] = {MT_CMD_HBPP_DATA_PACKET, 0, 0, 4, 0, 0, 0, 0, 0, 4};
for (int i = 0; i < 10; i++) transfer(s, good[i]);
for (int i = 1; i < 21; i++) transfer(s, 0);
g_assert_cmpuint(s->cur_cmd, ==, 0);
destroy_sensor(s);
}
static void test_velocity(void)
{
IPodTouchMultitouchState *s = new_sensor();
MTFrame *f = get_frame(s, MT_EVENT_TOUCH_MOVED, .4, .6, 1, 1, 1, 1);
g_free(f);
f = get_frame(s, MT_EVENT_TOUCH_MOVED, .4, .7, 1, 1, 1, 1);
g_assert_cmpint(f->finger_data.velX, ==, 0);
g_assert_cmpint(f->finger_data.velY, >, 0);
g_free(f);
f = get_frame(s, MT_EVENT_TOUCH_MOVED, .4, .7, 1, 1, 1, 1);
g_assert_cmpint(f->finger_data.velX, ==, 0);
g_assert_cmpint(f->finger_data.velY, ==, 0);
g_free(f);
destroy_sensor(s);
}
static void test_buttons(void)
{
IPodTouchGPIOState *s = IPOD_TOUCH_GPIO(object_new(TYPE_IPOD_TOUCH_GPIO));
g_assert_cmpuint(GPIO2PAD(GPIO_BUTTON_HOME), <, NUM_GPIO_PADS);
gpio_set_on(s->gpio_state, GPIO_BUTTON_HOME);
gpio_set_on(s->gpio_state, GPIO_BUTTON_POWER);
g_assert_true(gpio_is_on(s->gpio_state, GPIO_BUTTON_HOME));
g_assert_true(gpio_is_on(s->gpio_state, GPIO_BUTTON_POWER));
gpio_set_off(s->gpio_state, GPIO_BUTTON_HOME);
g_assert_true(gpio_is_off(s->gpio_state, GPIO_BUTTON_HOME));
g_assert_true(gpio_is_on(s->gpio_state, GPIO_BUTTON_POWER));
object_unref(OBJECT(s));
}
static void test_gpio_ranges(void)
{
IPodTouchSYSICState *s = IPOD_TOUCH_SYSIC(object_new(TYPE_IPOD_TOUCH_SYSIC));
uint32_t arrays[4][GPIO_NUMINTGROUPS];
s->gpio_int_level[0] = 0x1234;
memcpy(arrays[0], s->gpio_int_level, sizeof arrays[0]);
memcpy(arrays[1], s->gpio_int_status, sizeof arrays[1]);
memcpy(arrays[2], s->gpio_int_enabled, sizeof arrays[2]);
memcpy(arrays[3], s->gpio_int_type, sizeof arrays[3]);
/* Les quatre trous entre groupes ne doivent pas accéder au tableau suivant. */
for (int addr = GPIO_INTLEVEL; addr <= GPIO_INTTYPE; addr += 0x20) {
ipod_touch_sysic_write(s, addr + GPIO_NUMINTGROUPS * 4, 0xffffffff, 4);
g_assert_cmpuint(ipod_touch_sysic_read(s, addr + GPIO_NUMINTGROUPS * 4, 4), ==, 0);
}
g_assert_cmpmem(arrays[0], sizeof arrays[0], s->gpio_int_level, sizeof arrays[0]);
g_assert_cmpmem(arrays[1], sizeof arrays[1], s->gpio_int_status, sizeof arrays[1]);
g_assert_cmpmem(arrays[2], sizeof arrays[2], s->gpio_int_enabled, sizeof arrays[2]);
g_assert_cmpmem(arrays[3], sizeof arrays[3], s->gpio_int_type, sizeof arrays[3]);
object_unref(OBJECT(s));
}
static void test_reset(void)
{
IPodTouchMultitouchState *s = new_sensor();
IPodTouchSYSICState *sysic = IPOD_TOUCH_SYSIC(object_new(TYPE_IPOD_TOUCH_SYSIC));
s->sysic = sysic;
ipod_touch_multitouch_on_touch(s);
/* Capture d'une trame, puis interruption au milieu d'une autre commande. */
transfer(s, MT_CMD_FRAME_READ);
for (int i = 1; i < 16; i++) transfer(s, 0);
transfer(s, MT_CMD_GET_INTERFACE_VERSION);
s->hbpp_atn_ack_response[0] = 0xaa;
g_assert_true(timer_pending(s->touch_timer));
g_assert_nonnull(s->read_frame);
g_assert_nonnull(s->out_buffer);
device_cold_reset(DEVICE(s));
g_assert_false(timer_pending(s->touch_timer));
g_assert_false(timer_pending(s->touch_end_timer));
g_assert_false(s->touch_down);
g_assert_null(s->read_frame);
g_assert_null(s->in_buffer);
g_assert_null(s->out_buffer);
g_assert_cmpuint(s->cur_cmd, ==, 0);
g_assert_cmpuint(s->hbpp_atn_ack_response[0], ==, 0);
g_assert_cmpuint(sysic->gpio_int_status[3], ==, 0);
g_assert_cmpuint(s->next_frame->finger_data.event, ==, MT_EVENT_TOUCH_FULL_END);
transfer(s, MT_CMD_GET_INTERFACE_VERSION);
transfer(s, 0);
g_assert_cmpuint(transfer(s, 0), ==, MT_INTERFACE_VERSION);
destroy_sensor(s);
object_unref(OBJECT(sysic));
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
module_call_init(MODULE_INIT_QOM);
qemu_init_main_loop(&error_abort);
qemu_init_cpu_loop();
qemu_mutex_lock_iothread();
g_test_add_func("/ipod-multitouch/unknown", test_unknown);
g_test_add_func("/ipod-multitouch/snapshot", test_snapshot);
g_test_add_func("/ipod-multitouch/hbpp", test_hbpp);
g_test_add_func("/ipod-multitouch/spi-abort", test_spi_abort);
g_test_add_func("/ipod-multitouch/reports", test_reports);
g_test_add_func("/ipod-multitouch/velocity", test_velocity);
g_test_add_func("/ipod-multitouch/reset", test_reset);
g_test_add_func("/ipod-input/buttons", test_buttons);
g_test_add_func("/ipod-input/gpio-ranges", test_gpio_ranges);
return g_test_run();
}
@@ -0,0 +1,107 @@
#!/usr/bin/env python3
"""Vérifie le reset du lanceur sans QEMU : synchronisation et nouveau journal de boot."""
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
import time
import unittest
ROOT = Path(__file__).resolve().parents[3]
BOOT = b'AppleMultitouchSPIUserClient::attach\n' * 2 + b'SCHelper "EXIT"\n'
class ResetLauncher(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.home = Path(self.tmp.name)
self.run = self.home / 'run'
self.run.mkdir()
(self.run / 'nand').mkdir()
(self.run / 'nand/temoin').write_bytes(b'NAND conservee')
(self.run / 'qemu.pid').write_text(str(os.getpid()))
# Une ancienne erreur et beaucoup de données ne doivent pas contaminer le nouveau boot.
self.old = b'Fatal Exception\x00\n' + b'x' * 100000 + b'\n' + BOOT
(self.run / 'serial.log').write_bytes(self.old)
tools = self.home / 'bin'
tools.mkdir()
self.launcher = tools / 'ipod-emu'
shutil.copy2(ROOT / 'contrib/ipod-emu/bin/ipod-emu', self.launcher)
scripts = {
'ipod-agent': '''#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' "$1" >> "$IPOD_EMU_HOME/actions"
if [ "$1" = sync ] && [ "${IPOD_TEST_SYNC_FAIL:-0}" = 1 ]; then exit 1; fi
''',
'ipod-qmp': '''#!/usr/bin/env bash
set -euo pipefail
printf 'reset\n' >> "$IPOD_EMU_HOME/actions"
'''
}
for name, content in scripts.items():
path = tools / name
path.write_text(content)
path.chmod(0o755)
self.env = dict(os.environ, IPOD_EMU_HOME=str(self.home))
def tearDown(self):
self.tmp.cleanup()
def test_reset_preserves_nand(self):
subprocess.run([str(self.launcher), 'reset'], env=self.env,
capture_output=True, check=True, timeout=5)
self.assertEqual((self.home / 'actions').read_text().splitlines(), ['sync', 'reset'])
self.assertEqual(int((self.run / 'boot-log-offset').read_text()), len(self.old))
self.assertEqual((self.run / 'nand/temoin').read_bytes(), b'NAND conservee')
self.assertEqual((self.run / 'qemu.pid').read_text(), str(os.getpid()))
def test_wait_ignores_previous_boot(self):
proc = subprocess.Popen([str(self.launcher), 'reset', '-W'], env=self.env,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
try:
deadline = time.monotonic() + 5
while not (self.run / 'boot-log-offset').exists():
self.assertLess(time.monotonic(), deadline)
self.assertIsNone(proc.poll())
time.sleep(.02)
# L'ancien journal contient déjà tous les marqueurs, mais l'attente doit continuer.
time.sleep(3.5)
self.assertIsNone(proc.poll())
self.assertNotIn('unlock', (self.home / 'actions').read_text())
with (self.run / 'serial.log').open('ab') as log:
log.write(BOOT)
out, err = proc.communicate(timeout=15)
self.assertEqual(proc.returncode, 0, err.decode())
self.assertIn('SpringBoard prêt', out.decode())
self.assertEqual((self.home / 'actions').read_text().splitlines(), ['sync', 'reset', 'unlock'])
finally:
if proc.poll() is None:
proc.kill()
proc.communicate()
def test_sync_failure_prevents_reset(self):
env = dict(self.env, IPOD_TEST_SYNC_FAIL='1')
r = subprocess.run([str(self.launcher), 'reset'], env=env,
capture_output=True, timeout=5)
self.assertNotEqual(r.returncode, 0)
self.assertEqual((self.home / 'actions').read_text().splitlines(), ['sync'])
self.assertFalse((self.run / 'boot-log-offset').exists())
def test_respring_preserves_session(self):
subprocess.run([str(self.launcher), 'respring'], env=self.env,
capture_output=True, check=True, timeout=5)
self.assertEqual((self.home / 'actions').read_text().splitlines(), ['sync', 'respring'])
self.assertEqual(int((self.run / 'boot-log-offset').read_text()), len(self.old))
self.assertEqual((self.run / 'nand/temoin').read_bytes(), b'NAND conservee')
self.assertEqual((self.run / 'qemu.pid').read_text(), str(os.getpid()))
def test_invalid_option_does_not_restart(self):
r = subprocess.run([str(self.launcher), 'reset', '-f'], env=self.env,
capture_output=True, timeout=5)
self.assertNotEqual(r.returncode, 0)
self.assertFalse((self.home / 'actions').exists())
if __name__ == '__main__':
unittest.main()
+155
View File
@@ -0,0 +1,155 @@
/* Régressions du D1759 sur le bus I²C réel de QEMU. */
#include "qemu/osdep.h"
#include "qemu/main-loop.h"
#include "qemu/option.h"
#include "sysemu/cpus.h"
#include "hw/arm/ipod_touch_pcf50633_pmu.c"
static QemuOptsList rtc_test_opts = {
.name = "rtc-test",
.head = QTAILQ_HEAD_INITIALIZER(rtc_test_opts.head),
.desc = {
{ .name = "base", .type = QEMU_OPT_STRING },
{ .name = "clock", .type = QEMU_OPT_STRING },
{ }
}
};
static I2CBus *bus;
static Pcf50633State *pmu;
static DeviceState *parent;
static void setup(const char *base, const char *clock)
{
QemuOpts *opts = qemu_opts_create(&rtc_test_opts, NULL, 0, &error_abort);
qemu_opt_set(opts, "base", base, &error_abort);
qemu_opt_set(opts, "clock", clock, &error_abort);
configure_rtc(opts);
qemu_opts_del(opts);
parent = qdev_new(TYPE_PCF50633);
bus = i2c_init_bus(parent, "rtc-test");
pmu = PCF50633(i2c_slave_create_simple(bus, TYPE_D1759, 0x73));
}
static void teardown(void)
{
qdev_unrealize(DEVICE(pmu));
object_unref(OBJECT(parent));
}
static void select_register(uint8_t reg, bool stop)
{
g_assert_cmpint(i2c_start_transfer(bus, 0x73, false), ==, 0);
g_assert_cmpint(i2c_send(bus, reg), ==, 0);
if (stop) {
i2c_end_transfer(bus);
}
g_assert_cmpint(i2c_start_transfer(bus, 0x73, true), ==, 0);
}
static uint32_t read_word(uint8_t reg, bool stop)
{
uint32_t value = 0;
select_register(reg, stop);
for (int i = 0; i < 4; i++) {
value |= (uint32_t)i2c_recv(bus) << (i * 8);
}
i2c_end_transfer(bus);
return value;
}
static void write_word(uint8_t reg, uint32_t value)
{
g_assert_cmpint(i2c_start_transfer(bus, 0x73, false), ==, 0);
g_assert_cmpint(i2c_send(bus, reg), ==, 0);
for (int i = 0; i < 4; i++) {
g_assert_cmpint(i2c_send(bus, value >> (i * 8)), ==, 0);
}
i2c_end_transfer(bus);
}
static void test_host_utc(void)
{
time_t before = time(NULL);
/* Le fuseau de l'hôte ne doit pas décaler le compteur Unix. */
setenv("TZ", "Pacific/Honolulu", 1);
tzset();
setup("utc", "host");
uint32_t value = read_word(0x5c, false);
g_assert_cmpuint(value, >=, before);
g_assert_cmpuint(value, <=, time(NULL));
g_assert_cmpuint(read_word(0x64, true), ==, 0);
teardown();
unsetenv("TZ");
tzset();
}
static void test_fixed_base(void)
{
setup("2024-02-29T23:59:59", "vm");
g_assert_cmpint(rtc_clock, ==, QEMU_CLOCK_VIRTUAL);
g_assert_cmpuint(read_word(0x5c, true), ==, 1709251199);
g_assert_cmpuint(read_word(0x5c, false), ==, 1709251199);
teardown();
}
static void test_snapshot_rollover(void)
{
setup("2024-02-29T23:59:59", "vm");
uint32_t boundaries[] = { 1709251199, 0x00ffffff, 0xffffffff };
for (size_t n = 0; n < G_N_ELEMENTS(boundaries); n++) {
pmu->rtc_tick_offset = boundaries[n];
select_register(0x5c, false);
uint32_t value = i2c_recv(bus);
/* Le temps change entre deux octets, après capture du premier. */
pmu->rtc_tick_offset++;
for (int i = 1; i < 4; i++) {
value |= (uint32_t)i2c_recv(bus) << (8 * i);
}
i2c_end_transfer(bus);
g_assert_cmpuint(value, ==, boundaries[n]);
g_assert_cmpuint(read_word(0x5c, true), ==, boundaries[n] + 1);
}
teardown();
}
static void test_offsets(void)
{
setup("2024-02-29T23:59:59", "vm");
uint32_t counter = read_word(0x5c, true);
uint32_t offsets[] = { 3600, (uint32_t)-86400, 0x12345678, 0 };
for (size_t i = 0; i < G_N_ELEMENTS(offsets); i++) {
write_word(0x64, offsets[i]);
g_assert_cmpuint(read_word(0x64, false), ==, offsets[i]);
g_assert_cmpuint(read_word(0x5c, true), ==, counter);
}
/* Un changement de date ne doit pas réécrire le compteur matériel. */
write_word(0x5c, 1234);
g_assert_cmpuint(read_word(0x5c, false), ==, counter);
teardown();
}
static void test_boot_registers(void)
{
setup("2024-02-29T23:59:59", "vm");
write_word(0x60, 0xffffffff);
g_assert_cmpuint(read_word(0x60, true), ==, 0);
g_assert_cmpuint(read_word(0x5c, false), ==, 1709251199);
g_assert_cmpuint(read_word(0x64, true), ==, 0);
teardown();
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
module_call_init(MODULE_INIT_QOM);
qemu_init_main_loop(&error_abort);
qemu_init_cpu_loop();
qemu_mutex_lock_iothread();
g_test_add_func("/ipod-rtc/host-utc", test_host_utc);
g_test_add_func("/ipod-rtc/fixed-base", test_fixed_base);
g_test_add_func("/ipod-rtc/snapshot-rollover", test_snapshot_rollover);
g_test_add_func("/ipod-rtc/offsets", test_offsets);
g_test_add_func("/ipod-rtc/boot-registers", test_boot_registers);
return g_test_run();
}
+180
View File
@@ -0,0 +1,180 @@
#!/usr/bin/env python3
"""Teste les transferts réels de l'agent sur socket local, sans firmware Apple."""
import importlib.util
from importlib.machinery import SourceFileLoader
import io
import os
from pathlib import Path
import socket
import subprocess
import tempfile
import threading
import unittest
ROOT = Path(__file__).resolve().parents[1]
loader = SourceFileLoader('ipod_agent', str(ROOT / 'bin/ipod-agent'))
spec = importlib.util.spec_from_loader(loader.name, loader)
client = importlib.util.module_from_spec(spec)
loader.exec_module(client)
class Transfers(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.work = tempfile.TemporaryDirectory()
cls.binary = Path(cls.work.name) / 'transfer-agent'
source = (ROOT / 'agent/emuagent.c').read_text()
# Compile les fonctions de production, sans dépendances privées d'iOS.
functions = source[source.index('static int send_all('):source.index('/* tampon dynamique */')]
functions += source[source.index('static int read_full('):source.index('static void cmd_ls(')]
harness = '''#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <unistd.h>
static void mkdirs(const char *path) { (void)path; }
''' + functions + '''
int main(int argc, char **argv) {
if (argc < 4) return 2;
int fd = atoi(argv[1]);
if (!strcmp(argv[2], "get")) cmd_get(fd, argv+3, argc-3);
else cmd_put(fd, argv+3, argc-3);
char ignored[256]; read(fd, ignored, sizeof ignored);
close(fd); return 0;
}
'''
c = Path(cls.work.name) / 'transfer-agent.c'
c.write_text(harness)
subprocess.run(['cc', '-Wall', '-Wextra', '-Werror', '-fsanitize=undefined',
'-o', str(cls.binary), str(c)], check=True)
@classmethod
def tearDownClass(cls):
cls.work.cleanup()
def agent(self, command, *args):
a, b = socket.socketpair()
process = subprocess.Popen([str(self.binary), str(b.fileno()), command, *map(str, args)],
pass_fds=(b.fileno(),), env=dict(os.environ, UBSAN_OPTIONS='halt_on_error=1'))
b.close()
agent = client.Agent.__new__(client.Agent)
agent.s, agent.f = a, a.makefile('rb')
self.addCleanup(agent.f.close)
self.addCleanup(a.close)
self.addCleanup(lambda: self.assertEqual(process.wait(timeout=5), 0))
return agent
def test_large_round_trip(self):
data = os.urandom(8 * 1024 * 1024 + 37)
with tempfile.TemporaryDirectory() as d:
path = Path(d) / 'file'
a = self.agent('put', path, '640', len(data))
# Le harness reçoit les arguments hors bande, puis le contenu du protocole.
a.s.sendall(data)
ok, _, _ = a.call() # ligne vide ignorée par ce harness
self.assertTrue(ok)
self.assertEqual(path.read_bytes(), data)
self.assertEqual(path.stat().st_mode & 0o777, 0o640)
a = self.agent('get', path)
with (Path(d) / 'download').open('wb') as out:
ok, _, _ = a.call(sink=out)
self.assertTrue(ok)
self.assertEqual((Path(d) / 'download').read_bytes(), data)
def test_interrupted_put_preserves_existing_file(self):
with tempfile.TemporaryDirectory() as d:
path = Path(d) / 'file'
path.write_bytes(b'ancienne version')
a = self.agent('put', path, '644', 100000)
a.s.sendall(b'court')
a.s.shutdown(socket.SHUT_WR)
response = a.f.read()
self.assertTrue(response.startswith(b'ERR '))
self.assertEqual(path.read_bytes(), b'ancienne version')
self.assertEqual(list(Path(d).iterdir()), [path])
def test_empty_file(self):
with tempfile.TemporaryDirectory() as d:
path = Path(d) / 'empty'
a = self.agent('put', path, '644', 0)
self.assertTrue(a.call()[0])
self.assertEqual(path.read_bytes(), b'')
self.assertEqual(self.agent('get', path).call()[1], b'')
def test_download_failure_preserves_destination(self):
listener = socket.socket()
listener.bind(('127.0.0.1', 0)); listener.listen()
def server():
connection, _ = listener.accept()
connection.recv(1024)
connection.sendall(b'OK 100\npartiel')
connection.close()
thread = threading.Thread(target=server); thread.start()
try:
with tempfile.TemporaryDirectory() as d:
destination = Path(d) / 'download'
destination.write_bytes(b'original')
r = subprocess.run([str(ROOT / 'bin/ipod-agent'), 'get', '/file', str(destination)],
env=dict(os.environ, IPOD_AGENT_PORT=str(listener.getsockname()[1]),
IPOD_AGENT_HOST='127.0.0.1'), capture_output=True)
self.assertEqual(r.returncode, 1)
self.assertIn('tronquée', r.stderr.decode())
self.assertEqual(destination.read_bytes(), b'original')
self.assertEqual(list(Path(d).iterdir()), [destination])
finally:
thread.join(); listener.close()
def test_upload_respects_announced_size(self):
a, b = socket.socketpair()
agent = client.Agent.__new__(client.Agent)
agent.s, agent.f = a, a.makefile('rb')
received = []
def server():
f = b.makefile('rb')
received.append(f.readline())
received.append(f.read(3))
b.sendall(b'OK 0\n')
f.close(); b.close()
thread = threading.Thread(target=server); thread.start()
try:
data = io.BytesIO(b'abcEXTRA')
self.assertTrue(agent.call('put', '/file', '644', 3, data=data)[0])
self.assertEqual(data.tell(), 3)
self.assertEqual(received, [b'put\t/file\t644\t3\n', b'abc'])
finally:
agent.f.close(); a.close(); thread.join()
def test_get_rejects_fifo_without_blocking(self):
with tempfile.TemporaryDirectory() as d:
path = Path(d) / 'fifo'
os.mkfifo(path)
self.assertFalse(self.agent('get', path).call()[0])
def test_fragmented_and_truncated_reply(self):
for truncated in (False, True):
a, b = socket.socketpair()
agent = client.Agent.__new__(client.Agent)
agent.s, agent.f = a, a.makefile('rb')
def server():
b.recv(100)
for part in (b'OK ', b'10\n', b'123', b'4567', b'890' if not truncated else b''):
b.sendall(part)
b.close()
thread = threading.Thread(target=server)
thread.start()
try:
if truncated:
with self.assertRaisesRegex(ConnectionError, 'tronquée'):
agent.call('get', '/test')
else:
self.assertEqual(agent.call('get', '/test')[1], b'1234567890')
finally:
agent.f.close(); a.close(); thread.join()
if __name__ == '__main__':
unittest.main()
+122
View File
@@ -0,0 +1,122 @@
/* Reset UART : registres, FIFO, IRQ et reconnexion du relais série. */
#include "qemu/osdep.h"
#include "qemu/main-loop.h"
#include "qemu/config-file.h"
#include "qemu/option.h"
#include "sysemu/cpus.h"
#include "sysemu/sysemu.h"
#include <sys/socket.h>
#include <sys/un.h>
#include "hw/arm/ipod_touch_uart.c"
static void irq_changed(void *opaque, int n, int level)
{
*(int *)opaque = level;
}
static void test_register_reset(void)
{
IPodTouchUARTState *s = IPOD_TOUCH_UART(object_new(TYPE_IPOD_TOUCH_UART));
int level = 0;
qemu_irq irq = qemu_allocate_irq(irq_changed, &level, 0);
sysbus_connect_irq(SYS_BUS_DEVICE(s), 0, irq);
uint8_t bytes[] = { 0x7e, 0xff, 0x03, 0x7d };
uart_write(s, ULCON, 3, 4);
uart_write(s, UCON, UCON_RX_INT | UCON_RXTO_INT | UCON_TX_INT, 4);
uart_write(s, UMCON, 1, 4);
uart_write(s, UBRDIV, 100, 4);
uart_receive(s, bytes, sizeof bytes);
g_assert_cmpint(level, ==, 1);
g_assert_cmpuint(uart_read(s, UFSTAT, 4), ==, sizeof bytes);
device_cold_reset(DEVICE(s));
g_assert_cmpint(level, ==, 0);
g_assert_cmpuint(uart_read(s, UTRSTAT, 4), ==,
UTRSTAT_TXBUF_EMPTY | UTRSTAT_TX_EMPTY);
g_assert_cmpuint(uart_read(s, UFSTAT, 4), ==, 0);
g_assert_cmpuint(uart_read(s, ULCON, 4), ==, 0);
g_assert_cmpuint(uart_read(s, UCON, 4), ==, 0);
g_assert_cmpuint(uart_read(s, UMCON, 4), ==, 0);
g_assert_cmpuint(uart_read(s, UBRDIV, 4), ==, 0);
object_unref(OBJECT(s));
qemu_free_irq(irq);
}
static void wait_open(IPodTouchUARTState *s)
{
int64_t deadline = g_get_monotonic_time() + 5 * G_USEC_PER_SEC;
while (!qemu_chr_fe_backend_open(&s->chr)) {
g_assert_cmpint(g_get_monotonic_time(), <, deadline);
main_loop_wait(true);
g_usleep(1000);
}
}
static int connect_client(const char *path)
{
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr = { .sun_family = AF_UNIX };
g_strlcpy(addr.sun_path, path, sizeof addr.sun_path);
g_assert_cmpint(fd, >=, 0);
struct timeval limit = { .tv_sec = 5 };
g_assert_cmpint(setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &limit, sizeof limit), ==, 0);
g_assert_cmpint(connect(fd, (struct sockaddr *)&addr, sizeof addr), ==, 0);
return fd;
}
static void test_socket_reconnect(void)
{
char *dir = g_dir_make_tmp("ipod-uart-XXXXXX", NULL);
g_assert_nonnull(dir);
char *path = g_build_filename(dir, "uart.sock", NULL);
char *spec = g_strdup_printf("unix:%s,server=on,wait=off", path);
Chardev *chr = qemu_chr_new("uart-test", spec, NULL);
g_assert_nonnull(chr);
IPodTouchUARTState *s = IPOD_TOUCH_UART(object_new(TYPE_IPOD_TOUCH_UART));
qemu_chr_fe_init(&s->chr, chr, &error_abort);
uart_realize(DEVICE(s), &error_abort);
for (int cycle = 0; cycle < 3; cycle++) {
int fd = connect_client(path);
wait_open(s);
uint8_t incoming = 0x41 + cycle;
g_assert_cmpint(write(fd, &incoming, 1), ==, 1);
int64_t deadline = g_get_monotonic_time() + 5 * G_USEC_PER_SEC;
while (!s->rx_count) {
g_assert_cmpint(g_get_monotonic_time(), <, deadline);
main_loop_wait(true);
g_usleep(1000);
}
g_assert_cmpuint(uart_read(s, URXH, 1), ==, incoming);
uart_write(s, UTXH, 0x7e, 1);
uint8_t b = 0;
g_assert_cmpint(read(fd, &b, 1), ==, 1);
g_assert_cmpuint(b, ==, 0x7e);
device_cold_reset(DEVICE(s));
/* Le relais voit la fin du lien et peut renégocier avec le nouvel iOS. */
g_assert_false(qemu_chr_fe_backend_open(&s->chr));
g_assert_cmpint(read(fd, &b, 1), ==, 0);
close(fd);
}
qemu_chr_fe_deinit(&s->chr, false);
object_unref(OBJECT(s));
object_unparent(OBJECT(chr));
unlink(path);
rmdir(dir);
g_free(spec);
g_free(path);
g_free(dir);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
module_call_init(MODULE_INIT_QOM);
qemu_add_opts(&qemu_chardev_opts);
qemu_init_main_loop(&error_abort);
qemu_init_cpu_loop();
qemu_mutex_lock_iothread();
g_test_add_func("/ipod-uart/register-reset", test_register_reset);
g_test_add_func("/ipod-uart/socket-reconnect", test_socket_reconnect);
return g_test_run();
}
+3 -2
View File
@@ -328,7 +328,7 @@ static void ipod_touch_machine_init(MachineState *machine)
nms->sysic = (IPodTouchSYSICState *) g_malloc0(sizeof(struct IPodTouchSYSICState));
memory_region_add_subregion(sysmem, SYSIC_MEM_BASE, &sysic_state->iomem);
busdev = SYS_BUS_DEVICE(dev);
for(int grp = 0; grp < GPIO_NUMINTGROUPS; grp++) {
for (int grp = 0; grp < G_N_ELEMENTS(S5L8900_GPIO_IRQS); grp++) {
sysbus_connect_irq(busdev, grp, s5l8900_get_irq(nms, S5L8900_GPIO_IRQS[grp]));
}
@@ -461,7 +461,7 @@ static void ipod_touch_machine_init(MachineState *machine)
sysbus_connect_irq(busdev, 0, s5l8900_get_irq(nms, S5L8720_I2C0_IRQ));
// init the PMU
I2CSlave * pmu = i2c_slave_create_simple(i2c_state->bus, "pcf50633", 0x73);
I2CSlave * pmu = i2c_slave_create_simple(i2c_state->bus, "d1759", 0x73);
// init the accelerometer
I2CSlave *accelerometer = i2c_slave_create_simple(i2c_state->bus, "lis302dl", 0x1D);
@@ -508,6 +508,7 @@ static void ipod_touch_machine_init(MachineState *machine)
IPodTouchMIPIDSIState *mipi_dsi_state = IPOD_TOUCH_MIPI_DSI(dev);
nms->mipi_dsi_state = mipi_dsi_state;
memory_region_add_subregion(sysmem, MIPI_DSI_MEM_BASE, &mipi_dsi_state->iomem);
sysbus_realize(SYS_BUS_DEVICE(dev), &error_fatal);
// init LCD
dev = qdev_new("ipodtouch.lcd");
+25 -10
View File
@@ -16,8 +16,10 @@ static uint64_t ipod_touch_lcd_read(void *opaque, hwaddr addr, unsigned size)
return 2;
case 0x4:
return s->lcd_con;
case 0x8:
return s->int_enable;
case 0xC:
return 0x1; //s->unknown1;
return s->int_status;
case 0x20:
return s->w1_display_depth_info;
case 0x24:
@@ -46,10 +48,14 @@ static void ipod_touch_lcd_write(void *opaque, hwaddr addr, uint64_t val, unsign
case 0x4:
s->lcd_con = val;
break;
case 0x8:
s->int_enable = val;
qemu_set_irq(s->irq, !!(s->int_status & s->int_enable));
break;
case 0xC:
s->render = val;
qemu_irq_lower(s->irq);
// qemu_irq_raise(s->irq);
/* Acquittement W1C, indépendant du masque d'interruptions. */
s->int_status &= ~val;
qemu_set_irq(s->irq, !!(s->int_status & s->int_enable));
break;
case 0x20:
s->w1_display_depth_info = val;
@@ -159,8 +165,6 @@ static void ipod_touch_lcd_mouse_event(void *opaque, int x, int y, int z, int bu
float fy = 1 - y / pow(2, 15);
IPodTouchLCDState *lcd = (IPodTouchLCDState *) opaque;
lcd->mt->prev_touch_x = lcd->mt->touch_x;
lcd->mt->prev_touch_y = lcd->mt->touch_y;
lcd->mt->touch_x = fx;
lcd->mt->touch_y = fy;
@@ -176,10 +180,8 @@ static void refresh_timer_tick(void *opaque)
{
IPodTouchLCDState *s = (IPodTouchLCDState *)opaque;
if (s->render == 0x1)
qemu_irq_raise(s->irq);
else if (s->render == 0xFF)
qemu_irq_lower(s->irq);
s->int_status |= 1;
qemu_set_irq(s->irq, !!(s->int_status & s->int_enable));
timer_mod(s->refresh_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + NANOSECONDS_PER_SECOND / 60);//LCD_REFRESH_RATE_FREQUENCY);
}
@@ -209,11 +211,24 @@ static void ipod_touch_lcd_init(Object *obj)
sysbus_init_irq(sbd, &s->irq);
}
static void ipod_touch_lcd_reset(DeviceState *dev)
{
IPodTouchLCDState *s = IPOD_TOUCH_LCD(dev);
s->lcd_con = s->int_enable = s->int_status = 0;
s->w1_display_resolution_info = s->w1_framebuffer_base = 0;
s->w1_hspan = s->w1_display_depth_info = 0;
s->invalidate = 1;
lcd_brightness = 255;
qemu_irq_lower(s->irq);
}
static void ipod_touch_lcd_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = ipod_touch_lcd_realize;
dc->reset = ipod_touch_lcd_reset;
}
static const TypeInfo ipod_touch_lcd_info = {
+10
View File
@@ -77,11 +77,21 @@ static void ipod_touch_mipi_dsi_init(Object *obj)
s->return_panel_id = 0;
}
static void ipod_touch_mipi_dsi_reset(DeviceState *dev)
{
IPodTouchMIPIDSIState *s = IPOD_TOUCH_MIPI_DSI(dev);
s->pkthdr_reg = s->clkctrl_reg = 0;
s->return_panel_id = false;
qemu_irq_lower(s->irq);
}
static void ipod_touch_mipi_dsi_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = ipod_touch_mipi_dsi_realize;
dc->reset = ipod_touch_mipi_dsi_reset;
}
static const TypeInfo ipod_touch_mipi_dsi_info = {
+132 -32
View File
@@ -1,4 +1,6 @@
#include "hw/arm/ipod_touch_multitouch.h"
#include "qemu/log.h"
#include "qemu/bswap.h"
static void prepare_interface_version_response(IPodTouchMultitouchState *s) {
memset(s->out_buffer + 1, 0, 15);
@@ -62,7 +64,9 @@ static void prepare_report_info_response(IPodTouchMultitouchState *s, uint8_t re
report_length = MT_REPORT_SENSOR_DIMENSIONS_SIZE;
}
else {
hw_error("Unknown report ID 0x%02x\n", report_id);
qemu_log_mask(LOG_GUEST_ERROR, "%s: rapport inconnu 0x%02x\n",
__func__, report_id);
s->out_buffer[2] = 1;
}
s->out_buffer[3] = (report_length & 0xFF);
@@ -98,12 +102,13 @@ static void prepare_short_control_response(IPodTouchMultitouchState *s, uint8_t
s->out_buffer[3] = MT_SENSOR_REGION_PARAM;
}
else if(report_id == MT_REPORT_SENSOR_DIMENSIONS) {
uint32_t *ob_int32 = (uint32_t *)&s->out_buffer[3];
ob_int32[0] = MT_SENSOR_SURFACE_WIDTH;
ob_int32[1] = MT_SENSOR_SURFACE_HEIGHT;
stl_le_p(&s->out_buffer[3], MT_SENSOR_SURFACE_WIDTH);
stl_le_p(&s->out_buffer[7], MT_SENSOR_SURFACE_HEIGHT);
}
else {
hw_error("Unknown report ID 0x%02x\n", report_id);
qemu_log_mask(LOG_GUEST_ERROR, "%s: rapport inconnu 0x%02x\n",
__func__, report_id);
s->out_buffer[2] = 1;
}
// compute and set the checksum
@@ -116,6 +121,19 @@ static void prepare_short_control_response(IPodTouchMultitouchState *s, uint8_t
s->out_buffer[15] = (checksum >> 8) & 0xFF;
}
static MTFrame *get_frame(IPodTouchMultitouchState *s, uint8_t event,
float x, float y, uint16_t radius1, uint16_t radius2,
uint16_t radius3, uint16_t contactDensity);
static void finish_transfer(IPodTouchMultitouchState *s)
{
g_clear_pointer(&s->in_buffer, g_free);
g_clear_pointer(&s->out_buffer, g_free);
s->cur_cmd = 0;
s->buf_size = s->buf_ind = s->in_buffer_ind = 0;
s->in_buffer_capacity = 0;
}
static uint32_t ipod_touch_multitouch_transfer(SSIPeripheral *dev, uint32_t value)
{
IPodTouchMultitouchState *s = IPOD_TOUCH_MULTITOUCH(dev);
@@ -126,10 +144,11 @@ static uint32_t ipod_touch_multitouch_transfer(SSIPeripheral *dev, uint32_t valu
//printf("Starting command 0x%02x\n", value);
// we're currently not in a command - start a new command
s->cur_cmd = value;
s->out_buffer = malloc(0x100);
s->out_buffer = g_malloc0(0x100);
s->out_buffer[0] = value; // the response header
s->buf_ind = 0;
s->in_buffer = malloc(0x100);
s->in_buffer = g_malloc0(0x100);
s->in_buffer_capacity = 0x100;
s->in_buffer_ind = 0;
if(value == 0x18) { // filler packet??
@@ -193,20 +212,56 @@ static uint32_t ipod_touch_multitouch_transfer(SSIPeripheral *dev, uint32_t valu
else if(value == MT_CMD_SHORT_CONTROL_READ) {
s->buf_size = 16;
}
else if(value == MT_CMD_FRAME_READ) {
s->buf_size = sizeof(MTFrame);
free(s->out_buffer);
s->out_buffer = (uint8_t *) s->next_frame;
else if(value == MT_CMD_FRAME_READ || value == MT_CMD_FRAME_READ_ALT) {
s->buf_size = sizeof(MTFrameLengthPacket);
g_free(s->out_buffer);
/* La trame doit rester stable entre sa longueur et ses données,
même si un événement hôte arrive pendant les lectures SPI. */
s->out_buffer = g_memdup2(s->read_frame ? s->read_frame : s->next_frame,
sizeof(MTFrame));
}
else {
printf("%s Unknown command 0x%02x!\n", __func__, value);
qemu_log_mask(LOG_GUEST_ERROR, "%s: commande inconnue 0x%02x\n",
__func__, value);
finish_transfer(s);
return 0xff;
}
}
if (s->buf_ind >= s->buf_size || s->in_buffer_ind >= s->in_buffer_capacity) {
finish_transfer(s);
return 0xff;
}
s->in_buffer[s->in_buffer_ind] = value;
s->in_buffer_ind++;
if(s->cur_cmd == MT_CMD_HBPP_DATA_PACKET && s->in_buffer_ind == 10) {
if ((s->cur_cmd == MT_CMD_FRAME_READ || s->cur_cmd == MT_CMD_FRAME_READ_ALT) &&
s->in_buffer_ind == 3) {
/* Le troisième octet distingue longueur (0) et données (1).
Chaque requête est une transaction complète : une lecture de
longueur abandonnée pendant la veille ne décale pas la suivante. */
if (value == 1) {
MTFrame *frame = (MTFrame *)s->out_buffer;
/* Le deuxième octet est déjà émis avant de connaître le sélecteur.
Il est ignoré dans les données, mais participe au checksum. */
uint8_t second = s->out_buffer[1];
frame->frame_packet.unused1 = second;
frame->frame_packet.checksum_pad = -(frame->frame_packet.cmd +
second + frame->frame_packet.length1 + frame->frame_packet.length2);
memmove(s->out_buffer, &frame->frame_packet,
sizeof(MTFrame) - sizeof(MTFrameLengthPacket));
s->buf_size = sizeof(MTFrame) - sizeof(MTFrameLengthPacket);
g_clear_pointer(&s->read_frame, g_free);
} else if (value == 0) {
g_free(s->read_frame);
s->read_frame = g_memdup2(s->next_frame, sizeof(MTFrame));
memcpy(s->out_buffer, s->read_frame, sizeof(MTFrame));
} else {
finish_transfer(s);
return 0xff;
}
}
else if(s->cur_cmd == MT_CMD_HBPP_DATA_PACKET && s->in_buffer_ind == 10) {
// verify the header checksum
uint32_t checksum = 0;
for(int i = 2; i < 8; i++) {
@@ -214,17 +269,22 @@ static uint32_t ipod_touch_multitouch_transfer(SSIPeripheral *dev, uint32_t valu
}
if(checksum != (s->in_buffer[8] << 8 | s->in_buffer[9])) {
hw_error("HBPP data header checksum doesn't match!");
qemu_log_mask(LOG_GUEST_ERROR, "%s: somme de contrôle HBPP invalide\n",
__func__);
finish_transfer(s);
return 0xff;
}
uint32_t data_len = (s->in_buffer[2] << 10) | (s->in_buffer[3] << 2) + 5;
// extend the lengths of the in/out buffers
free(s->in_buffer);
s->in_buffer = malloc(data_len + 0x10);
free(s->out_buffer);
s->out_buffer = malloc(data_len);
memset(s->out_buffer, 0, data_len);
uint32_t data_len = ((s->in_buffer[2] << 10) | (s->in_buffer[3] << 2)) + 5;
if (data_len < s->in_buffer_ind) {
finish_transfer(s);
return 0xff;
}
/* Conserver l'en-tête reçu et dimensionner aussi les petites trames. */
s->in_buffer_capacity = MAX(data_len + 10, 0x100);
s->in_buffer = g_realloc(s->in_buffer, s->in_buffer_capacity);
g_free(s->out_buffer);
s->out_buffer = g_malloc0(data_len);
s->buf_size = data_len;
s->buf_ind = 0;
}
@@ -255,17 +315,14 @@ static uint32_t ipod_touch_multitouch_transfer(SSIPeripheral *dev, uint32_t valu
}
// we're done with the command
s->cur_cmd = 0;
s->buf_size = 0;
//free(s->out_buffer);
//free(s->in_buffer);
finish_transfer(s);
}
return ret_val;
}
static MTFrame *get_frame(IPodTouchMultitouchState *s, uint8_t event, float x, float y, uint16_t radius1, uint16_t radius2, uint16_t radius3, uint16_t contactDensity) {
MTFrame *frame = calloc(sizeof(MTFrame), sizeof(uint8_t *));
MTFrame *frame = g_new0(MTFrame, 1);
uint16_t data_len = sizeof(MTFrameHeader) + sizeof(FingerData) + 2;
@@ -310,12 +367,17 @@ static MTFrame *get_frame(IPodTouchMultitouchState *s, uint8_t event, float x, f
// compute the velocity
int diff_x = (int)((x - s->prev_touch_x) * MT_INTERNAL_SENSOR_SURFACE_WIDTH);
int diff_y = (int)((x - s->prev_touch_y) * MT_INTERNAL_SENSOR_SURFACE_HEIGHT);
frame->finger_data.velX = diff_x / (elapsed_ns + 1 - s->last_frame_timestamp) * 1000;
frame->finger_data.velY = diff_y / (elapsed_ns + 1 - s->last_frame_timestamp) * 1000;
int diff_y = (int)((y - s->prev_touch_y) * MT_INTERNAL_SENSOR_SURFACE_HEIGHT);
uint64_t dt = MAX(elapsed_ns - s->last_frame_timestamp, 1);
frame->finger_data.velX = CLAMP((int64_t)diff_x * 1000 / (int64_t)dt,
INT16_MIN, INT16_MAX);
frame->finger_data.velY = CLAMP((int64_t)diff_y * 1000 / (int64_t)dt,
INT16_MIN, INT16_MAX);
s->prev_touch_x = x;
s->prev_touch_y = y;
frame->finger_data.x = (int)(x * MT_INTERNAL_SENSOR_SURFACE_WIDTH);
frame->finger_data.y = (int)(y * MT_INTERNAL_SENSOR_SURFACE_HEIGHT);
frame->finger_data.x = (int)(x * MT_INTERNAL_SENSOR_SURFACE_WIDTH) + MT_INTERNAL_SENSOR_ORIGIN_X;
frame->finger_data.y = (int)(y * MT_INTERNAL_SENSOR_SURFACE_HEIGHT) + MT_INTERNAL_SENSOR_ORIGIN_Y;
frame->finger_data.radius1 = radius1;
frame->finger_data.radius2 = radius2;
frame->finger_data.radius3 = radius3;
@@ -344,6 +406,7 @@ static void ipod_touch_multitouch_inform_frame_ready(IPodTouchMultitouchState *s
void ipod_touch_multitouch_on_touch(IPodTouchMultitouchState *s) {
s->touch_down = true;
g_free(s->next_frame);
s->next_frame = get_frame(s, MT_EVENT_TOUCH_START, s->touch_x, s->touch_y, 100, 660, 580, 150);
ipod_touch_multitouch_inform_frame_ready(s);
@@ -351,6 +414,7 @@ void ipod_touch_multitouch_on_touch(IPodTouchMultitouchState *s) {
}
void ipod_touch_multitouch_on_release(IPodTouchMultitouchState *s) {
g_free(s->next_frame);
s->next_frame = get_frame(s, MT_EVENT_TOUCH_ENDED, s->touch_x, s->touch_y, 0, 0, 0, 0);
s->touch_down = false;
ipod_touch_multitouch_inform_frame_ready(s);
@@ -363,6 +427,7 @@ static void touch_timer_tick(void *opaque)
{
IPodTouchMultitouchState *s = (IPodTouchMultitouchState *)opaque;
g_free(s->next_frame);
s->next_frame = get_frame(s, MT_EVENT_TOUCH_MOVED, s->touch_x, s->touch_y, 100, 660, 580, 150);
ipod_touch_multitouch_inform_frame_ready(s);
@@ -375,14 +440,22 @@ static void touch_timer_tick(void *opaque)
static void touch_end_timer_tick(void *opaque)
{
IPodTouchMultitouchState *s = (IPodTouchMultitouchState *)opaque;
g_free(s->next_frame);
s->next_frame = get_frame(s, MT_EVENT_TOUCH_FULL_END, s->touch_x, s->touch_y, 0, 0, 0, 0);
s->touch_down = false;
ipod_touch_multitouch_inform_frame_ready(s);
}
static void multitouch_cs(void *opaque, int pin, int level)
{
IPodTouchMultitouchState *s = opaque;
if (level) finish_transfer(s);
}
static void ipod_touch_multitouch_realize(SSIPeripheral *d, Error **errp)
{
IPodTouchMultitouchState *s = IPOD_TOUCH_MULTITOUCH(d);
qdev_init_gpio_in_named(DEVICE(d), multitouch_cs, SSI_GPIO_CS, 1);
memset(s->hbpp_atn_ack_response, 0, 2);
s->touch_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, touch_timer_tick, s);
s->touch_end_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, touch_end_timer_tick, s);
@@ -390,11 +463,38 @@ static void ipod_touch_multitouch_realize(SSIPeripheral *d, Error **errp)
s->prev_touch_x = 0;
s->prev_touch_y = 0;
s->last_frame_timestamp = 0;
/* Une lecture sans toucher doit renvoyer une trame de fin valide. */
s->next_frame = get_frame(s, MT_EVENT_TOUCH_FULL_END, 0, 0, 0, 0, 0, 0);
}
static void ipod_touch_multitouch_reset(DeviceState *dev)
{
IPodTouchMultitouchState *s = IPOD_TOUCH_MULTITOUCH(dev);
finish_transfer(s);
timer_del(s->touch_timer);
timer_del(s->touch_end_timer);
g_clear_pointer(&s->read_frame, g_free);
g_clear_pointer(&s->next_frame, g_free);
memset(s->hbpp_atn_ack_response, 0, sizeof s->hbpp_atn_ack_response);
s->touch_down = false;
s->touch_x = s->touch_y = s->prev_touch_x = s->prev_touch_y = 0;
s->frame_counter = 0;
s->last_frame_timestamp = 0;
if (s->sysic) {
s->sysic->gpio_int_status[3] &= ~(1 << 13);
if (!s->sysic->gpio_int_status[3]) {
qemu_irq_lower(s->sysic->gpio_irqs[3]);
}
}
s->next_frame = get_frame(s, MT_EVENT_TOUCH_FULL_END, 0, 0, 0, 0, 0, 0);
}
static void ipod_touch_multitouch_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
SSIPeripheralClass *k = SSI_PERIPHERAL_CLASS(klass);
dc->reset = ipod_touch_multitouch_reset;
k->realize = ipod_touch_multitouch_realize;
k->transfer = ipod_touch_multitouch_transfer;
}
+102
View File
@@ -1,5 +1,8 @@
#include "hw/arm/ipod_touch_pcf50633_pmu.h"
#include "hw/arm/ipod_touch_lcd.h"
#include "qemu/cutils.h"
#include "sysemu/rtc.h"
#include "sysemu/sysemu.h"
static int pcf50633_event(I2CSlave *i2c, enum i2c_event event)
{
@@ -125,9 +128,108 @@ static const TypeInfo pcf50633_info = {
.class_init = pcf50633_class_init,
};
/* Le pilote AppleD1759PMURTC lit un compteur LE32 en 0x5c et un
* décalage LE32 en 0x64. settimeofday ne réécrit que ce décalage.
* Les registres 0x61/0x62 concernent les erreurs de démarrage, pas le RTC.
*/
static uint32_t d1759_counter(Pcf50633State *s)
{
return s->rtc_tick_offset +
qemu_clock_get_ns(rtc_clock) / NANOSECONDS_PER_SECOND;
}
static int d1759_event(I2CSlave *i2c, enum i2c_event event)
{
Pcf50633State *s = PCF50633(i2c);
if (event == I2C_START_SEND || event == I2C_FINISH) {
s->ready = 1;
} else if (event == I2C_START_RECV) {
/* Une lecture multioctet reste cohérente au passage d'une seconde. */
s->rtc_snapshot = d1759_counter(s);
}
return 0;
}
static uint8_t d1759_recv(I2CSlave *i2c)
{
Pcf50633State *s = PCF50633(i2c);
uint8_t reg = s->curreg++;
if (reg >= 0x5c && reg <= 0x5f) {
return s->rtc_snapshot >> ((reg - 0x5c) * 8);
}
if (reg >= 0x64 && reg <= 0x67) {
return s->rtc_unix_offset[reg - 0x64];
}
if (reg >= 0x50 && reg <= 0x5a) {
return s->rtc_alarm[reg - 0x50];
}
switch (reg) {
case 0x02: /* Conversion ADC terminée. */
return 2;
case PMU_MBCS1:
return 1;
case 0x69:
case 0x76:
default:
return 0;
}
}
static int d1759_send(I2CSlave *i2c, uint8_t data)
{
Pcf50633State *s = PCF50633(i2c);
uint8_t reg;
if (s->ready) {
s->curreg = data;
s->ready = 0;
return 0;
}
reg = s->curreg++;
if (reg >= 0x64 && reg <= 0x67) {
s->rtc_unix_offset[reg - 0x64] = data;
} else if (reg >= 0x50 && reg <= 0x5a) {
/* Conservation des réglages d'alarme ; IRQ RTC non émulée. */
s->rtc_alarm[reg - 0x50] = data;
} else if (reg == PMU_DSBL1) {
lcd_changebrightness(data);
}
return 0;
}
static void d1759_init(Object *obj)
{
Pcf50633State *s = PCF50633(obj);
struct tm tm;
qemu_get_timedate(&tm, 0);
s->rtc_tick_offset = mktimegm(&tm) -
qemu_clock_get_ns(rtc_clock) / NANOSECONDS_PER_SECOND;
s->ready = 1;
}
static void d1759_class_init(ObjectClass *klass, void *data)
{
I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
k->event = d1759_event;
k->recv = d1759_recv;
k->send = d1759_send;
}
static const TypeInfo d1759_info = {
.name = TYPE_D1759,
.parent = TYPE_PCF50633,
.instance_init = d1759_init,
.class_init = d1759_class_init,
};
static void pcf50633_register_types(void)
{
type_register_static(&pcf50633_info);
type_register_static(&d1759_info);
}
type_init(pcf50633_register_types)
+13 -3
View File
@@ -64,8 +64,11 @@ static void apple_spi_update_cs(IPodTouchSPIState *s)
BusState *b = BUS(s->spi);
BusChild *kid = QTAILQ_FIRST(&b->children);
if (kid) {
// TODO GPIO not properly setup yet
//qemu_set_irq(qdev_get_gpio_in_named(kid->child, SSI_GPIO_CS, 0), (REG(s, R_PIN) & R_PIN_CS) != 0);
if (object_dynamic_cast(OBJECT(kid->child), TYPE_IPOD_TOUCH_MULTITOUCH)) {
qemu_set_irq(qdev_get_gpio_in_named(kid->child, SSI_GPIO_CS, 0),
(REG(s, R_PIN) & R_PIN_CS) != 0);
}
/* La sélection de la NOR conserve son comportement historique. */
}
}
@@ -197,6 +200,11 @@ static void ipod_touch_spi_write(void *opaque, hwaddr addr, uint64_t data, unsig
case R_CTRL:
if (r & R_CTRL_TX_RESET) {
fifo8_reset(&s->tx_fifo);
/* Le pilote interrompt aussi les commandes au retour de veille.
Ne pas réutiliser les octets d'une transaction abandonnée. */
if (s->mt) {
qemu_set_irq(qdev_get_gpio_in_named(DEVICE(s->mt), SSI_GPIO_CS, 0), 1);
}
}
if (r & R_CTRL_RX_RESET) {
fifo8_reset(&s->rx_fifo);
@@ -255,6 +263,8 @@ static void ipod_touch_spi_reset(DeviceState *d)
memset(s->regs, 0, sizeof(s->regs));
fifo8_reset(&s->tx_fifo);
fifo8_reset(&s->rx_fifo);
s->last_irq = 0;
qemu_irq_lower(s->irq);
}
static uint32_t base_addr = 0;
@@ -325,4 +335,4 @@ static void ipod_touch_spi_register_types(void)
type_register_static(&ipod_touch_spi_info);
}
type_init(ipod_touch_spi_register_types)
type_init(ipod_touch_spi_register_types)
+8 -8
View File
@@ -15,22 +15,22 @@ static uint64_t ipod_touch_sysic_read(void *opaque, hwaddr addr, unsigned size)
case 0x7a:
case 0x7c:
return 1;
case GPIO_INTLEVEL ... (GPIO_INTLEVEL + GPIO_NUMINTGROUPS * 4):
case GPIO_INTLEVEL ... (GPIO_INTLEVEL + (GPIO_NUMINTGROUPS - 1) * 4):
{
uint8_t group = (addr - GPIO_INTLEVEL) / 4;
return s->gpio_int_level[group];
}
case GPIO_INTSTAT ... (GPIO_INTSTAT + GPIO_NUMINTGROUPS * 4):
case GPIO_INTSTAT ... (GPIO_INTSTAT + (GPIO_NUMINTGROUPS - 1) * 4):
{
uint8_t group = (addr - GPIO_INTSTAT) / 4;
return s->gpio_int_status[group];
}
case GPIO_INTEN ... (GPIO_INTEN + GPIO_NUMINTGROUPS * 4):
case GPIO_INTEN ... (GPIO_INTEN + (GPIO_NUMINTGROUPS - 1) * 4):
{
uint8_t group = (addr - GPIO_INTEN) / 4;
return s->gpio_int_enabled[group];
}
case GPIO_INTTYPE ... (GPIO_INTTYPE + GPIO_NUMINTGROUPS * 4):
case GPIO_INTTYPE ... (GPIO_INTTYPE + (GPIO_NUMINTGROUPS - 1) * 4):
{
uint8_t group = (addr - GPIO_INTTYPE) / 4;
return s->gpio_int_type[group];
@@ -58,11 +58,11 @@ static void ipod_touch_sysic_write(void *opaque, hwaddr addr, uint64_t val, unsi
case POWER_OFFCTRL:
s->power_state = val;
break;
case GPIO_INTLEVEL ... (GPIO_INTLEVEL + GPIO_NUMINTGROUPS * 4):
case GPIO_INTLEVEL ... (GPIO_INTLEVEL + (GPIO_NUMINTGROUPS - 1) * 4):
{
break;
}
case GPIO_INTSTAT ... (GPIO_INTSTAT + GPIO_NUMINTGROUPS * 4):
case GPIO_INTSTAT ... (GPIO_INTSTAT + (GPIO_NUMINTGROUPS - 1) * 4):
{
uint8_t group = (addr - GPIO_INTSTAT) / 4;
@@ -73,13 +73,13 @@ static void ipod_touch_sysic_write(void *opaque, hwaddr addr, uint64_t val, unsi
break;
}
case GPIO_INTEN ... (GPIO_INTEN + GPIO_NUMINTGROUPS * 4):
case GPIO_INTEN ... (GPIO_INTEN + (GPIO_NUMINTGROUPS - 1) * 4):
{
uint8_t group = (addr - GPIO_INTEN) / 4;
s->gpio_int_enabled[group] = val;
break;
}
case GPIO_INTTYPE ... (GPIO_INTTYPE + GPIO_NUMINTGROUPS * 4):
case GPIO_INTTYPE ... (GPIO_INTTYPE + (GPIO_NUMINTGROUPS - 1) * 4):
{
uint8_t group = (addr - GPIO_INTTYPE) / 4;
s->gpio_int_type[group] = val;
+26 -8
View File
@@ -1,6 +1,11 @@
#include "hw/arm/ipod_touch_tvout.h"
#include "qapi/error.h"
static void tvout_update_irq(IPodTouchTVOutState *s)
{
qemu_set_irq(s->irq, !!(s->sdo_irq & ~s->sdo_irq_mask & 1));
}
static uint64_t ipod_touch_tvout_sdo_read(void *opaque, hwaddr offset, unsigned size)
{
IPodTouchTVOutState *s = (IPodTouchTVOutState *)opaque;
@@ -37,11 +42,12 @@ static void ipod_touch_tvout_sdo_write(void *opaque, hwaddr offset, uint64_t val
s->sdo_config = value;
return;
case SDO_IRQ:
s->sdo_irq = 0x0;
qemu_irq_lower(s->irq);
s->sdo_irq &= ~value;
tvout_update_irq(s);
return;
case SDO_IRQMASK:
s->sdo_irq_mask = value;
tvout_update_irq(s);
return;
}
}
@@ -72,10 +78,10 @@ static void ipod_touch_tvout_mixer1_write(void *opaque, hwaddr offset, uint64_t
switch(offset) {
case MXR_STATUS:
if((value & 0x1) && (s->sdo_irq_mask & 1) == 0 && s->irq_count < 2) {
s->sdo_irq = 0x1;
s->irq_count += 1; // ugly hack for now
qemu_irq_raise(s->irq);
/* Chaque soumission doit pouvoir terminer, même après respring. */
if (value & 1) {
s->sdo_irq |= 1;
tvout_update_irq(s);
}
break;
case MXR_CFG:
@@ -157,9 +163,21 @@ static void ipod_touch_tvout_init(Object *obj)
sysbus_init_irq(sbd, &s->irq);
}
static void ipod_touch_tvout_reset(DeviceState *dev)
{
IPodTouchTVOutState *s = IPOD_TOUCH_TVOUT(dev);
s->mixer1_status = s->mixer1_cfg = 0;
s->mixer2_status = s->mixer2_cfg = 0;
s->sdo_clkcon = s->sdo_config = 0;
s->sdo_irq = s->sdo_irq_mask = 0;
tvout_update_irq(s);
}
static void ipod_touch_tvout_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->reset = ipod_touch_tvout_reset;
}
static const TypeInfo ipod_touch_tvout_type_info = {
@@ -175,4 +193,4 @@ static void ipod_touch_tvout_register_types(void)
type_register_static(&ipod_touch_tvout_type_info);
}
type_init(ipod_touch_tvout_register_types)
type_init(ipod_touch_tvout_register_types)
+12
View File
@@ -199,6 +199,17 @@ static void uart_init(Object *obj)
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
}
static void uart_reset(DeviceState *dev)
{
IPodTouchUARTState *s = IPOD_TOUCH_UART(dev);
s->ulcon = s->ucon = s->ufcon = s->umcon = s->ubrdiv = 0;
s->pending = s->rx_head = s->rx_count = 0;
qemu_irq_lower(s->irq);
/* La nouvelle instance de pppd doit établir un lien indépendant. */
qemu_chr_fe_disconnect(&s->chr);
}
static Property uart_properties[] = {
DEFINE_PROP_CHR("chardev", IPodTouchUARTState, chr),
DEFINE_PROP_END_OF_LIST(),
@@ -208,6 +219,7 @@ static void uart_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = uart_realize;
dc->reset = uart_reset;
device_class_set_props(dc, uart_properties);
}
+1 -1
View File
@@ -21,7 +21,7 @@ OBJECT_DECLARE_SIMPLE_TYPE(IPodTouchGPIOState, IPOD_TOUCH_GPIO)
#define GPIO_BUTTON_VOLUP_IRQ 0x78
#define GPIO_BUTTON_VOLDOWN_IRQ 0x62
#define NUM_GPIO_PADS 0xC
#define NUM_GPIO_PADS 0xD /* Ports 0 à C inclus, dont Home et Power. */
#define NUM_GPIO_PINS 0x20
#define GPIO2PIN(gpio) ((gpio) & 7)
+2 -1
View File
@@ -32,7 +32,8 @@ typedef struct IPodTouchLCDState
uint32_t w1_hspan;
uint32_t w1_display_depth_info;
uint32_t render;
uint32_t int_enable;
uint32_t int_status;
QEMUTimer *refresh_timer;
} IPodTouchLCDState;
+10 -3
View File
@@ -25,9 +25,13 @@ OBJECT_DECLARE_SIMPLE_TYPE(IPodTouchMultitouchState, IPOD_TOUCH_MULTITOUCH)
#define MT_SENSOR_SURFACE_WIDTH 5000
#define MT_SENSOR_SURFACE_HEIGHT 7500
// internal surface width/height
#define MT_INTERNAL_SENSOR_SURFACE_WIDTH (9000 - MT_SENSOR_SURFACE_WIDTH) * 84 / 73
#define MT_INTERNAL_SENSOR_SURFACE_HEIGHT (13850 - MT_SENSOR_SURFACE_HEIGHT) * 84 / 73
/* Calibration mesurée sur les pixels 320x480 de 5H11a avec TouchProbe.
* La transformation du pilote nécessite une étendue et une origine propres
* au capteur ; les dimensions annoncées dans les rapports restent 5000x7500. */
#define MT_INTERNAL_SENSOR_SURFACE_WIDTH 4718
#define MT_INTERNAL_SENSOR_SURFACE_HEIGHT 7328
#define MT_INTERNAL_SENSOR_ORIGIN_X (-68)
#define MT_INTERNAL_SENSOR_ORIGIN_Y (-188)
// report IDs
#define MT_REPORT_UNKNOWN1 0x70
@@ -52,6 +56,7 @@ OBJECT_DECLARE_SIMPLE_TYPE(IPodTouchMultitouchState, IPOD_TOUCH_MULTITOUCH)
#define MT_CMD_SHORT_CONTROL_WRITE 0xE4
#define MT_CMD_SHORT_CONTROL_READ 0xE6
#define MT_CMD_FRAME_READ 0xEA
#define MT_CMD_FRAME_READ_ALT 0xEB
// frame types
#define MT_FRAME_TYPE_PATH 0x44
@@ -152,6 +157,8 @@ typedef struct IPodTouchMultitouchState {
float prev_touch_x;
float prev_touch_y;
uint64_t last_frame_timestamp;
uint32_t in_buffer_capacity;
MTFrame *read_frame;
} IPodTouchMultitouchState;
void ipod_touch_multitouch_on_touch(IPodTouchMultitouchState *s);
+6
View File
@@ -10,6 +10,7 @@
#include "time.h"
#define TYPE_PCF50633 "pcf50633"
#define TYPE_D1759 "d1759"
OBJECT_DECLARE_SIMPLE_TYPE(Pcf50633State, PCF50633)
#define PMU_DSBL1 0x30
@@ -30,6 +31,11 @@ typedef struct Pcf50633State {
uint32_t cmd;
uint32_t ready;
uint32_t curreg;
/* D1759 : compteur RTC et décalage Unix, distincts du calendrier PCF. */
int64_t rtc_tick_offset;
uint32_t rtc_snapshot;
uint8_t rtc_unix_offset[4];
uint8_t rtc_alarm[11];
} Pcf50633State;
#endif
+1 -2
View File
@@ -38,7 +38,6 @@ typedef struct IPodTouchTVOutState {
uint32_t sdo_irq;
uint32_t sdo_irq_mask;
uint32_t irq_count;
} IPodTouchTVOutState;
#endif
#endif