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:
commit
0286ad8380
6 files changed
+232
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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__)
|
||||
|
||||
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compile et exécute les régressions UART avec ASan/UBSan sur un build QEMU existant."""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shlex
|
||||
import subprocess
|
||||
|
||||
p = argparse.ArgumentParser(description=__doc__)
|
||||
p.add_argument('--build', type=Path)
|
||||
a = p.parse_args()
|
||||
root = Path(__file__).resolve().parents[3]
|
||||
build = (a.build or root / 'build').resolve()
|
||||
source = Path(__file__).with_name('test-uart.c')
|
||||
commands = json.loads((build / 'compile_commands.json').read_text())
|
||||
c = next(c for c in commands if c['file'].endswith('ipod_touch_uart.c'))
|
||||
old_root = str(Path(c['directory']).parent)
|
||||
args = shlex.split(c['command'].replace(old_root, str(root)))
|
||||
obj = str(build / 'test-ipod-uart.o')
|
||||
args[args.index('-c') + 1] = str(source)
|
||||
args[args.index('-o') + 1] = obj
|
||||
args[args.index('-MF') + 1] = obj + '.d'
|
||||
args += ['-fsanitize=address,undefined', '-fno-omit-frame-pointer']
|
||||
subprocess.run(args, cwd=build, check=True)
|
||||
links = json.loads(subprocess.check_output(
|
||||
['ninja', '-C', str(build), '-t', 'compdb', '-x', 'c_LINKER_RSP', 'c_LINKER'], text=True))
|
||||
link = next(c for c in links if c.get('output') == 'qemu-system-arm')
|
||||
args = shlex.split(link['command'].replace(old_root, str(root)))
|
||||
args = [x for x in args if x not in (
|
||||
'qemu-system-arm.p/system_main.c.o',
|
||||
'libqemu-arm-softmmu.fa.p/hw_arm_ipod_touch_uart.c.o')]
|
||||
out = str(build / 'test-ipod-uart')
|
||||
args[args.index('-o') + 1] = out
|
||||
args += [obj, '-fsanitize=address,undefined']
|
||||
subprocess.run(args, cwd=build, check=True)
|
||||
# QEMU conserve volontairement ses registres de types et sa boucle globale à la sortie.
|
||||
env = dict(os.environ, ASAN_OPTIONS='detect_leaks=0', UBSAN_OPTIONS='halt_on_error=1')
|
||||
subprocess.run([out], env=env, check=True)
|
||||
@@ -0,0 +1,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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user