MAC Scheduler
The MAC scheduler allocates radio resources — time, frequency, and control channels — across all active cells in a DU. It runs once per slot per cell and produces a sched_result that the MAC layer uses to generate DL assignments and UL grants.
Component Hierarchy
scheduler_impl
├── sched_config_manager # Validates and distributes cell/UE config
├── cell_scheduler [per cell] # All resources specific to one cell
│ ├── cell_metrics_handler # Per-slot metric aggregation for this cell
│ ├── cell_resource_allocator # Circular-buffer resource grid (16 slots)
│ ├── ssb_scheduler # SSB bursts
│ ├── pdcch_resource_allocator # PDCCH/DCI allocation
│ ├── si_scheduler # SIB1 and SI messages
│ ├── csi_rs_scheduler # CSI-RS
│ ├── ra_scheduler # Random access response (RAR)
│ ├── prach_scheduler # PRACH occasions
│ ├── pucch_allocator # PUCCH resources (HARQ-ACK, SR, CSI)
│ ├── uci_allocator # UCI scheduling (PUCCH or PUSCH mux)
│ ├── srs_allocator # Sounding Reference Signal scheduling
│ ├── paging_scheduler # Paging messages
│ ├── uci_scheduler # Periodic SR and CSI scheduling
│ ├── srs_scheduler # Periodic SRS management
│ ├── uci_indication_selector # Matches UCI indications to their grants
│ ├── ue_cell_repository # UE contexts of this cell (ue_cell objects)
│ ├── cell_event_manager # Queues and dispatches every event of the cell
│ └── ue_cell_scheduler # UE grants for this cell (via ue_scheduler)
└── ue_scheduler [per cell group] # Shared UE state across a CA group
├── ue_repository # UE contexts of the cell group (ue objects)
├── cell_group_event_manager # Owns one event handler per cell of the group
├── ue_fallback_scheduler # SRB0 / contention-resolution grants
├── inter_slice_scheduler # Prioritizes slices for each slot
└── intra_slice_scheduler # PDSCH/PUSCH allocation within a slice
├── grant_params_selector # MCS, PRBs, HARQ selection
└── ue_cell_grid_allocator # Writes grants into the resource grid
A ue_scheduler is shared across all cells in a cell group (Carrier Aggregation), and holds the state that spans a UE's carriers. Each cell_scheduler holds a ue_cell_scheduler handle, which is an RAII view into the shared ue_scheduler.
The uci_scheduler and the srs_scheduler run after the schedulers of the common channels and before the UE scheduling step, as they have to reserve their periodic opportunities before any UE grant of the slot.
Slot Processing Flow
scheduler_impl::slot_indication(sl_tx, cell_index) is the entry point. It calls cell_scheduler::run_slot(), which executes in this order:
- Reset resource grid — clear stale allocations for this slot.
- Events — process everything that arrived since the last slot (
cell_event_manager). - SSB — schedule Synchronization Signal Blocks.
- CSI-RS — schedule CSI-RS if due.
- SI — schedule SIB1 and SI-message windows.
- PRACH — schedule PRACH occasions.
- RA — schedule random access (RA) grants (e.g. RAR and Msg3) for detected RACH preambles.
- Paging — schedule paging PDCCH and PDSCH.
- Periodic UCI — schedule the SR and CSI PUCCH opportunities (
uci_scheduler). - Periodic SRS — schedule the periodic and aperiodic SRS (
srs_scheduler). - UE scheduling (
ue_cell_scheduler::run_slot): - Advance UE state machines (DRX, timing advance, HARQ timers).
- Schedule configured grant PUSCH opportunities, if configured.
- Schedule SRB0 / fallback grants (
ue_fallback_scheduler). - Prioritize slices for this slot (
inter_slice_scheduler::slot_indication). - For each slice in priority order:
- Schedule PDSCH retransmissions, then new transmissions.
- Schedule PUSCH retransmissions, then new transmissions.
- Post-process allocations (finalize PUCCH/UCI state).
- Inject synthetic BSRs for the UEs that need a triggered UL grant.
- UCI indications — match the UCI grants of the finished slot to their indications (
uci_indication_selector). - Logging and metrics — flush the event log, the result log and the slot metrics.
Event Handling
Everything that reaches the scheduler outside a slot indication — RACH, CRC, UCI and SRS indications, buffer status and power headroom reports, MAC CEs, paging and SI requests, positioning requests, UE creation, reconfiguration and deletion — is an event. Events arrive from other executors, so none of them are applied where they arrive:
cell_event_manager(cell_event_manager.h) queues the event in a lock-free MPMC queue, with the payloads that do not fit in the callback taken from a pool of their own. The queue holds every payload the pools can hand out, so an event type is only ever limited by its own pool. One queue per cell means the events of a cell keep their arrival order, whatever their type.- The queue is drained at the start of the cell slot indication, in the cell executor. An event that only touches the state of the cell is applied there.
- An event that needs the state shared by a UE's carriers is handed to the
cell_group_event_handlerof the cell and applied synchronously. It reports back whether it applied the event, so that the cell logs it and accounts for it in its metrics only when it did.
cell_group_event_handler (cell_group_event_handler.h) groups the two interfaces that the cell calls:
cell_group_ue_config_handler for the UE configuration requests, and cell_group_ue_indication_handler for the UE
indications and for the outcomes that a cell derives for one of its UEs while processing its own events.
cell_group_event_manager owns one implementation of it per cell of the group. The cell scheduler creates its
ue_cell_scheduler handle before its cell_event_manager, so the handler already exists when the manager is given the
reference.
The cell also aggregates the DL buffer occupancy updates that it receives for a bearer since the last slot, and relays only the resulting update.
Resource Grid
cell_resource_allocator is a circular ring buffer over cell_slot_resource_allocator entries — one per slot. Each entry contains:
sched_result— the scheduling decisions (PDCCHs, PDSCHs, PUSCHs, etc.)- A DL and UL
carrier_subslot_resource_gridper SCS — a Symbol × CRB bitmap used for collision detection and availability checks.
Allocators receive a cell_slot_resource_allocator& and write into it directly. The ring buffer provides a lookahead window so allocators can inspect and reserve resources in future slots (e.g., PUSCH scheduled K2 slots ahead of the PDCCH slot).
RAN Slicing
The scheduler supports multiple RAN slices, each configured with a slice_rrm_policy_config (min/max RB limits, S-NSSAI). Two scheduling layers handle slicing:
Inter-slice scheduler (slicing/inter_slice_scheduler.h) — runs once per slot and produces a priority-ordered queue of ran_slice_candidate objects for DL and UL. Priority is computed from slice SLA parameters, recent utilization, and RB limits.
Intra-slice scheduler (ue_scheduling/intra_slice_scheduler.h) — consumes one slice candidate at a time and allocates PDSCH/PUSCH grants for UEs in that slice. Within a slice, UE ordering is determined by the slice's scheduler_policy.
Scheduling Policies
scheduler_policy (policy/scheduler_policy.h) is the pluggable per-slice algorithm interface:
compute_ue_dl_priorities()/compute_ue_ul_priorities()— assign aue_sched_priorityto each UE candidate.save_dl_newtx_grants()/save_ul_newtx_grants()— called after allocation to update internal state.add_ue()/rem_ue()— track UE membership.
Available implementations (in policy/) include Round-Robin with QoS weighting and time-domain fair scheduling.