Émulation du baseband Calypso — qemu-calypso

Faire tourner le vrai firmware GSM d'un téléphone (Motorola C1xx / Compal E88) sans aucun matériel, en émulant son baseband TI Calypso dans QEMU. Code : github.com/bbaranoff/qemu-calypso — Quick Start : QUICK_START.md

Danger

Le point d’avancement (campagne du 2026-08-24)

  • Mode shunt_legit (défaut) : la pile GSM complète fonctionne de bout enbout — synchro, camp, Location Update, authentification COMP128v1, chiffrementA5/1, SMS dans les deux sens et appel voix avec audio entre deuxabonnés. Le firmware ARM osmocom-bb non patché tourne réellement ; c’estgr-gsm, côté hôte, qui démodule.
  • Mode native (le vrai DSP) : avancée majeure — le Frequency Burst (FB) estdésormais acquis par le vrai DSP mask-ROM (437 transitions mesurées, écritespar la ROM elle-même à PC=0x79e4, sans aucune injection). Le mur actuel estle décodage du SCH : la tâche tourne mais son décodeur sort une valeurconstante (0xf8d8) indépendante de l’entrée — donc il ne décode pas encore.

Ce que fait ce projet, en une phrase

QEMU-Calypso émule le SoC GSM TI Calypso en faisant tourner deux cœurs en même temps, exactement comme le silicium réel :

  • un ARM qui exécute le firmware osmocom-bb d'origine, non patché (la Layer 1 temps réel) ;
  • un DSP TMS320C54x qui exécute la vraie mask-ROM DSP de TI.

Les deux communiquent par la mailbox API RAM à l'adresse 0xFFD00000, comme sur la puce. À la connaissance de l'auteur, aucun autre émulateur ne fait tourner à la fois le firmware constructeur intact et le DSP de couche 1.

flowchart LR NET["Réseau GSM de test<br/>osmo-bts / fake_trx"] -->|"downlink I/Q"| QEMU subgraph QEMU["QEMU-Calypso (machine calypso)"] direction TB ARM["ARM7<br/>firmware osmocom-bb<br/>layer1.highram.elf"] DSP["DSP C54x<br/>mask-ROM TI"] ARM <-->|"mailbox API RAM 0xFFD00000"| DSP end QEMU -->|"L1CTL /tmp/osmocom_l2"| MOBILE["mobile (layer23)"]
Tip

Le firmware est interchangeable. N’importe quel layer1.highram.elf construitdepuis n’importe quel commit d’osmocom-bb boote sans adaptation ni patch. C’est letest qui distingue « j’émule la plateforme » de « j’ai fait marcher ce binaire ».


1. Les deux familles de modes

C'est le concept central. Un mode répond à une seule question : qui fait quoi ?

profil FB / SB SI ce qu'il prouve
shunt_legit hôte gr-gsm le défaut — la pile de bout en bout, jusqu'à la voix. Rien sur le DSP.
native_twl hôte / TWL DSP le DSP traite-t-il le SI ? On lui donne la synchro pour poser la question
native DSP DSP la vérité sur l'acquisition. FB acquis, SCH pas encore décodé
native_helped DSP, entrée reroutée DSP ⚠️ sous béquille — toute mesure ici est à citer comme telle
empty rien rien banc gate par gate
flowchart TD Q{"Qui acquiert<br/>la synchro FB/SB ?"} Q -->|"l'hôte (gr-gsm)"| S["famille SHUNT<br/>→ prouve la pile complète<br/>ne prouve rien sur le DSP"] Q -->|"le vrai DSP"| N["famille NATIVE<br/>→ prouve l'acquisition<br/>ne campe pas encore"]
Warning

Frontière à ne jamais franchir sans changer de nom : dès que les SystemInformation viennent de gr-gsm, on court-circuite le DSP — c’est shunt_legit,pas un mode natif. La seule source de vérité sur ce qu’un run a réellement obtenuest le manifeste imprimé par le modèle (qemu-manifest.log), jamais laligne de commande.


2. Piloter le lab

Tout vit dans le conteneur osmo-operator-1. Chaque mode a sa porte d'entrée — elles ne sont pas interchangeables, et c'est pourquoi les journaux atterrissent à deux endroits différents.

# shunt_legit (le défaut) — la pile complète, jusqu'à l'appel voix
cd /opt/GSM/osmo_egprs && CALYPSO_BRIDGE=pont ENCRYPTION='a5 1' ./start-direct.sh

# native — le vrai DSP à la manœuvre
cd /opt/GSM/qemu-src && CALYPSO_MODE=native ./run.sh

Arrêt : la même porte avec --stop. Si une relance coince (port pris, pile à moitié morte), faire le --stop puis tuer les python restants, qui survivent au teardown et retiennent les FIFO I/Q :

pkill -f python3 ; pkill -f python ; sleep 2 ; pgrep -af 'python|qemu-system-arm'
Warning

Le lancement n’est pas cosmétique. shunt_legit démarré par run.sh sansCALYPSO_BRIDGE=pont ne monte pas : la BTS n’atteint pas le transceiver(send() failed on TRXD … Connection refused ×1268 mesuré), le réseau ne répondjamais, et la Location Update expire (T3211 ×74). Par la bonne porte : 3erreurs BTS, LU acceptée.

Où sont les journaux — le piège n°1

lanceur LOG_DIR
run.sh (native) /tmp/calypso/logs
start-direct.sh (shunt_legit) /tmp/osmo-nitb/logs

Analyser le mauvais répertoire donne des compteurs périmés qui ressemblent à des pannes.

L'instrument à dégainer en premier

tools/rapport-run.sh          # état complet du run courant, lecture seule

Sa règle de conception : un compteur nul n'est jamais une absence prouvée. Il affiche pour chaque grandeur une ligne témoin et annonce un zéro comme « motif jamais vu ».


3. La discipline anti-illusion

Ce qui distingue ce projet, c'est une méthode de rétro-ingénierie vérifiable — chaque affirmation est présentée pour être cassée :

  • MESURE (relevé, avec sa commande) / HYPOTHÈSE (déduite du code) / INVALIDE (affirmation retirée, à ne pas réintroduire).
  • Les sondes de diagnostic sont inertes par défaut et en lecture seule.
  • Un système de « sas » (CALYPSO_FIX_*) met en quarantaine les correctifs non encore validés.
  • Toute sonde muette doit s'accompagner d'un contrôle armé sur un site dont on sait qu'il s'exécute — sans lui, « la sonde n'a rien vu » et « le code ne tourne pas » sont indiscernables.
Tip

Vérifier soi-même que le FB natif vient bien du DSP (aucune injection posée) :compter les écritures faites par la mask-ROM elle-même, et confirmer qu’ellessortent toutes du même PC.

grep -c 'FBDET-WR .*0x0000 -> 0x0001' /tmp/calypso/logs/qemu.log   # ~437
grep    'FBDET-WR .*0x0000 -> 0x0001' /tmp/calypso/logs/qemu.log | \
  grep -oE 'PC=0x[0-9a-f]+' | sort -u                              # PC=0x79e4

4. Pour aller plus loin