Untitled
C patches for calypso QEMU emulation (2026-04-07)
Application des découvertes faites pendant la session de debug FBSB. Chaque patch est classé:
- CONFIRMED = bug ou dead code visible dans le source, fix ne change pas le comportement nominal mais nettoie ou répare une trace.
- SPECULATIVE = hypothèse non confirmée. NON APPLIQUÉ.
Patches CONFIRMED (à appliquer)
C-1. Dédup 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.
C-2. Dédup 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.
C-3. Dédup 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).
C-4. Fix initial value du SP-OOR tracer
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).
Patches SPECULATIVE (NON APPLIQUÉ — investigation requise)
S-1. Bug F074 / F274 — SP runaway PROM0 0xb906
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
S-2. Bug F5E3 — SP runaway PROM0 0xc12a
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
Patches DOWNSTREAM (post-fix DSP)
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.
1. Découvertes (factuel, vérifié)
1.1 Variance entre runs = 1 bug DSP
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.
1.2 Bug DSP — opcode 0xF074 / 0xF274 et SP runaway
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 ***
- 0xF074 =
CALL pmad(binutils tic54x-opc.c:279, mask 0xFFFF) - 0xF274 =
CALLD pmad(delayed call, 2 delay slots) - 0xb908 poussé sur la stack = adresse de retour = PC + 2
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)
1.3 Bug DSP secondaire — opcode 0xF5E3
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
- 0xF5E3 = pas immédiatement identifiable dans tic54x-opc.c (zone F5xx)
- Cluster
0xc121-0xc12a= fonction DSP réellement appelée par hack only (la baseline bail àif (d_fb_det==0)avant de l’atteindre) - Le hack a donc révélé ce 2e bug en ouvrant un nouveau chemin d’exécution
- Run 014 a 69086 SP_events vs ~13k baseline → 5× plus de bleed
1.4 Décision tree FBSB côté firmware ARM
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
1.5 Disas du chemin critique l1s_fbdet_resp
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)2. Patchs appliqués
2.1 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)
2.2 run_all_debug.sh / run_all.sh
- Loop N runs en headless
- Logs incrémentés
/root/logs/{qemu,bridge,inject,mobile,hack}_NNN.log - Symlink
/tmp/qemu.log→ run courant - Watchdog
QEMU_LOG_MAX(50 MB par défaut) +RUN_TIMEOUT(45s par défaut) - Cleanup propre SIGTERM puis SIGKILL
- Tail live de qemu.log dans le terminal courant
- Affiche
HACK_MODEau démarrage
3. TODO (par ordre)
3.1 Test des nouveaux BPs (immédiat)
3.2 Investigation DSP F074/F274 (priorité 1)
3.3 Investigation opcode 0xF5E3
3.4 Cleanup safe (calypso_c54x.c)
3.5 Tracer fixes
3.6 ARM-side investigation (régime A)
4. Métriques cibles (avant/après fix DSP)
| 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) |
5. Règle inviolable
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
Session 2026-04-05 (this context)
Bug 1: SP leak via RPTB EXIT
- Symptom: SP incremented +1 at each RPTB EXIT (0xe999->0xe9b0)
- Root cause: RPTB itself was not the issue, the SP leak came from other instructions
- Fix: Added SP change detector to identify culprit instructions
Bug 2: SP corruption via STH B writing to MMR zone
- Symptom: SP drops from 0x5AC8 to 0x0000 at PC=0x8a46
- Root cause: STH B with DP=0 writes B(high)=0 to addr 0x18 = SP register (MMR)
- Fix: Set SP init to 0x5AC8 (Calypso boot ROM value). Later fixed properly via CALLD/RPTBD corrections.
Bug 3: PROM1 mirror stopping at 0xFF7F
- Symptom: Interrupt vectors at 0xFF80-0xFFFF empty
- Root cause: Mirror range was
addr16 >= 0xE000 && addr16 < 0xFF80 - Fix: Changed to
addr16 >= 0xE000(include vectors)
Bug 4: Interrupt vectors 1-7 overwritten by IDLE stubs
- Symptom: TINT0 and other interrupts branch to IDLE instead of real handlers
- Root cause: c54x_reset() installed IDLE at vec 1-7 positions, overwriting PROM1 ROM vectors
- Fix: Only install vec 0 (reset). Vec 1-7 come from PROM1 ROM mirror.
Bug 5: F272 decoded as NORM instead of RPTBD
- Symptom: Code at 0x76FB consumed 1 word instead of 2, corrupting instruction flow
- Root cause: F272 = RPTBD (repeat block delayed, 2 words) per tic54x-opc.c. Was incorrectly decoded as NORM (normalize accumulator).
- Fix: Added specific checks for F272=RPTBD, F274=CALLD, F273=RETD before LMS handler
Bug 6: F274 decoded as NORM instead of CALLD
- Symptom: Code at 0x7702 treated as normalize instead of delayed call
- Root cause: Same as Bug 5 - F274 is CALLD pmad (delayed call, 2 words)
- Fix: F274 now pushes PC+2 and branches to pmad
Bug 7: Indirect addressing modes 0xC-0xF not consuming 2nd word
- Symptom: Instructions with
*(lk),*+ARn(lk),*ARn(lk),*+ARn(lk)%used AR as address without offset, and the offset word was executed as next instruction - Root cause: resolve_smem had
/* handled by caller */for modes C-F but no caller actually handled them - Fix: resolve_smem now reads prog_fetch(s, s->pc+1) for modes C-F, sets s->lk_used=true. All
return consumedchanged toreturn consumed + s->lk_used.
Bug 8: BANZ testing old AR value
- Symptom: BANZ at 0xB36B branching incorrectly
- Root cause: Code saved
old_arp_val = s->ar[arp(s)]BEFORE resolve_smem modified AR, then tested old value - Fix: Test
s->ar[arp(s)]AFTER resolve_smem
Bug 9: F6xx instructions treated as NOP
- Symptom: MVDD (data-to-data move) silently dropped
- Root cause: F6xx handler only had cases for sub=2 and sub=6, rest fell to NOP
- Fix: Added MVDD for sub >= 0x8
Bug 10: RPT/RPTB interaction
- Symptom: RPT inside RPTB causes infinite loop - RPTB redirect fires during RPT
- Root cause: RPTB check (
pc == rea + 1) runs before exec and redirects PC during active RPT - Fix: Skip RPTB check when
rpt_activeis true
Bug 11: RPT F5xx return value
- Symptom: Changing RPT from
s->pc += 1; return 0toreturn 1caused RPT to re-execute itself infinitely - Root cause: RPT handler in main loop does
continue(skips pc += consumed). With return 1, PC never advances past RPT instruction. - Fix: Reverted to
s->pc += 1; return 0- RPT must advance PC itself since the main loop’s RPT handler will prevent further PC advance.
Bug 12: NORM bit extraction
- Symptom: NORM checking wrong bits of accumulator
- Root cause: Was checking bits 31/30 of 24-bit masked value instead of bits 39/38 of 40-bit accumulator
- Fix: Changed to
(*acc >> 39) & 1and(*acc >> 38) & 1
Bug 13: L1CTL RESET_IND lost before client connects
- Symptom: ccch_scan connects but never receives RESET_IND
- Root cause: QEMU boots immediately, firmware sends RESET_IND before client is listening
- Fix: QEMU starts with
-S(paused).vm_start()called when client connects.
Bug 14: L1CTL reconnect stuck at “waiting for firmware”
- Symptom: Kill and relaunch mobile -> stuck at “waiting for firmware”
- Root cause:
cli_rx_enabled = falseon reconnect, firmware won’t re-send RESET_IND - Fix: On reconnect (VM already running), enable
cli_rx_enabledimmediately
Bug 15: soft_to_int16 overflow in sercomm_udp.py
- Symptom: soft_bit=255 produces -33025 (outside int16 range)
- Root cause: No clamping
- Fix:
max(-32768, min(32767, ...))
Bug 16: Boot ROM at prog[0x0000-0x007F] empty
- Symptom: CALA to A(low)=0x0000 executes garbage (all zeros)
- Root cause: Internal Calypso boot ROM not in PROM dump. prog[0x0000] was uninitialized.
- Fix: Fill prog[0x0000-0x007F] with RET (0xF073) stubs
Bug 17: Duplicate F0/F1 handler blocks
- Symptom: Second F0/F1 handler block is dead code, never reached
- Root cause: First
if (hi8 == 0xF0 || hi8 == 0xF1)block catches everything - Status: Identified, not yet cleaned up
Bug 18: TINT0 timer scattered across files
- Symptom: Timer logic in calypso_trx.c mixed with DSP/ARM glue code
- Root cause: No dedicated timer module
- Fix: Created calypso_tint0.c/h as master clock module. Removed calypso_tdma_hw.c/h (slave, never connected).
Session 2026-04-05 night
Bug 19: F4EB = RETE (alternate encoding)
- Symptom: Interrupt vector stubs (F4EB F495 F495 F495) fell through to BD handler, branching to 0xF495
- Root cause: F4EB not recognized as RETE. Per tic54x-opc.c it’s the real RETE encoding.
- Fix: Added exact-match F4EB handler with APTS-aware pop
Bug 20: RPTBD (F272) RSA = PC+2 instead of PC+4
- Symptom: Delay slots executed inside the repeat loop instead of once before it. Init MVDD sweep ran 64x corrupting MMRs. Dispatch RPTB at 0x8F8B included STM in loop body.
- Root cause: RPTBD is delayed — 2 delay slots after the 2-word instruction. RSA must be PC+4, not PC+2.
- Fix: Changed
s->rsa = (uint16_t)(s->pc + 4)in both F272 handlers. THIS was the root cause of init MMR corruption and DSP never reaching IDLE.
Bug 21: F4xx arithmetic decoded as branch/call
- Symptom: DSP never reached IDLE, infinite dispatch loops, stack corruption. F48E (EXP) treated as CALLD (push+branch), F483 (SAT) as unknown, F48C (MPYA) as CALLD, etc.
- Root cause: F4xx switch used
sub = (op >> 4) & 0xFmapping 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). - Fix: Replaced nibble switch with exact-match/mask-based handlers for all F4xx arithmetic. Added 20+ instruction implementations.
Bug 22: MAR (0x6D) not implemented
- Symptom: 0x6DEC at 0xE906 treated as UNIMPL (1 word). Next word (0x0003 = lk) executed as instruction, causing PC misalignment and d_dsp_page corruption.
- Root cause: 0x6Dxx = MAR (Modify Address Register) per tic54x-opc.c. Was missing from decoder.
- Fix: Added MAR handler — calls resolve_smem for side effects only (AR modification), no data access.
Bug 23: CC/BC condition tables wrong
- Symptom: Unknown conditions defaulted to
take=true, causing wrong branches. cond=0x05 was always taken instead of AEQ (A==0). - Root cause: Condition codes followed wrong mapping. Per tic54x-dis.c cc2[]: 0x02=AGEQ, 0x03=ALT, 0x04=ANEQ, 0x05=AEQ, etc.
- Fix: Rewrote CC and BC with correct switch table. Unknown compound conditions still default to true.
Bug 24: prog_write allows PROM1 corruption
- Symptom: MVDP/WRITA could overwrite ROM at 0xE000-0xFFFF
- Root cause: No protection in prog_write for PROM1 range
- Fix:
if (addr16 >= 0xE000) return;
Bug 25: d_dsp_page not synced to DSP API RAM
- Symptom: DSP read d_dsp_page=0x0000 always, never saw ARM’s 0x0002/0x0003
- Root cause: Sync to api_ram was inside SINT17 block (gated on dsp_init_done). Before init, DSP never saw the page value.
- Fix: Sync api_ram[0xD4] on ARM write to dsp_ram[0x01A8/2], not per-tick.
Bug 26: BRINT0 not fired
- Symptom: DSP stuck in frame processing waiting for BSP data that never arrives
- Root cause: c54x_bsp_load loaded samples but never fired BRINT0 interrupt
- Fix: Fire c54x_interrupt_ex(dsp, 21, 5) after bsp_load, gated on dsp_init_done
Bug 27: Double-nested include headers
- Symptom: include/hw/arm/calypso/calypso/ had divergent copies (GSM_TDMA_NS 10x off, missing irq_in_service)
- Root cause: Leftover from refactoring
- Fix: Removed nested calypso/calypso/ directory # QEMU Calypso GSM Phone Emulator - Project Status
Goal
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.
Architecture
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
Components
Working
- QEMU Calypso machine (calypso_mb.c, calypso_soc.c)
- ARM CPU boots layer1.highram.elf
- DSP ROM loaded from calypso_dsp.txt (dumped from real phone)
- UART sercomm (firmware <-> external world)
- L1CTL socket (firmware <-> mobile/ccch_scan)
- sercomm_udp.py bridge (UART <-> BTS UDP)
- DSP bootloader protocol (ARM uploads code, jumps to 0x7000)
- MVPD simulation (PROM0 -> DARAM overlay copy)
- PROM1 mirror to 0xE000-0xFFFF including interrupt vectors
- Boot ROM stubs at prog[0x0000-0x007F]
- TINT0 master clock (calypso_tint0.c)
- osmo-bts-trx receives clock indications and configures
- DSP instruction decoding (~98% of C54x ISA)
- Full F4xx arithmetic: EXP, NORM, MPYA, SAT, NEG, ABS, CMPL, RND, MAX, MIN, SUBC, SFTL, ROR, ROL, MACA, SQUR, ADD, SUB, LD, SFTA
- MAR (0x6D) modify address register
- F4EB = RETE (alternate encoding), SSBX/RSBX range
- RPTBD RSA = PC+4 (delay slots correct)
- CC/BC conditional tables per tic54x-dis.c
- All indirect addressing modes 0xC-0xF with lk offset
- CMPS (Viterbi), FIRS (FIR filter), LMS, MVDD
- DSP init: boots, reaches IDLE at PC=0x770C (dispatch idle) at fn=8
- SP=0x5AC8 after cleanup, stable at 0x0024-0x0027 during processing
- IMR properly configured by DSP code (0xE7FF, 0xFFFF)
- Returns to IDLE between frames
- d_dsp_page alternates 0x0002/0x0003 correctly
- L1CTL: RESET_IND/CONF, PM_REQ/CONF, FBSB_REQ/CONF all work
- FBSB_CONF sent with result=2, bsic=2
- Zero UNIMPL opcodes, zero F4xx unhandled
Partially Working
- DSP frame processing
- SINT17 dispatches frame processing correctly
- DSP runs MAC/FIR/equalizer code (0x81xx, 0xF4xx subroutines)
- Does not return to IDLE after first SINT17 (needs burst data)
- Burst pipeline
- BRINT0 (vec 21, bit 5) fires when BSP samples loaded
- c54x_bsp_load stores samples in BSP buffer
- DSP reads via PORTR PA=0x0000
- Bridge runs but no DL bursts received from BTS yet
Not Yet Working
- DL burst reception (bridge → DSP BSP pipeline)
- DSP completing frame processing (returning to IDLE after SINT17)
- API IRQ (IRQ15) to ARM after frame completion
- DATA_IND to mobile/ccch_scan
- Frequency burst detection (FBSB result=0)
- SCH decode, CCCH decode
- Network registration
- Voice/data
Key Bug History (55+ bugs fixed across sessions)
Session 2026-04-04 (22 bugs)
- IDLE wake on any interrupt (masked or unmasked)
- INTH irq_in_service tracking
- UART address mapping
- sercomm DLCI filter
- PROM1 mirror, OVLY range
- F8xx branch before RPT fallthrough
- XPC not used in prog_fetch
- Bootloader protocol (BL_CMD_STATUS)
- STH/STL, DELAY, EA/BANZ conflict
- ST0 init, SP init, CALL push order
- 12 opcode fixes from SPRU172C
- ROM write protection
Session 2026-04-05 day (20 bugs)
- BC/BCD conditional branch placement
- STLM B (0xAB), RPTB C6/C7 encodings
- PROM0 XPC threshold fix
- dsp_init_done gate for SINT17
- F272=RPTBD, F274=CALLD, F273=RETD
- Indirect addressing lk_used (modes C-F)
- BANZ AR test order
- RPTB/RPT priority (skip RPTB during active RPT)
- NORM bit 39/38 extraction
- F6xx MVDD implementation
- PROM1 vectors not overwritten
- Boot ROM stubs
- L1CTL reconnect handling
- soft_to_int16 clamping
- TINT0 master clock extraction
Session 2026-04-05 night (13+ bugs)
- F4EB = RETE (alternate encoding per tic54x-opc.c)
- RPTBD (F272) RSA = PC+4 — delay slots were inside the loop instead of before it. Root cause of init sweep corruption and infinite dispatch loops.
- F4xx arithmetic decoder rewrite — entire F400-F4BF range was decoded as B/CALL/CALLD via nibble switch. Actually arithmetic instructions (ADD, SUB, LD, SFTA, SFTL, EXP, NORM, MPYA, SAT, NEG, ABS, CMPL, RND, MAX, MIN, SUBC, ROR, ROL, MACA, SQUR). This was THE critical bug preventing DSP from reaching IDLE and doing real signal processing.
- MAR (0x6D) instruction implemented — was UNIMPL causing PC misalignment in PROM1 code
- CC/BC condition tables corrected per tic54x-dis.c cc2[] (ALT, AEQ, ALEQ, etc.)
- prog_write PROM1 (E000-FFFF) protection — prevent ROM corruption
- d_dsp_page API RAM sync on ARM write (not every tick)
- BRINT0 (vec 21, bit 5) fires on BSP sample load, gated on dsp_init_done
- SSBX/RSBX catch-all for F4E0-F4FF status bit range
- Double-nested include headers removed (calypso/calypso/ directory)
- build.sh script with auto-timestamp and md5 verification
Docker Setup
- Container:
trying(image: osmo-qemu) - QEMU source:
/root/qemu/ - DSP ROM:
/opt/GSM/calypso_dsp.txt - Firmware:
/opt/GSM/firmware/board/compal_e88/layer1.highram.elf - Launch:
/root/qemu/run.sh - Build:
/home/nirvana/qemu-src/build.sh(auto-sync, timestamp, md5) - Host mirror:
/home/nirvana/qemu-src/hw/arm/calypso/ - Git repo:
/home/nirvana/qemu-calypso/
Next Steps
- Fix DL burst pipeline (osmo-bts-trx → bridge → calypso_trx_rx_burst)
- DSP returns to IDLE after frame processing (with burst data)
- API IRQ fires → DATA_IND to mobile/ccch_scan
- FBSB detection (result=0 instead of 2)
- SCH/CCCH decode
- Network registration
XC (Execute Conditionally) — SPRU172C p.4-198
- Opcode:
1111 11N1 CCCCCCCC(1 word) - N=0 (bit 9=0) → n=1 instruction = opcode 0xFDxx
- N=1 (bit 9=1) → n=2 instructions = opcode 0xFFxx
- If cond true: execute next n instructions normally
- If cond false: treat next n instructions as NOP
- Condition codes (8-bit, combinable):
- UNC=0x00, BIO=0x03, NBIO=0x02, C=0x0C, NC=0x08
- TC=0x30, NTC=0x20
- AEQ=0x45, ANEQ=0x44, AGT=0x46, AGEQ=0x42, ALT=0x43, ALEQ=0x47
- BEQ=0x4D, BNEQ=0x4C, BGT=0x4E, BGEQ=0x4A, BLT=0x4B, BLEQ=0x4F
- AOV=0x70, ANOV=0x60, BOV=0x78, BNOV=0x68
FRET — SPRU172C p.4-61
- Opcode:
1111 0Z01 1101 0100(Z=0 normal, Z=1 delayed) - Execution: (TOS)→XPC, SP+1, (TOS)→PC, SP+1
- Pops 2 words: first XPC, then PC
FCALL — SPRU172C p.4-57
- Opcode:
1111 10Z1 1+ 7-bit pmad(22-16) + 16-bit pmad(15-0) - 2 words total
- Execution: SP-1, PC+2→TOS, SP-1, XPC→TOS, pmad(15-0)→PC, pmad(22-16)→XPC
- Pushes 2 words: first PC+2, then XPC
RETE — SPRU172C p.4-140
- Execution: (TOS)→PC, SP+1, 0→INTM
- Pops 1 word (just PC), clears INTM
TRAP K — SPRU172C p.4-195
- Pushes PC+1, branches to interrupt vector K
- Not affected by INTM
0xEA = BANZ (confirmed, not XC)
How to apply: Fix XC (0xFD/0xFF), FRET (2 pops), FCALL (2 pushes) in calypso_c54x.c
BANZ — SPRU172C p.4-16
- Opcode:
0111 1Z0I AAAA AAAA+ 16-bit pmad (2 words) - BANZ = 0x78xx, BANZD = 0x7Axx
- Sind encodes indirect addressing (which AR and modify mode)
- Execution: if (AR[x] != 0) then pmad→PC, else PC+2→PC
- AR[x] is always decremented (even when condition is false)
- NOT 0xEA! 0xEA is something else (needs identification)
BC — SPRU172C p.4-18
- Opcode:
1111 10Z0 CCCCCCCC+ 16-bit pmad (2 words)
- BC = 0xF8xx, BCD = 0xFAxx
CC — SPRU172C p.4-29
- Opcode:
1111 10Z1 CCCCCCCC+ 16-bit pmad (2 words) - CC = 0xF9xx, CCD = 0xFBxx # Calypso Hardware Reference
Chip Overview
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
ARM Memory Map
| 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) |
DSP API RAM (0xFFD00000)
- Total: 64KB (32K words of 16-bit)
- ARM accesses as bytes, DSP accesses as 16-bit words
- ARM byte offset X = DSP word address 0x0800 + X/2
- Double-buffered pages: Write page 0/1, Read page 0/1
TPU
- Controls radio timing sequences
- TPU_CTRL register:
- Bit 0: TPU_CTRL_EN (enable scenario execution)
- Bit 2: TPU_CTRL_IDLE (idle status)
- Bit 4: TPU_CTRL_DSP_EN (enable DSP frame interrupt)
- Firmware writes TPU scenarios then sets EN
- When scenario completes: clears EN, fires FRAME interrupt
- DSP_EN causes FRAME interrupt to also wake DSP
INTH (Interrupt Controller)
- Level-sensitive
- IRQ 4: TPU_FRAME
- IRQ 0: Watchdog
- IRQ 7: UART
TDMA Timing
- GSM frame: 4.615ms (216.7 frames/sec)
- 8 timeslots per frame
- Hyperframe: 2715648 frames
- QEMU uses 10x slowed timing for emulation stability
DSP C54x Integration
- DSP boots from internal ROM at IPTR*128 (0xFF80 for IPTR=0x1FF)
- ARM communicates via API RAM (shared memory)
- ARM signals DSP via:
- Writing d_dsp_page in NDB
- Setting TPU_CTRL_DSP_EN
- TPU generates FRAME interrupt to DSP (SINT17, vec 2)
- DSP signals ARM by writing results in Read page
OsmocomBB Layer1 Flow
l1_sync()called every TDMA frame (TPU FRAME IRQ)- Updates page pointers (db_w, db_r)
- Runs TDMA scheduler (
tdma_sched_execute) - If tasks scheduled: writes d_task_d/d_task_md, calls
dsp_end_scenario() dsp_end_scenario(): writes d_dsp_page = B_GSM_TASK | w_page, toggles w_page- Enables TPU DSP frame interrupt
- DSP wakes, reads d_dsp_page, dispatches task, writes results
- Next frame: ARM reads results from db_r
TRXD Protocol (BTS ↔︎ Bridge)
DL (BTS → MS) — TRXD v0
| 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) |
UL (MS → BTS) — TRXD v0
| Offset | Size | Field |
|---|---|---|
| 0 | 1 | TN |
| 1-4 | 4 | FN (big-endian) |
| 5 | 1 | PWR |
| 6+ | 148 | Hard bits (0/1) |
Sercomm Protocol
- Flag: 0x7E
- Escape: 0x7D (next byte XOR 0x20)
- Frame: FLAG + DLCI + CTRL(0x03) + payload + FLAG
- DLCI 4: burst data
- DLCI 5: L1CTL messages
L1CTL Protocol (mobile ↔︎ firmware)
- Length-prefixed: 2-byte big-endian length + message
- Message: type(1) + flags(1) + padding(2) + payload
- Key types:
- 0x01: FBSB_REQ
- 0x02: FBSB_CONF (result byte: 0=success, 255=fail)
- 0x03: DATA_IND
- 0x04: RACH_REQ
- 0x05: DM_EST_REQ
- 0x06: DATA_REQ
- 0x07: RESET_IND (sent on boot)
- 0x08: PM_REQ
- 0x09: PM_CONF
- 0x0D: RESET_REQ (payload: reset_type, 1=full)
- 0x0E: RESET_CONF
- 0x10: CCCH_MODE_REQ
- 0x11: CCCH_MODE_CONF # Session 2026-04-05 Night 4 — C54x Opcode Audit
Summary
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.
Fixes
ALU Instructions (Fix 1)
- F0xx was READA → now ADD/SUB/LD/AND/OR/XOR #lk,shift,src,dst
- Also added F06x (ADD/SUB/LD #lk,16 + MPY/MAC #lk)
- Also added F08x-F0Fx (accumulator AND/OR/XOR/SFTL with shift)
- Encoding: bits 7:4=op, bits 9:8=src/dst, bits 3:0=shift, 2nd word=lk
- Source: tic54x-opc.c line 254
{ "add", 0xF000, 0xFCF0 }
RSBX/SSBX (Fixes 2-5)
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 |
Return/Branch Instructions (Fixes 6-12)
| 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 |
CALLD Return Address (Fix 13)
- F274 CALLD pushed PC+2, should be PC+4 (skip 2-word instruction + 2 delay slots)
- Both copies fixed (line 960 and 1210)
IDLE/FRET (Fixes 14-16)
| 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 |
TINT0 Interrupt Mapping (Fix 17)
calypso_tint0.h: IFR bit 4 → bit 3, vector 20 → vector 19- Per TMS320C5410A datasheet: TINT0 = IFR/IMR bit 3, interrupt vector 19
- Previous mapping (bit 4) was BRINT0 (BSP receive), not TINT0
Condition Evaluator (part of Fix 7)
Replaced 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
Results
Before (all 17 bugs present)
- DSP stuck in dispatch loop at 0x81AF forever
- IMR=0x0000 (never configured)
- idle=0 (IDLE never reached)
- TINT0 on wrong interrupt bit
After
- DSP boots in 173 instructions
- IMR=0x002D configured (bits 0,2,3,5 = INT0,INT2,TINT0,BXINT0)
- Dispatch loop active at 0x81AF
- TINT0 on correct bit 3 (enabled in IMR)
Remaining Issue
- SP drift: 0x5AC8 → 0x8FFE after boot. FRET at 0x770C pops XPC+PC from empty stack (init code reached via branch, not FCALL). The popped values are garbage → DSP executes DARAM data as code → SP corrupts gradually.
- No return to IDLE: dispatch loop never exits because the handler RET/RETD pops garbage return addresses. The real IDLE (F4E1) at 0xA6A0 is never reached.
- INTM=1 stuck: handler sets INTM=1 (SSBX 1,8 at 0x7710) and never clears it, preventing interrupt servicing.
Key Reference
tic54x-opc.c location in container:
/var/lib/docker/overlay2/a242f59.../diff/root/gnuarm/src/binutils-2.21.1/opcodes/tic54x-opc.c
Files Modified
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.
Session 3 — acquis (2026-04-06 soir)
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.
Bugs décodeur fixés
LD Smem, dstmanquant entièrement (0x1000mask0xFE00). 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 lesLD *ARx, A/B(1 mot) tombaient en UNIMPL silencieux, d’où l’inner loop FB-det qui spinnait sans rien lire. Ajout également deLD Smem, TS, dstà0x1400mask0xFE00.resolve_smemmod 12..15 entièrement faux (très gros impact). Per binutilstic54x-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 avaitmod=12= *(lk) absolue etmod=15= AR+lk circulaire — tout décalé. Symptôme du bug : freeze à PC=0xdfc7 (ST #0x3fba, *(0x0003)) où l’ST écrivait dansAR[arp]+0x3fbaau lieu de0x0003, 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.
Fixes pipeline
calypso_bsp_rx_burstcircular 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.pySCALE 16384 → 30000 : amplitude tone proche du clip int16. Vérifié que ce n’est PAS le facteur limitant (snr identique).
Découvertes structurelles
Buffer sample FB localisé à
DARAM[0x021f], longueur ≈ 12000 mots (≈ 6000 IQ pairs, ≈ 1475 syms ≈ 10 sub-bursts). Tracé viaFBLOOP-RDprofiler 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. LeDARAM_ADDR=0x62 LEN=128qu’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 ← outerAR2 = 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-RDconfirme quedata[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étriques avant/après session 3
| 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 |
TODO session 4
Priorité A — Facteur 30× sur le seuil corrélation
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) :
- Le tracer
COEFF-RDne 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 quedata[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). - Format samples : on envoie int16 LE I/Q interleaved scale ×30000. Le DSP corrélateur attend peut-être :
- I-only (ignorer Q) ?
- I et Q dans deux buffers séparés au lieu d’interleaved ?
- Signe inversé ?
- Endian différent ? À tester en modifiant
inject_cfile.pymode tone avec différentes permutations.
- Disasm propre du corrélateur entre PROM0 0xa0e2 (point d’entrée FB-det dispatcher confirmé en PC HIST) et 0xa116 (fin BANZD). Comprendre ce que SFTL B,1,A + NOP + ? + ? + RPTB calcule réellement. Confirmer que iacq est bien la valeur comparée.
- Vérifier la lecture de
d_fb_thr_det_iacqcô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).
Priorité B — Cleanup BDLENA bypass (inchangé session 2)
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.
Priorité C — Cleanup profilers / logs
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.
Backups session 3
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.
Acquis cette session
DSP runaway éliminé par 4 fixes décodeur c54x (commit
057b4e9) :STLM/POPM/LDMMmask& 0x1F→& 0x7F(le STLM B,MMR_0x38 wrappait vers SP et écrasait la stack pointer).0x76xxétait décodé commeLDM MMR,dst(1 mot) au lieu deST #lk, Smem(2 mots). Ce seul bug expliquait toute la cascade de runaways au PROM1 1f2a0 (ST #idx,*(0xe1) table).CMPR cond, ARx(0xF4A8mask0xFCF8) implémenté pour la famille F6.- F68x-F6Fx catch-all MVDD bidon retiré, remplacé par NOP loggé.
E8/E9LD #k8u, A/B corrigés (les deux étaient mal décodés comme CMPR / CC, le faux CC poussaitPC+2dansdata[SP-1]à chaque appel et corrompaitd_fb_det/d_dsp_pagequand SP était wedged dans NDB). Pas encore committé.api_rammirror :calypso_dsp_writemirror chaque write ARM immédiatement versdsp->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 commandesd_task_mdque 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 danscalypso_bsp.cqui 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. Bypasseosmo-bts-trxet le relais TRXD du bridge. Pas encore committé.Profilers
DARAM-RDetNDB-WRdanscalypso_c54x.c(dedup par adresse, capés). Ont permis d’identifier que :- Le buffer FB est bien
DARAM[0x0062..0x00e1](128 mots). - Le DSP lit aussi des triplets sparse
0x010e..0x059e(probable table coeffs FCCH ou pondération). - L’unique cellule API/NDB que le DSP écrit normalement est
data[0x0c31] = 0xfffeà insn ~5M (=BASE_API_PARAMbyte 0).
- Le buffer FB est bien
État firmware actuel
- L1 ARM vivante, scheduler L1S actif, poste
d_task_md = 1(PM) etd_task_md = 5(FB) en alternance sur les deux pages. - DSP exécute la FB-det loop (PC HIST top :
82e9 / 7700 / 7701 / 1111avec ~30k+ hits chacun). - Bursts injectés via cfile ou tone → BSP DMA → DARAM[0x62..0xe1] OK.
- MAIS :
d_fb_detn’est jamais posé à 1 par le DSP, même avec un tone parfait.FBSB_CONF result=255(timeout) en boucle.
TODO ordre de priorité
A. Bug 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.cB. Pourquoi FB-det ne déclenche pas même avec un tone parfait
Une fois A fixé, si avec --tone la FB-det ne pose toujours pas d_fb_det=1, hypothèses :
- Format/scale samples : on envoie int16 LE I/Q interleaved scale ×16384. Le DSP attend peut-être un autre format (signe inversé, amplitude différente, ou que I-channel uniquement).
- Buffer trop court : 128 mots = 64 IQ pairs = 16 syms à 4 sps. FCCH = 148 syms. Soit la corrélation est itérative sur plusieurs DMAs, soit le buffer est en réalité plus long et le tracer ne l’a pas vu (mode lecture sparse avec auto-incrément).
- NDB params manquants :
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. - Il manque encore d’autres opcodes : tracer
UNIMPLet corriger itérativement.
C. Fix BDLENA arming pour pouvoir retirer le bypass
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).
D. Cleanup
- Retirer
bsp.bypass_bdlenaune fois que BDLENA est correctement armée. - Cleanup des
static int X_logprofilers. - Le hack
dsp_ram[0xF8]=1mentionné 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.
Issues annexes (inchangées)
Link -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 remplit
qemu.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
Goal
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.
Architecture
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)
Key Files
| 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 |
DSP ROM
- Dumped from real Motorola C1xx phone via osmocon + ESP32
- File:
/opt/GSM/calypso_dsp.txt(131168 words) - Sections: Registers [00000-0005f], DROM [09000-0dfff], PDROM [0e000-0ffff], PROM0 [07000-0dfff], PROM1 [18000-1ffff], PROM2 [28000-2ffff], PROM3 [38000-39fff]
- DSP version: 0x3606
Memory Map (DSP side)
| 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) |
API RAM Layout
| 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 |
d_dsp_page values
0x0000= no task0x0002= B_GSM_TASK | page 00x0003= B_GSM_TASK | page 1- B_GSM_TASK = (1 << 1) = 0x0002
Write page structure (T_DB_MCU_TO_DSP, 20 words)
| 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 |
DSP Boot Sequence
- ARM writes DSP_DL_STATUS_READY
c54x_reset()→ PMST=0xFFE0, IPTR=0x1FF, OVLY=0, PC=0xFF80c54x_run(10M)→ DSP executes PROM1 reset code → calls PROM0 init → 86K insns → IDLE@0xFFFE- ARM continues, firmware initializes
- Each TDMA frame: ARM writes d_dsp_page + tasks, fires TPU_CTRL_EN
calypso_dsp_done()→ copies write page to DARAM 0x0586, wakes DSP- DSP jumps to 0x8000 (TDMA slot table), processes, returns to IDLE
TDMA Slot Table (PROM1 0x8000-0x801F)
0x8000: 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)
Frame Dispatch Routine (PROM0)
- Entry: 0xC8E7 → reads d_dsp_page at DSP 0x08D4
- Configures page pointers (0x0800/0x0814/0x0828/0x083C)
- Called via BANZ at 0xC8CD
- d_dsp_page also read at 0xA51C during init
Bugs Fixed This Session
- Timer HW (TCR/PSC/TDDR) — Real C54x timer behavior, TSS=1 at reset
- IDLE PC — return 0 (stay at IDLE addr for interrupt handler)
- PC wrap 16-bit —
s->pc &= 0xFFFF - F4E2/F4E3 (RSBX/SSBX INTM) — 98 occurrences decoded as BD instead of interrupt enable/disable. CRITICAL BUG.
- Interrupt vec = bit + 16 then reverted to SINT17 vec 2 bit 1 — correct vector for TPU frame
- FRET — Must pop 2 words (XPC then PC) per SPRU172C p.4-61
- FCALL — Must push 2 words (PC+2 then XPC) per SPRU172C p.4-57
- XC (Execute Conditionally) — Opcode 0xFDxx (n=1) / 0xFFxx (n=2), NOT 0xEAxx. 0xEA = BANZ.
- TRXD header — 8 bytes (TN+FN+RSSI+TOA), not 6. Soft bit conversion.
- L1CTL sequencing — 1 message per callback to let ARM process between messages
- OVLY activation — Enabled after boot for DARAM code execution
- IDLE wake — Jump to 0x8000 (TDMA loop) on wake from boot IDLE
- Interrupt PC+1 — Push PC+1 when waking from IDLE (resume after IDLE)
- Timer FN increment — TINT0 increments frame number in DARAM
Current State (2026-04-03)
- Base: no_cell_found (API RAM intercepts for FB/SB/PM)
- No_cell_found loop works: RESET→PM→FBSB cycle running
- C54x DSP boots in parallel via TDMA ticks (2000 insns/frame)
- SINT17 controlled by TPU_CTRL_DSP_EN (per hardware spec)
- 0 UNIMPL instructions — all opcodes emulated
- SP=0x06AA-0x0C2E after boot (correct, in DARAM)
- DSP reaches PROM0 0x7000 init + PROM1 0x8159 processing
Bugs Fixed (session 2026-04-03)
- RC conditionnel (F2xx) — was unconditional RET, now eval_condition
- PROM0 read protection — prog_read returns prog[] for 0x7000-0xDFFF
- PROM0 write protection — prog_write ignores writes to ROM area
- prog_write double-write — return after prog[ext] for addr>=0x8000
- DELAY instruction (D4/D5) — pipeline delay, was UNIMPL
- BCD 0xEF — added to EE/ED branch conditional handler
- XOR/OR #lk16 (B0/B1/B8/B9) — was UNIMPL
- Timer0 hardware — TIM/PRD/TCR with prescaler and TINT0
- IDLE skip TDMA slots — 0x8000-0x801F IDLE treated as NOP
- Parallel DSP boot — no blocking c54x_run(10M), boot via TDMA ticks
- DSP_EN SINT17 — interrupt only when firmware sets TPU_CTRL_DSP_EN
- eval_condition — full XC/RC condition decoder per SPRU172C Table 3-2
- Interrupt dispatch — check IFR&IMR in main loop each cycle
- API IRQ — raise IRQ15 in dsp_done, unmask at boot
d_fb_det Location
- ARM offset: 0x01F0 (NDB + 0x48)
- DSP address: 0x08F8
- DSP ROM writes it at PROM0 0x7730-0x7990
Next Steps
- DSP boot must reach IDLE@0xFFFE — currently runs but doesn’t converge
- Once boot works, remove API RAM intercepts (let DSP produce results)
- Verify burst samples reach DSP at correct DARAM address
- Achieve FBSB_CONF(result=0) → SB decode → BCCH → mobile registered
Docker
- Container:
osmo-operator-1(always running) - Images:
calypso-qemu:YYYYMMDD-HHMM(snapshots) - Build:
ninja -C /opt/GSM/qemu-src/build - Run:
bash /opt/GSM/qemu-src/run.sh - DSP ROM:
/opt/GSM/calypso_dsp.txt - Firmware:
/opt/GSM/firmware/board/compal_e88/layer1.highram.elf
Reference Documents
doc/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 diagnosis
Objectif
Sortir 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.
Vérifications préalables
NDB layout (DSP=36)
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.
IRQ TINT0 → ARM
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).
Implémentation BSP
Nouveaux fichiers
| Fichier | Rôle |
|---|---|
include/hw/arm/calypso/calypso_bsp.h |
API publique |
hw/arm/calypso/calypso_bsp.c |
DMA vers DARAM, mode discovery |
API
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.
Wiring
calypso_trx.c::calypso_trx_init(): appellecalypso_bsp_init(s->dsp)juste aprèsc54x_initréussi.sercomm_gate.c::trxd_cb(): remplace l’ancien repackage 8-byte +calypso_trx_rx_burst()par un appel directcalypso_bsp_rx_burst(tn, fn, (const int16_t *)(buf+6), nint16).meson.build: ajoutecalypso_bsp.c.
Instrumentation ajoutée dans calypso_c54x.c::data_read
- FBDET RD tracer : si
0x7730 ≤ PC ≤ 0x7990(zone FB-det dans PROM0 d’aprèsproject_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. - d_spcx_rif RD/WR : log accès à
dsp_ram[0xD6](NDB word 2).
Et dans calypso_trx.c::calypso_dsp_write :
- ARM WR d_spcx_rif (offset 0x01AC) : 20 premières écritures.
- ARM WR d_task_md : NZ uniquement, page 0/1 (offsets 0x08/0x30).
- ARM WR d_task_d : NZ, page 0/1 (offsets 0x00/0x28).
- d_dsp_page WR : 200 écritures avec n° + fn.
Build
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 de validation
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>&1contvia 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
Observations
BSP fonctionne
[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èsdsp.c:429→ cohérent avec un firmware qui programme bien le BSP.- GMSK :
|z| ≈ 32766confirme le signal complexe full-scale du modulateur gr-gsm. Bridge → gate → BSP transmet correctement. - 0 FBDET RD : sur tout le run le DSP n’entre jamais dans
[0x7730..0x7990]. Le DSP boucle dans le dispatcher0x81a7..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/14
fn=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.
Diagnostic principal
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.
Hypothèses budget DSP & famine ARM
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.
État des fichiers
| 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) |
Prochains chantiers
- Trouver pourquoi
tdma_schedule_setn’aboutit pas àl1s_fbdet_cmd. Pistes :- Logger côté ARM/QEMU les writes au queue TDMA (mais c’est de la mémoire IRAM banale, difficile à intercepter sans hooks CPU). Plus simple : patcher temporairement
prim_fbsb.c/sync.cpour ajouter desprintdau début/sortie del1s_fbsb_req,tdma_schedule_set,l1s_fbdet_cmd, et reflasherlayer1.highram.elf. - OU instrumenter dans QEMU le retour des appels via le PC : si PC entre dans
l1s_fbsb_reqsymbole connu, log.
- Logger côté ARM/QEMU les writes au queue TDMA (mais c’est de la mémoire IRAM banale, difficile à intercepter sans hooks CPU). Plus simple : patcher temporairement
- Une fois
d_task_md = FB_DSP_TASKposté, vérifier que le dispatcher DSP y répond et entre dans[0x7730..0x7990]. Le tracer FBDET RD donnera l’adresse DARAM cible, à mettre dansCALYPSO_BSP_DARAM_ADDR. - Une fois la DARAM cible connue, désactiver le mode discovery et constater (ou pas) que
d_fb_detpasse à 1 grâce au vrai code DSP — ce qui validerait la chaîne complète. - Fix propre du link
-lmdans le meson de qemu-arm-softmmu.
Mémoires associées
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 Calypso
1. Le vrai hardware Calypso
Le Calypso a deux chemins de données complètement séparés :
Chemin radio (bursts)
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.
Chemin contrôle (L1CTL / sercomm)
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.
2. Protocole sercomm (source: osmocom-bb/src/target/firmware/comm/sercomm.c)
Format trame
FLAG(0x7E) | DLCI(1) | CTRL(0x03) | DATA(N) | FLAG(0x7E)
Escaping
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
DLCIs enregistrés par le firmware layer1
| 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 |
State machine RX (sercomm_drv_rx_char)
WAIT_START ──(0x7E)──→ ADDR ──(byte)──→ CTRL ──(byte)──→ DATA
│
(0x7D)→ ESCAPE ──(byte^0x20)──→ DATA
(0x7E)→ dispatch_rx_msg(dlci, msg) → WAIT_START
Binding UART
- Compal E88 :
sercomm_bind_uart(UART_MODEM)(board/compal_e88/init.c:104) - UART modem = 0xFFFF5800 (notre calypso_uart “modem”)
- IRQ handler :
uart_irq_handler_sercommdans calypso/uart.c
Flow RX complet (vrai hardware)
UART 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
Flow TX complet (vrai hardware)
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
3. DSP Frame Dispatch (source: calypso/dsp.c)
Séquence par frame TDMA
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]
Constantes
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 paramsDSP Task IDs (l1_environment.h)
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 checksum4. BSP — Baseband Serial Port
Hardware
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
Flow DL (downlink — BTS → phone)
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++]
C54x BSP implementation (calypso_c54x.c)
// 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++]);5. Architecture QEMU — Ce qu’il faut implémenter
Chemins de données
┌─────────────────────────────────────────────────────────┐
│ 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 │
│ └──────────┘ │
└─────────────────────────────────────────────────────────┘
sercomm_gate.c — Rôle exact
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.
Bridge (sercomm_udp.py) — Deux rôles
- Bursts DL : BTS TRXD → GMSK modulation → écriture directe vers QEMU (actuellement via PTY sercomm DLCI 4 — à changer en UDP/socket direct)
- Clock : CLK IND → BTS pour synchronisation
- Bursts UL : PTY sercomm DLCI 4 → TRXD → BTS (firmware envoie les bursts UL via sercomm_sendmsg)
Problème actuel du bridge
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.
NDB d_dsp_page — Mapping mémoire
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)
6. Résumé des fichiers
| 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 |
Calypso DSP ROM Map
ROM Dump Sections
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] |
Key Code Locations
PROM1 (mirrored to 0x8000-0xFFFF)
| 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) |
PROM0 (0x7000-0xDFFF)
| 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 |
PDROM (data space 0xE000-0xFFFF, prog space via XPC=0)
| Address | Content |
|---|---|
| 0xE000+ | DSP runtime code (accessed as prog[0x8000+] with XPC=0) |
Interrupt Vector Table (IPTR=0x1FF → base 0xFF80)
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)
MVPD Locations in PROM0
16 MVPD (0x8Cxx) instructions at: 0x75C0, 0x8700, 0x8C80, 0x8CA0 These are NOT reached during the 86K-instruction boot — they’re in processing code.
Key Data Addresses (DSP data space)
| 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 |
Session 2026-04-03 — Fix LD #k9,DP (0xEA) + TDMA boot
Bug Found: 0xEA decoded as BANZ instead of LD #k9,DP
Root cause
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.
Evidence
- 0xEA appears 202 times in the Calypso DSP ROM
- 114 uses have bit7=0 (direct addressing) which is impossible for BANZ (requires indirect)
- BANZ (0x6C/0x6E) appears 304 times separately in the ROM
- Confirmed via GNU binutils tic54x-opc.c:
LD #k9,DP= opcode 0xEA00, mask 0xFE00
Fix
Replaced 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
}Impact
- DSP boot now executes 197M instructions (was 86K) — full ROM init including DARAM population
data[0x1231] = 0x115F(was 0x0000) — DARAM correctly populated with processing codePMST = 0x4D86(OVLY=1) — DSP correctly enables overlay moded_dsp_pagealternates 0x0002/0x0003 — firmware/DSP page protocol workstask_md=5(FB search) correctly dispatched to DSP
Boot approach: TDMA-tick (from GSMTAP_WORKING)
Instead 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.
Remaining issues
FB detection (result=255)
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.
Unimplemented opcodes (non-blocking, 145 hits / 197M instructions)
| 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.
Files modified
calypso_c54x.cline 810: 0xEA BANZ -> LD #k9,DPcalypso_trx.cline 135: boot approach changed to TDMA-tickcalypso_trx.cline 292: added DSP run + SINT17 in TDMA tick
Key addresses reference
- d_fb_det = NDB+0x48 = ARM 0xFFD001F0 = DSP 0x08F8
- d_dsp_page = NDB+0x00 = ARM 0xFFD001A8 = DSP 0x08D4
- Write page 0 = ARM 0xFFD00000 = DSP 0x0800
- Write page 1 = ARM 0xFFD00028 = DSP 0x0814
- Read page 0 = ARM 0xFFD00050 = DSP 0x0828
- Read page 1 = ARM 0xFFD00078 = DSP 0x083C # Calypso BSP/RIF DMA — QEMU implementation
Rôle
Sur 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.
Fichiers
| 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 |
Configuration
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.
Procédure de discovery
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.
Statut (2026-04-06)
- Infra BSP complète, build clean (link manuel
-lmrequis). - Runtime vérifié : les bursts arrivent au BSP avec une enveloppe GMSK correcte (|z| ≈ 32766),
d_spcx_rifprogrammé par ARM à0x0179à fn=0. - Discovery bloquée : le DSP n’entre jamais dans la plage PC FB-det ; il busy-loop dans le dispatcher à
0x81a7..0x81d6. La cause n’est pas un bug DSP — c’est que le firmware ARM n’écrit jamaisd_task_md = FB_DSP_TASK. VoirTODO.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
Current File Structure
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
What Was Refactored
TINT0 Extraction
- Before: Timer creation, tick callback, start/stop all inside calypso_trx.c
- After: calypso_tint0.c owns the QEMU timer. calypso_trx.c exposes
calypso_tint0_do_tick(fn)as the work callback. - Removed: calypso_tdma_hw.c/h (was a “slave” TDMA counter, never connected to build)
What Still Needs Refactoring
- Remove DSP internal timer from c54x main loop
- The per-instruction TIM/PRD/TCR tick at lines ~2490-2520 of calypso_c54x.c
- TINT0 should be the only timer source
- Remove the FN increment from timer expiry (FN comes from calypso_tint0)
- Clean up duplicate F0/F1 handler blocks in c54x
- Two identical
if (hi8 == 0xF0 || hi8 == 0xF1)blocks - The second one (starting around line 736) is dead code
- Delete the second block entirely
- Two identical
- Separate DSP instruction decoder into its own file
- calypso_c54x.c is 2600 lines and growing
- Could split: c54x_decode.c (instruction handlers), c54x_core.c (exec loop, memory, interrupts), c54x_rom.c (ROM loader)
- calypso_trx.c does too much
- Currently handles: DSP API, TPU regs, TSP regs, ULPD regs, SIM regs, bootloader protocol, burst I/O, frame processing
- Could split: calypso_tpu.c, calypso_sim.c, calypso_dsp_api.c
- Normalize header includes
- Some files use
#include "hw/arm/calypso/foo.h", others use#include "foo.h" - Standardize to one convention
- Some files use