Application des découvertes faites pendant la session de debug FBSB. Chaque patch est classé:
RETD handler (F273)Fichier:
hw/arm/calypso/calypso_c54x.c
Bug: deux implémentations byte-identiques de
0xF273 (RETD): - Lignes 1483-1487 (dans le bloc F2xx
interne) - Lignes 1701-1705 (dans le bloc F2xx du dispatch hi8=0xF2)
La 2e (1701-1705) est dead code — la 1ère gagne toujours.
Action: supprimer le bloc 1701-1705 (en gardant un commentaire de référence).
Risque: nul. Code mort.
CALLD handler (F274)Fichier:
hw/arm/calypso/calypso_c54x.c
Bug: deux implémentations byte-identiques de
0xF274 (CALLD): - Lignes 1473-1481 (premier bloc) - Lignes
1691-1699 (deuxième bloc)
La 2e est dead code.
Action: supprimer le bloc 1691-1699.
Risque: nul.
RPTBD handler (F272)Fichier:
hw/arm/calypso/calypso_c54x.c
Bug: cinq (!) implémentations de F272 (RPTBD): - Lignes 1461-1471 (premier) - Lignes 1680-1689 (deuxième dans hi8=0xF2) - Lignes 2258-2280 (RPTBD générique) - Lignes 3408-3420 - Lignes 3469-3477
D’après l’agent Explore, les 4 dernières sont des doublons potentiellement divergents. À vérifier au cas par cas avant suppression.
Action: lire les 5 implémentations et garder la première (ligne 1461) si toutes équivalentes. Sinon, fusionner.
Risque: faible si toutes équivalentes, moyen si divergence (à investiguer).
Fichier: hw/arm/calypso/calypso_c54x.c
ligne ~3633
Bug: prev_sp = 0xEEFF initial dans le
tracer SP-OOR alors que le boot SP DSP = 0x5AC8. Le premier
événement est faussé (semble OOR alors que c’est juste
l’initialisation).
Action: initialiser prev_sp = s->sp
au reset, ou marquer le premier event comme “INIT” et l’ignorer.
Risque: nul. Tracer uniquement (DBG_SP).
Symptôme observé: 6/10 runs baseline ont un SP
runaway de ~13000 events, tous à
prev_PC=0xb906 prev_op=0xf074. SP descend de 0x5AC8 vers
0x0000, atteint 0x08F8 = adresse NDB de d_fb_det, écrase
d_fb_det avec des return addresses (0xb908 = PC+2). ARM lit
alors d_fb_det != 0 et croit avoir trouvé un FB.
Hypothèses (toutes non confirmées): 1.
Self-loop firmware: prog[0xb907] == 0xb906
— CALL à soi-même. 2. CALL à l’intérieur d’un RPTB:
F074 à PC=REA d’un bloc RPTB. Le wrap check (ligne 3842
if (s->pc == s->rea + 1)) ne déclenche jamais car le
CALL met PC=target. Le bloc s’exécute en boucle infinie via le wrap
RPTB, chaque tour pousse une nouvelle return address sans jamais RET. 3.
Bug prog_fetch sur PROM0 0xB000+: 0xb907 mal lu (mais
le fix XPC est OK).
Pourquoi pas appliqué: aucune des 3 hypothèses n’est vérifiée. Patcher calypso_c54x.c sans savoir lequel = casser plus que ça ne répare. Le user a explicitement interdit les hacks C.
Action requise pour confirmer: - a. Dumper PROM[0xb906..0xb910] depuis le firmware bin → confirme/infirme #1 - b. Logger l’état RPTB (rsa, rea, rptb_active, brc) au moment où PC=0xb906 → confirme/infirme #2 - c. Vérifier que prog_fetch(0xb907) renvoie la valeur attendue → confirme/infirme #3
Symptôme: avec hack r8=1, run 014
expose un 2e site de SP runaway:
prev_PC=0xc12a prev_op=0xf5e3. Cluster 0xc121-0xc12a est
une fonction DSP réellement appelée par le path FB-found (que la
baseline ne traverse jamais).
Identification opcode F5E3: F5E3 = 1111 0101 1110 0011. Pas immédiatement visible dans tic54x-opc.c. Probable C548 extension Texas Instruments non documentée.
Pourquoi pas appliqué: opcode pas identifié, semantics inconnues. Avant de le coder, il faut savoir ce qu’il fait.
Action requise: - a. Grep tic54x-opc.c pour
0xF5E0 mask 0xFFF0 ou 0xF5C0 mask
0xFFC0 - b. Si non trouvé → consulter SPRU172C section
“C548 enhancements” - c. Désasser
arm-elf-objdump -d -m tic54x sur la zone PROM si
possible
Une fois S-1 et S-2 confirmés et corrigés, vérifier: - Les régimes de variance disparaissent (toutes runs en régime “B” sain) - d_fb_det reste à 0 sauf si vraiment détecté (pas de garbage) - Mobile reçoit FBSB_CONF (avec result=255 puisque DSP pas encore capable de vraies corrélations FB) # Calypso QEMU — Patches & TODO (2026-04-07)
Session de debug FBSB/FB-detect côté DSP/ARM. 27 runs exécutés, agents Explore sur calypso C + osmocom firmware. Findings consolidés ci-dessous.
3 régimes observés sur 10 runs baseline
(run_all.sh):
| Régime | Runs (sur 10) | Caractéristique |
|---|---|---|
| A. Mort précoce | 004 | FBSB=0, l1ctl=13. L1 scheduler jamais armé. max_fn ~52k |
| B. SNR=0 propre | 005, 007, 009 | FBSB 64-70, snr=0, 0 SP runaway, 0 d_fb_det WR |
| C. Garbage NDB | 006, 008, 010-013 | FBSB 32-44, snr=46 const, ~13k SP_events, 700+ d_fb_det WR |
Corrélation parfaite SP_runaway ↔︎ d_fb_det WR: 6/10 runs ont les deux, 4/10 n’ont ni l’un ni l’autre.
Trace observée :
[DBG_SP] SP-STEP: 2c00->2bff prev_PC=0xb906 prev_op=0xf074
[DBG_SP] SP-STEP: 2800->27ff prev_PC=0xb906 prev_op=0xf074
...
[DBG_CORRUPT] DSP WR d_fb_det = 0xb908 PC=0xb906 SP=0x08f8 *** GARBAGE ***
CALL pmad (binutils
tic54x-opc.c:279, mask 0xFFFF)CALLD pmad (delayed call, 2
delay slots)Cascade : 1. CALL ou CALLD à PC=0xb906 push une return address sur la
stack 2. SP descend à chaque appel 3. Le callee ne RET jamais (ou
re-CALL en boucle) 4. Quand SP atteint 0x08F8 (= adresse DSP de
d_fb_det dans la NDB), chaque PUSH écrase
d_fb_det avec une return address ≠ 0 5. ARM lit
d_fb_det != 0 → croit avoir trouvé un FB 6. Lit snr/freq
depuis NDB pourrie → FBSB_CONF result=255
Implémentation actuelle (calypso_c54x.c) : F074 et F274 sont codés correctement en isolation (push return, set PC=target, return 0 = “PC déjà set, ne pas avancer”). Lignes 1311-1317 et 1473-1481.
Hypothèses pour le mécanisme exact (non confirmées)
: - a. Self-loop firmware :
prog[0xb907] == 0xb906 (call à soi-même). → vérifier en
dumpant PROM[0xb906..0xb910] - b. Bug RPTB+CALL : CALL
à PC=REA d’un bloc RPTB. Le wrap check (ligne 3842 :
if (s->pc == s->rea + 1)) ne déclenche jamais car le
CALL met PC=target (≠ REA+1) - c. Bug prog_fetch :
0xb907 lu mal (mais XPC fix vérifié OK)
Run 014 (avec hack r8=1) expose un deuxième site de SP runaway :
[DBG_SP] SP-STEP: 5000->4fff prev_PC=0xc12a prev_op=0xf5e3
0xc121-0xc12a = fonction DSP réellement
appelée par hack only (la baseline bail à
if (d_fb_det==0) avant de l’atteindre)Source :
/opt/GSM/osmocom-bb/src/target/firmware/layer1/prim_fbsb.c
l1s_fbdet_resp (ligne 399)
├─ ligne 404: if (!d_fb_det) → bail (BP#1 patche r8=1 ici)
├─ read_fb_result → lit snr/freq_diff depuis NDB (a_sync_demod[D_SNR/D_ANGLE])
├─ ligne 449: if (abs(freq_diff) < freq_err_thresh1 && snr > FB0_SNR_THRESH=0)
│ ├─ PASS → schedule FB1
│ └─ FAIL → afc_retries++; si retries >= 30 → attempt=13, l1s_compl_sched
├─ ligne 488: if (abs(freq_diff) < freq_err_thresh2 && snr > FB1_SNR_THRESH=0)
│ ├─ PASS → schedule SB
│ └─ FAIL → reschedule FB1
└─ l1a_fb_compl (ligne 523, async via l1s_compl_sched(L1_COMPL_FB))
├─ if (attempt >= 13) → l1ctl_fbsb_resp(255) ← FAIL
└─ else → l1ctl_fbsb_resp(0) ← SUCCESS
0x826424 <l1s_fbdet_resp>:
826430: ldrh r8, [r3, #72] ; r8 = ndb->d_fb_det
826434: lsl r2, r2, #16 ← BP#1 (après ldrh)
826438: cmp r8, #0
826444: bne 0x826490 (FB found path)
826448: cmp r7, #11 (attempt vs 11)
...
; FB found, après plein de read NDB:
8265a8: ldrsh r2, [r4, #16] ; r2 = freq_diff (signed)
8265ac: ldrh r3, [r4, #36] ; r3 = freq_err_thresh1
8265b0: cmp r2, #0
8265b4: rsblt r2, r2, #0 ; r2 = abs(freq_diff)
8265b8: cmp r2, r3
8265bc: bge 0x8265d8 ← BP#2 (skip bge → force PASS)
8265c0: ldrh r3, [r4, #14] ; r3 = fb.snr
8265c4: cmp r3, #0 ← BP#3 (force r3=1)
8265c8: movne r0, #1
8265d0: movne r2, r0
8265d4: bne 0x826704 (success → schedule FB1)
hack_gdb.py — 4 breakpoints (mode hardcoded par
défaut)| BP | Adresse | Action | But |
|---|---|---|---|
| #1 | 0x826434 |
r8 := 1 |
Force d_fb_det == 1 (l1s_fbdet_resp+0x10) |
| #2 | 0x8265bc |
PC := PC+4 |
Skip bge, force \|fd\| < thresh |
| #3 | 0x8265c4 |
r3 := 1 |
Force snr > 0 |
| #4 | 0x826754 |
r3 := 0 |
Force attempt < 13 (l1a_fb_compl+0x8) |
Modes de résolution (par ordre de priorité): 1.
HACK_BP_FBDET=0xADDR / HACK_BP_FBCOMPL=0xADDR
env override 2. HACK_DISCOVER=1 → resolve via objdump
symbol table de layer1.highram.elf 3. Hardcoded defaults
(mode normal)
run_all_debug.sh / run_all.sh/root/logs/{qemu,bridge,inject,mobile,hack}_NNN.log/tmp/qemu.log → run courantQEMU_LOG_MAX (50 MB par défaut) +
RUN_TIMEOUT (45s par défaut)HACK_MODE au démarrage| Métrique | Baseline médian | Cible |
|---|---|---|
| SP_events / run | 13000 (régime C) | 0 |
| d_fb_det WR garbage | 760 (régime C) | 0 |
| FBSB result=0 | 0 / 27 runs | ≥ 1 / 5 runs (avec hack) |
| DATA_IND | 0 | ≥ 1 (rêve à long terme) |
Le code C est sacré. Aucun fix C sans: 1. Cause-racine identifiée et reproductible 2. Référence datasheet (SPRU172C) ou binutils (tic54x-opc.c) 3. Test sur ≥ 5 runs avant et après
Tout hack doit vivre exclusivement dans
hack.py/hack_gdb.py. # C54x DSP Emulator - All Bugs and Fixes
addr16 >= 0xE000 && addr16 < 0xFF80addr16 >= 0xE000
(include vectors)*(lk),
*+ARn(lk), *ARn(lk), *+ARn(lk)%
used AR as address without offset, and the offset word was executed as
next instruction/* handled by caller */ for modes C-F but no caller
actually handled themreturn consumed changed to
return consumed + s->lk_used.old_arp_val = s->ar[arp(s)] BEFORE resolve_smem modified
AR, then tested old values->ar[arp(s)] AFTER
resolve_smempc == rea + 1)
runs before exec and redirects PC during active RPTrpt_active
is trues->pc += 1; return 0 to return 1 caused RPT
to re-execute itself infinitelycontinue (skips pc += consumed). With return 1, PC never
advances past RPT instruction.s->pc += 1; return 0 - RPT must advance PC itself since
the main loop’s RPT handler will prevent further PC advance.(*acc >> 39) & 1 and
(*acc >> 38) & 1-S (paused).
vm_start() called when client connects.cli_rx_enabled = false on
reconnect, firmware won’t re-send RESET_INDcli_rx_enabled immediatelymax(-32768, min(32767, ...))if (hi8 == 0xF0 || hi8 == 0xF1) block catches
everythings->rsa = (uint16_t)(s->pc + 4) in both F272 handlers.
THIS was the root cause of init MMR corruption and DSP never reaching
IDLE.sub = (op >> 4) & 0xF mapping sub=0→B,
sub=6→CALL, sub=8→CALLD. But per tic54x-opc.c, F400-F4BF are arithmetic
instructions (ADD, SUB, LD, SFTA, SFTL, EXP, NORM, MPYA, SAT, NEG, ABS,
CMPL, RND, MAX, MIN, SUBC, ROR, ROL, MACA, SQUR).take=true, causing wrong branches. cond=0x05 was always
taken instead of AEQ (A==0).if (addr16 >= 0xE000) return;Emulate a Calypso GSM phone in QEMU: ARM7 CPU running OsmocomBB layer1 firmware + TMS320C54x DSP running the Calypso DSP ROM. The emulated phone connects to osmo-bts-trx (GSM BTS) and registers on the network.
osmo-bsc
|
osmo-bts-trx
|
sercomm_udp.py (bridge)
UDP 5700-5702
|
QEMU (calypso machine)
+-------------------+
| ARM7 (layer1.elf) |
| | |
| API RAM (shared) |
| | |
| C54x DSP (ROM) |
+-------------------+
|
L1CTL socket
/tmp/osmocom_l2_1
|
mobile / ccch_scan
trying (image: osmo-qemu)/root/qemu//opt/GSM/calypso_dsp.txt/opt/GSM/firmware/board/compal_e88/layer1.highram.elf/root/qemu/run.sh/home/nirvana/qemu-src/build.sh (auto-sync,
timestamp, md5)/home/nirvana/qemu-src/hw/arm/calypso//home/nirvana/qemu-calypso/1111 11N1 CCCCCCCC (1 word)1111 0Z01 1101 0100 (Z=0 normal, Z=1
delayed)1111 10Z1 1 + 7-bit pmad(22-16) + 16-bit
pmad(15-0)How to apply: Fix XC (0xFD/0xFF), FRET (2 pops), FCALL (2 pushes) in calypso_c54x.c
0111 1Z0I AAAA AAAA + 16-bit pmad (2
words)1111 10Z0 CCCCCCCC + 16-bit pmad (2
words)1111 10Z1 CCCCCCCC + 16-bit pmad (2 words)TI Calypso (TWL3014/DBB) — GSM baseband processor - ARM7TDMI CPU (ARM926EJ-S in QEMU, close enough) - TMS320C54x DSP core - Shared API RAM between ARM and DSP - TPU (Time Processing Unit) for radio timing - ABB (Analog Baseband) for RF - BSP (Baseband Serial Port) for DSP↔︎ABB data - DMA controller
| Address | Size | Peripheral |
|---|---|---|
| 0x00000000 | 2MB | Internal ROM |
| 0x00800000 | 256KB | Internal SRAM |
| 0xFFFE0000 | TPU registers | |
| 0xFFFF0000 | INTH (interrupt controller) | |
| 0xFFFF1000 | TPU control | |
| 0xFFFC0000 | UART modem | |
| 0xFFFC8000 | UART IrDA | |
| 0xFFD00000 | 64KB | DSP API RAM (shared with C54x) |
l1_sync() called every TDMA frame (TPU FRAME IRQ)tdma_sched_execute)dsp_end_scenario()dsp_end_scenario(): writes d_dsp_page = B_GSM_TASK |
w_page, toggles w_page| Offset | Size | Field |
|---|---|---|
| 0 | 1 | TN (timeslot number, 3 bits) |
| 1-4 | 4 | FN (frame number, big-endian) |
| 5 | 1 | RSSI |
| 6-7 | 2 | TOA (big-endian) |
| 8+ | 148 | Soft bits (0=strong 1, 127=uncertain, 255=strong 0) |
| Offset | Size | Field |
|---|---|---|
| 0 | 1 | TN |
| 1-4 | 4 | FN (big-endian) |
| 5 | 1 | PWR |
| 6+ | 148 | Hard bits (0/1) |
Massive opcode audit of calypso_c54x.c against the authoritative
tic54x-opc.c from binutils-2.21.1 (found at
/var/lib/docker/overlay2/.../gnuarm/src/binutils-2.21.1/opcodes/tic54x-opc.c).
17 bugs fixed. DSP now boots correctly: IDLE reached, IMR configured, dispatch loop active.
{ "add", 0xF000, 0xFCF0 }Per tic54x-opc.c: RSBX=0xF4B0 mask 0xFDF0, SSBX=0xF5B0 mask 0xFDF0. This covers 4 opcode ranges:
| Fix | Opcode | Was | Now | Encoding |
|---|---|---|---|---|
| 2 | F4Bx | NOP catch-all | RSBX ST0 | bit9=0, bit8=0 |
| 3 | F5Bx | RPT #0xBx (176+!) | SSBX ST0 | bit9=0, bit8=1 |
| 4 | F6Bx | MVDD Xmem,Ymem | RSBX ST1 | bit9=1, bit8=0 |
| 5 | F7Bx | LD #k8 → AR7 | SSBX ST1 | bit9=1, bit8=1 |
| Fix | Opcode | Was | Now | tic54x-opc.c |
|---|---|---|---|---|
| 6 | FC00 | LD #k<<16, B | RET (return) | line 391 |
| 7 | FE00 | LD #k, B | RETD (return delayed) | line 392 |
| 8 | F073 | RET (pop stack) | B pmad (branch, 2-word) | line 264 |
| 9 | F074 | RETE (pop+clr INTM) | CALL pmad (call, 2-word) | line 279 |
| 10 | F072 | FRET (far return) | RPTB pmad (block repeat) | line 410 |
| 11 | F070 | RET (catch-all) | RPT #lku (repeat, 2-word) | line 409 |
| 12 | F071 | RET (catch-all) | RPTZ dst,#lku (repeat-zero) | line 412 |
| Fix | Opcode | Was | Now | tic54x-opc.c |
|---|---|---|---|---|
| 14 | F4E4 | IDLE | FRET (far return) | line 306 |
| 15 | F4E1 | NOP (catch-all) | IDLE (the real one) | line 310 |
| 16 | F4E5 | NOP (catch-all) | FRETE (far return from IRQ) | line 308 |
calypso_tint0.h: IFR bit 4 → bit 3,
vector 20 → vector 19Replaced incomplete if-else chain with proper tic54x-opc.c encoding: - CC1=0x40 (accumulator test), CCB=0x08 (B vs A) - EQ=0x05, NEQ=0x04, LT=0x03, LEQ=0x07, GT=0x06, GEQ=0x02 - OV=0x70, NOV=0x60, TC=0x30, NTC=0x20, C=0x0C, NC=0x08
tic54x-opc.c location in container:
/var/lib/docker/overlay2/a242f59.../diff/root/gnuarm/src/binutils-2.21.1/opcodes/tic54x-opc.c
hw/arm/calypso/calypso_c54x.c — md5: 5a474083hw/arm/calypso/calypso_tint0.h — md5: 65a588f7 # TODO —
chemin FBSB QEMU CalypsoÉtat au 2026-04-06 fin de session 3.
Saut massif sur le pipeline FB-det. snr passé de 0 → 30-60 stable, inner loop identifié, buffer FB localisé, plusieurs bugs décodeur majeurs corrigés.
LD Smem, dst manquant entièrement
(0x1000 mask 0xFE00). Le commentaire dans le
code disait “real LD is at 0x1000, NOT in 0x6xxx range” — mais on avait
supprimé le faux 0x6xxx LD sans jamais ajouter le vrai. Toutes les
LD *ARx, A/B (1 mot) tombaient en UNIMPL silencieux, d’où
l’inner loop FB-det qui spinnait sans rien lire. Ajout également de
LD Smem, TS, dst à 0x1400 mask
0xFE00.
resolve_smem mod 12..15 entièrement
faux (très gros impact). Per binutils
tic54x-dis.c sprint_indirect_address, la table correcte est
:
mod=12 → *ARx(lk) (addr = AR + lk, AR
inchangé)mod=13 → *+ARx(lk) (pre-add : AR += lk,
addr = AR)mod=14 → *+ARx(lk)% (pre-add
circulaire)mod=15 → *(lk) absolue,
sans aucun ARx On avait mod=12 = *(lk) absolue et
mod=15 = AR+lk circulaire — tout décalé. Symptôme du bug :
freeze à PC=0xdfc7 (ST #0x3fba, *(0x0003)) où l’ST écrivait
dans AR[arp]+0x3fba au lieu de 0x0003,
atterrissant sur d_dsp_page selon la valeur d’AR — 57293 corruptions
identiques par run avant fix, 0-1 après. Ce fix est probablement
la cause de plein de comportements bizarres antérieurs dans le
DSP au-delà du chemin FB.calypso_bsp_rx_burst circular
append : chaque burst BSP (1184 mots) s’append à un offset wrap
dans [daram_addr .. daram_addr+daram_len) au lieu d’écraser
le même bloc. Modélise la BSP qui remplit un long buffer FB sample sur
de multiples bursts série avant que le corrélateur ne sweep.
Indispensable une fois le buffer FB identifié comme >> 1184
mots.
inject_cfile.py SCALE 16384 → 30000
: amplitude tone proche du clip int16. Vérifié que ce n’est PAS le
facteur limitant (snr identique).
Buffer sample FB localisé à
DARAM[0x021f], longueur ≈ 12000 mots (≈ 6000 IQ
pairs, ≈ 1475 syms ≈ 10 sub-bursts). Tracé via FBLOOP-RD
profiler en logguant les data_read avec PC dans la zone de l’inner loop
et reportant min/max d’AR4 (le pointeur sample). AR4 part de 0x021f et
balaye linéairement vers le haut. Le
DARAM_ADDR=0x62 LEN=128 qu’on utilisait jusqu’ici était
faux d’un facteur 100, c’est pourquoi la FB-det ne triggait pas
même avec un tone parfait : les samples étaient déposés dans un coin du
DARAM que le DSP ne lit pas en mode idle.
⇒ env à utiliser maintenant :
CALYPSO_BSP_DARAM_ADDR=0x021f CALYPSO_BSP_DARAM_LEN=12000
CALYPSO_BSP_BYPASS_BDLENA=1Inner loop corrélateur FB localisé à PROM0
0xa10d..0xa116 :
a10d: f272 a114 RPTBD pmad=0xa114
a10f: 1092 LD *AR2,A ← lecture sample
a110: f495 NOP
a111: f0e2 SFTL B,1,A
a112: 3c87 ?
a113: 8295 ?
a114: f280 (fin RPTB body)
a115: 6e89 a10d BANZD a10d ← outer
AR2 = pointeur DROM coeffs (constant 0xcc7e pendant la phase active), AR3 = counter (0x000b), AR4 = pointeur sample read, AR5 = return addr du caller (0x0a14).
DROM bien chargée et lue : tracer
COEFF-RD confirme que data[0x9000..0xdfff]
contient les vraies valeurs du dump (data[0xc21b]=0x1306,
0xc60d=0xe10f, etc., non-zéro). La table de coeffs FCCH
N’EST PAS le problème. À l’init, la DSP fait un copy-loop de la DROM via
PC=0xfd23, AR2 incrémenté par 0x3f2 — vraie phase d’initialisation,
normale.
| Métrique | Début session | Fin session |
|---|---|---|
FBSB_CONF snr |
0 | 30-60 stable |
| Buffer FB connu | non | oui (0x021f, 12000w) |
| Inner loop tracé | non | oui (a10d..a116) |
LD Smem,dst (0x1000) |
UNIMPL | OK |
resolve_smem mod 12..15 |
shifté/cassé | conforme binutils |
| BSP DMA | single-shot écrase | circular append |
| Freeze PC=0xdfc7 | 57k corruptions/run | 0-1 |
| Coeffs DROM | inconnu | confirmés présents |
Résultat actuel : snr brut côté DSP ≈ 240..480 (= 30..60
<< 3). Seuil d_fb_thr_det_iacq = 0x3333 = 13107. Il
manque un facteur ~30× pour que d_fb_det = 1.
Hypothèses (à creuser dans cet ordre) :
COEFF-RD ne capte que les premières
32 reads pendant la phase init (PC=0xfd23, copy DROM). Il faut
RE-CIBLER pour logguer les reads pendant la phase corrélation active (PC
∈ a10d..a116, AR2 constant 0xcc7e). Vérifier que
data[0xcc7e..0xcc7e+N] n’est pas réellement zéro/garbage à
ce moment-là (peut-être que la DROM est loadée mais qu’une copie DARAM
intermédiaire est mal synchronisée).inject_cfile.py mode tone avec différentes
permutations.d_fb_thr_det_iacq côté
DSP : ajouter un tracer sur l’adresse api_ram correspondante
pour s’assurer que le DSP lit bien 0x3333 (= la valeur uploadée par L1)
et pas 0xFFFF ou 0 (cas où dsp_set_params ne serait pas mirrorée).L1 n’arme BDLENA que ~10 fois par run au lieu des centaines attendues. Probablement même type de mismatch buffer/format pour PM_CONF (qui retourne pm=0). À fixer une fois la chaîne FB validée.
FBLOOP-RD, COEFF-RD, DARAM-RD,
NDB-WR, STACK-WR, FBDET-RD,
FB-CALL etc. — beaucoup de tracers diagnostic encore en
place. À factoriser ou guarder par flag env une fois la FB-det au
vert.
3 tar Docker dans /home/nirvana/ (24G chacun) : -
osmocom-run-sftl-20260406.tar (22:04) — état SFTL milestone
- osmocom-run-mod-fix-20260406-2300.tar (23:04) — milestone
mod fix + circular BSP, snr=30-60 stable, état le plus
testé de la session -
osmocom-run-coeff-rd-20260406-2314.tar (23:18) — latest,
contient en plus le tracer COEFF-RD diagnostic
Sources synced sur les 3 emplacements : -
/home/nirvana/qemu-src (working tree) -
/home/nirvana/qemu-calypso (git tracked) - container
22da3f2aefb9:/opt/GSM/qemu-src
État au 2026-04-06 fin de session 2.
DSP runaway éliminé par 4 fixes décodeur c54x
(commit 057b4e9) :
STLM/POPM/LDMM mask & 0x1F →
& 0x7F (le STLM B,MMR_0x38 wrappait vers SP et écrasait
la stack pointer).0x76xx était décodé comme LDM MMR,dst (1
mot) au lieu de ST #lk, Smem (2 mots). Ce seul bug
expliquait toute la cascade de runaways au PROM1 1f2a0 (ST #idx,*(0xe1)
table).CMPR cond, ARx (0xF4A8 mask
0xFCF8) implémenté pour la famille F6.E8/E9 LD #k8u, A/B corrigés (les
deux étaient mal décodés comme CMPR / CC, le faux CC poussait
PC+2 dans data[SP-1] à chaque appel et
corrompait d_fb_det/d_dsp_page quand SP était
wedged dans NDB). Pas encore committé.
api_ram mirror :
calypso_dsp_write mirror chaque write ARM immédiatement
vers dsp->api_ram[] au même offset mot, modélisant le
SRAM physiquement partagé entre ARM et DSP. Avant ce fix, le DSP ne
voyait JAMAIS les commandes d_task_md que l’ARM postait, et
le firmware ne lançait jamais aucune tâche. Après le fix : 200+
d_task_md events, FB-det loop activement exécutée, NDB writes côté DSP.
Pas encore committé.
CALYPSO_BSP_BYPASS_BDLENA=1 : env
var diagnostic dans calypso_bsp.c qui désactive le gating
IOTA/TPU et laisse passer tous les bursts TN=0. Permet de valider la
chaîne BSP→DARAM→FB-det sans dépendre de l’arming BDLENA par la L1 (qui
actuellement n’arme la fenêtre que ~10 fois par run). Pas encore
committé.
inject_cfile.py +
run_tmp.sh : injection directe d’un cfile
RTL-SDR (ou d’un tone synthétique +67.7 kHz via --tone) au
format TRXDv0 vers UDP 6702. Bypasse osmo-bts-trx et le
relais TRXD du bridge. Pas encore committé.
Profilers DARAM-RD et
NDB-WR dans calypso_c54x.c (dedup par
adresse, capés). Ont permis d’identifier que :
DARAM[0x0062..0x00e1] (128
mots).0x010e..0x059e
(probable table coeffs FCCH ou pondération).data[0x0c31] = 0xfffe à insn ~5M (=
BASE_API_PARAM byte 0).d_task_md = 1 (PM) et d_task_md = 5 (FB) en
alternance sur les deux pages.82e9 / 7700 / 7701 / 1111 avec ~30k+ hits chacun).d_fb_det n’est jamais posé à 1
par le DSP, même avec un tone parfait. FBSB_CONF result=255
(timeout) en boucle.0x6184 à PC=0xe26e (en cours)Symptôme : trace tail montre :
STACK WR [0xEEB3] = 0xe040 PC=0xe26e SP=0xe040
IMR change 0xe040 → 0x0000 PC=0xe260
DSP WR d_dsp_page = 0xe040 PC=0xe26e *** CORRUPT
DSP WR d_fb_det = 0xe040 PC=0xe26e *** GARBAGE
À PC=0xe26e l’opcode PROM est 0x6184
(vérifié : PDROM ligne 0e260, position +0xe). Cet opcode :
- Charge 0xe040 dans SP (SP wedged dans
PROM/IMR area). - Toggle IMR avec la même valeur 0xe040. - Une fois SP
wedged, les pushes suivants atterrissent dans NDB
(d_dsp_page, d_fb_det).
Action : identifier le vrai sens de
0x61xx dans tic54x-opc.c et corriger le
décodeur.
docker exec 22da3f2aefb9 grep -nE "0x61[0-9a-f]{2}" /root/gnuarm/src/binutils-2.21.1/opcodes/tic54x-opc.c
Une fois A fixé, si avec --tone la FB-det ne pose
toujours pas d_fb_det=1, hypothèses :
d_fb_thr_det_iacq (threshold acquisition) et autres
paramètres FB doivent être posés par L1 init. Vérifier qu’ils sont bien
dans le mirror api_ram.UNIMPL et corriger itérativement.Actuellement la L1 ne fait que ~10 BDLENA pulses par run au lieu des
centaines attendues. Cause probable : la L1 reste en mode PM scan parce
que pm=0 (PM_CONF retourne 0), donc elle ne commit jamais
sur ARFCN 514. Une fois la chaîne FB validée avec le bypass diagnostic,
attaquer le bug PM (probablement même genre de mismatch
buffer/format).
bsp.bypass_bdlena une fois que BDLENA est
correctement armée.static int X_log profilers.dsp_ram[0xF8]=1 mentionné dans la version
précédente du TODO doit être vérifié — pas sûr qu’il existe encore après
le mirror api_ram.-lm casséninja -C build qemu-system-arm échoue avec
undefined reference to sqrtf@@GLIBC_2.2.5. Workaround
manuel :
cd /opt/GSM/qemu-src/build
ninja -t commands qemu-system-arm | tail -1 > /tmp/link.sh
sed -i 's|$| -lm|' /tmp/link.sh
bash /tmp/link.sh
/tmp tmpfs 16G se remplitqemu.log peut atteindre 12G avec les
logs PC HIST + DARAM-RD/NDB-WR. Surveiller df -h /tmp.
Killer qemu-system-arm libère le fichier. # Calypso GSM
Baseband Emulator — Project Status
Run real OsmocomBB layer1.highram.elf firmware on emulated TI Calypso (ARM7 + TMS320C54x DSP) in QEMU. Connect to a real BTS via TRX protocol through a bridge. Mobile/ccch_scan sees the BTS and decodes BCCH/CCCH.
BTS (osmo-bts-trx)
↕ UDP (TRXC 5701, TRXD 5702, CLK 5700)
bridge.py
↕ PTY/UART (sercomm DLCI 4 = bursts, DLCI 5 = L1CTL)
QEMU (ARM7 calypso_trx.c + C54x calypso_c54x.c)
↕ Unix socket /tmp/osmocom_l2_1 (L1CTL length-prefixed)
mobile / ccch_scan (OsmocomBB host tools)
| File | Role |
|---|---|
calypso_c54x.c |
TMS320C54x DSP emulator (~1800 lines) |
calypso_c54x.h |
DSP state struct, constants, API |
calypso_trx.c |
Calypso SoC: DSP API RAM, TPU, TDMA, burst RX |
l1ctl_sock.c |
Unix socket L1CTL ↔︎ sercomm bridge |
calypso_uart.c |
UART with sercomm DLCI routing |
calypso_inth.c |
ARM interrupt controller |
calypso_soc.c |
SoC glue, memory map |
calypso_mb.c |
Machine/board definition |
bridge.py |
BTS TRX UDP ↔︎ UART sercomm bridge |
run.sh |
Launches QEMU + bridge + BTS + ccch_scan in tmux |
sync.sh |
Syncs files between host and docker container |
l1ctl_test.py |
Direct L1CTL test script |
/opt/GSM/calypso_dsp.txt (131168 words)| DSP Address | ARM Address | Content |
|---|---|---|
| 0x0000-0x001F | — | MMR (registers) |
| 0x0020-0x007F | — | DARAM (low, scratch) |
| 0x0080-0x07FF | — | DARAM (overlay with prog when OVLY=1) |
| 0x0800-0x27FF | 0xFFD00000-0xFFD03FFF | API RAM (shared ARM↔︎DSP) |
| 0x7000-0xDFFF | — | PROM0 (program ROM) |
| 0x8000-0xFFFF | — | PROM1 mirror (program ROM page 1) |
| 0x9000-0xDFFF | — | DROM (data ROM) |
| 0xE000-0xFFFF | — | PDROM (patch data ROM) |
| ARM Offset | DSP Address | Name |
|---|---|---|
| 0x0000 (W_PAGE_0) | 0x0800 | Write page 0 (MCU→DSP, 20 words) |
| 0x0028 (W_PAGE_1) | 0x0814 | Write page 1 |
| 0x0050 (R_PAGE_0) | 0x0828 | Read page 0 (DSP→MCU, 20 words) |
| 0x0078 (R_PAGE_1) | 0x083C | Read page 1 |
| 0x01A8 (NDB) | 0x08D4 | d_dsp_page (first word of NDB) |
| 0x0862 (PARAM) | 0x0C31 | DSP parameters |
0x0000 = no task0x0002 = B_GSM_TASK | page 00x0003 = B_GSM_TASK | page 1| Offset | Field |
|---|---|
| 0 | d_task_d (downlink task) |
| 1 | d_burst_d |
| 2 | d_task_u (uplink task) |
| 3 | d_burst_u |
| 4 | d_task_md (monitoring/FB/SB task: 5=FB, 6=SB) |
| 5 | d_background |
| 6 | d_debug |
| 7 | d_task_ra |
| 8-19 | results area |
c54x_reset() → PMST=0xFFE0, IPTR=0x1FF, OVLY=0,
PC=0xFF80c54x_run(10M) → DSP executes PROM1 reset code → calls
PROM0 init → 86K insns → IDLE@0xFFFEcalypso_dsp_done() → copies write page to DARAM 0x0586,
wakes DSP0x8000: 12f8 3fc5 f4e3 f4e4 → slot 0: SUB data[0x3FC5], A; SSBX INTM; IDLE
0x8004: 12f8 322a f4e3 f4e4 → slot 1
...8 slots of 4 words each...
0x8020: processing code starts (reads d_dsp_page, dispatches tasks)
s->pc &= 0xFFFFosmo-operator-1 (always running)calypso-qemu:YYYYMMDD-HHMM (snapshots)ninja -C /opt/GSM/qemu-src/buildbash /opt/GSM/qemu-src/run.sh/opt/GSM/calypso_dsp.txt/opt/GSM/firmware/board/compal_e88/layer1.highram.elfdoc/spru172c.pdf — TMS320C54x DSP Reference Set Volume
2: Mnemonic Instruction Setdoc/C54X_INSTRUCTIONS.md — Key instruction encodings
extracted from SPRU172C # Session 2026-04-06 — BSP DMA path + dispatcher
diagnosisSortir du hack dsp_ram[0xF8]=1 (faux
d_fb_det posé par QEMU) et brancher un vrai chemin BSP :
I/Q GMSK depuis le bridge → DMA dans la DARAM DSP → laisser le DSP faire
FB-det lui-même.
Firmware compal_e88 compilé avec DSP=36
(l1_environment.h:9), CHIPSET=12,
ANLG_FAM=2 (Iota), W_A_DSP_IDLE3=1,
AMR=undef.
NDB T_NDB_MCU_DSP (branche DSP == 34..36)
commence par d_dsp_page (offset 0). Comptage des champs
jusqu’à d_fb_det :
0 d_dsp_page 18 d_hole2_ndb[0]
1 d_error_status 19 d_mcsi_select
2 d_spcx_rif 20 d_apcdel1_bis
3 d_tch_mode 21 d_apcdel2_bis
4 d_debug1 22 d_apcdel2
5 d_dsp_test 23 d_vbctrl2
6 d_version_number1 24 d_bulgcal
7 d_version_number2 25 d_afcctladd
8 d_debug_ptr 26 d_vbuctrl
9 d_debug_bk 27 d_vbdctrl
10 d_pll_config 28 d_apcdel1
11 p_debug_buffer 29 d_apcoff
12 d_debug_buffer_sz 30 d_bulioff
13 d_debug_trace_type 31 d_bulqoff
14 d_dsp_state 32 d_dai_onoff
15 d_hole1_ndb[0] 33 d_auxdac
16 d_hole1_ndb[1] 34 d_vbctrl1 (ANLG_FAM=2)
17 d_hole_debug_amr 35 d_bbctrl
36 d_fb_det ←
BASE_API_NDB = 0xFFD001A8 → mot 0x01A8/2 =
dsp_ram[0xD4].
d_fb_det = dsp_ram[0xD4 + 36] = dsp_ram[0xF8].
→ L’offset que calypso_trx.c poke est
correct. L’hypothèse “mauvais offset” était fausse.
Confirmé : calypso_tint0_do_tick() lower puis raise
IRQ_TPU_FRAME (= IRQ4) à chaque tick. Firmware utilise
IRQ_TPU_FRAME pour L1S, IRQ_API (15) est masqué
(irq.c: [IRQ_API] = 0xff).
| Fichier | Rôle |
|---|---|
include/hw/arm/calypso/calypso_bsp.h |
API publique |
hw/arm/calypso/calypso_bsp.c |
DMA vers DARAM, mode discovery |
void calypso_bsp_init(C54xState *dsp);
void calypso_bsp_rx_burst(uint8_t tn, uint32_t fn,
const int16_t *iq, int n_int16);
État interne (bsp.dsp, bsp.daram_addr,
bsp.daram_len, compteurs). Configuration runtime via env
:
| Var | Défaut | Rôle |
|---|---|---|
CALYPSO_BSP_DARAM_ADDR |
0 |
Adresse cible DARAM (mots) |
CALYPSO_BSP_DARAM_LEN |
1184 |
Nombre max de int16 par burst |
addr=0 → mode DISCOVERY : log les
bursts mais n’écrit pas en DARAM.
calypso_trx.c::calypso_trx_init() : appelle
calypso_bsp_init(s->dsp) juste après
c54x_init réussi.sercomm_gate.c::trxd_cb() : remplace l’ancien repackage
8-byte + calypso_trx_rx_burst() par un appel direct
calypso_bsp_rx_burst(tn, fn, (const int16_t *)(buf+6), nint16).meson.build : ajoute calypso_bsp.c.calypso_c54x.c::data_read0x7730 ≤ PC ≤ 0x7990 (zone FB-det dans PROM0 d’après
project_dsp_fb_det), log les 200 premières lectures data —
révélera l’adresse DARAM cible quand le DSP atteindra réellement la
routine FB-det.dsp_ram[0xD6] (NDB word 2).Et dans calypso_trx.c::calypso_dsp_write :
GCC 9.5 / meson sur Debian-like :
ninja -C build qemu-system-arm échoue au link avec
undefined reference to symbol 'sqrtf@@GLIBC_2.2.5' / DSO missing from command line.
La regen meson n’inclut pas -lm dans la commande de link de
la cible.
Workaround : extraire la commande, lui ajouter
-lm à la fin, relancer manuellement :
cd /opt/GSM/qemu-src/build
ninja -t commands qemu-system-arm | tail -1 > /tmp/link.sh
sed -i 's|$| -lm|' /tmp/link.sh
bash /tmp/link.sh
À fixer proprement plus tard (probablement une dépendance softfloat
non propagée à qemu-arm-softmmu).
run.sh ne rentrait pas via docker exec -d
(tmux). Lancement manuel :
qemu-system-arm -M calypso -cpu arm946 -icount shift=auto -serial pty -serial pty -monitor unix:...,server,nowait -kernel layer1.highram.elf > /tmp/qemu.log 2>&1cont via socat sur le monitorpython3 bridge.py /dev/pts/0osmo-bts-trx -c /etc/osmocom/osmo-bts-trx.cfgmobile -c /root/.osmocom/bb/mobile_group1.cfg[BSP] init dsp=0x... daram_addr=0x0000 len=1184 (DISCOVERY mode — no DMA)
[calypso-trx] ARM WR d_spcx_rif = 0x0179 (sz=2 fn=0)
[BSP] rx_burst fn=869 tn=0 n=1184 (target unset, sample[0]=32766 sample[1]=-1)
[BSP] rx_burst fn=869 tn=1 n=1184 (target unset, sample[0]=30267 sample[1]=-12552)
...
d_spcx_rif = 0x0179 : ARM écrit la
valeur attendue d’après dsp.c:429 → cohérent avec un
firmware qui programme bien le BSP.|z| ≈ 32766 confirme le signal
complexe full-scale du modulateur gr-gsm. Bridge → gate → BSP transmet
correctement.[0x7730..0x7990]. Le DSP
boucle dans le dispatcher 0x81a7..0x81d6 (= une seule
fonction handler) à chaque frame.d_task_md jamais positionnéARM WR d_task_md[p0] = 0 (init) fn=0
ARM WR d_task_md[p1] = 0 (init) fn=0
... (rien d'autre, pas d'écritures NZ)
Aucune écriture non-nulle de d_task_md sur les deux
pages, donc le firmware n’envoie jamais
FB_DSP_TASK au DSP, donc le dispatcher ne peut que tourner
sur sa task par défaut. Ce n’est pas un bug de dispatcher
DSP : le DSP fait ce qu’on lui demande (= rien).
d_dsp_page toggle 1/14fn=181 page=0x0000 (B_GSM_TASK pas encore set)
fn=182 page=0x0002 (B_GSM_TASK | w_page=0)
...
fn=196 page=0x0002 ← reste 14 frames
fn=197 page=0x0003 (B_GSM_TASK | w_page=1)
fn=200 page=0x0003
Décodage d_dsp_page (l1_environment.h:249)
: - bit 0 = w_page (devrait toggle chaque frame) - bit 1 =
B_GSM_TASK
Le toggle de w_page ne se produit qu’une fois
sur 14 frames parce que dsp_end_scenario() n’est
appelé que si sched_flags & TDMA_IFLG_DSP (sync.c:275).
Sur les frames sans task DSP planifiée, le page ne bouge pas.
Comportement normal tant que rien n’est dans la queue TDMA.
Le pipeline est cassé à un endroit unique :
mobile L1CTL_FBSB_REQ
→ bridge (PTY)
→ firmware sercomm UART_MODEM
→ l1ctl_rx_fbsb_req() [l23_api.c:230]
→ l1s_reset()
→ l1s_fbsb_req(1, ...) [prim_fbsb.c:538]
→ tdma_schedule_set(1, fb_sched_set, 0)
→ ??? ← ICI
→ l1s_fbdet_cmd() ❌ jamais appelée
→ db_w->d_task_md = FB_DSP_TASK
Mobile reçoit FBSB RESP result=255 (échec après timeout)
sans qu’aucune écriture d_task_md = FB_DSP_TASK n’ait eu
lieu. Soit tdma_schedule_set drop le set, soit le scheduler
tick ne dépile jamais le set, soit la queue est saturée par un set
précédent.
Vérifié : c54x_run() ne consomme pas de
temps virtuel QEMU (boucle hors clock). Donc même si
do_tick brûle 5M instructions DSP par tick, l’ARM garde
tout son budget virtuel. La théorie “famine ARM par DSP” est incorrecte
en virtual time.
Le code calypso_trx.c ligne ~370 calculait
int budget = 5000000 constamment, sans utiliser le flag
dsp_init_done (qui pourtant existe et est correctement
maintenu à true depuis fn=1). Le commentaire prévoyait
“500K post-init”. Patch préparé en local mais non poussé parce que la
cause racine n’est pas la famine.
| Fichier | Statut |
|---|---|
include/hw/arm/calypso/calypso_bsp.h |
nouveau, pushé |
hw/arm/calypso/calypso_bsp.c |
nouveau, pushé |
hw/arm/calypso/meson.build |
+ calypso_bsp.c, pushé |
hw/arm/calypso/calypso_trx.c |
+ bsp_init, instrumentations, pushé |
hw/arm/calypso/sercomm_gate.c |
trxd_cb appelle BSP, pushé |
hw/arm/calypso/calypso_c54x.c |
FBDET / d_spcx_rif tracers, pushé |
hw/arm/calypso/doc/BSP_DMA.md |
rédigé, non pushé (interruption) |
tdma_schedule_set n’aboutit
pas à l1s_fbdet_cmd. Pistes :
prim_fbsb.c / sync.c pour ajouter des
printd au début/sortie de l1s_fbsb_req,
tdma_schedule_set, l1s_fbdet_cmd, et reflasher
layer1.highram.elf.l1s_fbsb_req symbole connu, log.d_task_md = FB_DSP_TASK
posté, vérifier que le dispatcher DSP y répond et entre dans
[0x7730..0x7990]. Le tracer FBDET RD donnera l’adresse
DARAM cible, à mettre dans CALYPSO_BSP_DARAM_ADDR.d_fb_det passe à 1
grâce au vrai code DSP — ce qui validerait la chaîne complète.-lm dans le meson
de qemu-arm-softmmu.project_dsp_fb_det — emplacement supposé du handler FB
en PROM0feedback_no_hack_functions — règle “no DSP
shortcuts”calypso_trx_role — calypso_trx ne fait que
forwarderfeedback_sync_host — sync vers
/home/nirvana/qemu-src/ à faire # Sercomm Gate Architecture
— QEMU CalypsoLe Calypso a deux chemins de données complètement séparés :
Antenne → RF frontend → ABB (Analog Baseband)
→ BSP (Baseband Serial Port, hardware)
→ DSP lit via PORTR PA=0xF430
→ DSP traite (FIR, equalizer, Viterbi)
→ Résultats dans API RAM (DB read page)
→ ARM lit les résultats
Le BSP est un port série hardware (registre à 0xF430 dans l’espace I/O du DSP C54x). Le DSP reçoit un BRINT0 (interrupt vec 21, IMR bit 5) quand un burst complet est disponible. L’ARM ne touche jamais aux bursts radio — c’est 100% hardware BSP → DSP.
Host (mobile/ccch_scan) → UART PTY → sercomm HDLC
→ DLCI 5 (L1A_L23) → firmware ARM (l1a_l23_rx callback)
→ Firmware écrit tâches dans API DB write page
→ d_dsp_page = B_GSM_TASK | page
→ TPU frame IRQ → SINT17 → DSP exécute
L’UART ne transporte que du L1CTL et du debug. Jamais de bursts.
FLAG(0x7E) | DLCI(1) | CTRL(0x03) | DATA(N) | FLAG(0x7E)
Les octets 0x7E, 0x7D et 0x00 sont échappés : - Remplacés par
0x7D suivi de octet XOR 0x20 - Décodage :
quand on reçoit 0x7D, le byte suivant est XOR 0x20
| DLCI | Constante | Callback | Usage |
|---|---|---|---|
| 4 | SC_DLCI_DEBUG | aucun dans layer1 | Debug (non utilisé) |
| 5 | SC_DLCI_L1A_L23 | l1a_l23_rx |
L1CTL — commandes mobile↔︎firmware |
| 9 | SC_DLCI_LOADER | cmd_handler (loader only) |
Chargement firmware |
| 10 | SC_DLCI_CONSOLE | non enregistré dans layer1 | Console texte |
| 128 | SC_DLCI_ECHO | sercomm_sendmsg (loopback) |
Test echo |
WAIT_START ──(0x7E)──→ ADDR ──(byte)──→ CTRL ──(byte)──→ DATA
│
(0x7D)→ ESCAPE ──(byte^0x20)──→ DATA
(0x7E)→ dispatch_rx_msg(dlci, msg) → WAIT_START
sercomm_bind_uart(UART_MODEM)
(board/compal_e88/init.c:104)uart_irq_handler_sercomm dans
calypso/uart.cUART RHR register → uart_irq_handler_sercomm (IRQ)
→ uart_getchar_nb() lit chaque byte
→ sercomm_drv_rx_char(ch) parse HDLC
→ quand trame complète: dispatch_rx_msg(dlci, msg)
→ callback[dlci](dlci, msg)
→ pour DLCI 5: l1a_l23_rx() enqueue dans l23_rx_queue
→ l1a_l23_handler() (appelé depuis main loop) déqueue et traite
Firmware veut envoyer (ex: L1CTL_FBSB_CONF) :
→ sercomm_sendmsg(SC_DLCI_L1A_L23, msg)
→ msgb_push(msg, 2) pour ajouter DLCI + CTRL en tête
→ enqueue dans dlci_queues[5]
→ uart_irq_enable(UART_IRQ_TX_EMPTY, 1)
→ uart_irq_handler_sercomm (THR interrupt)
→ sercomm_drv_pull(&ch) lit un byte de la queue
→ uart_putchar_nb(ch) écrit dans UART THR
→ byte sort sur le PTY → host
1. ARM écrit tâches dans DB write page :
dsp_api.db_w->d_task_d = FB_DSP_TASK (5) ou NB_DSP_TASK (21) etc.
dsp_api.db_w->d_burst_d = burst_id (0-3)
dsp_api.db_w->d_ctrl_system |= tsc & 7
2. ARM appelle dsp_end_scenario() :
dsp_api.ndb->d_dsp_page = B_GSM_TASK | dsp_api.w_page
dsp_api.w_page ^= 1 (flip page)
tpu_dsp_frameirq_enable() → TPU_CTRL |= DSP_EN
tpu_frame_irq_en(1, 1)
3. TPU hardware génère SINT17 (frame IRQ) au DSP
4. DSP ROM dispatcher :
- Lit d_dsp_page (DSP addr 0x08D4)
- Vérifie B_GSM_TASK (bit 1)
- Lit page number (bit 0) → sélectionne DB page 0 ou 1
- Exécute d_task_d (DL), d_task_u (UL), d_task_md (monitoring)
- Écrit résultats dans DB read page (a_pm, a_serv_demod, a_sch)
- Fait IDLE
5. ARM lit résultats de DB read page :
dsp_api.db_r->a_serv_demod[D_TOA/D_PM/D_ANGLE/D_SNR]
dsp_api.db_r->a_pm[0..2]
B_GSM_TASK = (1 << 1) = 0x02 // Task flag in d_dsp_page
B_GSM_PAGE = (1 << 0) = 0x01 // Page select in d_dsp_page
// d_dsp_page = 0x02 (page 0, task) ou 0x03 (page 1, task)
BASE_API_NDB = 0xFFD001A8 // ARM address
BASE_API_W_PAGE_0= 0xFFD00000 // 20 words MCU→DSP
BASE_API_W_PAGE_1= 0xFFD00028
BASE_API_R_PAGE_0= 0xFFD00050 // 20 words DSP→MCU
BASE_API_R_PAGE_1= 0xFFD00078
BASE_API_PARAM = 0xFFD00862 // 57 words params
NO_DSP_TASK = 0 // No task
FB_DSP_TASK = 5 // Frequency Burst (idle)
SB_DSP_TASK = 6 // Sync Burst (idle)
TCH_FB_DSP_TASK = 8 // Frequency Burst (dedicated)
TCH_SB_DSP_TASK = 9 // Sync Burst (dedicated)
RACH_DSP_TASK = 10 // RACH transmit
AUL_DSP_TASK = 11 // SACCH UL
DUL_DSP_TASK = 12 // SDCCH UL
TCHT_DSP_TASK = 13 // TCH traffic
NBN_DSP_TASK = 17 // Normal BCCH neighbour
EBN_DSP_TASK = 18 // Extended BCCH neighbour
NBS_DSP_TASK = 19 // Normal BCCH serving
NP_DSP_TASK = 21 // Normal Paging
EP_DSP_TASK = 22 // Extended Paging
ALLC_DSP_TASK = 24 // CCCH reading
CB_DSP_TASK = 25 // CBCH
DDL_DSP_TASK = 26 // SDCCH DL
ADL_DSP_TASK = 27 // SACCH DL
TCHD_DSP_TASK = 28 // TCH traffic DL
CHECKSUM_DSP_TASK = 33 // DSP checksum
Le BSP est un port série synchrone du DSP C54x qui connecte directement à l’ABB (Analog Baseband). - Port address : 0xF430 (BSP data register) - Interrupt : BRINT0 (vec 21, IMR bit 5) — “BSP Receive Interrupt” - Data format : int16 I/Q samples, 1 sample par symbole GSM
Vrai hardware :
ABB convertit le signal RF en baseband I/Q
→ BSP DMA transfère les samples dans un buffer DSP
→ BRINT0 signale "burst reçu"
→ DSP traite : dérotation, FIR, equalizer, Viterbi decode
→ Résultats dans API RAM
QEMU émulation :
osmo-bts-trx → TRXD UDP (soft bits)
→ bridge (sercomm_udp.py) GMSK modulation → int16 I/Q
→ calypso_trx_rx_burst()
→ c54x_bsp_load(dsp, samples, n) // charge bsp_buf[]
→ c54x_interrupt_ex(dsp, 21, 5) // BRINT0
→ DSP lit via PORTR PA=0xF430 // bsp_buf[bsp_pos++]
// Structure
uint16_t bsp_buf[160]; // burst samples
int bsp_len; // number of samples
int bsp_pos; // read position
// Load (called by calypso_trx.c)
void c54x_bsp_load(C54xState *s, const uint16_t *samples, int n);
// Read (called by PORTR instruction)
if (op2 == 0xF430 && s->bsp_pos < s->bsp_len)
data_write(s, addr, s->bsp_buf[s->bsp_pos++]);
┌─────────────────────────────────────────────────────────┐
│ QEMU Calypso │
│ │
│ ┌──────────┐ TRXD UDP ┌──────────────────┐ │
│ │ Bridge │───────────────→│ calypso_trx.c │ │
│ │ (python) │ │ rx_burst() │ │
│ └──────────┘ │ → c54x_bsp_load │ │
│ ↑ │ → BRINT0 │ │
│ │ PTY └────────┬─────────┘ │
│ │ │ │
│ ┌────┴─────┐ ┌─────┴──────┐ │
│ │ UART │ sercomm_gate │ DSP C54x │ │
│ │ modem │───────────────→ │ PORTR F430│ │
│ │ │ DLCI 5 → FIFO │ bsp_buf[] │ │
│ └──────────┘ (L1CTL only) └────────────┘ │
│ ↑ │
│ │ L1CTL socket │
│ ┌────┴─────┐ │
│ │ l1ctl │ │
│ │ _sock.c │ ← mobile/ccch_scan │
│ └──────────┘ │
└─────────────────────────────────────────────────────────┘
Le gate parse le flux sercomm entrant sur l’UART modem et route par DLCI : - Tous les DLCIs → re-wrap et push dans le FIFO UART (firmware ARM les traite) - Pas de routage spécial pour DLCI 4 — le firmware n’a pas de handler pour DLCI 4 - Le gate ne touche PAS aux bursts — ils arrivent par un autre chemin (TRXD → BSP)
Le gate remplace le parser sercomm inline qui était dans calypso_uart.c. C’est un simple parser HDLC qui re-injecte les trames dans le FIFO.
Le bridge envoie les bursts DL via le PTY en sercomm DLCI 4. C’est incorrect : - Sur le vrai hardware, les bursts DL arrivent par le BSP, pas l’UART - Le firmware n’a pas de handler pour DLCI 4 (SC_DLCI_DEBUG) - Les bursts DL dans le FIFO UART polluent le firmware
Solution : le bridge doit envoyer les bursts DL par un canal séparé (UDP socket, pipe, ou shared memory) directement à calypso_trx_rx_burst(), qui charge le BSP via c54x_bsp_load() et fire BRINT0.
ARM offset 0x01A8 = DSP addr 0x08D4 = d_dsp_page
bit 0 = page number (0 ou 1)
bit 1 = B_GSM_TASK (1 = tâche à exécuter)
ARM offset 0x01C4 = DSP addr 0x08E2 = d_dsp_state
0 = run, 1 = Idle1, 2 = Idle2, 3 = Idle3
Firmware init: d_dsp_state = 3 (C_DSP_IDLE3)
| Fichier | Rôle | Touche aux bursts ? |
|---|---|---|
| sercomm_gate.c | Parse sercomm UART → FIFO (L1CTL) | Non |
| calypso_uart.c | Hardware UART, appelle sercomm_gate | Non |
| calypso_trx.c | TDMA tick, BSP load, SINT17, TPU | Oui (rx_burst → bsp_load) |
| calypso_c54x.c | DSP emulation, PORTR 0xF430 | Oui (bsp_buf read) |
| sercomm_udp.py | Bridge BTS↔︎QEMU | Oui (TRXD → GMSK → PTY/BSP) |
| l1ctl_sock.c | L1CTL socket ↔︎ mobile | Non |
Source: /opt/GSM/calypso_dsp.txt — dumped from Motorola
C1xx via osmocon + ESP32
| Section | Address Range | Size (words) | Loaded Into |
|---|---|---|---|
| Registers | 0x00000-0x0005F | 96 | data[0x00-0x5F] |
| DROM | 0x09000-0x0DFFF | 20480 | data[0x9000-0xDFFF] |
| PDROM | 0x0E000-0x0FFFF | 8192 | data[0xE000-0xFFFF] |
| PROM0 | 0x07000-0x0DFFF | 28672 | prog[0x7000-0xDFFF] |
| PROM1 | 0x18000-0x1FFFF | 32768 | prog[0x18000-0x1FFFF] + mirror at prog[0x8000-0xFFFF] |
| PROM2 | 0x28000-0x2FFFF | 32768 | prog[0x28000-0x2FFFF] |
| PROM3 | 0x38000-0x39FFF | 8192 | prog[0x38000-0x39FFF] |
| Address | Content |
|---|---|
| 0xFF80-0xFFFE | RESET boot code (runs sequentially, NOT separate interrupt vectors) |
| 0xFFFE | IDLE instruction (end of boot) |
| 0x8000-0x801F | TDMA slot table (8 slots × 4 words: SUB + SSBX INTM + IDLE) |
| 0x8020+ | Processing code (after TDMA slots) |
| Address | Content |
|---|---|
| 0x7000-0x7025 | Boot init routines (called from PROM1 RESET handler) |
| 0x7026-0x71FF | Boot polling loop (writes API RAM tables) |
| 0xA4CA-0xA530 | Frame init / page setup |
| 0xA51C | Reads d_dsp_page (instruction: 10f8 08d4) |
| 0xC860-0xC8C8 | Frame dispatcher setup |
| 0xC8CD | BANZ to 0xC8E7 (dispatch entry) |
| 0xC8E7-0xC920 | Frame dispatch: reads d_dsp_page, configures pages, branches to task handlers |
| 0xC920+ | Task processing code |
| Address | Content |
|---|---|
| 0xE000+ | DSP runtime code (accessed as prog[0x8000+] with XPC=0) |
The table at 0xFF80-0xFFFF in PROM1 is boot code, not separate handlers. Vectors 0-31 fall into inline boot code. Only useful vectors: - Vec 0 (0xFF80): RESET entry point - Most other vectors: inline boot code (context save/restore + RETE)
16 MVPD (0x8Cxx) instructions at: 0x75C0, 0x8700, 0x8C80, 0x8CA0 These are NOT reached during the 86K-instruction boot — they’re in processing code.
| Address | Content |
|---|---|
| 0x0007 | Used by TDMA slot table (LD/ST with offsets) |
| 0x08D4 | d_dsp_page (NDB offset 0) |
| 0x08D5 | d_error_status (NDB offset 1) |
| 0x0800-0x0813 | Write page 0 |
| 0x0814-0x0827 | Write page 1 |
| 0x0828-0x083B | Read page 0 |
| 0x083C-0x084F | Read page 1 |
| 0x3FB0 | Internal: page state variable |
| 0x3FC1-0x3FC2 | Internal: current page pointers |
| 0x3FDC-0x3FE0 | Internal: boot state variables |
The C54x emulator treated opcode 0xEA as BANZ (branch on auxiliary
register not zero). In reality: - BANZ = opcode
0110 11Z0 IAAAAAAA = 0x6C/0x6E (per
SPRU172C p.4-16) - 0xEA =
1110 101k kkkk kkkk = LD #k9, DP (load
data page pointer)
This caused the DSP to branch to random DARAM addresses (e.g., 0x1231) instead of continuing sequential execution. The DSP would get lost in empty DARAM.
LD #k9,DP =
opcode 0xEA00, mask 0xFE00Replaced the 0xEA BANZ handler with:
if ((op & 0xFE00) == 0xEA00) {
uint16_t k9 = op & 0x01FF;
s->st0 = (s->st0 & ~ST0_DP_MASK) | k9;
return consumed; // 1 word
}
data[0x1231] = 0x115F (was 0x0000) — DARAM correctly
populated with processing codePMST = 0x4D86 (OVLY=1) — DSP correctly enables overlay
moded_dsp_page alternates 0x0002/0x0003 — firmware/DSP page
protocol workstask_md=5 (FB search) correctly dispatched to DSPInstead of c54x_run(10M) at DSP_DL_STATUS_READY, we now:
1. c54x_reset() + running = true 2. DSP runs
1M instructions per TDMA frame tick 3. SINT17 interrupt delivered when
firmware sets TPU_CTRL_DSP_EN
This lets the timer (TINT0) fire between frames, which the DSP boot code needs.
DSP runs FB search (task_md=5) but never sets d_fb_det=1 at NDB+0x48 (0xFFD001F0). Burst samples arrive via calypso_trx_rx_burst() and are written to DARAM at 0x03F0 and 0x04F0, but the DSP FB code may read from different addresses.
| Opcode | Mnemonic | Words | Encoding |
|---|---|---|---|
| 0x9Bxx | RPT #k (11-bit) | 1 | 1001 1kkk kkkk kkkk |
| 0xC9xx | STL B, Smem | 1 | 1100 1SII IIII IIII |
| 0xA1xx | LD Smem, B | 1 | 1010 0SII IIII IIII |
| 0xD6xx | LD Smem, T | 1 | 1101 0III IIII IIII |
| 0xE0xx | BANZ pmad, *ARn- | 2 | standard BANZ alt? needs investigation |
| 0xF1xx | FIRS Xmem,Ymem,pmad | 2 | Symmetric FIR filter |
Priority: RPT #k (0x98-0x9F) is critical — used for block copies during boot/processing.
calypso_c54x.c line 810: 0xEA BANZ -> LD #k9,DPcalypso_trx.c line 135: boot approach changed to
TDMA-tickcalypso_trx.c line 292: added DSP run + SINT17 in TDMA
tickSur le vrai matériel, le BSP (Baseband Serial Port) est le lien série synchrone qui DMA les samples I/Q de l’IOTA RF frontend directement dans la DARAM du DSP C54x. Le code DSP (FCCH/SCH/burst detection en PROM0) lit ces samples depuis un buffer DARAM fixe et poste les résultats dans le NDB.
Dans QEMU on n’émule pas le bus série IOTA. À la place :
bridge.py (gr-gsm GMSK modulateur)
│
│ UDP TRXD v0 (port 6802)
▼
sercomm_gate.c::trxd_cb()
│
│ calypso_bsp_rx_burst(tn, fn, int16 *iq, n_int16)
▼
calypso_bsp.c::calypso_bsp_rx_burst()
│
│ copie de mots dans dsp->data[<DARAM target>]
▼
C54x DARAM ──read──▶ PROM0 FB-det handler ──▶ NDB d_fb_det
Pas de poke dans le NDB, pas de faux d_fb_det, pas
d’ancien header rx_burst. Le seul boulot du module BSP est
de poser le flux int16 au bon endroit en DARAM ; le firmware DSP fait
tout le reste.
| Path | Rôle |
|---|---|
include/hw/arm/calypso/calypso_bsp.h |
API publique |
hw/arm/calypso/calypso_bsp.c |
Implémentation DMA |
hw/arm/calypso/sercomm_gate.c::trxd_cb |
Appelle calypso_bsp_rx_burst |
hw/arm/calypso/calypso_trx.c::calypso_trx_init |
Appelle calypso_bsp_init(s->dsp) |
hw/arm/calypso/calypso_c54x.c::data_read |
Tracer FBDET / d_spcx_rif |
hw/arm/calypso/meson.build |
Ajoute calypso_bsp.c |
Deux variables d’environnement (lues une fois à
calypso_bsp_init) :
| Env var | Défaut | Sens |
|---|---|---|
CALYPSO_BSP_DARAM_ADDR |
0 |
Adresse de mot dans le data-space DSP (DARAM) |
CALYPSO_BSP_DARAM_LEN |
1184 |
Nombre max de mots int16 copiés par burst (148×4×2) |
Quand CALYPSO_BSP_DARAM_ADDR=0, le BSP tourne en mode
DISCOVERY : chaque burst est loggué mais rien n’est
écrit en DARAM. C’est le défaut sûr pendant le debug DSP.
L’adresse cible du buffer DARAM utilisée par la routine FB-det est
identifiée à l’exécution en traçant les data reads émis quand le PC DSP
est dans le handler FB-det en PROM0 (plage 0x7730..0x7990,
mémoire project_dsp_fb_det) :
[c54x] FBDET RD [0x<addr>]=0x<val> PC=0x<pc> insn=<n>
Le cluster d’<addr> (avec PC juste avant la
première boucle MAC/FIR) est la cible BSP. Mettre
CALYPSO_BSP_DARAM_ADDR en conséquence et relancer.
-lm
requis).d_spcx_rif programmé par ARM à
0x0179 à fn=0.0x81a7..0x81d6. La cause n’est pas un bug DSP —
c’est que le firmware ARM n’écrit jamais
d_task_md = FB_DSP_TASK. Voir TODO.md.Dès que le DSP atteindra 0x7730+, le tracer FBDET
révélera le buffer DARAM et on pourra fixer
CALYPSO_BSP_DARAM_ADDR. # Code Refactoring Guide
hw/arm/calypso/
calypso_c54x.c (~2600 lines) - TMS320C54x DSP emulator
calypso_c54x.h (~170 lines) - DSP state struct, constants
calypso_trx.c (~610 lines) - Calypso HW glue (DSP-ARM, TPU, TSP, SIM)
calypso_trx.h (~80 lines) - TRX definitions, IRQ numbers
calypso_tint0.c (~100 lines) - TINT0 master clock (NEW)
calypso_tint0.h (~55 lines) - TINT0 constants (NEW)
calypso_mb.c - Machine board
calypso_soc.c - SoC init
l1ctl_sock.c - L1CTL unix socket (firmware <-> mobile)
meson.build - Build config
calypso_tint0_do_tick(fn) as the work
callback.if (hi8 == 0xF0 || hi8 == 0xF1)
blocks#include "hw/arm/calypso/foo.h", others
use #include "foo.h"