Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53ebaa7e40 | ||
|
|
11a98a7bac | ||
|
|
d2c3478fa6 | ||
|
|
21a155d35f | ||
|
|
436bdc536c |
No files matched your search
@@ -1,8 +1,48 @@
|
||||
# Passation — simulateur iPod Touch 2G / iPhone OS 2.2.1
|
||||
|
||||
État au 2026-09-30. Environnement : conteneur Arch Linux, utilisateur `dev` avec sudo, sans `CAP_NET_ADMIN`, sans `/dev/ppp` ni TUN.
|
||||
État au 2026-10-01. Environnement : conteneur Arch Linux, utilisateur `dev` avec sudo, sans `CAP_NET_ADMIN`, sans `/dev/ppp` ni TUN.
|
||||
|
||||
## Dernière mise à jour — fiabilisation GPIO/SPI et agent v12
|
||||
## Dernière mise à jour — RTC D1759
|
||||
|
||||
Le modèle `d1759` remplace `pcf50633` sur la machine 2G. Il corrige le
|
||||
pointeur I²C et fournit le compteur LE32 en `0x5c` avec décalage Unix LE32 en
|
||||
`0x64`. Les notes anciennes attribuant un accès indirect RTC à `0x61/0x62`
|
||||
étaient incorrectes : ces registres concernent le démarrage.
|
||||
`start -W` et `wait` ne règlent plus l'heure via l'agent.
|
||||
Voir [RTC.md](RTC.md) pour le protocole, les preuves et les limites.
|
||||
Le raccordement GPIO est aussi borné aux cinq IRQ définies, pour éviter
|
||||
l'accès hors limites révélé à la recompilation.
|
||||
|
||||
## Validation du 1er octobre 2026 — iPhone OS 2.1.1
|
||||
|
||||
- Image distincte `5F138-validation-20261001`, créée depuis la base 5F138 conservée,
|
||||
avec agent v12, TouchProbe et HelloEmu. Session : `work/validation-20261001/`.
|
||||
`system-version.plist` confirme 2.1.1 / 5F138 ; démarrage déverrouillé en 28 s.
|
||||
- Calibration : sept points exacts de `(20,40)` à `(300,460)` dans
|
||||
`calibration-complete.log`, après attente du marqueur `TouchProbe ready`.
|
||||
Les ordonnées du journal sont relatives à la vue, située à y=20.
|
||||
- Six cycles alternant Power/Home et Power/Power passent, sans perte du tactile.
|
||||
- Safari : clavier virtuel QWERTY, formulaire envoyé avec `texte=bonjour`,
|
||||
défilement jusqu'au lien final et requête `/fin` reçue par le serveur HTTP local.
|
||||
Journal : `http/requests.jsonl`. Une version réutilisable de la page et du serveur
|
||||
est fournie dans `tests/http-fixture.py` ; aucun site extérieur n'est requis.
|
||||
- Calculatrice : `7 × 8 = 56`, capture `calculatrice-56.png`.
|
||||
- Notes : note « Bonjour » créée par le clavier virtuel puis sauvegardée.
|
||||
Les lignes de `Note` et `note_bodies` sont identiques dans `notes-avant.db` et
|
||||
`notes-apres.db`, et la note réapparaît dans l'interface après redémarrage.
|
||||
- Nouvelle app `HelloEmu21.app` (bundle-id `com.ioslegacy.hello21`) installée via
|
||||
`push-app`, cache reconstruit, redémarrage sans réinitialiser la NAND en 50 s,
|
||||
lancement au premier plan et journal HelloEmu validés. Captures :
|
||||
`hello21-apres-push.png` et `note-apres-reboot.png`.
|
||||
- Synthèse locale : `validation.json`. Session et serveur HTTP de test arrêtés.
|
||||
Aucun défaut `Killing device`, `Fatal Exception` ou `MACH Reboot` relevé dans
|
||||
la console finale. Les images et NAND d'origine sont conservées.
|
||||
- Particularités : l'image 5F138 conserve ses réglages de langue et de fuseau
|
||||
d'origine ; les captures sont en anglais. Le lancement `--url` d'un Safari
|
||||
déjà ouvert n'a pas changé la page pendant le test : passer par les liens ou
|
||||
relancer le navigateur. Le RTC est depuis implémenté (voir ci-dessus) ; la veille profonde reste à faire.
|
||||
|
||||
## Historique — fiabilisation GPIO/SPI et agent v12
|
||||
|
||||
Les sections de diagnostic plus anciennes ci-dessous décrivent les étapes précédentes.
|
||||
Le workflow `push-app` puis déverrouillage/lancement a été validé avec l'agent v11.
|
||||
@@ -26,8 +66,8 @@ puis HelloEmu se lance au premier plan avec son journal. Diagnostics locaux :
|
||||
Les sessions de validation sont arrêtées.
|
||||
Les fichiers générés, images et diagnostics restent hors du dépôt. Les images
|
||||
5H11a et les NAND d'origine sont conservées ; le RTC, le Wi-Fi matériel, l'audio
|
||||
et la veille profonde restent à implémenter. Les nouveaux correctifs ne sont
|
||||
pas encore validés sur 5F138.
|
||||
et la veille profonde restent à implémenter. Les nouveaux correctifs n’étaient
|
||||
pas encore validés sur 5F138 à cette étape.
|
||||
|
||||
## Objectif
|
||||
|
||||
@@ -80,7 +120,7 @@ On veut le comportement le plus fidèle possible à un vrai appareil. La consign
|
||||
- Ancienne base sauvegardée : `images/5H11a/nand-readonly-backup`, `rootfs-v8-backup.img`,
|
||||
`machine-opts-readonly-backup`. Session d'origine : `run/nand-before-persistence` et `fw-before-persistence`.
|
||||
Session de test avec app et fichier de 8 Mio : `run/nand-persistence-verified` (firmware `persistence-test-v10`).
|
||||
2. **Horloge RTC du PMU** non émulée : le D1759 passe par un accès indirect, index en `0x61` et donnée en `0x62`. Le modèle PCF50633 existant est faux pour le 2G. Contournement en place : `settime`.
|
||||
2. **Horloge RTC du PMU** : compteur et décalage D1759 implémentés ; IRQ d'alarme et persistance du décalage entre instances QEMU restent absentes. Voir la dernière mise à jour et `RTC.md`.
|
||||
3. **Réglages > Wi-Fi** affiche « Pas de Wi-Fi », parce que la puce BCM4325 n'est pas émulée. Le réseau passe par une interface ppp0, que l'app peut voir comme un VPN ou une connexion transitoire. Émuler le WiFi pour de vrai (SDIO + firmware FullMAC) serait un gros chantier ; le pont slirp est réutilisable.
|
||||
4. **Pas encore écrits** :
|
||||
- un script `ipod-test-app` (construit l'image, boote, lance l'app avec `--log`, fait une capture, affiche les logs) ;
|
||||
@@ -91,7 +131,7 @@ On veut le comportement le plus fidèle possible à un vrai appareil. La consign
|
||||
## Démarrer
|
||||
|
||||
```bash
|
||||
ipod-emu start -f -W # 2.2.1 (défaut), NAND vierge, attend SpringBoard puis règle l'heure
|
||||
ipod-emu start -f -W # 2.2.1 (défaut), NAND vierge, attend SpringBoard
|
||||
ipod-agent ping # l'agent répond quand PPP est monté (quelques secondes)
|
||||
ipod-agent launch com.apple.mobilesafari --url http://example.com/
|
||||
ipod-qmp screenshot /tmp/s.png # tap X Y | swipe | home | hmp "info registers"
|
||||
|
||||
@@ -131,8 +131,7 @@ La vitesse verticale utilise `y`, les vitesses conservent leur signe et sont
|
||||
bornées à 16 bits. `apps/TouchProbe` permet de mesurer la calibration : compiler
|
||||
avec son Makefile, lancer avec `--log`, puis comparer les gestes QMP au marqueur
|
||||
bleu et aux coordonnées du journal (ajouter 20 pixels à l'ordonnée locale de la vue).
|
||||
La calibration a été vérifiée sur 5H11a, sur sept points de `(20,40)` à `(300,460)`.
|
||||
Les modifications n'ont pas encore été validées sur 5F138.
|
||||
La calibration a été vérifiée sur 5H11a et 5F138, sur sept points de `(20,40)` à `(300,460)`.
|
||||
|
||||
Agent **v12** : `put` et `get` utilisent des blocs de 64 Kio au lieu d'allouer
|
||||
le fichier entier dans l'iPod. Un upload incomplet supprime son temporaire et
|
||||
@@ -143,10 +142,50 @@ 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-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
|
||||
```
|
||||
|
||||
## Validation sous iPhone OS 2.1.1
|
||||
|
||||
Le firmware 5F138 a été vérifié avec l'agent v12 et les mêmes corrections QEMU.
|
||||
Conserver l'image 5F138 d'origine et produire une image dérivée avec un nom absent :
|
||||
|
||||
```bash
|
||||
ipod-mkimage -b 5F138 -o 5F138-agent-v12 agent/overlay apps/TouchProbe/TouchProbe.app apps/HelloEmu/HelloEmu.app
|
||||
```
|
||||
|
||||
Utiliser ensuite un environnement `IPOD_EMU_HOME` indépendant pour démarrer cette
|
||||
image ; un changement de firmware remplace la NAND de la session choisie.
|
||||
`SystemVersion.plist` confirme iPhone OS 2.1.1 / 5F138. Les sept points de
|
||||
calibration sont exacts et six cycles de veille/réveil alternant Home et Power
|
||||
passent, sans désactivation du tactile.
|
||||
|
||||
Pour tester Safari sans dépendre d'un site extérieur, depuis le dépôt :
|
||||
|
||||
```bash
|
||||
python3 contrib/ipod-emu/tests/http-fixture.py --output /chemin/diagnostic-http
|
||||
```
|
||||
|
||||
Ouvrir l'URL `http://10.0.2.2:<port>/` affichée dans Safari invité. Le serveur
|
||||
écoute uniquement sur la boucle locale de l'hôte ; il conserve le port et les
|
||||
requêtes dans le dossier choisi. Saisir `bonjour` avec le clavier virtuel et
|
||||
soumettre : le journal doit contenir `/submit?texte=bonjour`. Suivre le lien de
|
||||
défilement, glisser jusqu'au bas, attendre la fin de l'inertie et toucher le lien
|
||||
final : `/fin` doit apparaître. Arrêter le serveur avec Ctrl-C après le test.
|
||||
|
||||
Attendre l'affichage réel d'une application avant d'envoyer des gestes. Pour
|
||||
TouchProbe, attendre `TouchProbe ready` dans le journal ; une réponse de lancement
|
||||
ne garantit pas que la vue soit déjà prête. Sous 2.1.1, lancer Safari avec `--url`
|
||||
alors qu'il est déjà ouvert n'a pas changé la page lors du test ; utiliser les
|
||||
liens de la page ou relancer Safari pour ouvrir une autre URL.
|
||||
|
||||
Notes et la calculatrice complètent ce contrôle : saisir une note, la sauvegarder,
|
||||
puis comparer son texte dans `Library/Notes/notes.db` avant et après redémarrage ;
|
||||
vérifier visuellement `7 × 8 = 56` dans la calculatrice. Les diagnostics et le
|
||||
résultat détaillé de la validation du 1er octobre sont décrits dans `HANDOFF.md`.
|
||||
|
||||
## Limites
|
||||
|
||||
- **Persistance NAND** : les pages et métadonnées écrites par iOS, ainsi que les effacements de blocs, sont conservés
|
||||
@@ -156,8 +195,11 @@ python3 contrib/ipod-emu/tests/test-transfers.py # Python et compilateur C, soc
|
||||
- **Internet** : PPP sur UART1, pont `ipod-netd` vers libslirp ; DNS et HTTP fonctionnent. `ipod-agent fetch
|
||||
http://example.com/` teste le réseau depuis l'iPod. L'écran Réglages affiche toujours « Pas de Wi-Fi », car la puce
|
||||
BCM4325 n'est pas émulée.
|
||||
- **Heure** : le RTC n'est pas émulé ; `start -W` / `wait` règle l'heure depuis l'hôte dès que l'agent répond,
|
||||
avant la fin du démarrage de SpringBoard.
|
||||
- **Heure** : le RTC D1759 fournit l'heure dès le démarrage, avec la configuration QEMU `-rtc`
|
||||
(UTC et horloge hôte par défaut). `start -W` / `wait` ne modifie plus la date.
|
||||
`ipod-agent settime SECONDES_UNIX` reste disponible pour les tests explicites.
|
||||
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
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
# RTC du D1759 — iPod Touch 2G
|
||||
|
||||
Le modèle `d1759` fournit l'heure avant le lancement des services iOS. La
|
||||
machine 2G l'utilise à l'adresse I²C `0x73` ; le modèle historique `pcf50633`
|
||||
reste disponible séparément. Les images et l'agent v12 existants conviennent.
|
||||
Il faut reconstruire QEMU et utiliser le lanceur mis à jour.
|
||||
|
||||
## Protocole observé
|
||||
|
||||
Le désassemblage local du pilote `AppleD1759PMURTC` de 5H11a montre :
|
||||
|
||||
| Registres | Comportement |
|
||||
| --- | --- |
|
||||
| `0x5c` à `0x5f` | Compteur de secondes sur 32 bits, petit-boutiste |
|
||||
| `0x64` à `0x67` | Décalage ajouté au compteur pour obtenir les secondes Unix |
|
||||
| `0x50` à `0x5a` | Réglages d'alarme conservés en mémoire, sans IRQ |
|
||||
| `0x60` à `0x63` | Suivi du démarrage, indépendant du RTC |
|
||||
|
||||
Sur le kernelcache déchiffré, le segment `__PRELINK` a le décalage adresse
|
||||
virtuelle/fichier `0xc0029000`. La routine à `0xc03c8f08` lit quatre octets en
|
||||
`0x5c` et ajoute le décalage chargé au démarrage par `0xc03c8c10` depuis `0x64`.
|
||||
La routine à `0xc03c8f48` règle l'heure en écrivant la différence en `0x64`.
|
||||
Les routines PMU à `0xc03c11e8` et `0xc03c1228` transmettent directement le
|
||||
registre au nub I²C. Ce sont des observations du binaire local, pas des
|
||||
symboles exportés ni une spécification complète du composant.
|
||||
|
||||
Le protocole I²C standard, registre puis données, est aussi visible dans
|
||||
[OpeniBoot pour le S5L8720](https://github.com/iDroid-Project/openiBoot/blob/master/plat-s5l8720/pmu.c).
|
||||
Sa fonction RTC est laissée à implémenter et ne constitue pas une preuve du
|
||||
format du compteur. Les anciennes notes décrivant `0x61/0x62` comme un accès
|
||||
indirect RTC étaient incorrectes.
|
||||
|
||||
Une lecture capture le compteur au début de la réception pour éviter de
|
||||
mélanger deux secondes entre les octets. Les sélections de registre avec STOP
|
||||
et avec START répété sont acceptées. Le compteur est en lecture seule ; les
|
||||
écritures de décalage sont conservées pendant la session.
|
||||
|
||||
## Configuration et limites
|
||||
|
||||
QEMU initialise le compteur via `qemu_get_timedate`, puis le fait avancer avec
|
||||
`rtc_clock`. Les options habituelles `-rtc base=...,clock=...` sont respectées.
|
||||
Par défaut, la base est UTC et le temps suit l'horloge de l'hôte. Le fuseau
|
||||
horaire d'affichage reste un réglage d'iOS. Une base reproductible peut être
|
||||
passée au lanceur, par exemple :
|
||||
|
||||
```bash
|
||||
IPOD_QEMU_EXTRA='-rtc base=2024-02-29T23:59:59,clock=vm' ipod-emu start -W
|
||||
```
|
||||
|
||||
`start -W` et `wait` ne lancent plus `settime`. La commande
|
||||
`ipod-agent settime SECONDES_UNIX` reste un réglage volontaire pour les tests.
|
||||
Le décalage RTC ne persiste pas entre deux processus QEMU ; une nouvelle
|
||||
instance repart de sa configuration `-rtc`. Les IRQ d'alarme, la veille
|
||||
profonde et la migration de l'état RTC ne sont pas implémentées.
|
||||
|
||||
Un reset QEMU à chaud peut laisser PPP inaccessible. Pour redémarrer une
|
||||
session conservée, utiliser `ipod-agent sync`, `ipod-emu stop`, puis
|
||||
`ipod-emu start -W`, qui conserve la NAND.
|
||||
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
contrib/ipod-emu/tests/run-rtc
|
||||
```
|
||||
|
||||
Le test compile le modèle réel avec ASan/UBSan et utilise le bus I²C QEMU :
|
||||
base UTC avec fuseau hôte différent, date fixe bissextile, cohérence des
|
||||
lectures au changement de seconde, décalages positifs/négatifs et séparation
|
||||
des registres de démarrage.
|
||||
|
||||
Validation du 1er octobre 2026 sur des copies de NAND :
|
||||
|
||||
- 5H11a / iPhone OS 2.2.1 et 5F138 / iPhone OS 2.1.1 démarrent sans `settime` ;
|
||||
`gettimeofday` dans un programme armv6 retrouve l'heure Unix de l'hôte.
|
||||
- Un changement volontaire de date continue à avancer. Après un reset à
|
||||
chaud de 5F138, les journaux précoces d'iOS retrouvent le décalage écrit ;
|
||||
PPP ne reprend pas correctement après ce reset.
|
||||
- Les démarrages normaux, le déverrouillage et le lancement d'applications
|
||||
sont vérifiés avec le lanceur sans synchronisation par l'agent.
|
||||
|
||||
Les diagnostics restent hors du dépôt dans
|
||||
`/srv/homes/iphoneos2/ipod-emu/work/rtc-20261001/`.
|
||||
@@ -768,7 +768,7 @@ static void handle(int fd, char *line)
|
||||
else if (!strcmp(cmd, "sc")) cmd_sc(fd, a, na);
|
||||
else if (!strcmp(cmd, "routes")) cmd_routes(fd);
|
||||
else if (!strcmp(cmd, "settime")) {
|
||||
/* le RTC du PMU (D1759) n'est pas émulé : l'hôte donne l'heure (secondes Unix) */
|
||||
/* Réglage explicite de l'heure pour les tests (secondes Unix). */
|
||||
struct timeval tv = { na ? strtol(a[0], NULL, 10) : 0, 0 };
|
||||
if (na && !settimeofday(&tv, NULL)) {
|
||||
notify_post("com.apple.system.clock_set"); /* SpringBoard et les apps mettent leur horloge à jour */
|
||||
|
||||
@@ -34,10 +34,7 @@ ready() {
|
||||
|
||||
wait_ready() {
|
||||
local t=${1:-300} s=$SECONDS
|
||||
# Régler l'heure dès que PPP répond, avant que SpringBoard initialise
|
||||
# ses timers à partir de préférences persistées datées dans le futur.
|
||||
"$(dirname "$0")/ipod-agent" wait 60 >/dev/null 2>&1 &&
|
||||
timeout 15 "$(dirname "$0")/ipod-agent" settime "$(date +%s)" >/dev/null 2>&1 || true
|
||||
# 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; }
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Page HTTP compatible Safari mobile 3 pour valider saisie et défilement.
|
||||
|
||||
Lancer sur l'hôte avec --output DOSSIER ; ouvrir l'URL 10.0.2.2 affichée dans
|
||||
Safari invité. Le port et les requêtes reçues sont enregistrés dans ce dossier.
|
||||
Arrêter avec Ctrl-C après le test. Aucun accès Internet n'est nécessaire.
|
||||
"""
|
||||
import argparse
|
||||
from datetime import datetime, timezone
|
||||
from html import escape
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
import json
|
||||
from pathlib import Path
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--output', type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
args.output.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
with (args.output / 'requests.jsonl').open('a', encoding='utf-8') as log:
|
||||
log.write(json.dumps({'utc': datetime.now(timezone.utc).isoformat(),
|
||||
'path': self.path}, ensure_ascii=False) + '\n')
|
||||
url = urlparse(self.path)
|
||||
if url.path == '/submit':
|
||||
content = 'Recu : ' + escape(parse_qs(url.query).get('texte', [''])[0])
|
||||
elif url.path == '/scroll':
|
||||
content = '<h1>Debut</h1>' + ''.join(
|
||||
'<p style="height:60px">Ligne %02d</p>' % i for i in range(24))
|
||||
content += '<a href="/fin">Fin du defilement</a>'
|
||||
elif url.path == '/fin':
|
||||
content = '<h1>Defilement OK</h1>'
|
||||
else:
|
||||
content = ('<h1>Validation HTTP</h1><form action="/submit" method="get">'
|
||||
'<input type="text" name="texte"><input type="submit" value="Envoyer">'
|
||||
'</form><p><a href="/scroll">Test de defilement</a></p>')
|
||||
body = ('<html><head><meta name="viewport" content="width=device-width">'
|
||||
'<title>Test iPhone OS</title></head><body>' + content + '</body></html>').encode('utf-8')
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'text/html; charset=utf-8')
|
||||
self.send_header('Content-Length', str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
with HTTPServer(('127.0.0.1', 0), Handler) as server:
|
||||
(args.output / 'port').write_text(str(server.server_port) + '\n', encoding='utf-8')
|
||||
print(f'URL depuis l’iPod : http://10.0.2.2:{server.server_port}/', flush=True)
|
||||
try:
|
||||
server.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compile et exécute les régressions RTC 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-rtc.c')
|
||||
commands = json.loads((build / 'compile_commands.json').read_text())
|
||||
c = next(c for c in commands if c['file'].endswith('ipod_touch_pcf50633_pmu.c'))
|
||||
old_root = str(Path(c['directory']).parent)
|
||||
args = shlex.split(c['command'].replace(old_root, str(root)))
|
||||
obj = str(build / 'test-ipod-rtc.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_pcf50633_pmu.c.o')]
|
||||
out = str(build / 'test-ipod-rtc')
|
||||
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,155 @@
|
||||
/* Régressions du D1759 sur le bus I²C réel de QEMU. */
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/main-loop.h"
|
||||
#include "qemu/option.h"
|
||||
#include "sysemu/cpus.h"
|
||||
#include "hw/arm/ipod_touch_pcf50633_pmu.c"
|
||||
|
||||
static QemuOptsList rtc_test_opts = {
|
||||
.name = "rtc-test",
|
||||
.head = QTAILQ_HEAD_INITIALIZER(rtc_test_opts.head),
|
||||
.desc = {
|
||||
{ .name = "base", .type = QEMU_OPT_STRING },
|
||||
{ .name = "clock", .type = QEMU_OPT_STRING },
|
||||
{ }
|
||||
}
|
||||
};
|
||||
|
||||
static I2CBus *bus;
|
||||
static Pcf50633State *pmu;
|
||||
static DeviceState *parent;
|
||||
|
||||
static void setup(const char *base, const char *clock)
|
||||
{
|
||||
QemuOpts *opts = qemu_opts_create(&rtc_test_opts, NULL, 0, &error_abort);
|
||||
qemu_opt_set(opts, "base", base, &error_abort);
|
||||
qemu_opt_set(opts, "clock", clock, &error_abort);
|
||||
configure_rtc(opts);
|
||||
qemu_opts_del(opts);
|
||||
parent = qdev_new(TYPE_PCF50633);
|
||||
bus = i2c_init_bus(parent, "rtc-test");
|
||||
pmu = PCF50633(i2c_slave_create_simple(bus, TYPE_D1759, 0x73));
|
||||
}
|
||||
|
||||
static void teardown(void)
|
||||
{
|
||||
qdev_unrealize(DEVICE(pmu));
|
||||
object_unref(OBJECT(parent));
|
||||
}
|
||||
|
||||
static void select_register(uint8_t reg, bool stop)
|
||||
{
|
||||
g_assert_cmpint(i2c_start_transfer(bus, 0x73, false), ==, 0);
|
||||
g_assert_cmpint(i2c_send(bus, reg), ==, 0);
|
||||
if (stop) {
|
||||
i2c_end_transfer(bus);
|
||||
}
|
||||
g_assert_cmpint(i2c_start_transfer(bus, 0x73, true), ==, 0);
|
||||
}
|
||||
|
||||
static uint32_t read_word(uint8_t reg, bool stop)
|
||||
{
|
||||
uint32_t value = 0;
|
||||
select_register(reg, stop);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
value |= (uint32_t)i2c_recv(bus) << (i * 8);
|
||||
}
|
||||
i2c_end_transfer(bus);
|
||||
return value;
|
||||
}
|
||||
|
||||
static void write_word(uint8_t reg, uint32_t value)
|
||||
{
|
||||
g_assert_cmpint(i2c_start_transfer(bus, 0x73, false), ==, 0);
|
||||
g_assert_cmpint(i2c_send(bus, reg), ==, 0);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
g_assert_cmpint(i2c_send(bus, value >> (i * 8)), ==, 0);
|
||||
}
|
||||
i2c_end_transfer(bus);
|
||||
}
|
||||
|
||||
static void test_host_utc(void)
|
||||
{
|
||||
time_t before = time(NULL);
|
||||
/* Le fuseau de l'hôte ne doit pas décaler le compteur Unix. */
|
||||
setenv("TZ", "Pacific/Honolulu", 1);
|
||||
tzset();
|
||||
setup("utc", "host");
|
||||
uint32_t value = read_word(0x5c, false);
|
||||
g_assert_cmpuint(value, >=, before);
|
||||
g_assert_cmpuint(value, <=, time(NULL));
|
||||
g_assert_cmpuint(read_word(0x64, true), ==, 0);
|
||||
teardown();
|
||||
unsetenv("TZ");
|
||||
tzset();
|
||||
}
|
||||
|
||||
static void test_fixed_base(void)
|
||||
{
|
||||
setup("2024-02-29T23:59:59", "vm");
|
||||
g_assert_cmpint(rtc_clock, ==, QEMU_CLOCK_VIRTUAL);
|
||||
g_assert_cmpuint(read_word(0x5c, true), ==, 1709251199);
|
||||
g_assert_cmpuint(read_word(0x5c, false), ==, 1709251199);
|
||||
teardown();
|
||||
}
|
||||
|
||||
static void test_snapshot_rollover(void)
|
||||
{
|
||||
setup("2024-02-29T23:59:59", "vm");
|
||||
uint32_t boundaries[] = { 1709251199, 0x00ffffff, 0xffffffff };
|
||||
for (size_t n = 0; n < G_N_ELEMENTS(boundaries); n++) {
|
||||
pmu->rtc_tick_offset = boundaries[n];
|
||||
select_register(0x5c, false);
|
||||
uint32_t value = i2c_recv(bus);
|
||||
/* Le temps change entre deux octets, après capture du premier. */
|
||||
pmu->rtc_tick_offset++;
|
||||
for (int i = 1; i < 4; i++) {
|
||||
value |= (uint32_t)i2c_recv(bus) << (8 * i);
|
||||
}
|
||||
i2c_end_transfer(bus);
|
||||
g_assert_cmpuint(value, ==, boundaries[n]);
|
||||
g_assert_cmpuint(read_word(0x5c, true), ==, boundaries[n] + 1);
|
||||
}
|
||||
teardown();
|
||||
}
|
||||
|
||||
static void test_offsets(void)
|
||||
{
|
||||
setup("2024-02-29T23:59:59", "vm");
|
||||
uint32_t counter = read_word(0x5c, true);
|
||||
uint32_t offsets[] = { 3600, (uint32_t)-86400, 0x12345678, 0 };
|
||||
for (size_t i = 0; i < G_N_ELEMENTS(offsets); i++) {
|
||||
write_word(0x64, offsets[i]);
|
||||
g_assert_cmpuint(read_word(0x64, false), ==, offsets[i]);
|
||||
g_assert_cmpuint(read_word(0x5c, true), ==, counter);
|
||||
}
|
||||
/* Un changement de date ne doit pas réécrire le compteur matériel. */
|
||||
write_word(0x5c, 1234);
|
||||
g_assert_cmpuint(read_word(0x5c, false), ==, counter);
|
||||
teardown();
|
||||
}
|
||||
|
||||
static void test_boot_registers(void)
|
||||
{
|
||||
setup("2024-02-29T23:59:59", "vm");
|
||||
write_word(0x60, 0xffffffff);
|
||||
g_assert_cmpuint(read_word(0x60, true), ==, 0);
|
||||
g_assert_cmpuint(read_word(0x5c, false), ==, 1709251199);
|
||||
g_assert_cmpuint(read_word(0x64, true), ==, 0);
|
||||
teardown();
|
||||
}
|
||||
|
||||
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-rtc/host-utc", test_host_utc);
|
||||
g_test_add_func("/ipod-rtc/fixed-base", test_fixed_base);
|
||||
g_test_add_func("/ipod-rtc/snapshot-rollover", test_snapshot_rollover);
|
||||
g_test_add_func("/ipod-rtc/offsets", test_offsets);
|
||||
g_test_add_func("/ipod-rtc/boot-registers", test_boot_registers);
|
||||
return g_test_run();
|
||||
}
|
||||
@@ -328,7 +328,7 @@ static void ipod_touch_machine_init(MachineState *machine)
|
||||
nms->sysic = (IPodTouchSYSICState *) g_malloc0(sizeof(struct IPodTouchSYSICState));
|
||||
memory_region_add_subregion(sysmem, SYSIC_MEM_BASE, &sysic_state->iomem);
|
||||
busdev = SYS_BUS_DEVICE(dev);
|
||||
for(int grp = 0; grp < GPIO_NUMINTGROUPS; grp++) {
|
||||
for (int grp = 0; grp < G_N_ELEMENTS(S5L8900_GPIO_IRQS); grp++) {
|
||||
sysbus_connect_irq(busdev, grp, s5l8900_get_irq(nms, S5L8900_GPIO_IRQS[grp]));
|
||||
}
|
||||
|
||||
@@ -461,7 +461,7 @@ static void ipod_touch_machine_init(MachineState *machine)
|
||||
sysbus_connect_irq(busdev, 0, s5l8900_get_irq(nms, S5L8720_I2C0_IRQ));
|
||||
|
||||
// init the PMU
|
||||
I2CSlave * pmu = i2c_slave_create_simple(i2c_state->bus, "pcf50633", 0x73);
|
||||
I2CSlave * pmu = i2c_slave_create_simple(i2c_state->bus, "d1759", 0x73);
|
||||
|
||||
// init the accelerometer
|
||||
I2CSlave *accelerometer = i2c_slave_create_simple(i2c_state->bus, "lis302dl", 0x1D);
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "hw/arm/ipod_touch_pcf50633_pmu.h"
|
||||
#include "hw/arm/ipod_touch_lcd.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "sysemu/rtc.h"
|
||||
#include "sysemu/sysemu.h"
|
||||
|
||||
static int pcf50633_event(I2CSlave *i2c, enum i2c_event event)
|
||||
{
|
||||
@@ -125,9 +128,108 @@ static const TypeInfo pcf50633_info = {
|
||||
.class_init = pcf50633_class_init,
|
||||
};
|
||||
|
||||
/* Le pilote AppleD1759PMURTC lit un compteur LE32 en 0x5c et un
|
||||
* décalage LE32 en 0x64. settimeofday ne réécrit que ce décalage.
|
||||
* Les registres 0x61/0x62 concernent les erreurs de démarrage, pas le RTC.
|
||||
*/
|
||||
static uint32_t d1759_counter(Pcf50633State *s)
|
||||
{
|
||||
return s->rtc_tick_offset +
|
||||
qemu_clock_get_ns(rtc_clock) / NANOSECONDS_PER_SECOND;
|
||||
}
|
||||
|
||||
static int d1759_event(I2CSlave *i2c, enum i2c_event event)
|
||||
{
|
||||
Pcf50633State *s = PCF50633(i2c);
|
||||
|
||||
if (event == I2C_START_SEND || event == I2C_FINISH) {
|
||||
s->ready = 1;
|
||||
} else if (event == I2C_START_RECV) {
|
||||
/* Une lecture multioctet reste cohérente au passage d'une seconde. */
|
||||
s->rtc_snapshot = d1759_counter(s);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint8_t d1759_recv(I2CSlave *i2c)
|
||||
{
|
||||
Pcf50633State *s = PCF50633(i2c);
|
||||
uint8_t reg = s->curreg++;
|
||||
|
||||
if (reg >= 0x5c && reg <= 0x5f) {
|
||||
return s->rtc_snapshot >> ((reg - 0x5c) * 8);
|
||||
}
|
||||
if (reg >= 0x64 && reg <= 0x67) {
|
||||
return s->rtc_unix_offset[reg - 0x64];
|
||||
}
|
||||
if (reg >= 0x50 && reg <= 0x5a) {
|
||||
return s->rtc_alarm[reg - 0x50];
|
||||
}
|
||||
switch (reg) {
|
||||
case 0x02: /* Conversion ADC terminée. */
|
||||
return 2;
|
||||
case PMU_MBCS1:
|
||||
return 1;
|
||||
case 0x69:
|
||||
case 0x76:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int d1759_send(I2CSlave *i2c, uint8_t data)
|
||||
{
|
||||
Pcf50633State *s = PCF50633(i2c);
|
||||
uint8_t reg;
|
||||
|
||||
if (s->ready) {
|
||||
s->curreg = data;
|
||||
s->ready = 0;
|
||||
return 0;
|
||||
}
|
||||
reg = s->curreg++;
|
||||
if (reg >= 0x64 && reg <= 0x67) {
|
||||
s->rtc_unix_offset[reg - 0x64] = data;
|
||||
} else if (reg >= 0x50 && reg <= 0x5a) {
|
||||
/* Conservation des réglages d'alarme ; IRQ RTC non émulée. */
|
||||
s->rtc_alarm[reg - 0x50] = data;
|
||||
} else if (reg == PMU_DSBL1) {
|
||||
lcd_changebrightness(data);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void d1759_init(Object *obj)
|
||||
{
|
||||
Pcf50633State *s = PCF50633(obj);
|
||||
struct tm tm;
|
||||
|
||||
qemu_get_timedate(&tm, 0);
|
||||
s->rtc_tick_offset = mktimegm(&tm) -
|
||||
qemu_clock_get_ns(rtc_clock) / NANOSECONDS_PER_SECOND;
|
||||
s->ready = 1;
|
||||
}
|
||||
|
||||
static void d1759_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
|
||||
|
||||
k->event = d1759_event;
|
||||
k->recv = d1759_recv;
|
||||
k->send = d1759_send;
|
||||
}
|
||||
|
||||
static const TypeInfo d1759_info = {
|
||||
.name = TYPE_D1759,
|
||||
.parent = TYPE_PCF50633,
|
||||
.instance_init = d1759_init,
|
||||
.class_init = d1759_class_init,
|
||||
};
|
||||
|
||||
static void pcf50633_register_types(void)
|
||||
{
|
||||
type_register_static(&pcf50633_info);
|
||||
type_register_static(&d1759_info);
|
||||
}
|
||||
|
||||
type_init(pcf50633_register_types)
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "time.h"
|
||||
|
||||
#define TYPE_PCF50633 "pcf50633"
|
||||
#define TYPE_D1759 "d1759"
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(Pcf50633State, PCF50633)
|
||||
|
||||
#define PMU_DSBL1 0x30
|
||||
@@ -30,6 +31,11 @@ typedef struct Pcf50633State {
|
||||
uint32_t cmd;
|
||||
uint32_t ready;
|
||||
uint32_t curreg;
|
||||
/* D1759 : compteur RTC et décalage Unix, distincts du calendrier PCF. */
|
||||
int64_t rtc_tick_offset;
|
||||
uint32_t rtc_snapshot;
|
||||
uint8_t rtc_unix_offset[4];
|
||||
uint8_t rtc_alarm[11];
|
||||
} Pcf50633State;
|
||||
|
||||
#endif
|
||||
Reference in new issue
Block a user