>
Home5G NRMAC — Medium Access ControlLCP
🧩 MAC — Medium Access ControlIntermediate

Logical Channel Prioritization in 5G NR

How the UE splits an uplink grant across logical channels using LCP and its parameters.

📚 3GPP-basedTS 38.321

An uplink grant is one number: a transport block size, for one HARQ process, in one slot. It says nothing about which of the UE's bearers it is meant for, because the gNB does not know -- all it has is a Buffer Status Report that lumped the UE's queues into at most eight groups, several milliseconds ago. The UE, on the other hand, knows exactly what it has queued and exactly what each bearer was promised. So the division of the grant is left entirely to the UE, and the algorithm that does it is Logical Channel Prioritisation TS 38.321 cl. 5.4.3.1.

Contents
  1. 01The Problem LCP Solves
  2. 02Where a Logical Channel Comes From: QoS Flow to DRB to LCH
  3. 03The Per-Logical-Channel Parameters
  4. 04The Token Bucket: PBR, BSD and Bj
  5. 05Step 0: Which Logical Channels May Use This Grant At All
  6. 06The Three-Round Algorithm
  7. 07Worked Allocation 1: a 200-Octet Grant Across Three Channels
  8. 08Worked Allocation 2: the Same Grant, One Parameter Changed
  9. 09Worked Bj Accumulation Across Idle Slots
  10. 10Where MAC Control Elements Fit: the Fixed Priority Order
  11. 11RLC Segmentation, and How a Partly-Served Channel Resumes
  12. 12Intra-UE Prioritisation and Cancellation (Rel-16)
  13. 13Configuration Reference (ASN.1 and Ranges)
  14. 14Failure Modes and What Each One Means
  15. 15Illustrative Message Traces
  16. 16Release Deltas: Rel-15 to Rel-18
  17. 17Reading LCP in Logs: a Checklist
  18. 18Glossary
  19. 19References

1. The Problem LCP Solves

An uplink grant is one number: a transport block size, for one HARQ process, in one slot. It says nothing about which of the UE's bearers it is meant for, because the gNB does not know -- all it has is a Buffer Status Report that lumped the UE's queues into at most eight groups, several milliseconds ago. The UE, on the other hand, knows exactly what it has queued and exactly what each bearer was promised. So the division of the grant is left entirely to the UE, and the algorithm that does it is Logical Channel Prioritisation TS 38.321 cl. 5.4.3.1.

That division has to satisfy three things at once, and they conflict:

  • Priority must be respected. A voice bearer must not wait behind a file upload.
  • Guaranteed rates must be honoured. A bearer configured with a prioritised bit rate must actually get that rate on average, even when a higher-priority bearer always has data.
  • Nothing may be wasted. The transport block is going out regardless. Every octet not filled with data is filled with padding, which costs the same power and the same spectrum.

Strict priority alone satisfies the first and third and fails the second: a busy high-priority channel starves everything below it forever. A fixed per-bearer reservation satisfies the second and fails the third: a bearer with nothing to send wastes its reservation. LCP resolves this with a token bucket per logical channel and a two-stage allocation -- serve everybody's tokens first in priority order, then hand out whatever is left by strict priority.

💡
Key Point

LCP runs per grant, not per bearer and not per second. Everything it does is local to one transport block. The long-run rate guarantee is an emergent property of the token bucket across many grants, not something any single invocation of the algorithm enforces. That is why a single-grant trace can look grossly unfair and still be entirely correct.

2. Where a Logical Channel Comes From: QoS Flow to DRB to LCH

LCP's inputs are logical channels, and a logical channel is the bottom of a chain that starts at the 5GC. Each link in that chain is one-to-one, which is what makes the bottom of it interesting: five one-to-one pipelines meet one shared transport block.

QoS flow to logical channel to grantFrom a QoS Flow to a Logical Channel to One Uplink GrantOne-to-one all the way down, and then five-to-one at the bottom -- which is the whole reason LCP existsSource of the trafficRRCsignalling5QI 5IMS signalling5QI 1conv. voice5QI 2conv. video5QI 9backgroundRadio bearers (PDCP entity each)SRB1DRB 1DRB 2DRB 3DRB 4RLC entitiesRLC AMRLC AMRLC UMRLC UMRLC AMLogical channels -- where LCP operatesLCID 1prio 1, PBR infLCID 4prio 3, LCG 0LCID 5prio 4, LCG 1LCID 6prio 6, LCG 2LCID 7prio 9, LCG 2MACOne UL grant -- LCP divides itTBS is fixed; the split is notEverything above the logical channel layer is a per-bearer pipeline. At the logical channel layer, five independent pipelinesmeet one transport block whose size somebody else chose.
Figure 1. The chain from a QoS flow down to a logical channel. SDAP maps QoS flows to DRBs, PDCP and RLC are per-bearer, and MAC is the first layer that sees more than one bearer at once.
5QIResource typeDefault priority levelPacket delay budgetPacket error rateExample service
1GBR20100 ms10^-2Conversational voice
2GBR40150 ms10^-3Conversational video, live
3GBR3050 ms10^-3Real-time gaming, V2X messages
4GBR50300 ms10^-6Non-conversational buffered video
5Non-GBR10100 ms10^-6IMS signalling
6Non-GBR60300 ms10^-6Buffered video streaming, TCP
7Non-GBR70100 ms10^-3Voice, live video, interactive gaming
8Non-GBR80300 ms10^-6TCP-based, best effort
9Non-GBR90300 ms10^-6Default bearer -- what most traffic lands on
65GBR775 ms10^-2Mission-critical push-to-talk voice
69Non-GBR560 ms10^-6Mission-critical delay-sensitive signalling
82Delay-critical GBR1910 ms10^-4Discrete automation
85Delay-critical GBR215 ms10^-5Electricity distribution, high voltage

Table 1. Representative 5QI values, TS 23.501 Table 5.7.4-1. These are the intent; the LCH priority value is the implementation, and nothing in the specs forces them to agree.

⚠️
Common Pitfall

The 5QI priority level (1-127, lower is more urgent) and the logical channel priority (1-16, lower is more urgent) are two different fields in two different specs with two different ranges, and no automatic mapping between them. It is the gNB's job to translate, and a translation error is invisible in every 5GC-side trace: the QoS profile is correct, the DRB exists, and the bearer is still starved because its priority came out as 13 instead of 4. If a GBR bearer is under-performing, read the LogicalChannelConfig before anything else.

3. The Per-Logical-Channel Parameters

Everything LCP needs about a logical channel is in ul-SpecificParameters inside LogicalChannelConfig, signalled per bearer inside RLC-BearerConfig TS 38.331.

ParameterASN.1 rangeTypicalRole in LCPConsequence of getting it wrong
priority
INTEGER (1..16), 1 = highest1-3 SRBs, 4-6 delay-sensitive DRBs, 8-13 bulkOrders both round 1 and round 3Too low and the channel is served only from leftovers; too high and it starves everything beneath it in round 3
prioritisedBitRate
kBps0, kBps8, kBps16, kBps32, kBps64, kBps128, kBps256, kBps512, kBps1024, kBps2048, kBps4096, kBps8192, kBps16384, kBps32768, kBps65536, infinitykBps0 for bulk, kBps8-kBps64 for voice/video, infinity for SRB1Sets the token fill rate; kBps0 means the channel gets nothing in round 1kBps0 on a GBR bearer means it is served only from spare capacity. infinity on a bulk bearer starves everything else
bucketSizeDuration
ms5, ms10, ms20, ms50, ms100, ms150, ms300, ms500, ms1000ms50-ms100Caps the bucket at PBR x BSD, i.e. how much unused allowance may be bankedToo small and a bursty channel cannot claim what it was owed; too large and one channel can swallow a whole grant after being idle
logicalChannelGroup
INTEGER (0..7)0 for SRBs, 1-3 for DRBsNot used by LCP itself -- it decides how the channel is reported in a BSRTwo channels with very different urgency in one LCG become indistinguishable to the scheduler. See the companion 10 BSR, PHR, SR document
schedulingRequestID
SchedulingRequestId (0..7)0Which SR configuration this channel uses when it needs a grantA channel with no SR configuration and no grant falls back to Random Access
logicalChannelSR-Mask
BOOLEANfalseWhen true, suppresses SR triggering while a configured grant is availableSet on a channel whose configured grant is too small and the UE will never ask for more
logicalChannelSR-DelayTimerApplied
BOOLEANfalseApplies logicalChannelSR-DelayTimer before the SR is sentAdds deliberate latency; wrong on a delay-critical channel
allowedServingCells
SEQUENCE OF ServCellIndexabsentRestricts the channel to grants on specific serving cellsThe classic PDCP-duplication mistake: both legs restricted to the same cell means no diversity at all
allowedSCS-List
SEQUENCE OF SubcarrierSpacingabsentRestricts the channel to grants with specific numerologiesA URLLC channel restricted to 120 kHz never uses a 30 kHz grant, even an empty one
maxPUSCH-Duration
ms0p02, ms0p04, ms0p0625, ms0p125, ms0p25, ms0p5absent, or ms0p125 for URLLCRestricts the channel to short PUSCH transmissionsSet too tight and the channel is excluded from every ordinary slot-based grant
configuredGrantType1Allowed
ENUMERATED {true}absentPermits the channel to use a Type 1 configured grantAbsent means the channel may not use an RRC-configured grant -- a common cause of configured grants going out full of padding
allowedCG-List
SEQUENCE OF ConfiguredGrantConfigIndexMAC (Rel-16)absentRestricts the channel to specific configured grant configurationsWith up to 12 configured grants per BWP, an empty list here silently excludes the channel from all of them
allowedPHY-PriorityIndex
ENUMERATED {p0, p1} (Rel-16)absentRestricts the channel to grants of a given physical-layer priorityThe URLLC/eMBB separation mechanism. Mismatched with the DCI priority indicator, a channel is excluded from exactly the grants meant for it
bitRateQueryProhibitTimer
s0, s0dot4, s0dot8, s1dot6, s3, s6, s12, s30absentRate-limits Recommended Bit Rate Query CEs on this channelAbsent means the feature is off; s0 means an unthrottled query stream

Table 2. LogicalChannelConfig -> ul-SpecificParameters, the complete LCP-relevant set. The last five rows are eligibility gates rather than rate parameters, and they are checked before any arithmetic happens (§5).

4. The Token Bucket: PBR, BSD and Bj

Each logical channel j has a variable Bj, in octets, initialised to zero when the channel is established. Before every invocation of LCP, MAC does two things to it TS 38.321 cl. 5.4.3.1:

The Bj update, TS 38.321 cl. 5.4.3.1
Bj  =  Bj + PBR x T                  -- T = time since Bj was last incremented
if Bj > PBR x BSD:  Bj = PBR x BSD   -- the bucket cannot overflow

then, after round 1 of the allocation:

Bj  =  Bj - (octets of MAC SDU served to channel j in round 1)
                                     -- Bj MAY go negative, and does

Three properties fall straight out of those three lines and between them explain almost every LCP surprise:

  • The bucket fills to its cap after exactly BSD of idleness. Cap divided by rate is PBR x BSD / PBR = BSD. So bucketSizeDuration is literally how many milliseconds of unused allowance you may bank, independent of the rate.
  • Bj can go negative. Round 3 does not touch Bj, but round 1 can serve up to Bj and the subheader is not counted against it -- and more importantly, an implementation that has served a channel generously in round 3 will still credit it the full PBR next slot. Negative Bj is the mechanism that pays back over-service; it is not an error state.
  • PBR = kBps0 means Bj is permanently zero, so the channel is never served in round 1 at all and lives entirely off round 3. That is the correct configuration for best-effort traffic and a serious misconfiguration for a GBR bearer.
prioritisedBitRateOctets per secondOctets per slot at 15 kHzat 30 kHzat 120 kHz
kBps0
0000
kBps8
8 000841
kBps16
16 0001682
kBps32
32 00032164
kBps64
64 00064328
kBps128
128 0001286416
kBps256
256 00025612832
kBps512
512 00051225664
kBps1024
1 024 0001 024512128
kBps4096
4 096 0004 0962 048512
kBps65536
65 536 00065 53632 7688 192
infinity
unboundedServed to completion in round 1 before any lower-priority channel's PBR is met----

Table 3. Token fill rate per slot. Slot durations are 1 ms, 0.5 ms and 0.125 ms. The per-slot figure is what matters, because LCP runs per grant and grants arrive per slot at most.

bucketSizeDurationCap with kBps8Cap with kBps64Cap with kBps512What it means in practice
ms5
40 octets320 octets2 560 octetsAlmost no banking. Suits a constant-rate channel where a burst is a problem, not a feature
ms20
160 octets1 280 octets10 240 octetsAbsorbs one or two missed grants
ms50
400 octets3 200 octets25 600 octetsA common default. Comfortably covers a DRX short cycle
ms100
800 octets6 400 octets51 200 octetsSuits a bursty channel that goes quiet between bursts -- video keyframes, periodic telemetry
ms300
2 400 octets19 200 octets153 600 octetsEnough to claim a whole grant after a long DRX cycle. Rarely what you want on a shared cell
ms1000
8 000 octets64 000 octets512 000 octetsOne second of banked allowance. Effectively removes the fairness property of round 1 for that channel

Table 4. Bucket cap = PBR x BSD. Read the last column as the answer to "how long may this channel be idle and still claim its full allowance the moment it wakes up?"

Bj Filling During Idle Slots and Draining on a GrantBj Filling During Idle Slots and Draining on a GrantBj in octets, shown after that slot's increment. Red = the slot in which the channel was served and Bj was decremented to zero163248641632486480163248320320320320326496128160326496000000000000grantgrant['LCID 5', 'PBR kBps32', 'BSD ms5']['LCID 6', 'PBR kBps64', 'BSD ms5']['LCID 7', 'PBR kBps0']UL grant01234567891011Slot index (0.5 ms slots, 30 kHz SCS)Bj accumulatingBj drained by servicePBR = kBps0: Bj is always 0LCID 6 sat at its cap of 320 octets through slots 0-3: PBR x BSD is reached after exactly BSD of idleness andnothing accumulates after that. LCID 5 would cap at 160. LCID 7 has no tokens ever and is served only in round 3.
Figure 2. Bj across twelve slots for three channels. LCID 6 is at its cap before slot 0 and is drained to zero by the first grant; it then needs the full BSD to get back. LCID 7 never has a token.
💡
Key Point

Why the cap exists. Without it, a channel idle for ten seconds would arrive at the next grant with ten seconds of allowance and legitimately claim the entire transport block in round 1, ahead of every lower-priority channel with real-time data. bucketSizeDuration is the knob that bounds that burst. It is the single most under-used parameter in LCP tuning: most deployments set priority and prioritisedBitRate carefully and leave BSD at whatever the template shipped with.

5. Step 0: Which Logical Channels May Use This Grant At All

Before any arithmetic, MAC decides which logical channels are even candidates for this particular grant TS 38.321 cl. 5.4.3.1.2. This is not a priority question and it is not a rate question -- it is a compatibility question, and a channel that fails it is not starved, it is absent.

LCP step 0 eligibility gateStep 0: May This Logical Channel Use This Grant At All?TS 38.321 cl. 5.4.3.1.2 -- a condition that is not configured never restricts anything; a configured one must be satisfiedallowedSCS-List includes the grant's SCSmaxPUSCH-Duration >= the grant'sPUSCH transmission durationconfiguredGrantType1Allowed = true,if the grant is a Type 1 configured grantallowedServingCells includes the cellthe grant is forallowedCG-List and allowedPHY-PriorityIndexboth include the grant (Rel-16)Everyconfiguredconditionholds?NOT selected.Contributes nothingto this grant.Selected.Goes into rounds1 and 3.any one failsall holdA channel excluded here is invisible to the rest of LCP. It is not starved by priority -- it was never a candidate, and no amountof extra grant size will help it. This is the first thing to check when a bearer sends nothing at all.
Figure 3. The eligibility gate. Note the asymmetry: a restriction that is not configured never restricts, so an absent field is permissive, not prohibitive.
RestrictionWhat it gates onWhy it existsTypical use
allowedSCS-List
The subcarrier spacing of the grantA URLLC bearer needs the short slot of a high numerology; putting its data on a 15 kHz grant would blow the latency budget even if the grant were hugeRestrict a delay-critical DRB to 60 or 120 kHz grants only
maxPUSCH-Duration
The PUSCH transmission duration of the grantA grant can be a 2-symbol mini-slot or a full 14-symbol slot at the same numerology. Duration, not numerology, is what sets the transmission timeRestrict a URLLC bearer to sub-slot PUSCH so it is never queued behind a long transmission
configuredGrantType1Allowed
Whether the grant is a Type 1 configured grantType 1 configured grants are set up by RRC and never confirmed. A bearer that must not use un-acknowledged periodic resources can opt outKeep bulk data off a configured grant that was dimensioned for voice
allowedServingCells
The serving cell the grant is onPDCP duplication needs its two legs on different carriers to be worth anything. This is the field that forces thatLeg 1 restricted to the PCell, leg 2 to a specific SCell
allowedCG-List (Rel-16)Which configured grant configurationRel-16 allows up to 12 configured grant configurations per BWP with different periodicities and sizes; a bearer should use only the ones dimensioned for itBind a periodic sensor bearer to the one configured grant whose period matches its sampling rate
allowedPHY-PriorityIndex (Rel-16)The physical-layer priority indicator in the DCIRel-16 intra-UE prioritisation gives each dynamic grant a priority index. This field is how a logical channel says which class of grant it belongs toURLLC channels on p1, eMBB channels on p0, so a p1 grant is never diluted with best-effort data

Table 5. LCP restrictions. Every one of them exists because a grant is not just a quantity of bytes -- it has a numerology, a duration, a cell and a priority class, and those matter to some bearers and not to others.

📘
Spec Detail

Two special cases override the table. A grant addressed to a Random Access MSG3 or MSGA payload carries only CCCH; and CCCH itself is never subject to these restrictions -- the UE must always be able to send an RRCSetupRequest. If you see a MSG3 grant that a decoder claims contains DTCH data, the decode is wrong.

⚠️
Common Pitfall

The commonest LCP fault in the field is not a starved channel -- it is an excluded channel, and the two look identical in a throughput graph. A starved channel is served occasionally, from leftovers. An excluded channel is served never, on that class of grant, regardless of load. The test is simple: if the bearer sends nothing even when the cell is empty and the grants are large, it is not priority -- it is §5.

6. The Three-Round Algorithm

The Three-Round LCP AllocationThe Three-Round LCP AllocationTS 38.321 cl. 5.4.3.1. Rounds 1 and 3 both allocate; round 2 only does accountingUL grant arrives -- TBS is fixedStep 0: select the logical channelsthis grant is allowed to serveBj += PBR x T, capped at PBR x BSDROUND 1 -- in decreasing priority order, give eachselected channel min(Bj, its data, space left)special casePBR = infinity: serve ALL of that channel's data beforemeeting the PBR of any lower-priority channelROUND 2 -- Bj -= octets of MAC SDU served in round 1(Bj may go negative, and often does)ROUND 3 -- if space remains, serve in strict decreasing priorityorder, IGNORING Bj, until data or grant is exhaustedRound 1 is fairness: everybody with tokens gets served. Round 3 is efficiency: nothing is left unsent if a channel canuse it. Round 2 is what makes round 1 fair *over time* rather than only in this slot. The MAC PDU is then built withcontrol elements and SDUs in priority order, and padding last.
Figure 4. The allocation. Only rounds 1 and 3 hand out octets; round 2 is pure accounting, and it is what turns per-grant priority into a long-run rate guarantee.
RoundWhat it doesOrderBounded byEffect
1Allocate to each selected logical channelDecreasing prioritymin(Bj, data available, space remaining)Every channel with tokens gets served, even behind a busy higher-priority channel. This is the guaranteed-rate mechanism
1aSpecial case: prioritisedBitRate = infinitySameAll data available for that channelServe everything on that channel before meeting the PBR of any lower-priority channel. Intended for SRBs
2Decrement Bj by the MAC SDU octets served in round 1n/an/a -- Bj may go negativeCharges the channel for what it took, so next slot's round 1 is smaller. No octets move
3Allocate the remaining spaceStrict decreasing prioritymin(data remaining, space remaining)Fills the block completely. Bj is ignored entirely, so a channel with a deeply negative Bj can still be served here

Table 6. The three rounds, TS 38.321 cl. 5.4.3.1. Channels configured with equal priority should be served equally -- note that this is a should, and implementations differ.

Two implementation facts that the spec text does not spell out but that every trace shows:

  • Rounds 1 and 3 are combined into one RLC request per logical channel. A channel allocated 30 octets in round 1 and 70 in round 3 produces one subPDU of 100 octets, not two of 30 and 70. That matters for the subheader arithmetic -- one subheader, not two -- and it is why a trace shows one SDU per logical channel per PDU.
  • The space accounting includes subheaders. A channel allocated n octets of MAC SDU consumes n + 2 octets of the grant (or n + 3 if n > 255). The Bj decrement in round 2 uses the SDU size only. Every textbook worked example that omits this gets the last channel's allocation wrong.
⚠️
Common Pitfall

A worked example that ignores the subheader tax and one that includes it differ by 2 to 3 octets per active logical channel. On a 1500-octet grant that is noise. On the 56-octet grant a UE gets right after a Scheduling Request, three active channels means 6 to 9 octets -- more than 10% of the block -- and it is always the lowest-priority channel that pays, because it is served last.

7. Worked Allocation 1: a 200-Octet Grant Across Three Channels

30 kHz SCS, so a slot is 0.5 ms. Three logical channels are eligible for this grant; no MAC control element is pending.

ChannelpriorityprioritisedBitRatebucketSizeDurationBucket capBj nowData queued
LCID 5
4kBps32 = 16 oct/slot
ms5
160 oct50 oct40 oct
LCID 6
6kBps32 = 16 oct/slot
ms5
160 oct30 oct100 oct
LCID 7
9kBps0 = 0 oct/slot
ms5
0 oct0 oct80 oct

Table 7. Starting state. Total queued is 220 octets against a 200-octet grant, so something will be left behind -- the question is what.

🧮
Worked Calculation

Round 1 -- decreasing priority, each gets min(Bj, data, space left). Space starts at 200.

LCID 5 (prio 4): min(50, 40) = 40 octets of SDU, + 2 octets of subheader = 42 consumed. Space left 158.

LCID 6 (prio 6): min(30, 100) = 30 octets of SDU, + 2 octets of subheader = 32 consumed. Space left 126.

LCID 7 (prio 9): Bj = 0, so min(0, 80) = 0. Nothing consumed.

Round 2 -- decrement Bj by the SDU octets served:

LCID 5: 50 − 40 = 10 LCID 6: 30 − 30 = 0 LCID 7: 0 − 0 = 0

Round 3 -- strict decreasing priority, Bj ignored, 126 octets left.

LCID 5: buffer empty. Skip.

LCID 6: 70 octets still queued; its subPDU already exists so no new subheader. min(70, 126) = +70. Space left 56.

LCID 7: needs a new 2-octet subheader, so it can take at most 54. min(80, 54) = +54. Space left 0.

Result: LCID 5 = 40, LCID 6 = 100, LCID 7 = 54 octets of SDU.

Check: (2+40) + (2+100) + (2+54) = 42 + 102 + 56 = 200 octets = TBS.

The 200-Octet MAC PDU That Comes Out of Worked Allocation 1The 200-Octet MAC PDU That Comes Out of Worked Allocation 1Three subPDUs, no MAC CE, no padding. 6 octets of subheader + 194 octets of SDU = 200bit76543210Oct 1RF=0LCID = 5 (priority 4 channel)Oct 2L = 40 octetsOct 3MAC SDU: 40 octets of RLC PDU from LCID 5Oct 4RF=0LCID = 6 (priority 6 channel)Oct 5L = 100 octetsOct 6MAC SDU: 100 octets of RLC PDU from LCID 6Oct 7RF=0LCID = 7 (priority 9 channel)Oct 8L = 54 octetsOct 9MAC SDU: 54 octets -- an RLC SEGMENT, SO = 0, 26 octets left behindThe three payload rows stand for 40, 100 and 54 octets respectively; the subheader rows are literal. This is the figure tohold next to the arithmetic in the worked example -- the 6 octets of subheader are the difference between the textbookanswer and the real one.
Figure 5. The MAC PDU that results. Three subPDUs, no control element, no padding -- and 6 octets of the grant spent on subheaders.

LCID 7 keeps 26 octets queued. Note what did not happen: LCID 7 was not starved. It received 54 of the 80 octets it had, more than LCID 6 got in round 1, purely because rounds 1 and 3 together fill the block. A naive reading of "priority 9 behind priority 4 and 6" would predict zero.

⚠️
Common Pitfall

The same allocation with the subheader tax ignored gives LCID 7 = 60 octets and a 206-octet PDU that cannot be sent. The 6-octet difference went entirely to the lowest-priority channel, which is the general rule: overhead is always paid by whoever is served last. In a many-bearer UE on small grants that is a systematic bias against low-priority traffic that no parameter can correct.

8. Worked Allocation 2: the Same Grant, One Parameter Changed

Same grant, same buffers, same priorities. One change: LCID 7 is reconfigured from prioritisedBitRate = kBps0 to kBps32 with bucketSizeDuration = ms5, and by the time the grant arrives its bucket holds 100 octets.

🧮
Worked Calculation

Round 1 -- space starts at 200.

LCID 5 (prio 4): min(50, 40) = 40 + 2 subheader. Space left 158.

LCID 6 (prio 6): min(30, 100) = 30 + 2 subheader. Space left 126.

LCID 7 (prio 9): min(100, 80) = 80 + 2 subheader. Space left 44.

Round 2 -- LCID 5: 10, LCID 6: 0, LCID 7: 100 − 80 = 20

Round 3 -- 44 octets left, strict priority.

LCID 5: empty. LCID 6: 70 queued, no new subheader needed, takes +44. Space left 0. LCID 7: buffer already empty.

Result: LCID 5 = 40, LCID 6 = 74, LCID 7 = 80.

Check: (2+40) + (2+74) + (2+80) = 42 + 76 + 82 = 200 octets = TBS.

The Same 200-Octet Grant, One Parameter ChangedThe Same 200-Octet Grant, One Parameter ChangedBuffers 40 / 100 / 80 octets and priorities unchanged in both cases020406080100Octets of MAC SDU granted4040LCID 5 (prio 4)10074LCID 6 (prio 6)5480LCID 7 (prio 9)Config A -- LCID 7 PBR = kBps0Config B -- LCID 7 PBR = kBps32Priorities are identical in the two configurations; 26 octets moved from the middle-priority channel to the lowest-priority one purely because the latter was given tokens. PBR, not priority, decided the split.
Figure 6. Both allocations side by side. Priorities are identical in the two configurations; 26 octets moved from the middle channel to the bottom one because of one enumeration value.

This is the whole point of PBR, and it is worth stating plainly: prioritisedBitRate moves capacity up the priority order into round 1, where the low-priority channel is protected from the ones above it. priority alone cannot do that. Conversely, a prioritisedBitRate set generously on a bulk channel converts LCP from a priority scheduler into a weighted-fair one, which is sometimes exactly what an operator wants and is almost never what the template shipped with.

8.1 The PBR = infinity case

Take the same three channels and set LCID 5 to prioritisedBitRate = infinity with 150 octets queued instead of 40. Round 1 now serves all 150 octets of LCID 5 before meeting the PBR of anything below it:

🧮
Worked Calculation

Round 1 -- space 200.

LCID 5 (infinity): all 150 octets + 2 subheader = 152. Space left 48.

LCID 6: min(30, 100) = 30, but only 48 − 2 = 46 of space is usable -> 30 + 2 subheader. Space left 16.

LCID 7: Bj = 0 -> 0.

Round 2 -- LCID 6: 0. Round 3 -- 16 octets left; LCID 6 takes +16 with no new subheader. Space left 0.

Result: LCID 5 = 150, LCID 6 = 46, LCID 7 = 0.

Check: 152 + 48 = 200 octets = TBS.

⚠️
Common Pitfall

infinity is safe on SRB1 because SRB1 carries kilobytes per procedure, not per second, and a starved RRCReconfigurationComplete is worse than a delayed video frame. It is catastrophic on anything that can be saturated: one bulk bearer with prioritisedBitRate = infinity reduces LCP to a single-queue scheduler and every other bearer to whatever leftovers round 3 provides. If you inherit a configuration with infinity on a DRB, that is a finding.

9. Worked Bj Accumulation Across Idle Slots

The rate guarantee only works because Bj accumulates while the channel is not being served. Here is that accumulation with real numbers, for a channel that has been quiet.

🧮
Worked Calculation

LCID 6: prioritisedBitRate = kBps64, bucketSizeDuration = ms5, 30 kHz SCS.

Fill rate per slot = 64 000 octets/s x 0.0005 s = 32 octets per slot

Bucket cap = 64 000 x 0.005 = 320 octets

Starting from Bj = 0 with no grants:

after 1 slot (0.5 ms): 32 octets

after 2 slots (1.0 ms): 64 octets

after 5 slots (2.5 ms): 160 octets

after 10 slots (5.0 ms): 320 octets -- cap reached, = BSD exactly

after 20 slots (10 ms): 320 octets -- no further accumulation

after 200 slots (100 ms): 320 octets

A grant then arrives and the channel is served 320 octets in round 1. Bj drops to 0, and it takes another 10 slots to be able to do that again. Sustained over many grants the channel gets 320 octets per 5 ms = 64 000 octets/s, which is exactly its PBR. The bucket does not change the average -- it changes the burstiness with which the average is delivered.

Now change only bucketSizeDuration to ms100. The fill rate is unchanged at 32 octets per slot, but the cap becomes 64 000 x 0.1 = 6 400 octets, reached after 200 slots of idleness. A channel that has been quiet for 100 ms can now claim 6 400 octets in a single round 1 -- more than four full 1500-octet transport blocks' worth of protected allocation, ahead of every lower-priority channel. The long-run rate is identical; the instantaneous impact on everything else is twenty times larger.

💡
Key Point

This is the interaction with DRX that catches people. A UE on a 160 ms long DRX cycle wakes up with every bucket at its cap. If the buckets are large, the first grant after every on-duration is consumed almost entirely by round 1, in priority order, and delay-sensitive channels that happen to sit lower in the priority order see their worst latency immediately after wake-up. Correlating LCP behaviour against the DRX cycle -- see the companion 11 DRX document -- explains a whole class of "latency spikes every 160 ms" reports.

10. Where MAC Control Elements Fit: the Fixed Priority Order

LCP allocates to logical channels, but a MAC PDU also has to carry control elements, and those are not logical channels and have no Bj. TS 38.321 clause 5.4.3.1.3 settles it with a fixed list -- not a configurable priority, a hard-coded order:

RankItemWhy it sits there
1C-RNTI MAC CE, or data from UL-CCCHBoth are identity: without them the network cannot tell which UE this is. Nothing else matters if that fails
2Configured Grant Confirmation / Multiple Entry Configured Grant ConfirmationThe network is waiting to know whether a periodic resource is live. An unconfirmed grant wastes resource every period until it is resolved
3BFR / Truncated BFR (and the Rel-17 Enhanced variants)A beam has failed. Delaying this delays recovery on a carrier that is currently useless
4LBT failure MAC CE (NR-U)Same argument: the UE cannot get channel access and the network needs to know now
5Sidelink Configured Grant ConfirmationAs rank 2, for sidelink
6BSR -- except a BSR included for paddingThe report that shapes every future grant. Above data, because mis-sized future grants cost more than a few delayed octets now
7Single Entry PHR / Multiple Entry PHRShapes future MCS and PRB decisions, but on a slower timescale than the buffer report
8Desired Guard Symbols (IAB)Rel-16 IAB timing negotiation
9Desired IAB-DU TX Power AdjustmentRel-17 IAB power negotiation
10Pre-emptive BSRRel-16 IAB. Reports data that has not arrived yet, so it is genuinely less urgent than a report of data that has
11Sidelink BSR -- except when included for paddingAs rank 6, for sidelink
12Data from any logical channel, except UL-CCCHEverything in §6 through §9 happens here, inside one rank of this list
13Recommended Bit Rate QueryA hint about a future codec rate. Nothing breaks if it waits
14Sidelink BSR included for paddingOpportunistic only
15BSR included for paddingLast, because by definition it is only being sent because the space would otherwise be padding

Table 8. MAC CE and data priority, TS 38.321 cl. 5.4.3.1.3. Ranks 8-11 and 14 are Rel-16/17 additions; the shape of the list -- identity first, reports above data, padding reports last -- is Rel-15.

💡
Key Point

Notice ranks 6 and 15: the same control element, the BSR, sits both above and below all data depending on why it is being sent. A triggered BSR takes octets away from the data; a padding BSR only ever uses octets that were going to be padding. That single distinction is why enabling padding BSRs is free and why a chronically-truncated BSR is a grant-size problem rather than a priority problem. The BSR triggers themselves are in the companion 10 BSR, PHR, SR document.

One more rule that has real operational effect: MAC does not build a PDU whose only contents are a padding BSR and padding. If nothing else wants to go out, the grant is skipped -- and with skipUplinkTxDynamic configured, skipped without any transmission at all. So the absence of an uplink transmission on a granted occasion is not necessarily a failure; it can be the correct behaviour.

11. RLC Segmentation, and How a Partly-Served Channel Resumes

LCP produces a size per logical channel, not data. MAC then asks the RLC entity for a PDU of that size, and RLC decides how to satisfy it TS 38.322. Two rules bound what RLC may do:

  • MAC shall not ask RLC to segment an RLC SDU if the whole SDU fits in the space allocated to that channel. Gratuitous segmentation costs header octets and reassembly state for nothing.
  • If segmentation is necessary, MAC shall maximise the segment size to fill the allocation as fully as possible. A half-used allocation is worse than a segment.
One Grant, Three Logical Channels, One MAC PDUOne Grant, Three Logical Channels, One MAC PDUGrantLCPBuildSendUE RLCUE MACgNBBuffers: LCID 5 = 40 oct, LCID 6 = 100 oct, LCID 7 = 80 oct.Bj: 50 / 30 / 0. No BSR or PHR pending.DCI 0_1: UL grant, TBS 200 octetsC-RNTI 0x5A31, HARQ pid 2, k2 = 41Step 0: all three channels pass allowedSCS-List,maxPUSCH-Duration and allowedServingCellsRound 1: 40 + 30 + 0 = 70 octets of SDU(+ 4 octets of subheader) -> 126 octets leftRound 2: Bj -> 10 / 0 / 0Round 3: LCID 6 +70, LCID 7 +54 (2 octets ofnew subheader) -> 0 octets leftRequest MAC SDU: LCID 5, 40 octetsone RLC request per logical channel, totalling rounds 1 and 32RLC PDU, 40 octetscomplete SDU, no segmentationRequest MAC SDU: LCID 6, 100 octets3RLC PDU, 100 octetscomplete SDURequest MAC SDU: LCID 7, 54 octets4RLC PDU, 54 octets -- SEGMENTEDSI = first segment, SO = 0; 26 octets stay in the RLC bufferPUSCH: MAC PDU, 200 octets, 3 subPDUsLCID 5 L=40 | LCID 6 L=100 | LCID 7 L=54 | no padding5Remaining: LCID 5 = 0, LCID 6 = 0, LCID 7 = 26 octets. No Regular BSR istriggered -- no new data arrived. The gNB learns about those 26 octets fromthe next Periodic or padding BSR.MAC asks RLC for a size, not for data. That is why the subheader tax has to be paid before the request is made, and whythe numbers in rounds 1 and 3 are SDU sizes rather than subPDU sizes.
Figure 7. The full sequence for Worked Allocation 1, including the RLC requests. MAC asks for sizes; RLC decides what fits and what gets segmented.
SituationWhat RLC returnsWhat is left behindNext grant
Allocation >= the whole SDUThe complete RLC PDUNothing for that SDUThe next SDU in the queue, from the start
Allocation < the SDU, first timeA segment with SI = first, SO = 0The remainder of the SDU, with its byte offset recordedA middle or last segment with SO set to the resumption point
Allocation < the remaining segmentA middle segmentStill more of the same SDUContinues from the new SO
An RLC STATUS PDU is pendingThe STATUS PDU, ahead of any data PDU on that channelAll the dataData resumes. A STATUS PDU can therefore consume a channel's whole allocation
An RLC retransmission is pendingThe retransmission, ahead of new data on that channelThe new dataNew data resumes once retransmissions are cleared
Allocation smaller than the minimum RLC headerNothingAll the dataThe allocation is wasted; MAC pads instead

Table 9. What happens inside one logical channel's allocation. The last three rows are why an allocation can produce less data than LCP allocated -- and none of them is visible in the LCP arithmetic itself.

📘
Spec Detail

Bj is decremented by the octets of MAC SDU actually served, so if RLC returns a STATUS PDU instead of data, the channel is still charged for those octets. Control traffic inside a logical channel competes with that channel's own data for that channel's tokens, not with other channels. A bearer in heavy RLC retransmission therefore delivers less user data per second than its PBR would suggest, and the shortfall does not appear anywhere in the MAC statistics.

Two consequences worth remembering. First, "data available for transmission" for LCP purposes is defined by RLC and PDCP, not by MAC TS 38.322 cl. 5.5 TS 38.323 cl. 5.10: it includes RLC SDUs not yet built into PDUs, PDUs pending initial transmission, PDUs pending retransmission, and PDCP SDUs and control PDUs. Second, a partially served channel needs no special handling at the MAC layer at all -- MAC keeps no per-channel position. The resumption point lives in RLC, which is why LCP looks stateless in a trace apart from Bj.

12. Intra-UE Prioritisation and Cancellation (Rel-16)

Rel-15 LCP assumes one grant at a time. Rel-16 URLLC broke that assumption: a UE can hold a periodic configured grant dimensioned for sensor traffic and receive a dynamic grant for a latency-critical message that overlaps it in time, and it can only transmit one of them. lch-BasedPrioritization in MAC-CellGroupConfig turns on the machinery that resolves this.

MechanismScopeWhat it comparesOutcome
Physical-layer priority indexOne grantphy-PriorityIndex in ConfiguredGrantConfig, or the priority indicator field in the DCILabels the grant p0 or p1. allowedPHY-PriorityIndex then decides which logical channels may use it (§5)
LCH-based prioritisation (lch-BasedPrioritization)Two overlapping uplink grants in the same MAC entityThe highest-priority logical channel, or MAC CE, that LCP selected for each grantThe grant with the more urgent contents is transmitted; the other is de-prioritised
De-prioritised MAC PDU handlingThe losing grantn/aThe already-built MAC PDU stays in its HARQ buffer and is not transmitted. The network can grant a retransmission for that HARQ process later, and the same PDU goes out then
SR versus PUSCHAn SR occasion overlapping a PUSCH transmissionThe priority of the logical channel that triggered the SR against the PUSCH contentsOne of the two is dropped. This is why an SR can go missing with no failure logged anywhere
UL cancellation indication (DCI format 2_4)Between UEs, not within oneNothing about this UE's bearersThe gNB tells a UE to stop transmitting on specific resources so a different, more urgent UE can use them. Often confused with the row above; it is a completely separate mechanism

Table 10. Rel-16 prioritisation mechanisms. The last row is the one that gets mixed up with the others in analysis -- it is inter-UE and has nothing to do with LCP.

⚠️
Common Pitfall

A de-prioritised MAC PDU is not discarded and not rebuilt. It sits in the HARQ buffer holding a copy of data that RLC also still believes is in flight. In a log this looks like a HARQ process that receives a retransmission grant with no prior initial transmission -- which is exactly what happened, and which a naive HARQ analysis will flag as a protocol error. See the companion 05 HARQ document for the buffer semantics.

13. Configuration Reference (ASN.1 and Ranges)

LogicalChannelConfig ::= SEQUENCE {
    ul-SpecificParameters      SEQUENCE {
        priority                     INTEGER (1..16),
        prioritisedBitRate           ENUMERATED {kBps0, kBps8, kBps16,
                                        kBps32, kBps64, kBps128, kBps256,
                                        kBps512, kBps1024, kBps2048,
                                        kBps4096, kBps8192, kBps16384,
                                        kBps32768, kBps65536, infinity},
        bucketSizeDuration           ENUMERATED {ms5, ms10, ms20, ms50,
                                        ms100, ms150, ms300, ms500, ms1000,
                                        spare7, ..., spare1},
        allowedServingCells          SEQUENCE (SIZE (1..maxNrofServingCells-1))
                                        OF ServCellIndex        OPTIONAL,
        allowedSCS-List              SEQUENCE (SIZE (1..maxSCSs))
                                        OF SubcarrierSpacing    OPTIONAL,
        maxPUSCH-Duration            ENUMERATED {ms0p02, ms0p04, ms0p0625,
                                        ms0p125, ms0p25, ms0p5,
                                        spare2, spare1}         OPTIONAL,
        configuredGrantType1Allowed  ENUMERATED {true}          OPTIONAL,
        logicalChannelGroup          INTEGER (0..maxLCG-ID)     OPTIONAL,
        schedulingRequestID          SchedulingRequestId        OPTIONAL,
        logicalChannelSR-Mask        BOOLEAN,
        logicalChannelSR-DelayTimerApplied  BOOLEAN,
        ...,
        bitRateQueryProhibitTimer    ENUMERATED {s0, s0dot4, s0dot8,
                                        s1dot6, s3, s6, s12, s30} OPTIONAL,
        [[ allowedCG-List-r16        SEQUENCE (SIZE (0..maxNrofConfiguredGrantConfigMAC-1-r16))
                                        OF ConfiguredGrantConfigIndexMAC-r16 OPTIONAL,
           allowedPHY-PriorityIndex-r16  ENUMERATED {p0, p1}     OPTIONAL ]]
    }                                                            OPTIONAL,
    ...
}

-- maxLCG-ID = 7, so eight logical channel groups exist.
-- ul-SpecificParameters is absent for a downlink-only logical channel,
-- which is the only legitimate way for a channel to have no LCP config.

Listing 1. Abridged from TS 38.331. ... marks omitted enumeration members and extension markers. Note that logicalChannelSR-Mask and logicalChannelSR-DelayTimerApplied are mandatory BOOLEANs, not OPTIONAL -- they are always present and always decidable.

Timer / counterWhereRangeRelevance to LCP
bucketSizeDuration
LogicalChannelConfig
ms5 .. ms1000Not a timer -- a multiplier. Caps Bj at PBR x BSD
bitRateQueryProhibitTimer
LogicalChannelConfig
s0 .. s30Rate-limits Recommended Bit Rate Query CEs, which compete at rank 13
logicalChannelSR-DelayTimer
BSR-Config
sf20, sf40, sf64, sf128, sf512, sf1024, sf2560Delays the SR that a channel's Regular BSR would otherwise trigger. Applies only to channels with logicalChannelSR-DelayTimerApplied = true
periodicBSR-Timer
BSR-Config
sf1 .. sf2560, infinityGoverns how often the report that shapes the next grant is refreshed
retxBSR-Timer
BSR-Config
sf10 .. sf10240The safety net when a BSR was sent and no grant followed
timeAlignmentTimer
TAG-Config
ms500 .. ms10240, infinityOn expiry the uplink is unusable, LCP stops running and all HARQ buffers are flushed

Table 11. Timers that touch LCP. Only the first is an LCP parameter proper; the rest govern whether LCP gets a grant to work on at all. The BSR timers are covered in the companion 10 BSR, PHR, SR document.

14. Failure Modes and What Each One Means

FailureDetected byWhat the UE doesDiagnostic pointer
A logical channel is excluded by a restrictionNobody -- it is correct behaviourNever puts that channel's data in that class of grantThe bearer sends nothing even on an empty cell with large grants. Read allowedSCS-List, maxPUSCH-Duration, allowedServingCells, allowedCG-List, allowedPHY-PriorityIndex (§5)
A logical channel is starved by priorityNobodyServes it occasionally from round 3 leftoversThroughput that tracks cell load inversely. Fix with prioritisedBitRate, not with priority -- see §8
prioritisedBitRate = infinity on a saturable bearerNobodyServes that channel to completion in round 1 every grantOne bearer at line rate and every other bearer at near zero, independent of priority (§8.1)
bucketSizeDuration too small for a bursty channelNobodyCannot bank allowance, so a burst is served at PBR rather than from the bucketA GBR bearer that meets its rate on average but misses its delay budget on every burst
bucketSizeDuration too largeNobodyOne channel claims a whole grant in round 1 after being idleLatency spikes on other bearers immediately after every DRX on-duration (§9)
logicalChannelGroup shared by channels of very different urgencyNobody at the UE; the scheduler mis-sizes grantsReports both channels' data as one number in the BSRGrants that are the right total size and the wrong shape. Check LCG assignment against priority
Two channels configured with equal priorityNobody"Should be served equally" -- but the spec says should, so the order is implementation-definedDifferent UE models splitting the same grant differently. Not a bug in either; avoid equal priorities if you need determinism
Grant too small to carry even one subheader plus one octetMACSends padding only, or skips the transmission entirely if skipUplinkTxDynamic is setGranted occasions with no PUSCH. Expected behaviour, not a failure
Every eligible channel excluded, or all buffers emptyMACSkips the transmission (with skipUplinkTxDynamic) or sends a padding-only PDUIf this is frequent, the scheduler is granting speculatively without a BSR to justify it
configuredGrantType1Allowed absent on the intended bearerNobodyThe configured grant goes out full of padding, every periodA steady stream of padding-only PUSCH at exactly the configured grant periodicity. One of the most wasteful and most easily missed misconfigurations in NR
A de-prioritised MAC PDU (Rel-16)MACKeeps the built PDU in the HARQ buffer and does not transmit itA HARQ process receiving a retransmission grant with no logged initial transmission (§12)
RLC STATUS or retransmission consuming a channel's allocationNobody at MAC levelServes control and retransmissions before new data within that channelUser throughput below PBR while MAC statistics show the channel being served correctly (§11)

Table 12. LCP failure modes. Almost every row is "detected by nobody": LCP has no failure condition of its own, so every fault presents as a performance symptom on some other bearer.

💡
Key Point

The two failures worth separating first, because they need opposite fixes: an excluded channel needs a restriction removed, and a starved channel needs a PBR added. Both look like "this bearer gets no throughput". The distinguishing test is load: starvation eases when the cell empties, exclusion does not move at all.

15. Illustrative Message Traces

🔍
About These Traces

Illustrative trace. Field names and encodings follow 3GPP; the values are constructed for this document and are not a capture from any deployed or lab network.

15.1 The configuration LCP is working from

[RRC-DL] rlc-BearerToAddModList -> mac-LogicalChannelConfig
RRCReconfiguration
 cellGroupConfig
  rlc-BearerToAddModList
   [0] logicalChannelIdentity ............ 1        -- SRB1, DCCH
       servedRadioBearer: srb-Identity ... 1
       mac-LogicalChannelConfig
        ul-SpecificParameters
         priority ......................... 1
         prioritisedBitRate ............... infinity
         bucketSizeDuration ............... ms5
         logicalChannelGroup .............. 0
         schedulingRequestID .............. 0
         logicalChannelSR-Mask ............ FALSE
         logicalChannelSR-DelayTimerApplied FALSE
   [1] logicalChannelIdentity ............ 5        -- DRB2, conv. voice
       servedRadioBearer: drb-Identity ... 2
       mac-LogicalChannelConfig
        ul-SpecificParameters
         priority ......................... 4
         prioritisedBitRate ............... kBps32   -- 16 oct/slot at 30 kHz
         bucketSizeDuration ............... ms5      -- cap 160 octets
         logicalChannelGroup .............. 1
         schedulingRequestID .............. 0
         allowedSCS-List .................. { kHz30 }
         maxPUSCH-Duration ................ ms0p125
   [2] logicalChannelIdentity ............ 6        -- DRB3, conv. video
        ul-SpecificParameters
         priority ......................... 6
         prioritisedBitRate ............... kBps32
         bucketSizeDuration ............... ms5
         logicalChannelGroup .............. 2
   [3] logicalChannelIdentity ............ 7        -- DRB4, background
        ul-SpecificParameters
         priority ......................... 9
         prioritisedBitRate ............... kBps0    -- round 3 only
         bucketSizeDuration ............... ms5
         logicalChannelGroup .............. 2
         configuredGrantType1Allowed ...... (absent) -- may NOT use CG type 1

Listing 2. The four logical channels used throughout §7 and §8. Note prioritisedBitRate = infinity on SRB1 and kBps0 on the background bearer -- the two extremes of the enumeration, both correctly used.

15.2 The allocation of Worked Allocation 1

[MAC-UL] LCP, 200-octet grant
14:22:07.4185  [MAC-UL] grant: DCI 0_1, C-RNTI 0x5A31, TBS 200 octets
                        HARQ pid 2, k2 = 4, PUSCH slot 4193.7, SCS 30 kHz,
                        PUSCH duration 2 symbols (0.07 ms), phy priority p0

14:22:07.4185  [MAC] LCP step 0 -- candidate selection
   LCID 1  DCCH  : allowedSCS-List absent, maxPUSCH-Duration absent
                   -> eligible, 0 octets available
   LCID 5  voice : allowedSCS-List {kHz30} matches the grant
                   maxPUSCH-Duration ms0p125 >= 0.07 ms  -> eligible
                   40 octets available,  Bj = 50
   LCID 6  video : eligible, 100 octets available, Bj = 30
   LCID 7  bulk  : eligible, 80 octets available,  Bj = 0   (PBR kBps0)
   pending MAC CEs: none

14:22:07.4185  [MAC] LCP round 1  (decreasing priority, bounded by Bj)
   LCID 1  prio 1  PBR=inf : 0 octets available            -> 0
   LCID 5  prio 4  Bj=50   : min(50, 40) = 40  + 2 subhdr  -> space 200 -> 158
   LCID 6  prio 6  Bj=30   : min(30, 100) = 30 + 2 subhdr  -> space 158 -> 126
   LCID 7  prio 9  Bj=0    : min(0, 80) = 0                -> space 126

14:22:07.4185  [MAC] LCP round 2  (decrement Bj by SDU octets served)
   LCID 5  Bj 50 -> 10
   LCID 6  Bj 30 -> 0
   LCID 7  Bj 0  -> 0

14:22:07.4185  [MAC] LCP round 3  (strict priority, Bj ignored, 126 octets)
   LCID 5 : buffer empty                                   -> +0
   LCID 6 : min(70, 126) = 70, subheader already counted    -> space -> 56
   LCID 7 : new subheader 2 octets, min(80, 54) = 54        -> space -> 0

14:22:07.4185  [MAC] build: 3 subPDUs, 0 padding
   LCID 5  L=40    RLC PDU complete
   LCID 6  L=100   RLC PDU complete
   LCID 7  L=54    RLC segment, SI=first, SO=0, 26 octets retained
   total 42 + 102 + 56 = 200 octets = TBS                   [consistent]

Listing 3. Round by round, with the space accounting explicit. Note that step 0 records why each channel was admitted, not just that it was -- which is what makes the failure in §15.3 diagnosable.

15.3 A failure: a bearer excluded from every ordinary grant

[MAC-UL] maxPUSCH-Duration excludes a bearer
14:31:02.010  [MAC-UL] grant: DCI 0_1, TBS 848 octets, SCS 30 kHz,
                        PUSCH 14 symbols = 0.5 ms, phy priority p0
14:31:02.010  [MAC] LCP step 0
   LCID 5  voice : maxPUSCH-Duration = ms0p125
                   grant PUSCH duration = 0.5 ms
                   *** 0.5 ms > 0.125 ms -> NOT eligible ***
   LCID 6  video : eligible
   LCID 7  bulk  : eligible
14:31:02.010  [MAC] LCP: LCID 5 contributed 0 octets

-- the same thing on the next 21 grants, all 14-symbol PUSCH --

14:31:12.510  [MAC] LCID 5: 0 octets sent in 10.5 s across 22 grants
                    total granted in that window: 17 264 octets
                    LCID 5 buffer occupancy: monotonically rising, 4 210 octets
14:31:12.510  [MAC] BSR: LCG 1 buffer size index 20  -> > 3909 and <= 5446
14:31:12.512  [MAC-UL] grant: TBS 1544 octets  -- scheduler responding to the BSR
14:31:12.512  [MAC] LCP step 0: LCID 5 still NOT eligible (0.5 ms PUSCH)
14:31:12.512  [MAC] LCID 5 contributed 0 octets; 1544-octet grant filled by
                    LCID 6 and LCID 7

-- Diagnosis: the cell never schedules sub-slot PUSCH, so a bearer restricted
-- to maxPUSCH-Duration ms0p125 can never be served. The BSR keeps reporting
-- growing data, the scheduler keeps granting more, and the grants keep being
-- consumed by the OTHER bearers. Larger grants make the symptom worse, not
-- better, because they attract more lower-priority data.

Listing 4. Exclusion, not starvation. The bearer's buffer grows, the scheduler responds correctly, and none of the extra capacity reaches the bearer that asked for it.

🔍
What You See In Logs

The signature to memorise from that trace: rising buffer occupancy, rising grant sizes, zero throughput on the affected bearer, and rising throughput on the others. A priority problem does not look like that -- under priority starvation the bearer gets something whenever the grant is generous. Zero across 22 grants totalling 17 kB is a restriction, and the restriction is named in the step-0 line.

15.4 Control elements winning against data

[MAC-UL] MAC CEs ahead of logical channel data
14:35:44.821  [MAC-UL] grant: TBS 56 octets, C-RNTI 0x5A31, HARQ pid 0
14:35:44.821  [MAC] pending: Regular BSR (LCG 1, LCG 2 have data)
                             Single Entry PHR (phr-PeriodicTimer expired)
                    buffers: LCID 5 = 620 oct, LCID 6 = 1400 oct

14:35:44.821  [MAC] multiplexing by cl. 5.4.3.1.3 priority order:
   rank  6  Long BSR (triggered, not padding)
            subheader 2 + bitmap 1 + 2 buffer sizes = 5 octets   -> 51 left
   rank  7  Single Entry PHR
            subheader 1 + 2 octets                  = 3 octets   -> 48 left
   rank 12  logical channel data -- LCP runs on the remaining 48 octets
            LCID 5  prio 4  Bj=160 (capped) : min(160, 620) = 46 + 2 subhdr
                                              -> space 48 -> 0
            LCID 6  prio 6                  : 0 octets available in this grant

14:35:44.821  [MAC] build: 3 subPDUs, 0 padding
   subPDU 1  LCID 5   L=46    (SDU first in the uplink)
   subPDU 2  LCID 62  Long BSR, LCG 1 idx 22, LCG 2 idx 24
   subPDU 3  LCID 57  Single Entry PHR
   total 48 + 5 + 3 = 56 octets = TBS                        [consistent]

-- 8 of 56 octets (14%) went to reports. The video bearer got nothing at all
-- from a grant that was nominally shared. On a grant this size the priority
-- order in cl. 5.4.3.1.3 dominates the LCP arithmetic completely.

Listing 5. Reports outrank data. Note that the reports were built first and placed last: uplink wire order is SDUs then CEs, but the allocation order is the priority list.

15.5 Rel-16 intra-UE prioritisation

[MAC-UL] lch-BasedPrioritization
14:41:19.2500  [MAC] configured grant CG#1 occasion: PUSCH slot 8238.4,
                     TBS 120 octets, phy priority p0
14:41:19.2500  [MAC] LCP for CG#1: LCID 7 (prio 9) selected, 118 octets
14:41:19.2500  [MAC] MAC PDU built for HARQ pid 4, 120 octets, NOT YET SENT

14:41:19.2502  [MAC-UL] DCI 0_1: dynamic grant, PUSCH slot 8238.4 (OVERLAP),
                        TBS 88 octets, priority indicator = p1
14:41:19.2502  [MAC] LCP step 0 for the dynamic grant:
   LCID 5  allowedPHY-PriorityIndex = {p1}  -> eligible
   LCID 7  allowedPHY-PriorityIndex = {p0}  -> NOT eligible
14:41:19.2502  [MAC] LCP for dynamic grant: LCID 5 (prio 4), 86 octets

14:41:19.2502  [MAC] lch-BasedPrioritization: two grants overlap in slot 8238.4
   CG#1          highest-priority selected LCH = LCID 7, priority 9
   dynamic grant highest-priority selected LCH = LCID 5, priority 4
   -> transmit the dynamic grant; DE-PRIORITISE CG#1

14:41:19.2503  [MAC-UL] PUSCH sent: 88 octets, HARQ pid 6, LCID 5 L=86
14:41:19.2503  [MAC] HARQ pid 4: MAC PDU retained in buffer, deprioritised,
                     no transmission occurred
14:41:19.2610  [MAC-DL] DCI 0_1: retransmission grant, HARQ pid 4, NDI unchanged
14:41:19.2612  [MAC-UL] PUSCH sent: HARQ pid 4, the retained 120-octet PDU

-- HARQ pid 4 shows a 'retransmission' with no initial transmission. That is
-- correct Rel-16 behaviour, not a protocol error.

Listing 6. A configured grant losing to a higher-priority dynamic grant, and the de-prioritised PDU going out later on the same HARQ process.

16. Release Deltas: Rel-15 to Rel-18

ReleaseChangeWhy it matters when reading LCP
Rel-15The token bucket, the three-round algorithm, the CE priority list, and the restrictions allowedServingCells, allowedSCS-List, maxPUSCH-Duration, configuredGrantType1AllowedEverything in §4 through §11. LCP itself has not changed since
Rel-16allowedCG-List and allowedPHY-PriorityIndex added to ul-SpecificParametersTwo new ways for a channel to be excluded from a grant, and both are commonly left unset in templates that predate them
Rel-16lch-BasedPrioritization: intra-UE prioritisation between overlapping uplink grants, and de-prioritised MAC PDUsA built MAC PDU may now not be transmitted, and a HARQ process may show a retransmission with no initial transmission (§12)
Rel-16Physical-layer priority index on dynamic and configured grantsA grant now has a priority of its own, independent of the logical channels that might fill it
Rel-16Pre-emptive BSR and its ranks in the CE priority list; up to 12 configured grant configurations per BWPTwo new middle ranks in the list in §10, and allowedCG-List becomes meaningful
Rel-16NR sidelink: sidelink BSR ranks, and a parallel LCP for the sidelink grantA UE can be running two LCP instances; make sure the trace you are reading is the uplink one
Rel-17IAB additions to the CE priority list (guard symbols, DU TX power adjustment); Enhanced BFR ranksMore ranks between reports and data. The relative order of BSR, PHR and data is unchanged
Rel-17Multi-PUSCH scheduling: one DCI granting several PUSCH occasionsLCP runs once per PUSCH occasion, not once per DCI. A trace that shows one grant and four LCP invocations is correct
Rel-18Further refinements around multi-carrier and low-latency scheduling; no change to the LCP algorithm itselfThe three rounds and the token bucket are the same code path they were in Rel-15

Table 13. LCP-relevant changes by release. Notably, the algorithm is unchanged since Rel-15 -- everything new is either an extra eligibility gate or an extra rank in the CE priority list.

🔄
Release Delta

Every Rel-16 addition to LCP is a way for a logical channel to be excluded rather than a way for it to be served better. That is the URLLC design philosophy in one sentence: guarantee latency by keeping the wrong traffic off the right resources, not by reprioritising within a shared pool. It also means the failure mode in §15.3 has become progressively easier to hit with each release.

17. Reading LCP in Logs: a Checklist

1. Get the grant's properties, not just its size. Numerology, PUSCH duration, serving cell, configured-grant index and physical-layer priority all gate eligibility before any arithmetic happens. A grant is not a quantity of bytes (§5).

2. Check eligibility before priority. If a bearer contributed zero octets, find out whether it was a candidate at all. Exclusion and starvation need opposite fixes and look identical in a throughput graph.

3. Read `prioritisedBitRate` and `bucketSizeDuration` together. Neither means anything alone: the fill rate is PBR and the burst allowance is PBR x BSD. A channel with a high PBR and ms5 behaves nothing like the same PBR with ms300.

4. Account for the subheaders. Two octets per active logical channel, three if its SDU exceeds 255. Sum the subPDUs against the TBS; if they do not match, your reconstruction of the allocation is wrong (§6).

5. Separate round 1 from round 3. A channel served in round 1 is being protected by its PBR; a channel served only in round 3 is living on leftovers. The totals can look the same and the robustness under load is completely different.

6. Check whether a MAC CE took the space. On grants below about 100 octets, a triggered BSR plus a PHR can be a fifth of the block, and they outrank all data (§10). Look for the reports before blaming the LCP arithmetic.

7. Correlate with the DRX cycle. Buckets fill during off-duration, so the first grant after each on-duration is the one most dominated by round 1. Latency spikes locked to the DRX period are usually a bucketSizeDuration story (§9).

8. Look for padding-only configured grants. A configured grant going out full of padding every period almost always means configuredGrantType1Allowed or allowedCG-List is missing on the bearer it was dimensioned for.

9. Check `logicalChannelGroup` against `priority`. LCP does not use LCG, but the BSR does, and a scheduler that cannot see the difference between two channels will grant the wrong shape of resource for both.

10. On Rel-16 and later, check for de-prioritised PDUs. A HARQ process with a retransmission and no initial transmission is lch-BasedPrioritization working, not a fault (§12).

18. Glossary

TermExpansionMeaning in this document
LCPLogical Channel PrioritisationThe MAC procedure that divides one uplink grant among the UE's logical channels TS 38.321 cl. 5.4.3.1
PBRPrioritised Bit RateprioritisedBitRate. The token fill rate, in kilobytes per second, and the rate a channel is protected at in round 1
BSDBucket Size DurationbucketSizeDuration. Multiplied by PBR it gives the bucket cap, i.e. how many milliseconds of unused allowance may be banked
Bj--The token count for logical channel j, in octets. Filled by PBR x T, capped at PBR x BSD, decremented by round 1 service. May go negative
Round 1 / 2 / 3--The three stages of the allocation: serve to Bj in priority order, decrement Bj, then fill the rest by strict priority
Step 0--The eligibility gate that runs before the rounds and decides which channels are candidates for this particular grant
LCHLogical ChannelOne DCCH or DTCH, identified by logicalChannelIdentity (1..32). The unit LCP operates on
LCGLogical Channel GroupOne of eight groups used for reporting buffer status. Not used by the allocation itself
priority
--The 1..16 ordering used by rounds 1 and 3. Distinct from the 5QI priority level, which is 1..127
TBSTransport Block SizeThe number of octets LCP has to fill exactly, chosen by the gNB scheduler
Subheader tax--Not a spec term. The 2 or 3 octets per active logical channel that never carry data, and are always paid by whoever is served last
lch-BasedPrioritization
--Rel-16 intra-UE prioritisation between overlapping uplink grants
De-prioritised MAC PDU--A MAC PDU that was built but not transmitted because a higher-priority grant overlapped it. Retained in the HARQ buffer

19. References

  • 3GPP TS 38.321 -- NR MAC protocol specification. Clause 5.4.3.1 (Logical channel prioritisation, the Bj update and the three rounds), 5.4.3.1.2 (selection of logical channels -- the restrictions), 5.4.3.1.3 (multiplexing of MAC CEs and MAC SDUs -- the priority list), 5.4.3.2 (multiplexing and assembly), 5.4.2 (uplink grant reception and prioritisation between overlapping grants), 5.4.4 (Scheduling Request), 5.4.5 (Buffer Status Reporting), 6.1.2 (the MAC PDU LCP produces).
  • 3GPP TS 38.331 -- RRC protocol specification. LogicalChannelConfig and ul-SpecificParameters, RLC-BearerConfig, MAC-CellGroupConfig (lch-BasedPrioritization, bsr-Config), ConfiguredGrantConfig (phy-PriorityIndex).
  • 3GPP TS 38.322 -- NR RLC protocol specification. Clause 5.2 (segmentation and reassembly), 5.5 (data volume calculation -- the "data available for transmission" that LCP consumes).
  • 3GPP TS 38.323 -- NR PDCP protocol specification. Clause 5.10 (data volume calculation), and PDCP duplication, which is what allowedServingCells exists for.
  • 3GPP TS 38.214 -- Physical layer procedures for data. Clause 5.1.3.2 and 6.1.4.2 (transport block size determination), clause 6.1 (PUSCH duration, which maxPUSCH-Duration gates on).
  • 3GPP TS 23.501 -- System architecture for the 5G System. Clause 5.7 and Table 5.7.4-1 (5QI characteristics: resource type, priority level, packet delay budget, packet error rate).
  • 3GPP TS 38.300 -- NR overall description. Clause 12 (QoS architecture: QoS flow to DRB mapping), clause 6.2 (MAC services).

Companion documents in this set

  • 05 HARQ -- the buffer semantics behind a de-prioritised MAC PDU, and the retransmissions that consume a channel's allocation.
  • 06 Channel Mapping -- where a logical channel identity comes from and what DCCH, DTCH and CCCH mean.
  • 07 MAC PDU and Control Elements -- the subPDU and subheader formats whose overhead the arithmetic in §7 accounts for, and every control element in the priority list of §10.
  • 08 Scheduling -- the other half of the story: who decides the transport block size that LCP then divides.
  • 10 BSR, PHR, SR -- how the network learns what to grant, the logical channel group mapping, and the reports that outrank data in §10.
  • 11 DRX -- the wake/sleep cycle that determines how full the buckets are when a grant arrives (§9).
  • 15 RRC Procedures -- where LogicalChannelConfig is signalled and reconfigured.