diff --git a/contrib/ipod-emu/HANDOFF.md b/contrib/ipod-emu/HANDOFF.md index cb30043c45..8e00eda38b 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. +## 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. + +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. + ## Dernière mise à jour — RTC D1759 Le modèle `d1759` remplace `pcf50633` sur la machine 2G. Il corrige le diff --git a/contrib/ipod-emu/README.md b/contrib/ipod-emu/README.md index b363410708..0c93708e4f 100644 --- a/contrib/ipod-emu/README.md +++ b/contrib/ipod-emu/README.md @@ -71,6 +71,29 @@ 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** : + +```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é +``` + +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. + ## Tester nos apps ```bash @@ -142,6 +165,7 @@ 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-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 diff --git a/contrib/ipod-emu/bin/ipod-qmp b/contrib/ipod-emu/bin/ipod-qmp index 708cd3cf30..39f0344824 100755 --- a/contrib/ipod-emu/bin/ipod-qmp +++ b/contrib/ipod-emu/bin/ipod-qmp @@ -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__) diff --git a/contrib/ipod-emu/tests/run-uart b/contrib/ipod-emu/tests/run-uart new file mode 100755 index 0000000000..2d01e281ef --- /dev/null +++ b/contrib/ipod-emu/tests/run-uart @@ -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) diff --git a/contrib/ipod-emu/tests/test-uart.c b/contrib/ipod-emu/tests/test-uart.c new file mode 100644 index 0000000000..addd137056 --- /dev/null +++ b/contrib/ipod-emu/tests/test-uart.c @@ -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 +#include +#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(); +} diff --git a/hw/arm/ipod_touch_uart.c b/hw/arm/ipod_touch_uart.c index f4ecfa902f..d1a149eaea 100644 --- a/hw/arm/ipod_touch_uart.c +++ b/hw/arm/ipod_touch_uart.c @@ -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); }