Skip to content

cell M6: the switch's own interruption (inter-frequency DAPS) - #560

Merged
josephnef merged 3 commits into
masterfrom
feat/m6-switch-interrupt
Nov 11, 2026
Merged

josephnef merged 3 commits into
masterfrom
feat/m6-switch-interrupt

Conversation

@josephnef

Copy link
Copy Markdown
Collaborator

Part of #260 (M6), epic #254. Follows #559.

The DL gap across an inter-frequency DAPS switch was ~2 superframes (225 ms) after #559's review fixes; the first uplink after the switch fired at the source's phase. This PR removes the switch's own share of the interruption.

UE (examples/cell)

  • The loop's pass after a switch is scheduled for the target's first superframe (+1 ms), not a full period later (the uplink beacon used to restart past that superframe's UL slot).
  • The TSF is placed for the target's UL slot right after the switch's retune (cell.ue.switch_place). Kestrel's first beacon after a TSF step fires at the old phase (predicted 57.2 ms, observed 57.3); if that stale TBTT would land in the target's active span, the restart is held past it.

DU (examples/cell)

  • pick_dl_pdus: a DL frame leads with not-yet-offered PDUs; an offered PDU goes only just before the fresh one that would push it out of the UE's reorder window while still recoverable. cell M6: DAPS across channels, downlink — the target answers each visit with its DL PDUs #559's rule (offered first once 24 sns behind) let a large offered backlog lead after the switch. Selftests: plain order, offered/fresh split, the lost-answer backlog, a 40-PDU backlog with a fresh PDU in the first frame.

CU (tools/ran-controller)

  • The target identity's UL slot is seeded from the handover command, so the first uplinks after the switch map to the right network superframe.
  • A UE's A3 is frozen while its handover is in flight: with a slow conclusion the DUs' stale evidence cleared the candidate and raised a second one for the same move (seen on the rig in m6_three_ho; no second command).

Rig (testrig m6_daps_xch.sh, 4 runs per case, cells on 149/157):

case UL lost before the switch DL gap across the switch
source dies, visits 0 (all 4) 122–123 ms
source dies, no visits 10–16 1498–2010 ms
source alive, visits 0 (all 4) 123 ms

Re-run on the final build (2 per case): 122–123 ms in 3 of 4 visit runs, 225 in one, where the first frame after the switch had no new PDU yet (100 ms PDU cadence); details in docs/cell-m6.md. Regression on the rig: measgap, three_ho (3 runs), crossing3, handover, daps, daps_dl, crossing all pass.

🤖 Generated with Claude Code

josephnef and others added 2 commits November 11, 2026 04:16
…e retune, fresh DL first, A3 frozen in flight

The UE loop's pass after a switch is scheduled for the target's first
superframe; the TSF is placed for the target's uplink slot right after the
switch's retune (Kestrel's first beacon after a TSF step fires at the old
phase: guarded against the target's active span); the CU seeds the target
identity's uplink slot from the handover command; a DL frame after a switch
leads with not-yet-offered PDUs (an offered one only just before the fresh
PDU that would push it out of the UE's reorder window); a UE's A3 is frozen
while its handover is in flight.

Rig (m6_daps_xch, 4 runs per case): DL gap across the switch 122-123 ms
with visits (225 before), no uplink lost before the switch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Reduce inter-frequency DAPS switch interruption

🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Restart the UE on the target’s first superframe and align uplinks without a stale-phase collision.
• Prioritize fresh downlink PDUs while preserving recoverable copies within the reorder window.
• Seed target uplink timing, freeze in-flight A3 decisions, and document rig results.
Diagram

sequenceDiagram
    participant CU as Controller
    participant SDU as Source DU
    participant UE as UE
    participant TDU as Target DU
    CU->>SDU: Handover command
    SDU->>UE: Switch instruction
    UE->>UE: Retune and place TSF
    UE->>TDU: Target-slot uplink
    TDU->>TDU: Select fresh-first PDUs
    TDU->>UE: Downlink frame
    TDU->>CU: UE report
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Explicitly schedule the first uplink
  • ➕ Could avoid relying on Kestrel’s stale-phase beacon timer.
  • ➖ Requires a different radio transmission path and precise scheduling guarantees not established by this PR.

Recommendation: Keep the targeted TSF placement and collision guard for the existing beacon path. Explicit uplink scheduling is worth considering only if the radio interface can guarantee its timing; it would substantially widen this change.

Files changed (5) +196 / -20

Bug fix (3) +101 / -12
cell.hSend fresh PDUs before non-urgent offered backlog +26/-10

Send fresh PDUs before non-urgent offered backlog

• Changes downlink queue selection so offered PDUs precede fresh ones only when the next fresh PDU would otherwise push a still-recoverable copy outside the UE’s reorder window. This lets a fresh PDU reach the first target frame despite a large offered backlog.

examples/cell/cell.h

main.cppAlign the first post-switch UE pass and beacon +63/-2

Align the first post-switch UE pass and beacon

• Schedules the next pass for the target’s first superframe and places the TSF for its uplink slot after an inter-frequency retune. If Kestrel’s first, old-phase beacon would enter the target’s active span, delays beacon restart until that transmission opportunity passes.

examples/cell/main.cpp

main.cppPreserve handover A3 state and seed target uplink timing +12/-0

Preserve handover A3 state and seed target uplink timing

• Skips A3 reevaluation for a UE with a handover in flight, preventing stale reports from clearing and re-raising its candidate. Seeds the target identity’s uplink slot from the handover command before the target DU reports the UE.

tools/ran-controller/main.cpp

Tests (1) +12 / -2
cell_node_selftest.cppCover fresh-first downlink ordering with a large backlog +12/-2

Cover fresh-first downlink ordering with a large backlog

• Adds a 40-PDU offered-backlog case asserting that the next fresh PDU appears in the first frame, after the one offered PDU made urgent by the reorder window. Updates the queue-order test description.

tests/cell_node_selftest.cpp

Documentation (1) +83 / -6
cell-m6.mdDocument the switch interruption and measured results +83/-6

Document the switch interruption and measured results

• Explains the old-phase first beacon, the timing and queue-order changes, and why target uplink mapping and A3 freezing matter. Records rig results, including a final-build run whose 225 ms gap was attributed to the 100 ms PDU cadence.

docs/cell-m6.md

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Lost visit answers leave downlink gaps ✓ Resolved
Description
pick_dl_pdus sends only the oldest offered PDU still within 32 sequence numbers of the offered
mark before a fresh PDU, even if the UE never received the offered answer. With offered sequences
100–139, a UE whose reception top is 99 receives 107 and then 140, causing its reorder window to
confirm 100–106 missing before their copies arrive.


Remediation recommended

2. A collision guard skips another uplink ✓ Resolved
Description
switch_place always puts the predicted stale beacon TBTT in the next target superframe, even when
that phase is still ahead in the current one. When the stale phase is ahead of the post-retune time
and after the target UL slot, beacon_hold_until extends past the following UL slot and the restart
guard suppresses its beacon too.

3. A failed clock write misplaces uplinks ✓ Resolved
Description
switch_place records a failed WriteTsf() in ok but neither retries placement nor prevents the
beacon from restarting. If the radio rejects that write after an inter-frequency retune,
place_ul_slot is discarded and the first beacon can use the old phase; the ordinary correction
requires a subsequent pass with a target grant.

4. One clock read loses the expedited pass ✓ Resolved
Description
run_ue clears next_pass_at before the scheduled pass reads the radio clock. If
ReadTsfAndFreeRun() fails on that pass, its continue returns to the normal 100 ms sleep instead
of promptly retrying, so the target's first beacon opportunity can pass unused.

View review recommended (1)
5. Failed handovers can bypass trigger delay ✓ Resolved
Description
The new in-flight continue skips A3Trigger::evaluate(), including the evaluation that would
clear a timer when neighbour evidence becomes stale. If that evidence expires during a flight but
returns after a timeout, the timeout removes only the flight, so the retained timer can immediately
qualify the same move without a new time-to-trigger interval.

Grey Divider

Tip of the day
💡 Did you know, you can import rules and skills only from the repositories your team follows

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

…s before the fresh one, placement retried, expedited pass kept, A3 restarted after a failed flight

- pick_dl_pdus: an answer may have been lost, so every queued offered PDU the
  fresh one would push out of the UE's reorder window goes first (selftest:
  a 40-PDU backlog, every answer lost, through DlStreamRx: no gap).
- switch_place: the stale TBTT counted from the expedited restart; a failed
  clock read/write keeps the placement pending, retried before the restart.
- The expedited pass survives a failed clock read (2 ms retry, bounded).
- The CU restarts a frozen A3 when its flight ends without the move.
- cell.dl logs sn0/fresh/offered after visit answers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@josephnef

Copy link
Copy Markdown
Collaborator Author

Review fixes pushed:

  1. Lost answers — fixed. Every queued offered PDU that the fresh one would push out of the UE's reorder window now goes first, since the offered mark is not proof of the UE's top. New selftest: a 40-PDU backlog with every answer lost, fed through DlStreamRx, confirms no gap. On the rig the queue after a switch spans ~39 sns, so ~7 PDUs go first and the fresh one still rides the first frame (now logged: cell.dl sn0/fresh/offered).
  2. Guard hold — the stale TBTT is now counted from the beacon's restart (the expedited pass at the target's next superframe +1 ms), not from the retune. A stale phase still ahead in the current superframe cannot fire there, because the beacon is stopped until that pass. Ending the hold at that instant would let the restart's first beacon collide in the next superframe. The anchor is explicit now, and it also covers a stale phase before the restart's +1 ms.
  3. Failed TSF write — fixed: the placement stays pending (cell.ue.switch_place_fail), is retried before the beacon restart (3 tries, 2 ms apart), and then falls back to the ordinary step.
  4. Expedited pass — fixed: a failed clock read or an unready sync retries 2 ms later (5 times at most), not at the 100 ms interval.
  5. A3 frozen through a failed flight — fixed: when a flight ends without the move (lost DU, timeout), the source key's A3 restarts, so a new candidate needs its own time-to-trigger. A refused command keeps the previous immediate re-command.

Rig on the fixed build: m6_daps_xch 3 runs per case passes (switch gap 123 ms in 4/6 visit runs; the two 225 ms runs had no fresh PDU queued yet at the first frame — cadence, per the new log). Regression passes: three_ho, measgap, crossing3, handover, daps, daps_dl, crossing.

@josephnef josephnef added the skip-qodo-gate Bypass the Qodo review gate (outage / maintainer decision) label Nov 11, 2026
@josephnef

Copy link
Copy Markdown
Collaborator Author

Qodo's re-review: all 5 findings resolved, 0 new; only alternatives remain. Adding skip-qodo-gate.

@josephnef
josephnef merged commit a232002 into master Nov 11, 2026
30 of 31 checks passed
@josephnef
josephnef deleted the feat/m6-switch-interrupt branch November 11, 2026 06:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-qodo-gate Bypass the Qodo review gate (outage / maintainer decision)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant