From 8037181992809a31a93abe9cb822b6d0594fae4d Mon Sep 17 00:00:00 2001 From: Workbench IA Date: Thu, 1 Oct 2026 10:12:48 +0000 Subject: [PATCH] =?UTF-8?q?R=C3=A9tablir=20le=20red=C3=A9marrage=20=C3=A0?= =?UTF-8?q?=20chaud=20de=20SpringBoard=20et=20de=20l=E2=80=99affichage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contrib/ipod-emu/HANDOFF.md | 49 ++++++-- contrib/ipod-emu/README.md | 70 ++++++++--- contrib/ipod-emu/bin/ipod-agent | 6 + contrib/ipod-emu/bin/ipod-emu | 35 +++++- contrib/ipod-emu/tests/run-display | 41 ++++++ contrib/ipod-emu/tests/test-display-reset.py | 96 ++++++++++++++ .../ipod-emu/tests/test-display-session.py | 110 ++++++++++++++++ contrib/ipod-emu/tests/test-display.c | 119 ++++++++++++++++++ contrib/ipod-emu/tests/test-multitouch.c | 42 +++++++ contrib/ipod-emu/tests/test-reset-launcher.py | 107 ++++++++++++++++ hw/arm/ipod_touch_2g.c | 1 + hw/arm/ipod_touch_lcd.c | 33 +++-- hw/arm/ipod_touch_mipi_dsi.c | 10 ++ hw/arm/ipod_touch_multitouch.c | 25 ++++ hw/arm/ipod_touch_spi.c | 4 +- hw/arm/ipod_touch_tvout.c | 34 +++-- include/hw/arm/ipod_touch_lcd.h | 3 +- include/hw/arm/ipod_touch_tvout.h | 3 +- 18 files changed, 732 insertions(+), 56 deletions(-) create mode 100755 contrib/ipod-emu/tests/run-display create mode 100644 contrib/ipod-emu/tests/test-display-reset.py create mode 100644 contrib/ipod-emu/tests/test-display-session.py create mode 100644 contrib/ipod-emu/tests/test-display.c create mode 100644 contrib/ipod-emu/tests/test-reset-launcher.py diff --git a/contrib/ipod-emu/HANDOFF.md b/contrib/ipod-emu/HANDOFF.md index 8e00eda38b..471bd66b6c 100644 --- a/contrib/ipod-emu/HANDOFF.md +++ b/contrib/ipod-emu/HANDOFF.md @@ -2,6 +2,38 @@ É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 à @@ -20,19 +52,10 @@ 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. -La reprise réseau et le retour de SpringBoard sont deux validations distinctes. -Les essais complets montrent encore une défaillance de l'affichage après le -reset : sur 5H11a, le modèle MIPI garde son état et le pilote de dalle échoue ; -une correction expérimentale de MIPI permet au pilote de démarrer mais laisse -SpringBoard bloqué dans `IOServiceClose` depuis la finalisation d'un objet -IOMobileFramebuffer. Le compteur matériel, la RAM et plusieurs autres callbacks -ont été explorés sans résoudre ce blocage. Ces essais ne font pas partie du -correctif PPP. `ipod-emu wait` peut reconnaître les anciens marqueurs du journal -après un reset brut ; ne pas le prendre comme preuve du nouveau boot. - -Diagnostics conservés dans `work/ppp-20261001` : journaux, pile des threads de -SpringBoard, correctifs expérimentaux et validation réseau. La session utilisateur -sur VNC 5900 et ses images d'origine ont été conservées. +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 diff --git a/contrib/ipod-emu/README.md b/contrib/ipod-emu/README.md index 0c93708e4f..56d5621af6 100644 --- a/contrib/ipod-emu/README.md +++ b/contrib/ipod-emu/README.md @@ -71,28 +71,35 @@ 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. -## Reprise PPP après un reset matériel - -Le reset de l'UART vide les registres et la FIFO, baisse son IRQ et ferme la -connexion série. `ipod-netd` voit cette fermeture, se reconnecte au même socket -et renégocie LCP/IPCP avec le nouveau `pppd`. Le relais et QEMU gardent leur PID ; -la NAND de session est conservée. - -Pour diagnostiquer un reset sur une **session de test séparée** : +## Redémarrage à chaud ```bash -ipod-agent sync # synchroniser les écritures avant le reset brut -ipod-qmp reset # system_reset, sans synchronisation implicite -ipod-agent wait 90 # attendre le retour du réseau -ipod-agent ping -ipod-agent net # vérifier ppp0 up et configd publié +ipod-emu respring -W # relance SpringBoard, attend puis déverrouille +ipod-emu reset -W # redémarre iOS dans le même processus QEMU ``` -Le retour de PPP ne valide pas celui de l'interface : SpringBoard peut encore -échouer ou se bloquer après un reset matériel. `ipod-emu wait` examine le journal -complet et peut reconnaître les marqueurs de l'ancien boot. Pour retrouver -l'interface, arrêter puis relancer QEMU avec **le même firmware**, sans `-f`. -Les corrections complètes du reset de l'affichage restent à traiter séparément. +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 @@ -166,6 +173,8 @@ 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 @@ -225,14 +234,35 @@ résultat détaillé de la validation du 1er octobre sont décrits dans `HANDOFF 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 +- **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 diff --git a/contrib/ipod-emu/bin/ipod-agent b/contrib/ipod-emu/bin/ipod-agent index 377aacc01d..57b01cf143 100755 --- a/contrib/ipod-emu/bin/ipod-agent +++ b/contrib/ipod-emu/bin/ipod-agent @@ -96,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}") diff --git a/contrib/ipod-emu/bin/ipod-emu b/contrib/ipod-emu/bin/ipod-emu index 85b654dbee..94d80196aa 100755 --- a/contrib/ipod-emu/bin/ipod-emu +++ b/contrib/ipod-emu/bin/ipod-emu @@ -19,6 +19,8 @@ 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 pilote tactile doit avoir terminé son démarrage ipod-emu stop | status | log | console @@ -27,9 +29,16 @@ 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() { @@ -37,7 +46,7 @@ wait_ready() { # 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 @@ -82,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 @@ -124,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; } diff --git a/contrib/ipod-emu/tests/run-display b/contrib/ipod-emu/tests/run-display new file mode 100755 index 0000000000..9081bf1572 --- /dev/null +++ b/contrib/ipod-emu/tests/run-display @@ -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) diff --git a/contrib/ipod-emu/tests/test-display-reset.py b/contrib/ipod-emu/tests/test-display-reset.py new file mode 100644 index 0000000000..5f507e59d4 --- /dev/null +++ b/contrib/ipod-emu/tests/test-display-reset.py @@ -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() diff --git a/contrib/ipod-emu/tests/test-display-session.py b/contrib/ipod-emu/tests/test-display-session.py new file mode 100644 index 0000000000..aa7744da6f --- /dev/null +++ b/contrib/ipod-emu/tests/test-display-session.py @@ -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() diff --git a/contrib/ipod-emu/tests/test-display.c b/contrib/ipod-emu/tests/test-display.c new file mode 100644 index 0000000000..152a1b38a4 --- /dev/null +++ b/contrib/ipod-emu/tests/test-display.c @@ -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(); +} diff --git a/contrib/ipod-emu/tests/test-multitouch.c b/contrib/ipod-emu/tests/test-multitouch.c index e01b0d9d00..79ab35f782 100644 --- a/contrib/ipod-emu/tests/test-multitouch.c +++ b/contrib/ipod-emu/tests/test-multitouch.c @@ -109,6 +109,14 @@ static void test_spi_abort(void) 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); @@ -181,6 +189,39 @@ static void test_gpio_ranges(void) 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); @@ -194,6 +235,7 @@ int main(int argc, char **argv) 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(); diff --git a/contrib/ipod-emu/tests/test-reset-launcher.py b/contrib/ipod-emu/tests/test-reset-launcher.py new file mode 100644 index 0000000000..17943bb247 --- /dev/null +++ b/contrib/ipod-emu/tests/test-reset-launcher.py @@ -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() diff --git a/hw/arm/ipod_touch_2g.c b/hw/arm/ipod_touch_2g.c index e39dadb25f..e415719359 100644 --- a/hw/arm/ipod_touch_2g.c +++ b/hw/arm/ipod_touch_2g.c @@ -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"); diff --git a/hw/arm/ipod_touch_lcd.c b/hw/arm/ipod_touch_lcd.c index f4b25b6139..c96915e300 100644 --- a/hw/arm/ipod_touch_lcd.c +++ b/hw/arm/ipod_touch_lcd.c @@ -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; @@ -174,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); } @@ -207,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 = { diff --git a/hw/arm/ipod_touch_mipi_dsi.c b/hw/arm/ipod_touch_mipi_dsi.c index 080185e82f..1cafbe4ee5 100644 --- a/hw/arm/ipod_touch_mipi_dsi.c +++ b/hw/arm/ipod_touch_mipi_dsi.c @@ -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 = { diff --git a/hw/arm/ipod_touch_multitouch.c b/hw/arm/ipod_touch_multitouch.c index eefe0f7a60..57df96e5a9 100644 --- a/hw/arm/ipod_touch_multitouch.c +++ b/hw/arm/ipod_touch_multitouch.c @@ -467,9 +467,34 @@ static void ipod_touch_multitouch_realize(SSIPeripheral *d, Error **errp) 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; } diff --git a/hw/arm/ipod_touch_spi.c b/hw/arm/ipod_touch_spi.c index 0d3614a037..47230b5a54 100644 --- a/hw/arm/ipod_touch_spi.c +++ b/hw/arm/ipod_touch_spi.c @@ -263,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; @@ -333,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) \ No newline at end of file +type_init(ipod_touch_spi_register_types) diff --git a/hw/arm/ipod_touch_tvout.c b/hw/arm/ipod_touch_tvout.c index 41dcd9f243..11c2d8339e 100644 --- a/hw/arm/ipod_touch_tvout.c +++ b/hw/arm/ipod_touch_tvout.c @@ -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) \ No newline at end of file +type_init(ipod_touch_tvout_register_types) diff --git a/include/hw/arm/ipod_touch_lcd.h b/include/hw/arm/ipod_touch_lcd.h index 518629bab1..e5d9a03475 100644 --- a/include/hw/arm/ipod_touch_lcd.h +++ b/include/hw/arm/ipod_touch_lcd.h @@ -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; diff --git a/include/hw/arm/ipod_touch_tvout.h b/include/hw/arm/ipod_touch_tvout.h index bf343b4a17..48717d4796 100644 --- a/include/hw/arm/ipod_touch_tvout.h +++ b/include/hw/arm/ipod_touch_tvout.h @@ -38,7 +38,6 @@ typedef struct IPodTouchTVOutState { uint32_t sdo_irq; uint32_t sdo_irq_mask; - uint32_t irq_count; } IPodTouchTVOutState; -#endif \ No newline at end of file +#endif