Skip to main content

How Mobility Reaches the CU-CP

The operator declares neighbors, report configurations and administrative intent on NRCell resources (Configure Mobility and Cell State). This page is how the controller makes the running CU-CP agree, and what has to be true on the air first.

Physical Prerequisite: Aligned Frames​

A single cell runs on the radio's internal clock. Two or more cells between which UEs are expected to hand over need their frames time-aligned: a UE measures a neighbor inside a window derived from its serving cell's timing, and two free-running cells put each other's SSB outside that window permanently. Connected-mode measurement, and so handover, is then physically impossible whatever the configuration says.

On USRPs that means a GPSDO on every radio, clockSource: gpsdo on every cell, and GPS lock before mobility is expected (external works the same way with a shared reference). The time source (syncSource) follows the clock source automatically when that source can supply time; a disciplined oscillator alone aligns frequency, not frames. How to check lock is on Connect a Radio.

Mobility​

The operator surface is spec.neighbors[] (directional relations, so declare both ways), spec.periodicReportCfgId, and spec.mobility.reportConfigs[], camelCase mirrors of the CU-CP's cu_cp.mobility.report_configs keys. Report configurations are CU-wide in the CU-CP, so the controller unions every cell's contributions by reportCfgId: define each id once, on one cell, or identically on all. Differing definitions of the same id resolve to the lexicographically first cell name and raise a ReportConfigConflict condition on the others. Two built-in ids exist unless overridden and are never removed: 1 (periodical, every 1024 ms) and 2 (A3 on RSRP, 3 dB offset, no hysteresis, 100 ms time to trigger).

One desired state, computed from the whole cell set, has two sinks:

  • The boot overlay, the ConfigMap nrcell-cu-cp-config in centralized-unit. A starting CU-CP reads it, so restarts converge from it with no controller action.
  • The runtime commands on the CU-CP's command WebSocket (neighbor_add, neighbor_remove, report_config_set, report_config_remove, periodic_report_set). This is the live path: a mobility edit takes effect without a CU-CP restart and without a DU rollout. The controller keeps the last state it pushed in the racora.io/applied-mobility annotation on the overlay ConfigMap, plans the difference, and sends only what changed; every command is an idempotent upsert or removal. A periodic re-push (racora-controller.mobility.resyncIntervalS, default 300 s) closes the one race left, a CU-CP crash inside the kubelet's ConfigMap sync window.

If the command surface is unreachable the sync logs that it is unavailable and returns; the boot overlay remains the convergence path.

One mobility setting is CU-wide and boot-only, so it lives on the controller chart rather than the CRD: racora-controller.mobility.triggerHandoverFromMeasurements, automatic A3-driven handover. Changing it takes effect at the next CU-CP restart.

What a Connected UE Sees​

None of the runtime commands pushes an RRC reconfiguration. A UE picks up its new measurement configuration at its next reconfiguration: a PDU session set up, modified or released, a handover, or a re-attach. An idle but connected phone does not see a new neighbor until such churn. This is gNB behavior, not a controller delay.

The CU-CP also rejects changing a referenced report configuration's class in place (periodical to event-triggered or back). Use a new id, or let a CU-CP restart apply the change wholesale from the overlay.

Administrative State​

spec.adminState: Unlocked|Locked and spec.cellBarred: true|false mirror the CU-CP's logical-cell intent. The same two-sink model applies:

  • at runtime, through cell_lock, cell_unlock, cell_bar and cell_unbar. Locking a serving cell drains it gracefully (bar, release the UEs, deactivate) and the CU-CP keeps the intent across DU restarts;
  • at boot, through the overlay's cu_cp.logical_cells list, which records every cell's intent by its controller-assigned sector_id.

The logical_cells section is emitted only when some cell departs from the default intent (Unlocked, not barred). When it is emitted, every cell with an identity is listed, because the declared set doubles as the CU-CP's activation whitelist: a cell that reports in from outside it is realised locked. A cell created while the CU-CP is running is by definition outside the booted whitelist, so it comes up dormant until the controller's runtime sync pushes its declared state, within a reconcile retry of the DU's F1 attach. With an all-default cell set there is no whitelist and a new cell activates at F1 setup; the dormant-first behavior depends on some cell carrying a non-default intent.

Interplay with the decision loop: a decision-driven DU rollout drains the cell with cell_lock and normally unlocks it afterwards. If spec.adminState is Locked, the post-rollout unlock is withheld: operator intent wins. The CU-CP tracks cellBarred independently of the lock, and a locked cell still appears in other cells' measurement configuration as a neighbor, so pair a long-term lock with removing the relations that point at it.