Merge pull request 'Rétablir PPP après un reset matériel de l’iPod' (#5) from agent/reprise-ppp into ipod_touch_2g

This commit was merged in pull request #5.
This commit is contained in:
Mahtan committed 2026-10-01 11:33:12 +02:00
commit 0286ad8380
6 files changed
+232

No files matched your search

+32
View File
@@ -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
+24
View File
@@ -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
+3
View File
@@ -8,6 +8,7 @@
ipod-qmp status état de la VM
ipod-qmp hmp COMMANDE commande du moniteur QEMU (ex. "info registers", "x/8i $pc")
ipod-qmp quit arrête l'émulateur
ipod-qmp reset reset matériel brut (synchroniser la NAND avec ipod-agent sync avant)
Socket : -s SOCK, sinon $IPOD_QMP, sinon ~/ipod-emu/run/qmp.sock
"""
@@ -106,6 +107,8 @@ def main():
print(q.cmd("human-monitor-command", **{"command-line": " ".join(a.args)}))
elif a.action == "quit":
q.cmd("quit")
elif a.action == "reset":
q.cmd("system_reset")
else:
sys.exit(__doc__)
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env python3
"""Compile et exécute les régressions UART avec ASan/UBSan sur un build QEMU existant."""
import argparse
import json
import os
from pathlib import Path
import shlex
import subprocess
p = argparse.ArgumentParser(description=__doc__)
p.add_argument('--build', type=Path)
a = p.parse_args()
root = Path(__file__).resolve().parents[3]
build = (a.build or root / 'build').resolve()
source = Path(__file__).with_name('test-uart.c')
commands = json.loads((build / 'compile_commands.json').read_text())
c = next(c for c in commands if c['file'].endswith('ipod_touch_uart.c'))
old_root = str(Path(c['directory']).parent)
args = shlex.split(c['command'].replace(old_root, str(root)))
obj = str(build / 'test-ipod-uart.o')
args[args.index('-c') + 1] = str(source)
args[args.index('-o') + 1] = obj
args[args.index('-MF') + 1] = obj + '.d'
args += ['-fsanitize=address,undefined', '-fno-omit-frame-pointer']
subprocess.run(args, cwd=build, check=True)
links = json.loads(subprocess.check_output(
['ninja', '-C', str(build), '-t', 'compdb', '-x', 'c_LINKER_RSP', 'c_LINKER'], text=True))
link = next(c for c in links if c.get('output') == 'qemu-system-arm')
args = shlex.split(link['command'].replace(old_root, str(root)))
args = [x for x in args if x not in (
'qemu-system-arm.p/system_main.c.o',
'libqemu-arm-softmmu.fa.p/hw_arm_ipod_touch_uart.c.o')]
out = str(build / 'test-ipod-uart')
args[args.index('-o') + 1] = out
args += [obj, '-fsanitize=address,undefined']
subprocess.run(args, cwd=build, check=True)
# QEMU conserve volontairement ses registres de types et sa boucle globale à la sortie.
env = dict(os.environ, ASAN_OPTIONS='detect_leaks=0', UBSAN_OPTIONS='halt_on_error=1')
subprocess.run([out], env=env, check=True)
+122
View File
@@ -0,0 +1,122 @@
/* Reset UART : registres, FIFO, IRQ et reconnexion du relais série. */
#include "qemu/osdep.h"
#include "qemu/main-loop.h"
#include "qemu/config-file.h"
#include "qemu/option.h"
#include "sysemu/cpus.h"
#include "sysemu/sysemu.h"
#include <sys/socket.h>
#include <sys/un.h>
#include "hw/arm/ipod_touch_uart.c"
static void irq_changed(void *opaque, int n, int level)
{
*(int *)opaque = level;
}
static void test_register_reset(void)
{
IPodTouchUARTState *s = IPOD_TOUCH_UART(object_new(TYPE_IPOD_TOUCH_UART));
int level = 0;
qemu_irq irq = qemu_allocate_irq(irq_changed, &level, 0);
sysbus_connect_irq(SYS_BUS_DEVICE(s), 0, irq);
uint8_t bytes[] = { 0x7e, 0xff, 0x03, 0x7d };
uart_write(s, ULCON, 3, 4);
uart_write(s, UCON, UCON_RX_INT | UCON_RXTO_INT | UCON_TX_INT, 4);
uart_write(s, UMCON, 1, 4);
uart_write(s, UBRDIV, 100, 4);
uart_receive(s, bytes, sizeof bytes);
g_assert_cmpint(level, ==, 1);
g_assert_cmpuint(uart_read(s, UFSTAT, 4), ==, sizeof bytes);
device_cold_reset(DEVICE(s));
g_assert_cmpint(level, ==, 0);
g_assert_cmpuint(uart_read(s, UTRSTAT, 4), ==,
UTRSTAT_TXBUF_EMPTY | UTRSTAT_TX_EMPTY);
g_assert_cmpuint(uart_read(s, UFSTAT, 4), ==, 0);
g_assert_cmpuint(uart_read(s, ULCON, 4), ==, 0);
g_assert_cmpuint(uart_read(s, UCON, 4), ==, 0);
g_assert_cmpuint(uart_read(s, UMCON, 4), ==, 0);
g_assert_cmpuint(uart_read(s, UBRDIV, 4), ==, 0);
object_unref(OBJECT(s));
qemu_free_irq(irq);
}
static void wait_open(IPodTouchUARTState *s)
{
int64_t deadline = g_get_monotonic_time() + 5 * G_USEC_PER_SEC;
while (!qemu_chr_fe_backend_open(&s->chr)) {
g_assert_cmpint(g_get_monotonic_time(), <, deadline);
main_loop_wait(true);
g_usleep(1000);
}
}
static int connect_client(const char *path)
{
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr = { .sun_family = AF_UNIX };
g_strlcpy(addr.sun_path, path, sizeof addr.sun_path);
g_assert_cmpint(fd, >=, 0);
struct timeval limit = { .tv_sec = 5 };
g_assert_cmpint(setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &limit, sizeof limit), ==, 0);
g_assert_cmpint(connect(fd, (struct sockaddr *)&addr, sizeof addr), ==, 0);
return fd;
}
static void test_socket_reconnect(void)
{
char *dir = g_dir_make_tmp("ipod-uart-XXXXXX", NULL);
g_assert_nonnull(dir);
char *path = g_build_filename(dir, "uart.sock", NULL);
char *spec = g_strdup_printf("unix:%s,server=on,wait=off", path);
Chardev *chr = qemu_chr_new("uart-test", spec, NULL);
g_assert_nonnull(chr);
IPodTouchUARTState *s = IPOD_TOUCH_UART(object_new(TYPE_IPOD_TOUCH_UART));
qemu_chr_fe_init(&s->chr, chr, &error_abort);
uart_realize(DEVICE(s), &error_abort);
for (int cycle = 0; cycle < 3; cycle++) {
int fd = connect_client(path);
wait_open(s);
uint8_t incoming = 0x41 + cycle;
g_assert_cmpint(write(fd, &incoming, 1), ==, 1);
int64_t deadline = g_get_monotonic_time() + 5 * G_USEC_PER_SEC;
while (!s->rx_count) {
g_assert_cmpint(g_get_monotonic_time(), <, deadline);
main_loop_wait(true);
g_usleep(1000);
}
g_assert_cmpuint(uart_read(s, URXH, 1), ==, incoming);
uart_write(s, UTXH, 0x7e, 1);
uint8_t b = 0;
g_assert_cmpint(read(fd, &b, 1), ==, 1);
g_assert_cmpuint(b, ==, 0x7e);
device_cold_reset(DEVICE(s));
/* Le relais voit la fin du lien et peut renégocier avec le nouvel iOS. */
g_assert_false(qemu_chr_fe_backend_open(&s->chr));
g_assert_cmpint(read(fd, &b, 1), ==, 0);
close(fd);
}
qemu_chr_fe_deinit(&s->chr, false);
object_unref(OBJECT(s));
object_unparent(OBJECT(chr));
unlink(path);
rmdir(dir);
g_free(spec);
g_free(path);
g_free(dir);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
module_call_init(MODULE_INIT_QOM);
qemu_add_opts(&qemu_chardev_opts);
qemu_init_main_loop(&error_abort);
qemu_init_cpu_loop();
qemu_mutex_lock_iothread();
g_test_add_func("/ipod-uart/register-reset", test_register_reset);
g_test_add_func("/ipod-uart/socket-reconnect", test_socket_reconnect);
return g_test_run();
}
+12
View File
@@ -199,6 +199,17 @@ static void uart_init(Object *obj)
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
}
static void uart_reset(DeviceState *dev)
{
IPodTouchUARTState *s = IPOD_TOUCH_UART(dev);
s->ulcon = s->ucon = s->ufcon = s->umcon = s->ubrdiv = 0;
s->pending = s->rx_head = s->rx_count = 0;
qemu_irq_lower(s->irq);
/* La nouvelle instance de pppd doit établir un lien indépendant. */
qemu_chr_fe_disconnect(&s->chr);
}
static Property uart_properties[] = {
DEFINE_PROP_CHR("chardev", IPodTouchUARTState, chr),
DEFINE_PROP_END_OF_LIST(),
@@ -208,6 +219,7 @@ static void uart_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = uart_realize;
dc->reset = uart_reset;
device_class_set_props(dc, uart_properties);
}