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
55 changes: 51 additions & 4 deletions docs/cell-m6.md
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,57 @@ pass).
position showed as DL out of slot (0.05 in slot); the frames themselves
were right. It now uses the grant in force.

## Re-joining through the RACH after a DU restart

Re-attachment (above) brings back a static UE: the restarted DU grants its
id again. A UE that joined through the RACH holds an id the new DU instance
does not know, so after re-attaching it is in no map.

- **The rule:** 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
(`cell.ue.rejoin`). That covers a DU that restarted and one that released
it. The rule is skipped mid-handover; static UEs never re-join.
- **What the re-join resets:** its uplink beacon goes off and its ACK
responder is cleared. The DL HARQ space of the old id goes. The RACH
client is new, with a **fresh nonce**: a DU answers a repeated (MAC,
nonce) request from its cache, which would be the old id.
- **The stable joiner MAC** (`CELL_JOINER_MAC`, or derived from host and
radio) is kept. A DU that still holds the UE's registry entry gives the
same position back.

**A driver bug it found.** The first re-joins never got an answer. Every
msgA arrived at the same phase (~29.9 ms, drifting with the UE's own
crystal), past the RACH slot, whatever offset the UE drew.
- **Not the cause:** the UE's timing checks passed.
- **The A/B:** with its uplink beacon left running, the msgA landed in slot.
- **The cause:** Kestrel's `StopBeacon` cleared the beacon-TX enable but
left the TBTT protection `StartBeacon` turns on (`TBTT_PROHIB_EN`,
`BRK_SETUP`). The port went on holding injected frames to its old beacon
grid. `StopBeacon` now clears both.
- **Not established:** that 400 × 32 µs of `BCN_HOLD` after the UE's TBTT
is exactly the ~13 ms observed.

**Rig result** (testrig `scripts/m6_rejoin.sh`).
- **The case:** UE1 joins cell 1 through the RACH (`CELL_UE=0`, an 8 ms RACH
slot), and DU1 is killed and restarted under it 35 s in. Keyed, as all
runs now are. 3 runs, all pass.

| KPI (3 runs) | 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, its grid still moving) |
| uplink of the new id at the restarted DU, from 10 sf after the join | heard 100 %, in slot 100 % |

- **With `CELL_UE_REJOIN_SF=0`** (the counterfactual, 1 run), the UE keeps a
dead id and the restarted DU never hears it.
- **Regression for the `StopBeacon` change,** all passing:
- 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.

## Grant-map authentication, and the suite keyed

The grant map IE (v2, `src/cell/GrantMap.h`) carries the cell key's tag
Expand Down Expand Up @@ -580,10 +631,6 @@ superframe when there is nothing to serve.
running ones, which never move. With three or more cells on different
channels, the 26 ms retune leaves no layout where every pair can measure
both ways. Re-phasing live cells, or longer superframes, is open.
- **A DU restart under RACH-joined UEs:** re-attachment (above) is for the
rig's static UEs, whose id the restarted DU grants again. A UE that joined
through the RACH holds an id the new DU instance does not know; re-joining
it is open.
- **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
Expand Down
5 changes: 5 additions & 0 deletions docs/scheduled-mac.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ beacons **autonomously**, so a beaconing session that ends without a device
power-cycle must call it — a killed process leaves the beacon airing
indefinitely (bench-bitten: a stale beacon with the same SA contaminated the
next test's witness).
On Kestrel it must also undo the port's TBTT protection (`TBTT_PROHIB_EN`,
`BRK_SETUP`), which `StartBeacon` turns on. Left set, the port went on
holding the UE's later injected frames to a fixed phase of its old beacon
grid. A stopped UE re-joining through the RACH sent every msgA ~6 ms past
the slot (M6 rig, `docs/cell-m6.md`).

Per generation: Jaguar2 replaces the retained `_bcn_mpdu` and re-downloads via
the steer path (the J2 engine loses the bcn-valid latch on re-latch, so the
Expand Down
58 changes: 56 additions & 2 deletions examples/cell/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@
// beacons outlast the DU's discipline pass
// (CELL_SYNC_LOOP_MS 500): a jump the DU undoes
// itself is never followed, only a lasting one.
// CELL_UE_REJOIN_SF [30] UE that joined through the RACH: not granted by
// the map in force this many superframes (its DU
// restarted or released it) = drop the id and
// join again, with a fresh nonce (cell.ue.rejoin).
// 0 = never. Static UEs never re-join.
// CELL_UE_REATTACH_SF [200] UE: for this long after a confirmed step of the
// DU's clock, a grant map at a LOWER epoch that is
// due near its beacon's superframe means the DU
Expand Down Expand Up @@ -2303,7 +2308,15 @@ int run_ue(IRadio *dev, const Cfg &c) {
joiner_mac = {0x02, 0xce, 0xfe, static_cast<uint8_t>(h >> 16), static_cast<uint8_t>(h >> 8),
static_cast<uint8_t>(h)};
}
const uint64_t nonce = (static_cast<uint64_t>(rng()) << 32) | rng();
uint64_t nonce = (static_cast<uint64_t>(rng()) << 32) | rng();
/* A UE that got its id through the RACH re-joins once the DU's authentic
* map in force has not granted it for CELL_UE_REJOIN_SF superframes: the
* DU restarted (a new instance does not know the id) or released it. A
* static UE (CELL_UE > 0) never does: its DU grants it again. */
const bool static_ue = me_a > 0;
const int64_t rejoin_sf = envl("CELL_UE_REJOIN_SF", 30, 0, 100000); /* 0 = never */
int64_t absent_from_sf = -1; /* first superframe of the current absence */
int rejoin_stop_fails = 0; /* consecutive failed beacon stops before a re-join */
devourer::cell::RachClient rach(joiner_mac, nonce, [&rng] { return static_cast<uint32_t>(rng()); });
std::vector<devourer::cell::RachResponse> rar_seen;
const auto mac = rach_mac(c);
Expand Down Expand Up @@ -2772,8 +2785,49 @@ int run_ue(IRadio *dev, const Cfg &c) {
continue;
const int me = me_a.load();
const Grant *g = mcopy.find(static_cast<uint16_t>(me));
if (!g)
if (!g) {
/* Not granted by the map in force. A joined UE (not mid-handover)
* absent this long re-joins: a fresh nonce (the DU answers a
* repeated one from its cache: the old id), its uplink beacon off,
* the id's DL sequence space gone. */
const int64_t sf_now = du_now / P;
if (absent_from_sf < 0)
absent_from_sf = sf_now;
if (!static_ue && rejoin_sf > 0 && !ho_back && !ho_pending &&
sf_now - absent_from_sf >= rejoin_sf) {
const int old_ue = me;
/* The old id's beacon must be off first: a failed stop defers the
* re-join to the next pass (the absence stands), never a RACH
* beside a live old beacon. */
if (beaconing && !dev->StopBeacon()) {
if (rejoin_stop_fails++ == 0) {
char l[120];
snprintf(l, sizeof l, "{\"ev\":\"cell.ue.rejoin_stop_fail\",\"ue\":%d}", old_ue);
emit(l);
}
continue;
}
beaconing = false;
rejoin_stop_fails = 0;
dev->ClearAckResponder();
nonce = (static_cast<uint64_t>(rng()) << 32) | rng();
rach = devourer::cell::RachClient(joiner_mac, nonce, [&rng] { return static_cast<uint32_t>(rng()); });
{
std::lock_guard<std::mutex> lk(mu);
rar_seen.clear();
hrx = HarqReceiver{};
}
me_a = 0;
char l[200];
snprintf(l, sizeof l,
"{\"ev\":\"cell.ue.rejoin\",\"old_ue\":%d,\"cell\":%u,\"absent_sf\":%lld,\"sf\":%lld}",
old_ue, cell_a.load(), (long long)(sf_now - absent_from_sf), (long long)sf_now);
emit(l);
absent_from_sf = -1;
}
continue;
}
absent_from_sf = -1;
/* The DU's slot indices must mean the same slots on this UE's grid (the
* grid is built from the shared env): a disagreement is a configuration
* error. Stop rather than transmit in someone else's slot. The pair may
Expand Down
3 changes: 3 additions & 0 deletions src/kestrel/HalKestrel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1764,6 +1764,9 @@ bool HalKestrel::stop_beacon() {
p = r::set_clr_word(p, r::MAC_AX_NET_TYPE_NO_LINK, r::B_AX_NET_TYPE_P0_MSK,
r::B_AX_NET_TYPE_P0_SH);
p &= ~r::B_AX_BCNTX_EN_P0;
/* And the TBTT protection start_beacon turned on (BCN_PRCT): left set, the
* port keeps prohibiting TX around a TBTT no beacon uses any more. */
p &= ~(r::B_AX_TBTT_PROHIB_EN_P0 | r::B_AX_BRK_SETUP_P0);
return _device.rtw_write32(r::R_AX_PORT_CFG_P0, p);
}

Expand Down
Loading