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.
This commit was merged in pull request #2.
This commit is contained in:
commit
436bdc536c
19 files changed
+964
-102
No files matched your search
@@ -20,3 +20,5 @@ __pycache__/
|
||||
/qemu-ios-generate-nor/
|
||||
!/patches/
|
||||
!/patches/*.patch
|
||||
|
||||
/apps/TouchProbe/TouchProbe.app/
|
||||
@@ -2,6 +2,33 @@
|
||||
|
||||
État au 2026-09-30. 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
|
||||
|
||||
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.
|
||||
Cette suite corrige les débordements GPIO/SYSIC, les commandes tactiles invalides,
|
||||
les trames instables et les transactions SPI abandonnées à la reprise de l'écran.
|
||||
Huit cycles veille/réveil alternant Home et Power ont passé sur 5H11a sans perte
|
||||
du tactile. La calibration TouchProbe retrouve les coordonnées écran sur sept
|
||||
points ; la vitesse verticale et le calcul signé des vitesses sont corrigés.
|
||||
|
||||
L'agent v12 conserve le protocole et transfère par blocs de 64 Kio avec fichiers
|
||||
temporaires distincts. `ipod-test-app` prépare une NAND indépendante, vérifie le
|
||||
premier plan, récupère journal et capture, puis arrête sa propre session.
|
||||
Voir le README pour les commandes et les tests reproductibles ASan/UBSan.
|
||||
Validation finale : huit régressions matérielles avec ASan/UBSan et sept tests
|
||||
sur les transferts ; HelloEmu réussit le workflow automatique, l'application
|
||||
absente échoue avec capture et arrêt de la VM. Un fichier de 8 460 608 octets
|
||||
fait l'aller-retour via l'agent v12 avec SHA-256 identique. Trois cycles de
|
||||
veille/réveil supplémentaires avec la calibration finale et l'agent v12 passent,
|
||||
puis HelloEmu se lance au premier plan avec son journal. Diagnostics locaux :
|
||||
`work/fiabilisation-20260930/` (dont `test-hello`, `test-echec`, `hello-final.png`).
|
||||
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.
|
||||
|
||||
## Objectif
|
||||
|
||||
On fournit un simulateur d'**iPod Touch 2G sous iPhone OS 2.2.1** qui sert surtout à un agent IA pour tester les applications iOS 2.x d'AlloVoisins. Les apps sont compilées en armv6 avec `/opt/cctools` et `/opt/theos/sdks/iPhoneOS2.2.1.sdk`. L'agent doit pouvoir faire les choses suivantes, sans intervention humaine :
|
||||
|
||||
@@ -27,7 +27,7 @@ redirection. `stop` nettoie le relais suivi par PID même si QEMU est déjà arr
|
||||
## Utilisation
|
||||
|
||||
```bash
|
||||
ipod-emu start -W # agent v11 : conserve la NAND, attend SpringBoard et déverrouille (~35–50 s)
|
||||
ipod-emu start -w 5H11a-agent-v12 -W # attend SpringBoard et déverrouille (~25–50 s)
|
||||
# Ajouter -f uniquement pour remettre la NAND à zéro.
|
||||
ipod-qmp screenshot ecran.png # capture 320x480
|
||||
ipod-qmp tap 160 326 # touche (coordonnées écran), ipod-qmp tap X Y 1.5 = appui long
|
||||
@@ -37,14 +37,13 @@ ipod-emu log # console du kernel + syslog iOS
|
||||
ipod-emu stop
|
||||
```
|
||||
|
||||
- Pas de touch avant que `ipod-emu wait` / `start -W` ait rendu la main : le contrôleur tactile plante QEMU s'il est
|
||||
touché pendant son démarrage.
|
||||
- Attendre `ipod-emu wait` / `start -W` avant les gestes : le pilote tactile invité doit avoir terminé son démarrage.
|
||||
- VNC sur `127.0.0.1:5900` pour interagir à la main (tunnel SSH : `ssh -L 5900:127.0.0.1:5900 …`).
|
||||
- Le lancement d'une app prend 10-20 s.
|
||||
|
||||
## Déverrouillage après redémarrage
|
||||
|
||||
Avec l'agent **v11**, `start -W` et `wait` vérifient le verrouillage, réveillent
|
||||
Avec l'agent **v11 ou ultérieur**, `start -W` et `wait` vérifient le verrouillage, réveillent
|
||||
l'écran et effectuent le glissement normal avant de rendre la main.
|
||||
`ipod-agent screen` retourne `locked=0 passcode=0` une fois déverrouillé ;
|
||||
`ipod-agent unlock` permet de refaire ce parcours après démarrage.
|
||||
@@ -89,6 +88,65 @@ Les `.app`/`.ipa` vont dans `/Applications` (re-signés avec `ldid -S` ; le kern
|
||||
la toolchain de l'environnement (`arm-apple-darwin9-gcc -isysroot /opt/theos/sdks/iPhoneOS2.2.1.sdk`), voir
|
||||
`apps/HelloEmu`. Environ 230 Mo libres dans l'image 2.2.1.
|
||||
|
||||
## Test automatique et diagnostics
|
||||
|
||||
```bash
|
||||
# Construire d'abord l'agent v12 et une image dédiée depuis la base inchangée :
|
||||
PATH=/opt/cctools/bin:$PATH make -C agent
|
||||
ipod-mkimage -b 5H11a -o 5H11a-agent-v12 agent/overlay
|
||||
|
||||
# Après avoir arrêté sa session, tester dans une NAND indépendante :
|
||||
ipod-test-app MonApp.app
|
||||
ipod-test-app MonApp.ipa --output /chemin/nouveau-dossier
|
||||
ipod-test-app paquet.deb --bundle-id com.exemple.monapp
|
||||
```
|
||||
|
||||
Les chemins `agent/` ci-dessus sont relatifs au dossier des outils ; dans le dépôt,
|
||||
utiliser `contrib/ipod-emu/agent`. Choisir un nom d'image absent lors de sa création.
|
||||
`--base` permet de choisir une autre image contenant l'agent v11 ou ultérieur.
|
||||
Le bundle-id est lu dans le plist des `.app` et `.ipa` ; il doit être fourni pour
|
||||
les `.deb` et overlays. Le runner refuse de démarrer si les ports VNC ou agent
|
||||
sont déjà occupés. Chaque test crée une image et une session dans son propre
|
||||
répertoire ; la NAND de `run/` et l'image de base restent conservées.
|
||||
|
||||
Le code de sortie vaut zéro uniquement si l'app apparaît puis reste au premier
|
||||
plan pendant `--settle` secondes (3 par défaut), si les diagnostics sont collectés
|
||||
et si la VM est arrêtée. Ce contrôle ne remplace pas les tests fonctionnels de l'app.
|
||||
Les résultats vont dans `work/tests/<date>-<identifiant>/`, ou dans un nouveau dossier
|
||||
`--output` : `result.json`, `workflow.log`, `app.log`, `screen.png` et les logs QEMU,
|
||||
série et réseau dans `session/run/`. En cas d'échec, les diagnostics disponibles
|
||||
sont conservés et la session de test est arrêtée.
|
||||
|
||||
## Tactile et transferts
|
||||
|
||||
Home/Power ne doivent plus écrire hors du tableau GPIO : le port C est inclus.
|
||||
Les trous des registres SYSIC ne débordent plus dans le groupe voisin.
|
||||
Le tactile traite séparément les requêtes de longueur et de données, accepte
|
||||
EA/EB, conserve une trame stable, et abandonne proprement une commande lors de
|
||||
la réinitialisation SPI. Les commandes et rapports inconnus ne font plus arrêter
|
||||
QEMU. Le découpage des requêtes est documenté également dans
|
||||
[OpeniBoot Z2](https://github.com/iDroid-Project/openiBoot/blob/master/plat-s5l8900/multitouch-z2.c).
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
conserve le fichier précédent ; le client fait de même pour les téléchargements.
|
||||
Le protocole reste compatible avec l'agent v11. Les téléchargements vers stdout
|
||||
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-multitouch # build QEMU arm existant, Ninja, compilateur avec ASan/UBSan
|
||||
python3 contrib/ipod-emu/tests/test-transfers.py # Python et compilateur C, sockets locales
|
||||
```
|
||||
|
||||
## Limites
|
||||
|
||||
- **Persistance NAND** : les pages et métadonnées écrites par iOS, ainsi que les effacements de blocs, sont conservés
|
||||
@@ -103,9 +161,9 @@ la toolchain de l'environnement (`arm-apple-darwin9-gcc -isysroot /opt/theos/sdk
|
||||
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
|
||||
le lancement de HelloEmu au premier plan sont validés après cette installation. Les gestes Home/Power restent
|
||||
susceptibles de déclencher un défaut du pilote lors de la reprise de l'écran ; le workflow automatique utilise
|
||||
le réveil par l'API d'affichage et vérifie le verrouillage.
|
||||
le lancement de HelloEmu au premier plan sont validés après cette installation. Les corrections GPIO/SPI
|
||||
ont permis huit cycles veille/réveil alternant Home et Power sur 5H11a. Le déverrouillage automatique
|
||||
continue à vérifier l'état de l'écran.
|
||||
- **Veille** : l'agent v10 pose `SleepDisabled` sur `IOPMrootDomain`, le SoC ne sachant pas reprendre une veille profonde.
|
||||
- Pas de son, pas d'accélération graphique (SpringBoard tourne avec `LK_ENABLE_MBX2D=0`).
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ struct rt_msghdr {
|
||||
#include <notify.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define VERSION "11"
|
||||
#define VERSION "12"
|
||||
#define PORT 7788
|
||||
#define PPP_TTY "/dev/tty.bluetooth"
|
||||
#define GUEST_IP "10.0.2.15"
|
||||
@@ -236,7 +236,7 @@ static int send_all(int fd, const void *b, size_t n)
|
||||
const char *p = b;
|
||||
while (n) {
|
||||
ssize_t w = write(fd, p, n);
|
||||
if (w < 0) { if (errno == EINTR) continue; return -1; }
|
||||
if (w <= 0) { if (w < 0 && errno == EINTR) continue; if (!w) errno = EIO; return -1; }
|
||||
p += w; n -= w;
|
||||
}
|
||||
return 0;
|
||||
@@ -569,49 +569,75 @@ static int read_full(int fd, void *b, size_t n)
|
||||
char *p = b;
|
||||
while (n) {
|
||||
ssize_t r = read(fd, p, n);
|
||||
if (r <= 0) { if (r < 0 && errno == EINTR) continue; return -1; }
|
||||
if (r <= 0) { if (r < 0 && errno == EINTR) continue; if (!r) errno = EIO; return -1; }
|
||||
p += r; n -= r;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Transferts par blocs : ne pas allouer la taille du fichier sur l'iPod. */
|
||||
static void cmd_put(int fd, char **args, int nargs)
|
||||
{
|
||||
if (nargs < 3) { reply_str(fd, 0, "usage: put<TAB>chemin<TAB>mode octal<TAB>taille + données"); return; }
|
||||
size_t size = strtoul(args[2], NULL, 10);
|
||||
char *data = malloc(size ? size : 1);
|
||||
if (read_full(fd, data, size)) { free(data); return; }
|
||||
char *end;
|
||||
errno = 0;
|
||||
unsigned long size = strtoul(args[2], &end, 10);
|
||||
if (errno || !args[2][0] || args[2][0] == '-' || *end) {
|
||||
reply_str(fd, 0, "taille invalide"); return;
|
||||
}
|
||||
char dir[1024];
|
||||
strncpy(dir, args[0], sizeof dir - 1);
|
||||
dir[sizeof dir - 1] = 0;
|
||||
if (strlen(args[0]) >= sizeof dir) { reply_str(fd, 0, "chemin trop long"); return; }
|
||||
strcpy(dir, args[0]);
|
||||
char *slash = strrchr(dir, '/');
|
||||
if (slash && slash != dir) { *slash = 0; mkdirs(dir); }
|
||||
char tmp[1100];
|
||||
snprintf(tmp, sizeof tmp, "%s.emuagent-tmp", args[0]);
|
||||
int f = open(tmp, O_WRONLY | O_CREAT | O_TRUNC, strtoul(args[1], NULL, 8));
|
||||
if (f < 0 || send_all(f, data, size) || fsync(f)) {
|
||||
reply_str(fd, 0, "écriture de %s : %s", args[0], strerror(errno));
|
||||
if (f >= 0) close(f);
|
||||
free(data);
|
||||
snprintf(tmp, sizeof tmp, "%s.emuagent-XXXXXX", args[0]);
|
||||
int f = mkstemp(tmp);
|
||||
if (f < 0) { reply_str(fd, 0, "création : %s", strerror(errno)); return; }
|
||||
char data[65536];
|
||||
size_t remaining = size;
|
||||
int failed = 0;
|
||||
while (remaining) {
|
||||
size_t chunk = remaining < sizeof data ? remaining : sizeof data;
|
||||
if (read_full(fd, data, chunk) || send_all(f, data, chunk)) { failed = 1; break; }
|
||||
remaining -= chunk;
|
||||
}
|
||||
if (!failed && (fchmod(f, strtoul(args[1], NULL, 8)) || fsync(f))) failed = 1;
|
||||
int saved = errno;
|
||||
if (close(f) && !failed) { failed = 1; saved = errno; }
|
||||
if (!failed && rename(tmp, args[0])) { failed = 1; saved = errno; }
|
||||
if (failed) {
|
||||
unlink(tmp);
|
||||
reply_str(fd, 0, "écriture de %s : %s", args[0], strerror(saved));
|
||||
return;
|
||||
}
|
||||
close(f);
|
||||
chmod(tmp, strtoul(args[1], NULL, 8));
|
||||
rename(tmp, args[0]);
|
||||
free(data);
|
||||
reply_str(fd, 1, "%zu octets écrits", size);
|
||||
reply_str(fd, 1, "%lu octets écrits", size);
|
||||
}
|
||||
|
||||
static void cmd_get(int fd, char **args, int nargs)
|
||||
{
|
||||
if (nargs < 1) { reply_str(fd, 0, "usage: get<TAB>chemin"); return; }
|
||||
int f = open(args[0], O_RDONLY | O_NONBLOCK);
|
||||
struct stat st;
|
||||
if (nargs < 1 || stat(args[0], &st) || !S_ISREG(st.st_mode)) { reply_str(fd, 0, "%s : %s", nargs ? args[0] : "?", strerror(errno)); return; }
|
||||
int f = open(args[0], O_RDONLY);
|
||||
char *data = malloc(st.st_size ? st.st_size : 1);
|
||||
if (f < 0 || read_full(f, data, st.st_size)) { reply_str(fd, 0, "lecture de %s : %s", args[0], strerror(errno)); free(data); if (f >= 0) close(f); return; }
|
||||
if (f < 0 || fstat(f, &st) || !S_ISREG(st.st_mode)) {
|
||||
reply_str(fd, 0, "lecture de %s impossible", args[0]);
|
||||
if (f >= 0) close(f);
|
||||
return;
|
||||
}
|
||||
char hdr[128], data[65536];
|
||||
snprintf(hdr, sizeof hdr, "OK %llu\n", (unsigned long long)st.st_size);
|
||||
if (send_all(fd, hdr, strlen(hdr))) { close(f); return; }
|
||||
off_t remaining = st.st_size;
|
||||
while (remaining) {
|
||||
size_t chunk = remaining < (off_t)sizeof data ? (size_t)remaining : sizeof data;
|
||||
/* Après l'en-tête, une erreur impose de fermer la connexion :
|
||||
le client détecte la réponse incomplète et conserve son ancien fichier. */
|
||||
if (read_full(f, data, chunk) || send_all(fd, data, chunk)) {
|
||||
close(f); shutdown(fd, SHUT_RDWR); return;
|
||||
}
|
||||
remaining -= chunk;
|
||||
}
|
||||
close(f);
|
||||
reply(fd, 1, NULL, data, st.st_size);
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void cmd_ls(int fd, char **args, int nargs)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>TouchProbe</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.ioslegacy.touchprobe</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>TouchProbe</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>2.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,10 @@
|
||||
# Sonde de calibration tactile pour iPhone OS 2.
|
||||
SDK := /opt/theos/sdks/iPhoneOS2.2.1.sdk
|
||||
CC := /opt/cctools/bin/arm-apple-darwin9-gcc
|
||||
all: TouchProbe.app/TouchProbe
|
||||
TouchProbe.app/TouchProbe: main.m Info.plist
|
||||
mkdir -p TouchProbe.app
|
||||
$(CC) -isysroot $(SDK) -arch armv6 -miphoneos-version-min=2.0 -O2 -o $@ main.m -framework UIKit -framework Foundation
|
||||
cp Info.plist TouchProbe.app/
|
||||
ldid -S $@
|
||||
.PHONY: all
|
||||
@@ -0,0 +1,30 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
@interface ProbeView : UIView
|
||||
@end
|
||||
@implementation ProbeView
|
||||
- (void)record:(NSSet *)touches phase:(NSString *)phase {
|
||||
CGPoint p=[[touches anyObject] locationInView:self];
|
||||
UIView *marker=[self viewWithTag:99];
|
||||
if (!marker) {
|
||||
marker=[[[UIView alloc] initWithFrame:CGRectMake(0,0,0,0)] autorelease];
|
||||
marker.tag=99; marker.backgroundColor=[UIColor blueColor]; marker.userInteractionEnabled=NO;
|
||||
[self addSubview:marker];
|
||||
}
|
||||
marker.frame=CGRectMake(p.x-2,p.y-2,5,5);
|
||||
NSLog(@"TOUCH %@ %.1f %.1f",phase,p.x,p.y);
|
||||
}
|
||||
- (void)touchesBegan:(NSSet *)t withEvent:(UIEvent *)e { [self record:t phase:@"begin"]; }
|
||||
- (void)touchesMoved:(NSSet *)t withEvent:(UIEvent *)e { [self record:t phase:@"move"]; }
|
||||
- (void)touchesEnded:(NSSet *)t withEvent:(UIEvent *)e { [self record:t phase:@"end"]; }
|
||||
@end
|
||||
@interface ProbeApp : NSObject <UIApplicationDelegate> { UIWindow *window; }
|
||||
@end
|
||||
@implementation ProbeApp
|
||||
- (void)applicationDidFinishLaunching:(UIApplication *)app {
|
||||
window=[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||
ProbeView *v=[[ProbeView alloc] initWithFrame:CGRectMake(0,20,320,460)];
|
||||
v.backgroundColor=[UIColor whiteColor];[window addSubview:v];[v release];[window makeKeyAndVisible];
|
||||
NSLog(@"TouchProbe ready window=%@ view=%@",NSStringFromCGRect(window.frame),NSStringFromCGRect(v.frame));
|
||||
}
|
||||
@end
|
||||
int main(int argc,char **argv) { NSAutoreleasePool *p=[[NSAutoreleasePool alloc] init];int rc=UIApplicationMain(argc,argv,nil,@"ProbeApp");[p release];return rc; }
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
Code de sortie : 0 si OK, 1 si erreur, code de sortie du programme pour exec.
|
||||
"""
|
||||
import os, socket, subprocess, sys, time
|
||||
import os, socket, subprocess, sys, tempfile, time
|
||||
|
||||
HOST = os.environ.get("IPOD_AGENT_HOST", "127.0.0.1")
|
||||
PORT = int(os.environ.get("IPOD_AGENT_PORT", "7788"))
|
||||
@@ -31,19 +31,41 @@ class Agent:
|
||||
self.s = socket.create_connection((HOST, PORT), timeout=timeout)
|
||||
self.f = self.s.makefile("rb")
|
||||
|
||||
def call(self, *fields, data=None):
|
||||
def call(self, *fields, data=None, sink=None):
|
||||
line = "\t".join(str(x) for x in fields) + "\n"
|
||||
self.s.sendall(line.encode())
|
||||
if data is not None:
|
||||
self.s.sendall(data)
|
||||
hdr = self.f.readline().decode().rstrip("\n")
|
||||
if hasattr(data, "read"):
|
||||
remaining = int(fields[-1])
|
||||
while remaining:
|
||||
chunk = data.read(min(65536, remaining))
|
||||
if not chunk:
|
||||
raise ConnectionError("fichier local tronqué pendant l'upload")
|
||||
self.s.sendall(chunk)
|
||||
remaining -= len(chunk)
|
||||
else:
|
||||
self.s.sendall(data)
|
||||
hdr = self.f.readline(4096).decode().rstrip("\n")
|
||||
if not hdr:
|
||||
raise ConnectionError("connexion fermée par l'agent")
|
||||
parts = hdr.split(" ", 2)
|
||||
if len(parts) < 2:
|
||||
raise ConnectionError("en-tête invalide")
|
||||
n = int(parts[1])
|
||||
body = self.f.read(n)
|
||||
if len(body) != n:
|
||||
raise ConnectionError(f"réponse tronquée : {len(body)} octets reçus sur {n}")
|
||||
if n < 0 or parts[0] not in ("OK", "ERR"):
|
||||
raise ConnectionError("en-tête invalide")
|
||||
body = bytearray()
|
||||
received = 0
|
||||
while received < n:
|
||||
chunk = self.f.read(min(65536, n - received))
|
||||
if not chunk:
|
||||
raise ConnectionError(f"réponse tronquée : {received} octets reçus sur {n}")
|
||||
received += len(chunk)
|
||||
if sink is not None and parts[0] == "OK":
|
||||
sink.write(chunk)
|
||||
else:
|
||||
body.extend(chunk)
|
||||
body = bytes(body)
|
||||
return parts[0] == "OK", body, parts[2] if len(parts) > 2 else None
|
||||
|
||||
|
||||
@@ -88,8 +110,7 @@ def unlock(a):
|
||||
if not ok:
|
||||
sys.exit(body.decode())
|
||||
time.sleep(2) # laisser le pilote réactiver l'écran et le tactile
|
||||
# La surface tactile émulée est légèrement plus petite que l'écran :
|
||||
# le geste doit atteindre le bord droit pour terminer le déverrouillage.
|
||||
# Terminer le geste au bord droit du curseur de déverrouillage.
|
||||
subprocess.run([qmp, "-s", sock, "swipe", "60", "450", "319", "450"], check=True)
|
||||
time.sleep(2)
|
||||
state = screen()
|
||||
@@ -175,20 +196,30 @@ def main():
|
||||
unlock(a)
|
||||
return
|
||||
if cmd == "get":
|
||||
ok, body, _ = a.call("get", rest[0])
|
||||
if not ok:
|
||||
sys.exit(body.decode())
|
||||
if len(rest) > 1:
|
||||
with open(rest[1], "wb") as fh:
|
||||
fh.write(body)
|
||||
else:
|
||||
sys.stdout.buffer.write(body)
|
||||
tmp = None
|
||||
try:
|
||||
if len(rest) > 1:
|
||||
destination = os.path.abspath(rest[1])
|
||||
fd, tmp = tempfile.mkstemp(prefix=".ipod-get-", dir=os.path.dirname(destination))
|
||||
with os.fdopen(fd, "wb") as fh:
|
||||
ok, body, _ = a.call("get", rest[0], sink=fh)
|
||||
fh.flush()
|
||||
os.fsync(fh.fileno())
|
||||
if ok:
|
||||
os.replace(tmp, destination)
|
||||
tmp = None
|
||||
else:
|
||||
ok, body, _ = a.call("get", rest[0], sink=sys.stdout.buffer)
|
||||
if not ok:
|
||||
sys.exit(body.decode())
|
||||
finally:
|
||||
if tmp is not None:
|
||||
os.unlink(tmp)
|
||||
return
|
||||
if cmd == "put":
|
||||
with open(rest[0], "rb") as fh:
|
||||
data = fh.read()
|
||||
mode = rest[2] if len(rest) > 2 else ("755" if os.access(rest[0], os.X_OK) else "644")
|
||||
ok, body, _ = a.call("put", rest[1], mode, len(data), data=data)
|
||||
with open(rest[0], "rb") as fh:
|
||||
ok, body, _ = a.call("put", rest[1], mode, os.fstat(fh.fileno()).st_size, data=fh)
|
||||
elif cmd == "push-app":
|
||||
push_app(a, rest[0])
|
||||
return
|
||||
@@ -207,4 +238,7 @@ def main():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
try:
|
||||
main()
|
||||
except (OSError, ValueError, ConnectionError) as e:
|
||||
sys.exit(f"Erreur de transfert ou de protocole : {e}")
|
||||
@@ -20,7 +20,7 @@ Usage : ipod-emu start [-f] [-W] [-w FW] démarre (headless, VNC $VNC, QMP $RU
|
||||
-W attend que SpringBoard soit prêt à recevoir des touches
|
||||
-w FW firmware à utiliser (défaut : $FW ; dispo : $(ls "$EMU_HOME/images" 2>/dev/null | tr '\n' ' '))
|
||||
ipod-emu wait [SECONDES] attend SpringBoard (défaut 300 s) — n'envoyez PAS de touch avant :
|
||||
le contrôleur tactile plante s'il est touché pendant son boot
|
||||
le pilote tactile doit avoir terminé son démarrage
|
||||
ipod-emu stop | status | log | console
|
||||
Pilotage (écran, touch, boutons) : ipod-qmp --help
|
||||
USAGE
|
||||
|
||||
Executable
+147
@@ -0,0 +1,147 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Teste une application dans une NAND indépendante et conserve les diagnostics."""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import plistlib
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
import zipfile
|
||||
|
||||
|
||||
def bundle_id(package):
|
||||
if package.is_dir() and package.suffix == '.app':
|
||||
with (package / 'Info.plist').open('rb') as f:
|
||||
return plistlib.load(f)['CFBundleIdentifier']
|
||||
if package.suffix == '.ipa':
|
||||
with zipfile.ZipFile(package) as z:
|
||||
plists = [n for n in z.namelist() if n.startswith('Payload/') and
|
||||
n.endswith('.app/Info.plist') and n.count('/') == 2]
|
||||
if len(plists) != 1:
|
||||
raise ValueError('IPA ambiguë : préciser --bundle-id')
|
||||
return plistlib.loads(z.read(plists[0]))['CFBundleIdentifier']
|
||||
raise ValueError('Préciser --bundle-id pour un paquet .deb ou un overlay')
|
||||
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser(description=__doc__)
|
||||
p.add_argument('package', type=Path, help='.app, .ipa, .deb ou overlay')
|
||||
p.add_argument('--bundle-id')
|
||||
p.add_argument('--base', default=os.environ.get('IPOD_TEST_BASE', '5H11a-agent-v12'))
|
||||
p.add_argument('--output', type=Path, help='nouveau dossier de résultats (doit être absent)')
|
||||
p.add_argument('--settle', type=float, default=3, help='attente après apparition au premier plan')
|
||||
args = p.parse_args()
|
||||
package = args.package.resolve()
|
||||
tools = Path(__file__).resolve().parent
|
||||
default_home = tools.parent if (tools.parent / 'images').is_dir() else Path.home() / 'ipod-emu'
|
||||
home = Path(os.environ.get('IPOD_EMU_HOME', default_home)).resolve()
|
||||
try:
|
||||
if not package.exists():
|
||||
raise ValueError('Application introuvable')
|
||||
identifier = args.bundle_id or bundle_id(package)
|
||||
if not isinstance(identifier, str) or not identifier or any(c in identifier for c in '\t\r\n'):
|
||||
raise ValueError('Bundle-id invalide')
|
||||
if Path(args.base).name != args.base or args.base in ('.', '..'):
|
||||
raise ValueError('Nom de firmware invalide')
|
||||
if args.settle < 0:
|
||||
raise ValueError('--settle doit être positif')
|
||||
base = home / 'images' / args.base
|
||||
if not (base / 'rootfs.img').is_file():
|
||||
raise ValueError(f'Image de base introuvable : {base}')
|
||||
# Les ports sont partagés par les sessions : ne jamais arrêter une VM tierce.
|
||||
vnc = os.environ.get('IPOD_VNC', '127.0.0.1:0')
|
||||
host, display = vnc.rsplit(':', 1)
|
||||
for address, port in [(os.environ.get('IPOD_AGENT_HOST', '127.0.0.1'),
|
||||
int(os.environ.get('IPOD_AGENT_PORT', '7788'))),
|
||||
(host or '127.0.0.1', 5900 + int(display))]:
|
||||
try:
|
||||
connection = socket.create_connection((address, port), timeout=1)
|
||||
except ConnectionRefusedError:
|
||||
continue
|
||||
else:
|
||||
connection.close()
|
||||
raise ValueError(f'Port {port} occupé : arrêter votre session avant ce test')
|
||||
if args.output:
|
||||
result = args.output.resolve()
|
||||
result.mkdir(parents=True, exist_ok=False)
|
||||
else:
|
||||
parent = home / 'work' / 'tests'
|
||||
parent.mkdir(parents=True, exist_ok=True)
|
||||
result = Path(tempfile.mkdtemp(prefix=time.strftime('%Y%m%d-%H%M%S-'), dir=parent))
|
||||
except (OSError, ValueError, KeyError, plistlib.InvalidFileException, zipfile.BadZipFile) as e:
|
||||
p.exit(1, f'{e}\n')
|
||||
|
||||
print(f'Diagnostics : {result}', flush=True)
|
||||
session = result / 'session'
|
||||
(session / 'images').mkdir(parents=True)
|
||||
(session / 'images' / args.base).symlink_to(base, target_is_directory=True)
|
||||
for dependency in ('xpwn', 'gen-v1', 'netd'):
|
||||
(session / dependency).symlink_to(home / dependency, target_is_directory=True)
|
||||
env = dict(os.environ, IPOD_EMU_HOME=str(session),
|
||||
IPOD_QEMU=os.environ.get('IPOD_QEMU', str(home.parent / 'qemu-ios/build/qemu-system-arm')))
|
||||
# Un socket de session explicite ne doit pas viser la VM de l'utilisateur.
|
||||
env.pop('IPOD_QMP', None)
|
||||
image_name = 'test-app' if args.base != 'test-app' else 'test-app-result'
|
||||
started = False
|
||||
report = {'package': str(package), 'bundle_id': identifier, 'base': args.base, 'ok': False}
|
||||
|
||||
with (result / 'workflow.log').open('w') as workflow:
|
||||
def run(tool, *argv, capture=False, timeout=360):
|
||||
r = subprocess.run([str(tools / tool), *map(str, argv)], env=env,
|
||||
stdout=subprocess.PIPE if capture else workflow,
|
||||
stderr=workflow, text=True, timeout=timeout)
|
||||
if capture:
|
||||
workflow.write(r.stdout)
|
||||
workflow.flush()
|
||||
if r.returncode:
|
||||
raise RuntimeError(f'{tool} {argv[0] if argv else ""} : code {r.returncode}')
|
||||
return r.stdout if capture else None
|
||||
|
||||
try:
|
||||
run('ipod-mkimage', '-b', args.base, '-o', image_name, package, timeout=900)
|
||||
started = True # nettoie également un démarrage partiel
|
||||
run('ipod-emu', 'start', '-w', image_name, '-W')
|
||||
run('ipod-agent', 'launch', identifier, '--log', '/private/var/mobile/ipod-test-app.log')
|
||||
deadline = time.monotonic() + 30
|
||||
while run('ipod-agent', 'front', capture=True, timeout=20).strip() != identifier:
|
||||
if time.monotonic() >= deadline:
|
||||
raise RuntimeError('Application absente du premier plan après 30 s')
|
||||
time.sleep(1)
|
||||
time.sleep(args.settle)
|
||||
front = run('ipod-agent', 'front', capture=True, timeout=20).strip()
|
||||
report['front'] = front
|
||||
if front != identifier:
|
||||
raise RuntimeError('Application quittée pendant la période de stabilisation')
|
||||
report['ok'] = True
|
||||
except (OSError, RuntimeError, subprocess.TimeoutExpired) as e:
|
||||
report['error'] = str(e)
|
||||
finally:
|
||||
if started:
|
||||
for tool, argv in [('ipod-agent', ['get', '/private/var/mobile/ipod-test-app.log', result / 'app.log']),
|
||||
('ipod-qmp', ['screenshot', result / 'screen.png'])]:
|
||||
try:
|
||||
run(tool, *argv, timeout=30)
|
||||
except (OSError, RuntimeError, subprocess.TimeoutExpired) as e:
|
||||
report.setdefault('diagnostic_errors', []).append(str(e))
|
||||
try:
|
||||
run('ipod-agent', 'sync', timeout=15)
|
||||
except (OSError, RuntimeError, subprocess.TimeoutExpired):
|
||||
pass
|
||||
try:
|
||||
run('ipod-emu', 'stop', timeout=30)
|
||||
except (OSError, RuntimeError, subprocess.TimeoutExpired) as e:
|
||||
report['ok'] = False
|
||||
report['stop_error'] = str(e)
|
||||
if report.get('diagnostic_errors'):
|
||||
report['ok'] = False
|
||||
(result / 'result.json').write_text(json.dumps(report, indent=2, ensure_ascii=False) + '\n')
|
||||
print('Test réussi' if report['ok'] else 'Test échoué : consulter workflow.log et result.json')
|
||||
return 0 if report['ok'] else 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compile et exécute les régressions tactiles 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-multitouch.c')
|
||||
commands = json.loads((build / 'compile_commands.json').read_text())
|
||||
c = next(c for c in commands if c['file'].endswith('ipod_touch_multitouch.c'))
|
||||
old_root = str(Path(c['directory']).parent)
|
||||
args = shlex.split(c['command'].replace(old_root, str(root)))
|
||||
obj = str(build / 'test-ipod-multitouch.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_multitouch.c.o',
|
||||
'libqemu-arm-softmmu.fa.p/hw_arm_ipod_touch_gpio.c.o',
|
||||
'libqemu-arm-softmmu.fa.p/hw_arm_ipod_touch_sysic.c.o',
|
||||
'libqemu-arm-softmmu.fa.p/hw_arm_ipod_touch_spi.c.o')]
|
||||
out = str(build / 'test-ipod-multitouch')
|
||||
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,200 @@
|
||||
/* Régressions du protocole tactile : commandes invalides et trames concurrentes. */
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/main-loop.h"
|
||||
#include "sysemu/cpus.h"
|
||||
#include "hw/arm/ipod_touch_multitouch.c"
|
||||
#include "hw/arm/ipod_touch_gpio.c"
|
||||
#include "hw/arm/ipod_touch_sysic.c"
|
||||
#include "hw/arm/ipod_touch_spi.c"
|
||||
|
||||
static IPodTouchMultitouchState *new_sensor(void)
|
||||
{
|
||||
IPodTouchMultitouchState *s = IPOD_TOUCH_MULTITOUCH(
|
||||
object_new(TYPE_IPOD_TOUCH_MULTITOUCH));
|
||||
ipod_touch_multitouch_realize(SSI_PERIPHERAL(s), &error_abort);
|
||||
return s;
|
||||
}
|
||||
|
||||
static void destroy_sensor(IPodTouchMultitouchState *s)
|
||||
{
|
||||
finish_transfer(s);
|
||||
timer_free(s->touch_timer);
|
||||
timer_free(s->touch_end_timer);
|
||||
g_free(s->next_frame);
|
||||
g_free(s->read_frame);
|
||||
object_unref(OBJECT(s));
|
||||
}
|
||||
|
||||
static uint8_t transfer(IPodTouchMultitouchState *s, uint8_t byte)
|
||||
{
|
||||
return ipod_touch_multitouch_transfer(SSI_PERIPHERAL(s), byte);
|
||||
}
|
||||
|
||||
static void test_unknown(void)
|
||||
{
|
||||
IPodTouchMultitouchState *s = new_sensor();
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
g_assert_cmpuint(transfer(s, 0xab), ==, 0xff);
|
||||
g_assert_cmpuint(s->cur_cmd, ==, 0);
|
||||
g_assert_null(s->in_buffer);
|
||||
}
|
||||
uint8_t out[16];
|
||||
out[0] = transfer(s, MT_CMD_GET_INTERFACE_VERSION);
|
||||
for (int i = 1; i < 16; i++) out[i] = transfer(s, 0);
|
||||
g_assert_cmpuint(out[2], ==, MT_INTERFACE_VERSION);
|
||||
destroy_sensor(s);
|
||||
}
|
||||
|
||||
static void test_snapshot(void)
|
||||
{
|
||||
IPodTouchMultitouchState *s = new_sensor();
|
||||
MTFrame expected = *s->next_frame;
|
||||
uint8_t out[sizeof(MTFrame)];
|
||||
out[0] = transfer(s, MT_CMD_FRAME_READ);
|
||||
for (int i = 1; i < 16; i++) out[i] = transfer(s, 0);
|
||||
g_free(s->next_frame);
|
||||
s->next_frame = get_frame(s, MT_EVENT_TOUCH_START, .4, .6,
|
||||
100, 660, 580, 150);
|
||||
g_assert_cmpuint(s->cur_cmd, ==, 0);
|
||||
out[16] = transfer(s, 0xeb);
|
||||
out[17] = transfer(s, 0);
|
||||
out[18] = transfer(s, 1);
|
||||
for (size_t i = 19; i < sizeof out; i++) out[i] = transfer(s, 0);
|
||||
expected.frame_packet.unused1 = expected.frame_length.length1;
|
||||
expected.frame_packet.checksum_pad = -(expected.frame_packet.cmd +
|
||||
expected.frame_packet.unused1 + expected.frame_packet.length1 +
|
||||
expected.frame_packet.length2);
|
||||
g_assert_cmpmem(out, sizeof out, &expected, sizeof expected);
|
||||
g_assert_cmpuint(s->next_frame->finger_data.event, ==, MT_EVENT_TOUCH_START);
|
||||
destroy_sensor(s);
|
||||
}
|
||||
|
||||
static void test_reports(void)
|
||||
{
|
||||
IPodTouchMultitouchState *s = new_sensor();
|
||||
uint8_t out[16];
|
||||
for (int cmd = MT_CMD_GET_REPORT_INFO; cmd <= MT_CMD_SHORT_CONTROL_READ; cmd += 3) {
|
||||
out[0] = transfer(s, cmd);
|
||||
out[1] = transfer(s, 0xff);
|
||||
for (int i = 2; i < 16; i++) out[i] = transfer(s, 0);
|
||||
g_assert_cmpuint(out[2], ==, 1);
|
||||
unsigned sum = 0;
|
||||
for (int i = 0; i < 14; i++) sum += out[i];
|
||||
g_assert_cmpuint(lduw_le_p(out + 14), ==, sum);
|
||||
}
|
||||
transfer(s, MT_CMD_SHORT_CONTROL_READ);
|
||||
transfer(s, MT_REPORT_SENSOR_DIMENSIONS);
|
||||
for (int i = 2; i < 16; i++) out[i] = transfer(s, 0);
|
||||
g_assert_cmpuint(ldl_le_p(out + 3), ==, 5000);
|
||||
g_assert_cmpuint(ldl_le_p(out + 7), ==, 7500);
|
||||
destroy_sensor(s);
|
||||
}
|
||||
|
||||
static void test_spi_abort(void)
|
||||
{
|
||||
IPodTouchSPIState *spi = IPOD_TOUCH_SPI(object_new(TYPE_IPOD_TOUCH_SPI));
|
||||
DEVICE(spi)->id = g_strdup("spi-test");
|
||||
set_spi_base(4);
|
||||
ipod_touch_spi_realize(DEVICE(spi), &error_abort);
|
||||
ipod_touch_spi_write(spi, R_TXCNT, 2, 4);
|
||||
ipod_touch_spi_write(spi, R_RXCNT, 2, 4);
|
||||
ipod_touch_spi_write(spi, R_TXDATA, MT_CMD_FRAME_READ, 4);
|
||||
ipod_touch_spi_write(spi, R_TXDATA, 2, 4);
|
||||
ipod_touch_spi_write(spi, R_CTRL, R_CTRL_RUN, 4);
|
||||
g_assert_cmpuint(spi->mt->in_buffer_ind, ==, 2);
|
||||
ipod_touch_spi_write(spi, R_CTRL, R_CTRL_TX_RESET | R_CTRL_RX_RESET, 4);
|
||||
g_assert_cmpuint(spi->mt->cur_cmd, ==, 0);
|
||||
g_assert_null(spi->mt->in_buffer);
|
||||
uint8_t response[16];
|
||||
response[0] = transfer(spi->mt, MT_CMD_GET_INTERFACE_VERSION);
|
||||
for (int i = 1; i < 16; i++) response[i] = transfer(spi->mt, 0);
|
||||
g_assert_cmpuint(response[2], ==, MT_INTERFACE_VERSION);
|
||||
timer_free(spi->mt->touch_timer);
|
||||
timer_free(spi->mt->touch_end_timer);
|
||||
g_free(spi->mt->next_frame);
|
||||
fifo8_destroy(&spi->tx_fifo);
|
||||
fifo8_destroy(&spi->rx_fifo);
|
||||
object_unref(OBJECT(spi));
|
||||
}
|
||||
|
||||
static void test_hbpp(void)
|
||||
{
|
||||
IPodTouchMultitouchState *s = new_sensor();
|
||||
uint8_t bad[10] = {MT_CMD_HBPP_DATA_PACKET, 0, 0, 4};
|
||||
for (int i = 0; i < 10; i++) transfer(s, bad[i]);
|
||||
g_assert_cmpuint(s->cur_cmd, ==, 0);
|
||||
uint8_t good[10] = {MT_CMD_HBPP_DATA_PACKET, 0, 0, 4, 0, 0, 0, 0, 0, 4};
|
||||
for (int i = 0; i < 10; i++) transfer(s, good[i]);
|
||||
for (int i = 1; i < 21; i++) transfer(s, 0);
|
||||
g_assert_cmpuint(s->cur_cmd, ==, 0);
|
||||
destroy_sensor(s);
|
||||
}
|
||||
|
||||
static void test_velocity(void)
|
||||
{
|
||||
IPodTouchMultitouchState *s = new_sensor();
|
||||
MTFrame *f = get_frame(s, MT_EVENT_TOUCH_MOVED, .4, .6, 1, 1, 1, 1);
|
||||
g_free(f);
|
||||
f = get_frame(s, MT_EVENT_TOUCH_MOVED, .4, .7, 1, 1, 1, 1);
|
||||
g_assert_cmpint(f->finger_data.velX, ==, 0);
|
||||
g_assert_cmpint(f->finger_data.velY, >, 0);
|
||||
g_free(f);
|
||||
f = get_frame(s, MT_EVENT_TOUCH_MOVED, .4, .7, 1, 1, 1, 1);
|
||||
g_assert_cmpint(f->finger_data.velX, ==, 0);
|
||||
g_assert_cmpint(f->finger_data.velY, ==, 0);
|
||||
g_free(f);
|
||||
destroy_sensor(s);
|
||||
}
|
||||
|
||||
static void test_buttons(void)
|
||||
{
|
||||
IPodTouchGPIOState *s = IPOD_TOUCH_GPIO(object_new(TYPE_IPOD_TOUCH_GPIO));
|
||||
g_assert_cmpuint(GPIO2PAD(GPIO_BUTTON_HOME), <, NUM_GPIO_PADS);
|
||||
gpio_set_on(s->gpio_state, GPIO_BUTTON_HOME);
|
||||
gpio_set_on(s->gpio_state, GPIO_BUTTON_POWER);
|
||||
g_assert_true(gpio_is_on(s->gpio_state, GPIO_BUTTON_HOME));
|
||||
g_assert_true(gpio_is_on(s->gpio_state, GPIO_BUTTON_POWER));
|
||||
gpio_set_off(s->gpio_state, GPIO_BUTTON_HOME);
|
||||
g_assert_true(gpio_is_off(s->gpio_state, GPIO_BUTTON_HOME));
|
||||
g_assert_true(gpio_is_on(s->gpio_state, GPIO_BUTTON_POWER));
|
||||
object_unref(OBJECT(s));
|
||||
}
|
||||
|
||||
static void test_gpio_ranges(void)
|
||||
{
|
||||
IPodTouchSYSICState *s = IPOD_TOUCH_SYSIC(object_new(TYPE_IPOD_TOUCH_SYSIC));
|
||||
uint32_t arrays[4][GPIO_NUMINTGROUPS];
|
||||
s->gpio_int_level[0] = 0x1234;
|
||||
memcpy(arrays[0], s->gpio_int_level, sizeof arrays[0]);
|
||||
memcpy(arrays[1], s->gpio_int_status, sizeof arrays[1]);
|
||||
memcpy(arrays[2], s->gpio_int_enabled, sizeof arrays[2]);
|
||||
memcpy(arrays[3], s->gpio_int_type, sizeof arrays[3]);
|
||||
/* Les quatre trous entre groupes ne doivent pas accéder au tableau suivant. */
|
||||
for (int addr = GPIO_INTLEVEL; addr <= GPIO_INTTYPE; addr += 0x20) {
|
||||
ipod_touch_sysic_write(s, addr + GPIO_NUMINTGROUPS * 4, 0xffffffff, 4);
|
||||
g_assert_cmpuint(ipod_touch_sysic_read(s, addr + GPIO_NUMINTGROUPS * 4, 4), ==, 0);
|
||||
}
|
||||
g_assert_cmpmem(arrays[0], sizeof arrays[0], s->gpio_int_level, sizeof arrays[0]);
|
||||
g_assert_cmpmem(arrays[1], sizeof arrays[1], s->gpio_int_status, sizeof arrays[1]);
|
||||
g_assert_cmpmem(arrays[2], sizeof arrays[2], s->gpio_int_enabled, sizeof arrays[2]);
|
||||
g_assert_cmpmem(arrays[3], sizeof arrays[3], s->gpio_int_type, sizeof arrays[3]);
|
||||
object_unref(OBJECT(s));
|
||||
}
|
||||
|
||||
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-multitouch/unknown", test_unknown);
|
||||
g_test_add_func("/ipod-multitouch/snapshot", test_snapshot);
|
||||
g_test_add_func("/ipod-multitouch/hbpp", test_hbpp);
|
||||
g_test_add_func("/ipod-multitouch/spi-abort", test_spi_abort);
|
||||
g_test_add_func("/ipod-multitouch/reports", test_reports);
|
||||
g_test_add_func("/ipod-multitouch/velocity", test_velocity);
|
||||
g_test_add_func("/ipod-input/buttons", test_buttons);
|
||||
g_test_add_func("/ipod-input/gpio-ranges", test_gpio_ranges);
|
||||
return g_test_run();
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Teste les transferts réels de l'agent sur socket local, sans firmware Apple."""
|
||||
import importlib.util
|
||||
from importlib.machinery import SourceFileLoader
|
||||
import io
|
||||
import os
|
||||
from pathlib import Path
|
||||
import socket
|
||||
import subprocess
|
||||
import tempfile
|
||||
import threading
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
loader = SourceFileLoader('ipod_agent', str(ROOT / 'bin/ipod-agent'))
|
||||
spec = importlib.util.spec_from_loader(loader.name, loader)
|
||||
client = importlib.util.module_from_spec(spec)
|
||||
loader.exec_module(client)
|
||||
|
||||
|
||||
class Transfers(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.work = tempfile.TemporaryDirectory()
|
||||
cls.binary = Path(cls.work.name) / 'transfer-agent'
|
||||
source = (ROOT / 'agent/emuagent.c').read_text()
|
||||
# Compile les fonctions de production, sans dépendances privées d'iOS.
|
||||
functions = source[source.index('static int send_all('):source.index('/* tampon dynamique */')]
|
||||
functions += source[source.index('static int read_full('):source.index('static void cmd_ls(')]
|
||||
harness = '''#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
static void mkdirs(const char *path) { (void)path; }
|
||||
''' + functions + '''
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 4) return 2;
|
||||
int fd = atoi(argv[1]);
|
||||
if (!strcmp(argv[2], "get")) cmd_get(fd, argv+3, argc-3);
|
||||
else cmd_put(fd, argv+3, argc-3);
|
||||
char ignored[256]; read(fd, ignored, sizeof ignored);
|
||||
close(fd); return 0;
|
||||
}
|
||||
'''
|
||||
c = Path(cls.work.name) / 'transfer-agent.c'
|
||||
c.write_text(harness)
|
||||
subprocess.run(['cc', '-Wall', '-Wextra', '-Werror', '-fsanitize=undefined',
|
||||
'-o', str(cls.binary), str(c)], check=True)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.work.cleanup()
|
||||
|
||||
def agent(self, command, *args):
|
||||
a, b = socket.socketpair()
|
||||
process = subprocess.Popen([str(self.binary), str(b.fileno()), command, *map(str, args)],
|
||||
pass_fds=(b.fileno(),), env=dict(os.environ, UBSAN_OPTIONS='halt_on_error=1'))
|
||||
b.close()
|
||||
agent = client.Agent.__new__(client.Agent)
|
||||
agent.s, agent.f = a, a.makefile('rb')
|
||||
self.addCleanup(agent.f.close)
|
||||
self.addCleanup(a.close)
|
||||
self.addCleanup(lambda: self.assertEqual(process.wait(timeout=5), 0))
|
||||
return agent
|
||||
|
||||
def test_large_round_trip(self):
|
||||
data = os.urandom(8 * 1024 * 1024 + 37)
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
path = Path(d) / 'file'
|
||||
a = self.agent('put', path, '640', len(data))
|
||||
# Le harness reçoit les arguments hors bande, puis le contenu du protocole.
|
||||
a.s.sendall(data)
|
||||
ok, _, _ = a.call() # ligne vide ignorée par ce harness
|
||||
self.assertTrue(ok)
|
||||
self.assertEqual(path.read_bytes(), data)
|
||||
self.assertEqual(path.stat().st_mode & 0o777, 0o640)
|
||||
a = self.agent('get', path)
|
||||
with (Path(d) / 'download').open('wb') as out:
|
||||
ok, _, _ = a.call(sink=out)
|
||||
self.assertTrue(ok)
|
||||
self.assertEqual((Path(d) / 'download').read_bytes(), data)
|
||||
|
||||
def test_interrupted_put_preserves_existing_file(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
path = Path(d) / 'file'
|
||||
path.write_bytes(b'ancienne version')
|
||||
a = self.agent('put', path, '644', 100000)
|
||||
a.s.sendall(b'court')
|
||||
a.s.shutdown(socket.SHUT_WR)
|
||||
response = a.f.read()
|
||||
self.assertTrue(response.startswith(b'ERR '))
|
||||
self.assertEqual(path.read_bytes(), b'ancienne version')
|
||||
self.assertEqual(list(Path(d).iterdir()), [path])
|
||||
|
||||
def test_empty_file(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
path = Path(d) / 'empty'
|
||||
a = self.agent('put', path, '644', 0)
|
||||
self.assertTrue(a.call()[0])
|
||||
self.assertEqual(path.read_bytes(), b'')
|
||||
self.assertEqual(self.agent('get', path).call()[1], b'')
|
||||
|
||||
def test_download_failure_preserves_destination(self):
|
||||
listener = socket.socket()
|
||||
listener.bind(('127.0.0.1', 0)); listener.listen()
|
||||
def server():
|
||||
connection, _ = listener.accept()
|
||||
connection.recv(1024)
|
||||
connection.sendall(b'OK 100\npartiel')
|
||||
connection.close()
|
||||
thread = threading.Thread(target=server); thread.start()
|
||||
try:
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
destination = Path(d) / 'download'
|
||||
destination.write_bytes(b'original')
|
||||
r = subprocess.run([str(ROOT / 'bin/ipod-agent'), 'get', '/file', str(destination)],
|
||||
env=dict(os.environ, IPOD_AGENT_PORT=str(listener.getsockname()[1]),
|
||||
IPOD_AGENT_HOST='127.0.0.1'), capture_output=True)
|
||||
self.assertEqual(r.returncode, 1)
|
||||
self.assertIn('tronquée', r.stderr.decode())
|
||||
self.assertEqual(destination.read_bytes(), b'original')
|
||||
self.assertEqual(list(Path(d).iterdir()), [destination])
|
||||
finally:
|
||||
thread.join(); listener.close()
|
||||
|
||||
def test_upload_respects_announced_size(self):
|
||||
a, b = socket.socketpair()
|
||||
agent = client.Agent.__new__(client.Agent)
|
||||
agent.s, agent.f = a, a.makefile('rb')
|
||||
received = []
|
||||
def server():
|
||||
f = b.makefile('rb')
|
||||
received.append(f.readline())
|
||||
received.append(f.read(3))
|
||||
b.sendall(b'OK 0\n')
|
||||
f.close(); b.close()
|
||||
thread = threading.Thread(target=server); thread.start()
|
||||
try:
|
||||
data = io.BytesIO(b'abcEXTRA')
|
||||
self.assertTrue(agent.call('put', '/file', '644', 3, data=data)[0])
|
||||
self.assertEqual(data.tell(), 3)
|
||||
self.assertEqual(received, [b'put\t/file\t644\t3\n', b'abc'])
|
||||
finally:
|
||||
agent.f.close(); a.close(); thread.join()
|
||||
|
||||
def test_get_rejects_fifo_without_blocking(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
path = Path(d) / 'fifo'
|
||||
os.mkfifo(path)
|
||||
self.assertFalse(self.agent('get', path).call()[0])
|
||||
|
||||
def test_fragmented_and_truncated_reply(self):
|
||||
for truncated in (False, True):
|
||||
a, b = socket.socketpair()
|
||||
agent = client.Agent.__new__(client.Agent)
|
||||
agent.s, agent.f = a, a.makefile('rb')
|
||||
def server():
|
||||
b.recv(100)
|
||||
for part in (b'OK ', b'10\n', b'123', b'4567', b'890' if not truncated else b''):
|
||||
b.sendall(part)
|
||||
b.close()
|
||||
thread = threading.Thread(target=server)
|
||||
thread.start()
|
||||
try:
|
||||
if truncated:
|
||||
with self.assertRaisesRegex(ConnectionError, 'tronquée'):
|
||||
agent.call('get', '/test')
|
||||
else:
|
||||
self.assertEqual(agent.call('get', '/test')[1], b'1234567890')
|
||||
finally:
|
||||
agent.f.close(); a.close(); thread.join()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -159,8 +159,6 @@ static void ipod_touch_lcd_mouse_event(void *opaque, int x, int y, int z, int bu
|
||||
float fy = 1 - y / pow(2, 15);
|
||||
|
||||
IPodTouchLCDState *lcd = (IPodTouchLCDState *) opaque;
|
||||
lcd->mt->prev_touch_x = lcd->mt->touch_x;
|
||||
lcd->mt->prev_touch_y = lcd->mt->touch_y;
|
||||
lcd->mt->touch_x = fx;
|
||||
lcd->mt->touch_y = fy;
|
||||
|
||||
|
||||
+107
-32
@@ -1,4 +1,6 @@
|
||||
#include "hw/arm/ipod_touch_multitouch.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qemu/bswap.h"
|
||||
|
||||
static void prepare_interface_version_response(IPodTouchMultitouchState *s) {
|
||||
memset(s->out_buffer + 1, 0, 15);
|
||||
@@ -62,7 +64,9 @@ static void prepare_report_info_response(IPodTouchMultitouchState *s, uint8_t re
|
||||
report_length = MT_REPORT_SENSOR_DIMENSIONS_SIZE;
|
||||
}
|
||||
else {
|
||||
hw_error("Unknown report ID 0x%02x\n", report_id);
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "%s: rapport inconnu 0x%02x\n",
|
||||
__func__, report_id);
|
||||
s->out_buffer[2] = 1;
|
||||
}
|
||||
|
||||
s->out_buffer[3] = (report_length & 0xFF);
|
||||
@@ -98,12 +102,13 @@ static void prepare_short_control_response(IPodTouchMultitouchState *s, uint8_t
|
||||
s->out_buffer[3] = MT_SENSOR_REGION_PARAM;
|
||||
}
|
||||
else if(report_id == MT_REPORT_SENSOR_DIMENSIONS) {
|
||||
uint32_t *ob_int32 = (uint32_t *)&s->out_buffer[3];
|
||||
ob_int32[0] = MT_SENSOR_SURFACE_WIDTH;
|
||||
ob_int32[1] = MT_SENSOR_SURFACE_HEIGHT;
|
||||
stl_le_p(&s->out_buffer[3], MT_SENSOR_SURFACE_WIDTH);
|
||||
stl_le_p(&s->out_buffer[7], MT_SENSOR_SURFACE_HEIGHT);
|
||||
}
|
||||
else {
|
||||
hw_error("Unknown report ID 0x%02x\n", report_id);
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "%s: rapport inconnu 0x%02x\n",
|
||||
__func__, report_id);
|
||||
s->out_buffer[2] = 1;
|
||||
}
|
||||
|
||||
// compute and set the checksum
|
||||
@@ -116,6 +121,19 @@ static void prepare_short_control_response(IPodTouchMultitouchState *s, uint8_t
|
||||
s->out_buffer[15] = (checksum >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
static MTFrame *get_frame(IPodTouchMultitouchState *s, uint8_t event,
|
||||
float x, float y, uint16_t radius1, uint16_t radius2,
|
||||
uint16_t radius3, uint16_t contactDensity);
|
||||
|
||||
static void finish_transfer(IPodTouchMultitouchState *s)
|
||||
{
|
||||
g_clear_pointer(&s->in_buffer, g_free);
|
||||
g_clear_pointer(&s->out_buffer, g_free);
|
||||
s->cur_cmd = 0;
|
||||
s->buf_size = s->buf_ind = s->in_buffer_ind = 0;
|
||||
s->in_buffer_capacity = 0;
|
||||
}
|
||||
|
||||
static uint32_t ipod_touch_multitouch_transfer(SSIPeripheral *dev, uint32_t value)
|
||||
{
|
||||
IPodTouchMultitouchState *s = IPOD_TOUCH_MULTITOUCH(dev);
|
||||
@@ -126,10 +144,11 @@ static uint32_t ipod_touch_multitouch_transfer(SSIPeripheral *dev, uint32_t valu
|
||||
//printf("Starting command 0x%02x\n", value);
|
||||
// we're currently not in a command - start a new command
|
||||
s->cur_cmd = value;
|
||||
s->out_buffer = malloc(0x100);
|
||||
s->out_buffer = g_malloc0(0x100);
|
||||
s->out_buffer[0] = value; // the response header
|
||||
s->buf_ind = 0;
|
||||
s->in_buffer = malloc(0x100);
|
||||
s->in_buffer = g_malloc0(0x100);
|
||||
s->in_buffer_capacity = 0x100;
|
||||
s->in_buffer_ind = 0;
|
||||
|
||||
if(value == 0x18) { // filler packet??
|
||||
@@ -193,20 +212,56 @@ static uint32_t ipod_touch_multitouch_transfer(SSIPeripheral *dev, uint32_t valu
|
||||
else if(value == MT_CMD_SHORT_CONTROL_READ) {
|
||||
s->buf_size = 16;
|
||||
}
|
||||
else if(value == MT_CMD_FRAME_READ) {
|
||||
s->buf_size = sizeof(MTFrame);
|
||||
free(s->out_buffer);
|
||||
s->out_buffer = (uint8_t *) s->next_frame;
|
||||
else if(value == MT_CMD_FRAME_READ || value == MT_CMD_FRAME_READ_ALT) {
|
||||
s->buf_size = sizeof(MTFrameLengthPacket);
|
||||
g_free(s->out_buffer);
|
||||
/* La trame doit rester stable entre sa longueur et ses données,
|
||||
même si un événement hôte arrive pendant les lectures SPI. */
|
||||
s->out_buffer = g_memdup2(s->read_frame ? s->read_frame : s->next_frame,
|
||||
sizeof(MTFrame));
|
||||
}
|
||||
else {
|
||||
printf("%s Unknown command 0x%02x!\n", __func__, value);
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "%s: commande inconnue 0x%02x\n",
|
||||
__func__, value);
|
||||
finish_transfer(s);
|
||||
return 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
if (s->buf_ind >= s->buf_size || s->in_buffer_ind >= s->in_buffer_capacity) {
|
||||
finish_transfer(s);
|
||||
return 0xff;
|
||||
}
|
||||
s->in_buffer[s->in_buffer_ind] = value;
|
||||
s->in_buffer_ind++;
|
||||
|
||||
if(s->cur_cmd == MT_CMD_HBPP_DATA_PACKET && s->in_buffer_ind == 10) {
|
||||
if ((s->cur_cmd == MT_CMD_FRAME_READ || s->cur_cmd == MT_CMD_FRAME_READ_ALT) &&
|
||||
s->in_buffer_ind == 3) {
|
||||
/* Le troisième octet distingue longueur (0) et données (1).
|
||||
Chaque requête est une transaction complète : une lecture de
|
||||
longueur abandonnée pendant la veille ne décale pas la suivante. */
|
||||
if (value == 1) {
|
||||
MTFrame *frame = (MTFrame *)s->out_buffer;
|
||||
/* Le deuxième octet est déjà émis avant de connaître le sélecteur.
|
||||
Il est ignoré dans les données, mais participe au checksum. */
|
||||
uint8_t second = s->out_buffer[1];
|
||||
frame->frame_packet.unused1 = second;
|
||||
frame->frame_packet.checksum_pad = -(frame->frame_packet.cmd +
|
||||
second + frame->frame_packet.length1 + frame->frame_packet.length2);
|
||||
memmove(s->out_buffer, &frame->frame_packet,
|
||||
sizeof(MTFrame) - sizeof(MTFrameLengthPacket));
|
||||
s->buf_size = sizeof(MTFrame) - sizeof(MTFrameLengthPacket);
|
||||
g_clear_pointer(&s->read_frame, g_free);
|
||||
} else if (value == 0) {
|
||||
g_free(s->read_frame);
|
||||
s->read_frame = g_memdup2(s->next_frame, sizeof(MTFrame));
|
||||
memcpy(s->out_buffer, s->read_frame, sizeof(MTFrame));
|
||||
} else {
|
||||
finish_transfer(s);
|
||||
return 0xff;
|
||||
}
|
||||
}
|
||||
else if(s->cur_cmd == MT_CMD_HBPP_DATA_PACKET && s->in_buffer_ind == 10) {
|
||||
// verify the header checksum
|
||||
uint32_t checksum = 0;
|
||||
for(int i = 2; i < 8; i++) {
|
||||
@@ -214,17 +269,22 @@ static uint32_t ipod_touch_multitouch_transfer(SSIPeripheral *dev, uint32_t valu
|
||||
}
|
||||
|
||||
if(checksum != (s->in_buffer[8] << 8 | s->in_buffer[9])) {
|
||||
hw_error("HBPP data header checksum doesn't match!");
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "%s: somme de contrôle HBPP invalide\n",
|
||||
__func__);
|
||||
finish_transfer(s);
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
uint32_t data_len = (s->in_buffer[2] << 10) | (s->in_buffer[3] << 2) + 5;
|
||||
// extend the lengths of the in/out buffers
|
||||
free(s->in_buffer);
|
||||
s->in_buffer = malloc(data_len + 0x10);
|
||||
|
||||
free(s->out_buffer);
|
||||
s->out_buffer = malloc(data_len);
|
||||
memset(s->out_buffer, 0, data_len);
|
||||
uint32_t data_len = ((s->in_buffer[2] << 10) | (s->in_buffer[3] << 2)) + 5;
|
||||
if (data_len < s->in_buffer_ind) {
|
||||
finish_transfer(s);
|
||||
return 0xff;
|
||||
}
|
||||
/* Conserver l'en-tête reçu et dimensionner aussi les petites trames. */
|
||||
s->in_buffer_capacity = MAX(data_len + 10, 0x100);
|
||||
s->in_buffer = g_realloc(s->in_buffer, s->in_buffer_capacity);
|
||||
g_free(s->out_buffer);
|
||||
s->out_buffer = g_malloc0(data_len);
|
||||
s->buf_size = data_len;
|
||||
s->buf_ind = 0;
|
||||
}
|
||||
@@ -255,17 +315,14 @@ static uint32_t ipod_touch_multitouch_transfer(SSIPeripheral *dev, uint32_t valu
|
||||
}
|
||||
|
||||
// we're done with the command
|
||||
s->cur_cmd = 0;
|
||||
s->buf_size = 0;
|
||||
//free(s->out_buffer);
|
||||
//free(s->in_buffer);
|
||||
finish_transfer(s);
|
||||
}
|
||||
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
static MTFrame *get_frame(IPodTouchMultitouchState *s, uint8_t event, float x, float y, uint16_t radius1, uint16_t radius2, uint16_t radius3, uint16_t contactDensity) {
|
||||
MTFrame *frame = calloc(sizeof(MTFrame), sizeof(uint8_t *));
|
||||
MTFrame *frame = g_new0(MTFrame, 1);
|
||||
|
||||
uint16_t data_len = sizeof(MTFrameHeader) + sizeof(FingerData) + 2;
|
||||
|
||||
@@ -310,12 +367,17 @@ static MTFrame *get_frame(IPodTouchMultitouchState *s, uint8_t event, float x, f
|
||||
|
||||
// compute the velocity
|
||||
int diff_x = (int)((x - s->prev_touch_x) * MT_INTERNAL_SENSOR_SURFACE_WIDTH);
|
||||
int diff_y = (int)((x - s->prev_touch_y) * MT_INTERNAL_SENSOR_SURFACE_HEIGHT);
|
||||
frame->finger_data.velX = diff_x / (elapsed_ns + 1 - s->last_frame_timestamp) * 1000;
|
||||
frame->finger_data.velY = diff_y / (elapsed_ns + 1 - s->last_frame_timestamp) * 1000;
|
||||
int diff_y = (int)((y - s->prev_touch_y) * MT_INTERNAL_SENSOR_SURFACE_HEIGHT);
|
||||
uint64_t dt = MAX(elapsed_ns - s->last_frame_timestamp, 1);
|
||||
frame->finger_data.velX = CLAMP((int64_t)diff_x * 1000 / (int64_t)dt,
|
||||
INT16_MIN, INT16_MAX);
|
||||
frame->finger_data.velY = CLAMP((int64_t)diff_y * 1000 / (int64_t)dt,
|
||||
INT16_MIN, INT16_MAX);
|
||||
s->prev_touch_x = x;
|
||||
s->prev_touch_y = y;
|
||||
|
||||
frame->finger_data.x = (int)(x * MT_INTERNAL_SENSOR_SURFACE_WIDTH);
|
||||
frame->finger_data.y = (int)(y * MT_INTERNAL_SENSOR_SURFACE_HEIGHT);
|
||||
frame->finger_data.x = (int)(x * MT_INTERNAL_SENSOR_SURFACE_WIDTH) + MT_INTERNAL_SENSOR_ORIGIN_X;
|
||||
frame->finger_data.y = (int)(y * MT_INTERNAL_SENSOR_SURFACE_HEIGHT) + MT_INTERNAL_SENSOR_ORIGIN_Y;
|
||||
frame->finger_data.radius1 = radius1;
|
||||
frame->finger_data.radius2 = radius2;
|
||||
frame->finger_data.radius3 = radius3;
|
||||
@@ -344,6 +406,7 @@ static void ipod_touch_multitouch_inform_frame_ready(IPodTouchMultitouchState *s
|
||||
void ipod_touch_multitouch_on_touch(IPodTouchMultitouchState *s) {
|
||||
s->touch_down = true;
|
||||
|
||||
g_free(s->next_frame);
|
||||
s->next_frame = get_frame(s, MT_EVENT_TOUCH_START, s->touch_x, s->touch_y, 100, 660, 580, 150);
|
||||
ipod_touch_multitouch_inform_frame_ready(s);
|
||||
|
||||
@@ -351,6 +414,7 @@ void ipod_touch_multitouch_on_touch(IPodTouchMultitouchState *s) {
|
||||
}
|
||||
|
||||
void ipod_touch_multitouch_on_release(IPodTouchMultitouchState *s) {
|
||||
g_free(s->next_frame);
|
||||
s->next_frame = get_frame(s, MT_EVENT_TOUCH_ENDED, s->touch_x, s->touch_y, 0, 0, 0, 0);
|
||||
s->touch_down = false;
|
||||
ipod_touch_multitouch_inform_frame_ready(s);
|
||||
@@ -363,6 +427,7 @@ static void touch_timer_tick(void *opaque)
|
||||
{
|
||||
IPodTouchMultitouchState *s = (IPodTouchMultitouchState *)opaque;
|
||||
|
||||
g_free(s->next_frame);
|
||||
s->next_frame = get_frame(s, MT_EVENT_TOUCH_MOVED, s->touch_x, s->touch_y, 100, 660, 580, 150);
|
||||
ipod_touch_multitouch_inform_frame_ready(s);
|
||||
|
||||
@@ -375,14 +440,22 @@ static void touch_timer_tick(void *opaque)
|
||||
static void touch_end_timer_tick(void *opaque)
|
||||
{
|
||||
IPodTouchMultitouchState *s = (IPodTouchMultitouchState *)opaque;
|
||||
g_free(s->next_frame);
|
||||
s->next_frame = get_frame(s, MT_EVENT_TOUCH_FULL_END, s->touch_x, s->touch_y, 0, 0, 0, 0);
|
||||
s->touch_down = false;
|
||||
ipod_touch_multitouch_inform_frame_ready(s);
|
||||
}
|
||||
|
||||
static void multitouch_cs(void *opaque, int pin, int level)
|
||||
{
|
||||
IPodTouchMultitouchState *s = opaque;
|
||||
if (level) finish_transfer(s);
|
||||
}
|
||||
|
||||
static void ipod_touch_multitouch_realize(SSIPeripheral *d, Error **errp)
|
||||
{
|
||||
IPodTouchMultitouchState *s = IPOD_TOUCH_MULTITOUCH(d);
|
||||
qdev_init_gpio_in_named(DEVICE(d), multitouch_cs, SSI_GPIO_CS, 1);
|
||||
memset(s->hbpp_atn_ack_response, 0, 2);
|
||||
s->touch_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, touch_timer_tick, s);
|
||||
s->touch_end_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, touch_end_timer_tick, s);
|
||||
@@ -390,6 +463,8 @@ static void ipod_touch_multitouch_realize(SSIPeripheral *d, Error **errp)
|
||||
s->prev_touch_x = 0;
|
||||
s->prev_touch_y = 0;
|
||||
s->last_frame_timestamp = 0;
|
||||
/* Une lecture sans toucher doit renvoyer une trame de fin valide. */
|
||||
s->next_frame = get_frame(s, MT_EVENT_TOUCH_FULL_END, 0, 0, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
static void ipod_touch_multitouch_class_init(ObjectClass *klass, void *data)
|
||||
|
||||
+10
-2
@@ -64,8 +64,11 @@ static void apple_spi_update_cs(IPodTouchSPIState *s)
|
||||
BusState *b = BUS(s->spi);
|
||||
BusChild *kid = QTAILQ_FIRST(&b->children);
|
||||
if (kid) {
|
||||
// TODO GPIO not properly setup yet
|
||||
//qemu_set_irq(qdev_get_gpio_in_named(kid->child, SSI_GPIO_CS, 0), (REG(s, R_PIN) & R_PIN_CS) != 0);
|
||||
if (object_dynamic_cast(OBJECT(kid->child), TYPE_IPOD_TOUCH_MULTITOUCH)) {
|
||||
qemu_set_irq(qdev_get_gpio_in_named(kid->child, SSI_GPIO_CS, 0),
|
||||
(REG(s, R_PIN) & R_PIN_CS) != 0);
|
||||
}
|
||||
/* La sélection de la NOR conserve son comportement historique. */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,6 +200,11 @@ static void ipod_touch_spi_write(void *opaque, hwaddr addr, uint64_t data, unsig
|
||||
case R_CTRL:
|
||||
if (r & R_CTRL_TX_RESET) {
|
||||
fifo8_reset(&s->tx_fifo);
|
||||
/* Le pilote interrompt aussi les commandes au retour de veille.
|
||||
Ne pas réutiliser les octets d'une transaction abandonnée. */
|
||||
if (s->mt) {
|
||||
qemu_set_irq(qdev_get_gpio_in_named(DEVICE(s->mt), SSI_GPIO_CS, 0), 1);
|
||||
}
|
||||
}
|
||||
if (r & R_CTRL_RX_RESET) {
|
||||
fifo8_reset(&s->rx_fifo);
|
||||
|
||||
@@ -15,22 +15,22 @@ static uint64_t ipod_touch_sysic_read(void *opaque, hwaddr addr, unsigned size)
|
||||
case 0x7a:
|
||||
case 0x7c:
|
||||
return 1;
|
||||
case GPIO_INTLEVEL ... (GPIO_INTLEVEL + GPIO_NUMINTGROUPS * 4):
|
||||
case GPIO_INTLEVEL ... (GPIO_INTLEVEL + (GPIO_NUMINTGROUPS - 1) * 4):
|
||||
{
|
||||
uint8_t group = (addr - GPIO_INTLEVEL) / 4;
|
||||
return s->gpio_int_level[group];
|
||||
}
|
||||
case GPIO_INTSTAT ... (GPIO_INTSTAT + GPIO_NUMINTGROUPS * 4):
|
||||
case GPIO_INTSTAT ... (GPIO_INTSTAT + (GPIO_NUMINTGROUPS - 1) * 4):
|
||||
{
|
||||
uint8_t group = (addr - GPIO_INTSTAT) / 4;
|
||||
return s->gpio_int_status[group];
|
||||
}
|
||||
case GPIO_INTEN ... (GPIO_INTEN + GPIO_NUMINTGROUPS * 4):
|
||||
case GPIO_INTEN ... (GPIO_INTEN + (GPIO_NUMINTGROUPS - 1) * 4):
|
||||
{
|
||||
uint8_t group = (addr - GPIO_INTEN) / 4;
|
||||
return s->gpio_int_enabled[group];
|
||||
}
|
||||
case GPIO_INTTYPE ... (GPIO_INTTYPE + GPIO_NUMINTGROUPS * 4):
|
||||
case GPIO_INTTYPE ... (GPIO_INTTYPE + (GPIO_NUMINTGROUPS - 1) * 4):
|
||||
{
|
||||
uint8_t group = (addr - GPIO_INTTYPE) / 4;
|
||||
return s->gpio_int_type[group];
|
||||
@@ -58,11 +58,11 @@ static void ipod_touch_sysic_write(void *opaque, hwaddr addr, uint64_t val, unsi
|
||||
case POWER_OFFCTRL:
|
||||
s->power_state = val;
|
||||
break;
|
||||
case GPIO_INTLEVEL ... (GPIO_INTLEVEL + GPIO_NUMINTGROUPS * 4):
|
||||
case GPIO_INTLEVEL ... (GPIO_INTLEVEL + (GPIO_NUMINTGROUPS - 1) * 4):
|
||||
{
|
||||
break;
|
||||
}
|
||||
case GPIO_INTSTAT ... (GPIO_INTSTAT + GPIO_NUMINTGROUPS * 4):
|
||||
case GPIO_INTSTAT ... (GPIO_INTSTAT + (GPIO_NUMINTGROUPS - 1) * 4):
|
||||
{
|
||||
uint8_t group = (addr - GPIO_INTSTAT) / 4;
|
||||
|
||||
@@ -73,13 +73,13 @@ static void ipod_touch_sysic_write(void *opaque, hwaddr addr, uint64_t val, unsi
|
||||
|
||||
break;
|
||||
}
|
||||
case GPIO_INTEN ... (GPIO_INTEN + GPIO_NUMINTGROUPS * 4):
|
||||
case GPIO_INTEN ... (GPIO_INTEN + (GPIO_NUMINTGROUPS - 1) * 4):
|
||||
{
|
||||
uint8_t group = (addr - GPIO_INTEN) / 4;
|
||||
s->gpio_int_enabled[group] = val;
|
||||
break;
|
||||
}
|
||||
case GPIO_INTTYPE ... (GPIO_INTTYPE + GPIO_NUMINTGROUPS * 4):
|
||||
case GPIO_INTTYPE ... (GPIO_INTTYPE + (GPIO_NUMINTGROUPS - 1) * 4):
|
||||
{
|
||||
uint8_t group = (addr - GPIO_INTTYPE) / 4;
|
||||
s->gpio_int_type[group] = val;
|
||||
|
||||
@@ -21,7 +21,7 @@ OBJECT_DECLARE_SIMPLE_TYPE(IPodTouchGPIOState, IPOD_TOUCH_GPIO)
|
||||
#define GPIO_BUTTON_VOLUP_IRQ 0x78
|
||||
#define GPIO_BUTTON_VOLDOWN_IRQ 0x62
|
||||
|
||||
#define NUM_GPIO_PADS 0xC
|
||||
#define NUM_GPIO_PADS 0xD /* Ports 0 à C inclus, dont Home et Power. */
|
||||
#define NUM_GPIO_PINS 0x20
|
||||
|
||||
#define GPIO2PIN(gpio) ((gpio) & 7)
|
||||
|
||||
@@ -25,9 +25,13 @@ OBJECT_DECLARE_SIMPLE_TYPE(IPodTouchMultitouchState, IPOD_TOUCH_MULTITOUCH)
|
||||
#define MT_SENSOR_SURFACE_WIDTH 5000
|
||||
#define MT_SENSOR_SURFACE_HEIGHT 7500
|
||||
|
||||
// internal surface width/height
|
||||
#define MT_INTERNAL_SENSOR_SURFACE_WIDTH (9000 - MT_SENSOR_SURFACE_WIDTH) * 84 / 73
|
||||
#define MT_INTERNAL_SENSOR_SURFACE_HEIGHT (13850 - MT_SENSOR_SURFACE_HEIGHT) * 84 / 73
|
||||
/* Calibration mesurée sur les pixels 320x480 de 5H11a avec TouchProbe.
|
||||
* La transformation du pilote nécessite une étendue et une origine propres
|
||||
* au capteur ; les dimensions annoncées dans les rapports restent 5000x7500. */
|
||||
#define MT_INTERNAL_SENSOR_SURFACE_WIDTH 4718
|
||||
#define MT_INTERNAL_SENSOR_SURFACE_HEIGHT 7328
|
||||
#define MT_INTERNAL_SENSOR_ORIGIN_X (-68)
|
||||
#define MT_INTERNAL_SENSOR_ORIGIN_Y (-188)
|
||||
|
||||
// report IDs
|
||||
#define MT_REPORT_UNKNOWN1 0x70
|
||||
@@ -52,6 +56,7 @@ OBJECT_DECLARE_SIMPLE_TYPE(IPodTouchMultitouchState, IPOD_TOUCH_MULTITOUCH)
|
||||
#define MT_CMD_SHORT_CONTROL_WRITE 0xE4
|
||||
#define MT_CMD_SHORT_CONTROL_READ 0xE6
|
||||
#define MT_CMD_FRAME_READ 0xEA
|
||||
#define MT_CMD_FRAME_READ_ALT 0xEB
|
||||
|
||||
// frame types
|
||||
#define MT_FRAME_TYPE_PATH 0x44
|
||||
@@ -152,6 +157,8 @@ typedef struct IPodTouchMultitouchState {
|
||||
float prev_touch_x;
|
||||
float prev_touch_y;
|
||||
uint64_t last_frame_timestamp;
|
||||
uint32_t in_buffer_capacity;
|
||||
MTFrame *read_frame;
|
||||
} IPodTouchMultitouchState;
|
||||
|
||||
void ipod_touch_multitouch_on_touch(IPodTouchMultitouchState *s);
|
||||
|
||||
Reference in new issue
Block a user