Compare commits
2
Commits
0286ad8380
...
6cdda3bc13
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cdda3bc13 | ||
|
|
8037181992 |
No files matched your search
+36
-13
@@ -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
|
||||
|
||||
|
||||
+50
-20
@@ -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
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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; }
|
||||
|
||||
Executable
+41
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
@@ -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
-8
@@ -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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
type_init(ipod_touch_spi_register_types)
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
|
||||
@@ -38,7 +38,6 @@ typedef struct IPodTouchTVOutState {
|
||||
uint32_t sdo_irq;
|
||||
uint32_t sdo_irq_mask;
|
||||
|
||||
uint32_t irq_count;
|
||||
} IPodTouchTVOutState;
|
||||
|
||||
#endif
|
||||
#endif
|
||||
Reference in new issue
Block a user