Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 121 additions & 6 deletions docs/cell-m6.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,9 @@ the source, which still holds it. This bounds the deaf time of a failed
handover.

**Trigger.** With one radio per DU, no DU hears a UE on another channel, so
the A3 has no neighbour measurement to compare. UE measurement gaps (the UE
briefly visits the other channel and reports what it hears) are the next
step. Until then, the test forces the trigger: `RAN_HO_FORCE=SRC:UE:TGT@SECONDS`
issues the command at that time through the normal execution path.
the A3 needs the UE's own measurements: see "Measurement gaps" below.
`RAN_HO_FORCE=SRC:UE:TGT@SECONDS` remains for tests: it issues one command
at that time through the normal execution path.

**Rig result** (testrig `scripts/m6_handover.sh --channel2 157`). Cell 1 is
on ch149 and cell 2 on ch157, both UNII-3. There were 5 normal runs and 3
Expand Down Expand Up @@ -181,6 +180,116 @@ with the target killed after staging; all 8 pass.
the retune, before its own retune back. Its cause is not established. The
rig script does not count it as the post-return beacon.

## Measurement gaps (the inter-frequency trigger)

The UE measures the cells' beacons (downlink): its serving DU's all the
time, and a neighbour's on another channel in a measurement gap
(`src/cell/MeasGap.h`).

**Where to listen.** The CU sends each DU its neighbours' channels and TBTT
phases (F1 `Neighbours`, every round). Every synced DU airs them as offsets
on its own grid, in a tagged NL IE with a generation, empty lists included:

offset = (phase_neighbour − phase_serving) mod P

On the shared timebase a neighbour's beacon always falls at that offset of
the UE's serving superframe, so the UE needs nothing beyond its serving
clock.

- **Newer only:** a UE takes only a list generation at least as new as the
last it accepted.
- **Clearing:** it drops a list not heard for 3 s, and clears it on a
handover.
- **Provenance:** the NL's tag is also what lets a UE count a beacon as a
measurement of a cell. A frame merely carrying a cell-shaped address is not
measured.
- **Residual risk:** a genuine beacon replayed by someone else, which is
only ever the real cell's beacon at the replayer's power.

**The gap.** Each measurable other-channel neighbour is revisited every
`CELL_UE_GAP_SF` (10) superframes; with several, the gaps are spaced
accordingly. For each, the UE computes `gap_window()`:

[offset − retune − guard, offset + beacon + retune + guard)

The window must clear the serving beacon and the UE's own DL and UL slots.

- **Retune bound:** both retunes are planned with a conservative bound,
`CELL_UE_GAP_RETUNE_US` (30 ms), raised by any slower retune measured. A
band change, which takes the full channel path, never gets a gap.
- **No fit:** the UE skips that neighbour (`cell.ue.gap_skip`), parks it, and
re-checks it rarely. No slot is ever sacrificed.
- **Late wake-up:** if the UE wakes for a gap later than half the guard, it
skips it (`cell.ue.gap_late`).
- **Outbound overrun:** an overrun shortens the listening, never the return
budget.
- **Failed return:** a return that fails is retried. If it still fails, the
beacon is stopped and the normal retune path recovers
(`cell.ue.gap_return_fail`).
- **TSF steps after a gap:** these wait 4 superframes. On the rig, the only
uplink losses seen with gaps (twice, two beacons each, in 10 runs) followed
a gap with a TA step in the next pass; the 20 gaps without one lost
nothing. The mechanism is not established. With the hold, 8 runs and 21
gaps lost none.

**Reports.** The UE L3-filters every authenticated cell beacon it hears,
serving or neighbour. It reports the filtered RSSI/SNR, with each entry's
sample age, in a tagged MR IE in its uplink beacon, refreshed each
`CELL_UE_MR_MS` (1 s).
- **Freshness:** the MR's generation is the serving superframe, strictly
increasing. The DU takes only a generation newer than the last and near
its own count, so a replayed report is refused.
- **Relay:** the DU forwards it over F1 (`Report.ue_meas`). Ages are sample
age plus report age, each UE's serving entry goes first, and the cap is
216, whole UEs only.

**The A3 on UE reports.** For each neighbour, the CU's A3 uses one
comparable pair:
- the UE's own (serving beacon, neighbour beacon) when the MR has both
(metric `ue_rssi`);
- otherwise the DUs' (serving uplink, neighbour-heard uplink).

The A3 runs on the margins, so a neighbour the UE did not report keeps its
DU-heard evidence.

**Rig result** (testrig `scripts/m6_measgap.sh`; 5 runs, and 8 on the final
code). Cell 1 is on
ch149 at phase 0; cell 2 is on ch157 at phase 60000 µs, so its beacon falls
in an idle part of cell 1's grid. The A3 runs at its **defaults** (offset
3 dB, hysteresis 1 dB, time-to-trigger 640 ms), with nothing forced. All
pass.

| KPI | min | median | max |
|---|---|---|---|
| gap deaf time (21 gaps, final code: 30 ms retune bound + 3 ms guard) | 60.0 ms | 60.1 ms | 60.2 ms |
| neighbour beacon in gaps | −46 dBm | −46 dBm | −46 dBm |
| A3 held before firing | 1002 ms | 1024 ms | 1024 ms |
| switch after the network instant (the UE's poll latency) | 21 ms | 21 ms | 28 ms |
| interruption (sf, first in-slot uplink at the target after the switch) | 3 | 3 | 3 |

- **The trigger:** the serving beacon read −55 to −57 dBm, and the
neighbour's −46 dBm in the gaps. That beat the 3 dB offset plus hysteresis,
so the A3 fired on UE-reported values in every run.
- **The gaps cost no uplink:** the source heard at least 98 % of the
superframes before the switch.
- **The extra interruption superframe** compared with the forced runs is
the phase layout. The UE switches at the source's boundary of network
superframe N, and cell 2's superframe N starts 60 ms later. The UE's grant
there takes effect at that boundary.
- **A limit of this layout:** cell 2's UE cannot measure cell 1. Cell 1's
beacon falls at offset 42400 µs of cell 2's grid, too close to that UE's
own uplink slot, so it logs `gap_skip`.

**TSF continuity, stated precisely.** The UE switches at the network
instant, the source's boundary of network superframe N. On the target's
clock that instant is:

eff_target · P − ((phase_t − phase_s) mod P)

The UE's shifted clock reads it plus its poll latency. The rig scripts
assert exactly this, across every same-channel, inter-frequency and
measurement-gap run.

## DAPS: the duplicated uplink (same channel)

**Common sequence.** A UE uplinks once per superframe. Every DU that hears
Expand Down Expand Up @@ -277,8 +386,14 @@ superframe when there is nothing to serve.
budget.
- **A moving UE:** the rig's UEs are stationary, so the crossing is forced
with a negative A3 offset.
- **The inter-frequency trigger:** UE measurement gaps. The test forces
the handover (`RAN_HO_FORCE`).
- **Measurement-gap scheduling:** a gap goes only where it clears the UE's
own slots, so some phase layouts leave a neighbour unmeasurable. Placing
cells' phases so every pair can measure each other is a CU planning task.
- **No crossing test:** every handover result here is from stationary UEs.
The trigger is either forced or the measured standing difference between
two cells. No A→B path-loss ramp has been run, so none of these numbers
validates a crossing. An emulated crossing (the source DU ramping its TX
power, which its beacons follow) is the way to get one.
- **Security context:** the cells share one key on the rig (`CELL_PSK`).
Pre-staging a per-cell context (802.11r-style) is open.
- **EVM:** not in the measurement reports.
Loading
Loading