Compare commits

...
Author SHA1 Message Date
Workbench IA 53ebaa7e40 Réconcilier la validation 2.1.1 avec le RTC D1759 2026-10-01 07:47:57 +00:00
Mahtan 11a98a7bac Merge pull request 'Émuler le RTC D1759 dès le démarrage' (#4) from agent/rtc-d1759 into ipod_touch_2g 2026-10-01 09:45:44 +02:00
Workbench IA d2c3478fa6 Émuler le RTC D1759 et supprimer le réglage automatique de l'heure 2026-10-01 07:41:33 +00:00
Workbench IA 21a155d35f Valider iPhone OS 2.1.1 et documenter les tests des applications 2026-10-01 07:17:19 +00:00
Mahtan 436bdc536c Valider les boutons, le tactile et les outils du simulateur
Relecture et validations locales terminées : iPhone OS 2.2.1 et 2.1.1, gestes, HTTP, saisie, installation et persistance.
2026-10-01 09:15:58 +02:00
11 changed files with 539 additions and 17 deletions

No files matched your search

+46 -6
View File
@@ -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"
+46 -4
View File
@@ -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
+82
View File
@@ -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/`.
+1 -1
View File
@@ -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 */
+1 -4
View File
@@ -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; }
+59
View File
@@ -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()
+39
View File
@@ -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)
+155
View File
@@ -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();
}
+2 -2
View File
@@ -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);
+102
View File
@@ -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)
+6
View File
@@ -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