Skip to content

cell M6: RACH re-join after a DU restart; Kestrel StopBeacon clears the TBTT protection - #538

Merged
josephnef merged 2 commits into
masterfrom
feat/m6-rach-rejoin
Nov 6, 2026
Merged

josephnef merged 2 commits into
masterfrom
feat/m6-rach-rejoin

Conversation

@josephnef

Copy link
Copy Markdown
Collaborator

M6 (#260, EPIC #254): a UE that joined through the RACH re-joins after its DU restarts. Closes the open item from #536. It also includes a Kestrel driver fix that this work turned up.

Why

A RACH-joined UE holds an id that a restarted DU instance doesn't know. After re-attaching (#536), the UE was in no map and was never heard again.

What

  • UE (examples/cell): a joined UE that the authentic map in force has not granted for CELL_UE_REJOIN_SF (30) superframes drops its id and joins again, logged as cell.ue.rejoin. That covers both a DU that restarted and one that released it.
    • Its uplink beacon goes off, its ACK responder is cleared, and the old id's DL HARQ space goes.
    • It gets a new RACH client with a fresh nonce. A DU answers a repeated (MAC, nonce) request from its cache, which would be the old id.
    • It keeps the stable joiner MAC.
    • The rule is skipped mid-handover. Static UEs never re-join.
  • Kestrel driver fix (HalKestrel::stop_beacon): StopBeacon cleared the beacon-TX enable but left the TBTT protection that StartBeacon turns on (TBTT_PROHIB_EN, BRK_SETUP). The port kept holding injected frames to its old beacon grid.
    • On the rig, every re-join msgA arrived at a fixed ~29.9 ms into the superframe, past the RACH slot. It drifted with the UE's own crystal, whatever offset the UE drew.
    • An A/B with the beacon left running landed in slot.
    • stop_beacon now clears both bits. The value of the ~13 ms hold, presumably BCN_HOLD, is not separately established.
  • Docs: docs/cell-m6.md gains a section on re-joining through the RACH, and the StopBeacon contract note in docs/scheduled-mac.md covers Kestrel.

Rig (testrig scripts/m6_rejoin.sh, keyed, 3/3 pass)

UE1 joins cell 1 through the RACH, and DU1 is killed and restarted under it 35 s in.

KPI value
re-attach, then one re-join every run
second join after the restarted DU's service start −4 to +28 sf (median 11)
its RACH attempts 4–8 (the first fall in the restarted DU's acquisition)
new id's uplink at the restarted DU, from 10 sf after the join heard 100 %, in slot 100 %

Counterfactual (CELL_UE_REJOIN_SF=0): the UE keeps a dead id and is never heard.

Regression for the StopBeacon change:

  • inter-frequency handover 2, plus fallback 1 (each a beacon stop and restart);
  • measurement-gap handover 2;
  • clock-fit restart and transient 1 + 1;
  • same-channel handover 1.

All pass.

🤖 Generated with Claude Code

…on clears the TBTT protection

A UE that joined through the RACH holds an id a restarted DU instance does
not know: after re-attaching it was in no map. Now a joined UE that the
authentic map in force has not granted for CELL_UE_REJOIN_SF (30)
superframes — its DU restarted or released it — drops the id and joins
again (cell.ue.rejoin): uplink beacon off, ACK responder cleared, the old
id's DL HARQ space gone, a new RACH client with a FRESH nonce (a DU answers
a repeated (MAC, nonce) request from its cache: the old id). Not
mid-handover; static UEs never re-join.

Kestrel driver fix found by it: StopBeacon cleared BCNTX_EN but left the
TBTT protection StartBeacon turns on (TBTT_PROHIB_EN, BRK_SETUP); the port
kept holding injected frames to its old beacon grid, so every re-join msgA
arrived ~29.9 ms into the superframe, past the RACH slot (an A/B with the
beacon left on landed in slot). stop_beacon now clears both.

Rig (testrig scripts/m6_rejoin.sh, keyed, 3/3): re-attach, one re-join, the
second join 4-8 attempts, the new id heard 100 % in slot by the restarted
DU; with CELL_UE_REJOIN_SF=0 never heard. Regression for StopBeacon:
inter-frequency handover 2 + fallback 1, measurement gaps 2, clock fit 1+1,
handover 1 — all pass.

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

Re-join RACH UEs after DU restart and clear Kestrel TBTT protection

🐞 Bug fix ✨ Enhancement 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Re-join RACH UEs after prolonged absence from authenticated grants, recovering from DU restarts or
 releases.
• Clear Kestrel TBTT protection when stopping a beacon so re-join requests reach the RACH slot.
• Document the recovery behavior and keyed rig results.
Diagram

graph TD
  M["Authenticated map"] --> E{"Rejoin eligible?"} -->|Yes| K["Stop beacon"] --> R["Reset UE state"] --> J["Fresh RACH"] --> D["DU join"]
  E -->|No| U["Keep current ID"]
  D --> M
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Restore DU join state across restarts
  • ➕ Could preserve the UE ID and avoid a new RACH exchange after a restart.
  • ➖ Requires durable or shared DU state.
  • ➖ Does not address a DU that deliberately releases the UE.

Recommendation: Keep the UE-side absence rule: it handles both restart and release without adding DU state persistence. The Kestrel fix is also necessary so requests sent after stopping the beacon can reach the RACH slot.

Files changed (4) +103 / -6

Enhancement (1) +44 / -2
main.cppRe-join when a RACH UE loses its grant +44/-2

Re-join when a RACH UE loses its grant

• Adds a configurable absence threshold for RACH-joined UEs, excluding static UEs and active handovers. On expiry, stops the beacon, clears old-ID receive and ACK state, and starts a new RACH attempt with the stable MAC and a fresh nonce.

examples/cell/main.cpp

Bug fix (1) +3 / -0
HalKestrel.cppDisable TBTT protection when stopping a beacon +3/-0

Disable TBTT protection when stopping a beacon

• Clears TBTT_PROHIB_EN and BRK_SETUP alongside beacon transmission enable, undoing the protection enabled by start_beacon so subsequent injected frames are not held to the old beacon grid.

src/kestrel/HalKestrel.cpp

Documentation (2) +56 / -4
cell-m6.mdDocument RACH re-join behavior and rig results +51/-4

Document RACH re-join behavior and rig results

• Explains when a RACH-joined UE discards its ID, what state it resets, and why the join nonce changes. Records the Kestrel timing diagnosis and keyed rig results, and removes the resolved open item.

docs/cell-m6.md

scheduled-mac.mdClarify Kestrel StopBeacon's TBTT cleanup contract +5/-0

Clarify Kestrel StopBeacon's TBTT cleanup contract

• Notes that stopping a Kestrel beacon must also disable TBTT protection, which otherwise delays later injected frames past the RACH slot.

docs/scheduled-mac.md

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

qodo-free-for-open-source-projects Bot commented Nov 6, 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


Remediation recommended

1. A failed stop leaves stale beacons airing ✓ Resolved
Description
run_ue proceeds to clear the responder and replace the UE identity even when StopBeacon()
returns false. If the Kestrel port write fails, its autonomous beacon may remain active during RACH
attempts, and the later grant path treats it as an existing beacon to update rather than starting a
clean one.

Grey Divider

Tip of the day
💡 Did you know, you can switch off images and animations for a plain-text comment

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

…ext pass, logged once) instead of a RACH beside the old beacon

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

Copy link
Copy Markdown
Collaborator Author

Qodo review addressed in f046721: a failed stop leaves stale beacons airing — the re-join now requires the old id's beacon to be stopped first. A failed StopBeacon() defers the whole transition (identity, nonce, RACH client, ACK responder untouched) to the next pass, with the absence kept so it retries, and logs cell.ue.rejoin_stop_fail once. ctest 95/95; rig re-check (m6_rejoin.sh restart case) passes: re-attach, one re-join, the new id heard 99.8 % / in slot 100 %.

@josephnef

Copy link
Copy Markdown
Collaborator Author

CI green; Qodo's bug resolved (f046721). The remaining item is the high-level alternative (persist DU join state across restarts), for which Qodo recommends this PR's UE-side absence rule (it covers restart and release without DU state persistence). Merging with skip-qodo-gate.

@josephnef josephnef added the skip-qodo-gate Bypass the Qodo review gate (outage / maintainer decision) label Nov 6, 2026
@josephnef
josephnef merged commit 7e73826 into master Nov 6, 2026
27 of 28 checks passed
@josephnef
josephnef deleted the feat/m6-rach-rejoin branch November 6, 2026 15:27
@josephnef josephnef mentioned this pull request Nov 6, 2026
3 of 5 tasks
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