Repository navigation
Description
Umbrella / tracking issue for building a 5G-NR-style scheduled Radio Access Network on devourer: time-synced AP cells serving multiple scheduled UEs (robots/drones), with per-UE timing advance, coordinated scheduling, and make-before-break handover — riding the bench-proven per-link primitives and modelled on 5G NR design decisions (not LTE).
docs/multi-ap-cellular.md promotes the architecture; this EPIC turns it into a step-by-step build. The per-milestone write-ups are docs/scheduled-mac.md (M0) and docs/cell-m1.md … docs/cell-m6.md. Rig orchestration and results are in the testrig repo (scripts/m*_*.sh, results/).
Status (2026-11-12)
Built and run on the testrig: three Topton J6412 boxes, each with two RTL8852CE (Kestrel) over PCIe/vfio, plus an N300 hub that is PTP grandmaster and CU host (a star of direct i226 links, no switch in the timing path). On the rig:
- three PTP-locked cells, on three channels, with UEs scheduled in fixed slots;
- HARQ and link adaptation;
- RACH join and departure;
- CU-coordinated schedules and phase planning;
- measurement gaps;
- conditional handover, and DAPS on one channel and across channels;
- per-cell keys;
- UL and DL sequence handling across handovers.
What remains:
- the QoS scheduler (M1);
- runtime channel moves (M5);
- the coarse/USB tier (Coarse-tier (USB) DU on a PTP node — the M4/M5 items split out of the PCIe tier #565);
- M6 follow-ups (Follow-ups left open by M6 (#260): EVM, roam baseline, the named test, residuals #566);
- a moving-UE bench (M6 test realism — a moving-UE handover bench (conducted path-loss matrix, then an over-the-air walk) #563).
The primitives (this is the multi-user/multi-cell layer)
Bench-proven and library-stable:
StartBeaconandUpdateBeaconPayload, a beacon body update measured frame-atomic on air (docs/scheduled-mac.md);PinBeaconTbtt, the absolute TSF-preserving TBTT actuator that closed an AP↔PTP loop to ~0.67 µs on the 8821CE;- on TSF-locked-TBTT parts (Kestrel/AX), TSF steps against the free-running counter (kestrel: multi-cell beacon sync on one PTP timebase — free-running-counter TSF discipline + on-air observer (#258) #513; TBTT p95 3–4 µs);
- a deterministic beacon departure,
SetBeaconBackoff(kestrel: IRadio::SetBeaconBackoff — deterministic hardware-beacon departure #514); TsfSync,WriteTsf/ReadTsfAndFreeRun,Packet::TxEgressTsf;- hardware ACK (
SetAckResponder, the 8852C responder included), A-MPDU (SetAmpduMode), per-frame TX status (TxReport, where the chip has it; Kestrel does not), per-packet rate/power,GetRxQuality; FastRetune/FastSetBandwidth/SetCcaMode.
5G-NR mapping
| 5G NR | Our realization |
|---|---|
| gNB (cell) | a devourer AP node |
| CU / DU split | DU = AP node (src/cell/ MAC + examples/cell/): per-cell scheduling, HARQ, TA. CU = tools/ran-controller/: multi-cell coordination, mobility, admission |
| SSB | the hardware beacon (StartBeacon) |
| DCI / grant (PDCCH) | beacon-carried grant map {ue, dl_slot, ul_slot, rate, pwr, ta}, with an epoch and its effective superframe (src/cell/GrantMap.h, updated with UpdateBeaconPayload) |
| Timing advance | a per-UE TA loop (TaLoop). The UE's uplink is its hardware beacon, steered through its TSF on Kestrel; PinBeaconTbtt can't hold an offset on TSF-locked parts |
| Numerology / mini-slots | flexible slot lengths + low-latency mini-slots |
| Bandwidth Part (BWP) | narrowband 5/10 MHz via FastSetBandwidth |
| Dynamic TDD | per-cell flexible DL/UL split |
| QoS flows / 5QI | per-UE 5QI class → HARQ budget, DL discard timer, link adaptation, admission priority (src/cell/CellScheduler.h; the fixed grid gives each UE one slot pair, so not slot allocation) |
| 2-step RACH | fast contention join (src/cell/Rach.h) |
| F1 interface | DU↔CU UDP control link (src/cell/F1.h, keyed) |
| Xn interface | direct DU↔DU coordination (inter-cell, M5/M6) |
| TSN / 802.1AS | PTP backhaul: each DU holds its beacon grid to its PHC (src/cell/TsfDiscipline.h service mode) |
| Measurement gaps | idle and suspend gaps for inter-frequency measurement (src/cell/MeasGap.h), with phase planning by the CU (src/cell/PhasePlan.h) |
| CHO | an A3 condition at the CU on filtered measurements (offset, hysteresis, time-to-trigger), two-phase command, TSF continuity, conditional fallback (src/cell/Handover.h, HoCommand.h) |
| DAPS handover | UL duplication with CU dedup, DL duplication to the target. Across channels: sequential UE visits to the target's channel, which answers with DL |
| PDCP SN / status report | UL SeqDedup; DL DlStreamRx; the UE's DL status report in its beacon (UeDlStatus), so a target sends only what the UE lacks |
| Security context (802.11r / NR key derivation) | per-cell, area and UE keys from one master; the CU wraps the target cell's key per handover (src/cell/Keys.h) |
Architecture boundary (enforced in every milestone)
-
src/cell/— the per-cell (DU) scheduled-MAC library (reusable):SlotGrid,GrantMap,UeRegistry,UeRxAttribution,Harq,LinkAdapt,Rach(M1–M3);TsfDiscipline,SyncHealth(M4);F1,Coord,PhasePlan(M5);Handover,HoCommand,MeasGap,Keys(M6).
It adds nothing to the radio core unless a bench test proves userspace can't. (
CellScheduler: 5QI classes;TimingAdvance: the per-UE TA loop, held per UE inUeRegistry'sUeContext.) -
examples/cell/— a reference gNB-DU + UE node. -
tools/ran-controller/— the CU / backhaul app (multi-cell scheduler, coordination, CHO/DAPS). Distributed software over the F1 control link (with direct Xn DU↔DU coordination where lower latency helps); never touches the radio. (Note: this is an NR-inspired centralized facility scheduler, not a faithful 3GPP CU — it owns scheduling, not PDCP/RRC.)
Rule: logic that would still make sense with a different radio underneath is NOT in the radio core.
Milestones (coarse; each demoable, implemented step by step)
Phase 0 — feasibility contracts (measure, don't assume):
- M0 M0 — Feasibility contracts (measure before building the cell) #261 — Feasibility contracts (DL departure jitter, dynamic grant delivery, unicast-ACK/
TxReportmatrix, per-UE RX attribution) — blocks M1
Phase A — single gNB-DU (one cell, N UEs):
- M1 M1 — Single-cell scheduled MAC (src/cell/ DU library + examples/cell node) #255 — Single-cell scheduled MAC (
src/cell/library + demo) - M2 M2 — Reliability & link adaptation (per-UE ACK, HARQ, rate/power) #256 — Reliability & link adaptation (per-UE ACK, HARQ, rate/power)
- M3 M3 — Admission: association + 2-step RACH #257 — Admission: 2-step RACH and departure (association descoped)
Phase B — multi-cell RAN (CU + mobility):
- M4 M4 — Network-wide time sync across cells (TSN/802.1AS-style) #258 — Network-wide time sync across cells (TSN/802.1AS-style) — closed on the PCIe tier
- M5 M5 — CU + coordinated scheduling (tools/ran-controller/) #259 — CU + coordinated scheduling — closed on the PCIe tier; runtime channel moves in cell M5: move a running cell to another channel (runtime channel separation) #567
- M6 M6 — Mobility: CHO + DAPS handover #260 — Mobility: CHO + DAPS handover
- M6 test realism M6 test realism — a moving-UE handover bench (conducted path-loss matrix, then an over-the-air walk) #563 — Moving-UE handover bench: a conducted path-loss matrix (UL and DL follow a trajectory, fading, several UEs), then an over-the-air walk
- Coarse-tier (USB) DU on a PTP node — the M4/M5 items split out of the PCIe tier #565 — Coarse-tier (USB) DU on a PTP node (split out of M4/M5)
- Follow-ups left open by M6 (#260): EVM, roam baseline, the named test, residuals #566 — Follow-ups left open by M6 (EVM, roam baseline, the named test, residuals)
Dependency chain: M0 → M1; M1 → M2, M3, M4; M4 → M5 → M6 → #563 (test realism).
Cross-cutting deliverables (from the architecture review)
- Slot-budget accounting — a valid slot = PSDU airtime + PHY preamble + SIFS/ACK + retry allowance + A-MPDU duration + radio turnaround + retune/settling + timing guard. Payload size alone is not a budget. Done in
src/cell/SlotGrid.h(A-MPDU airtime not modelled); a DL frame that would start past its slot's guard is not aired. - A versioned on-air protocol envelope — protocol version, cell+UE identity, superframe/slot number, schedule epoch, packet/HARQ sequence, integrity + replay protection. Done:
kCellProtoVersion(v5), cell-key tags on every cell frame, area tags for neighbours, replay floors. Integrity is on only with a key configured. - Fault detection — watchdogs for lost PTP sync, stalled beacon engine, missed schedule epochs, CU/backhaul outage, excessive TX-queue delay, over-budget retunes. Partly done:
- loss of PTP sync (
SyncHealth, auto-suspend), CU outage (DUs go autonomous), stale or late schedules (acks), and late DL frames are handled; - a stalled beacon engine is detected only indirectly, through the UEs' uplinks, and there is no general over-budget-retune alarm.
- loss of PTP sync (
- KPI-based validation — report distributions not averages: deadline-miss ratio, slot-arrival phase, collision rate, handover interruption, DAPS duplicate gain, per-UE fairness, clock holdover, control-plane update latency. Mostly done in the
docs/cell-m*.mdtables; still missing: per-UE fairness, a stated DAPS duplicate gain and clock holdover.
Out of scope / relationships
- OFDMA / UL-MU / MU-MIMO (5G air interface) = Kestrel 11ax, 802.11ax trigger-based UL + TWT (Kestrel / RTL8852) — scheduled contention-free TX, the standards answer to the TX-egress gap #236. OUT. UL is TDMA-only throughout; numerology/BWP are time/bandwidth analogs, not real SCS.
- PCIe: RTL8822BE (Jaguar2) + RTL8822CE (Jaguar3) support — bring-up recipe for when lab hardware arrives #214 (J3 PCIe) = optional tighter TBTT actuator for a production cell.
- Timing benchmark: SDIO vs USB (RTL8733BS vs 8733BU) — confirm/refute the transport-latency hypotheses #235 (SDIO) = the FPV-transport path.
- SPIKE: ARM cell-node feasibility for warehouse-scale RAN (PTP quality, vfio/SMMU, USB-tier PinBeaconTbtt discipline) #381 (ARM cell-node spike) — its USB-tier gate is the same question as Coarse-tier (USB) DU on a PTP node — the M4/M5 items split out of the PCIe tier #565.
- Multi-receiver ground station: N radios / N channels / one timebase — nothing measured beyond 2 TX → 1 RX #457 (multi-receiver ground station: N radios / N channels / one timebase) — the many-transmitters-into-one-site side of the same problem.
- It shares M4's single PTP timebase (its "N nodes over Ethernet" shape is the
testrigfleet) and M5's per-channel planning. - It extends M0's per-UE RX attribution from 2 transmitters to 10–25.
- M6 test realism — a moving-UE handover bench (conducted path-loss matrix, then an over-the-air walk) #563's attenuator matrix would serve it too.
- It shares M4's single PTP timebase (its "N nodes over Ethernet" shape is the
Standing risk register (status 2026-11-12)
- Deterministic downlink (M0/M1, headline): retired. Kestrel's DL departure was measured in M0, and in M1 the DL air start was p50/p99 31/41 µs after its slot start. A rare multi-ms
send_packettail remains, so a RACH slot is kept away from the next beacon. - No atomic beacon update (M0/M1): retired.
UpdateBeaconPayloadexists on every family and is frame-atomic on air (not atomic against the TBTT; it airs on the next or next-but-one TBTT). The grant map rides the beacon with an epoch and its effective superframe. - Unicast ACK per node, not multi-MACID (M0/M2): retired. One responder per node; the 8852CE responder measured 1036/1036. Kestrel has no per-frame
TxReport, so HARQ runs on explicit UE feedback. GetRxQualityis device-wide: addressed differently.UeRxAttributionexists, but link adaptation uses the SNR the UE reports for the downlink.send_packetUL has no sub-slot air-departure control: followed. The UL is the UE's hardware beacon, steered through its TSF: 1.4–1.8 µs rms.- ~ms
FastRetune: measured, 23.6 ms on the 8852CE. Different-channel DAPS is sequential (visits), with measurement gaps budgeted at 26 + 2 ms. - Timing advance corrects clock/actuation phase, not propagation: as designed.
- PCIe vs USB sync tiers: the tight tier is done, with loss-of-sync auto-suspend; the coarse tier is unmeasured (Coarse-tier (USB) DU on a PTP node — the M4/M5 items split out of the PCIe tier #565).
- A3/CHO tuning: hysteresis and time-to-trigger on filtered quality. In the selftest the filtered A3 made one handover per crossing (raw RSSI: 30.6); on the rig, 0 in the hover case. Fading is still untested (M6 test realism — a moving-UE handover bench (conducted path-loss matrix, then an over-the-air walk) #563).
- Half-duplex UE turnaround: budgeted in the grid (
turnaround_us).
Architecture review folded in: this epic and its milestones were revised per an external review — M0 added, terminology corrected (F1/Xn), the multi-MACID framing corrected, M1 numerology restricted to fixed, and cross-cutting deliverables added. Refreshed 2026-11-12 against what was built.
Activity
M0: measured feasibility contracts — DL guard, dynamic beacon grants,…
josephnef commentedon Aug 4, 2026
Test-rig hardware spike filed: #381 — ARM cell-node feasibility for warehouse scale-out (PTP quality on ARM MACs, vfio/SMMU gate for the PCIe transport, and the unmeasured
PinBeaconTbtt-over-USB discipline number that decides whether coarse-tier cells need PCIe at all). Doesn't gate M0–M6; informs the fleet platform choice for Phase B deployment.josephnef commentedon Sep 28, 2026
A concrete external customer for Phase A/B has turned up, with the traffic direction reversed from the warehouse case: a film-set multi-camera preview link — 2–25 cameras (HDMI/SDI in, hardware H.265) streaming 1080p60 into one video village at ~100 ms, 1–1.5 km. Almost all traffic is uplink (camera → tent). Conceptual write-up on branch
docs/film-set-preview-link: docs/film-set-preview-link.md.How it lands on the milestones:
PinBeaconTbttdiscipline number is the one that decides whether the camera box can carry a cell's timebase itself.Two things the roadmap does not cover that this customer needs, filed separately: a multi-receiver / multi-channel ground-station story (nothing measured beyond 2 TX → 1 RX today), and the HDMI/SDI ingest on the encoder side (ecosystem-wide gap, not a devourer item). Range at 1 km is unmeasured everywhere in the project and is the dominant risk; the write-up says so in the same sentence as every number.
docs: film-set multi-camera preview link - capacity, latency budget, …
kestrel: WriteTsf + PinBeaconTbtt for the RTL8852CE, and a TSF-steppi…
kestrel: IRadio::SetBeaconBackoff — deterministic hardware-beacon dep…
M0 contracts on Kestrel/PCIe: beacon update/stop, PPDU-status pairing…
cell: M1 core — SlotGrid (slot-budget accounting, fixed grid) + Grant…
cell: M1 core — SlotGrid (slot-budget accounting, fixed grid) + Grant…
examples/cell: M1 reference DU / UE / witness node + 2-UE on-air test (…
cell: M2 core — software HARQ with UE feedback + per-UE link adaptati…
examples/cell: M2 — DL HARQ from UE feedback + per-UE link adaptation…
cell: M3 core — authenticated 2-step RACH + fixed-position UE registry (
examples/cell: M3 — join a running cell through the authenticated RAC…
23 remaining items
cell M6: suspend gaps — phase planning beyond two cells (#546)
cell M6: three cells on the rig — suspend-gap scheduling fixes (#547)
cell M6: handover among three cells — A3 strongest-only, no re-plan m…
cell M6: a UE crossing three cells — suspend-aware measurement stalen…
cell M6: DAPS across channels — sequential uplink visits to the targe…
cell M6: DAPS across channels, downlink — the target answers each vis…
cell M6: the switch's own interruption (inter-frequency DAPS) (#560)
cell M6: the DL cadence at a DAPS switch (#561)
cell M6: a handover arrival's DL — the UE's DL status report (#562)
docs cell M3: departure measured on the rig; association descoped (#564)
cell M5: move a running cell to another channel (runtime channel sepa…
cell M6 test benches: placement-independent rig suites (CELL_UE_MEAS_…
cell M1: QoS classes (CellScheduler) — 5QI sets each UE's HARQ budget…
cell M1: per-UE state in the DU library (UeRegistry UeContext: TA loo…
cell M6: EVM over F1 (filtered uplink EVM in the measurement reports,…