What is the Logical Channel Prioritization and why we need it ? Before I answer this, let's think of high level view (100 km view). In the data transmission process, the data flow / storage from Logical channels to physical channel can be illustrated as below.
- Do Prioritization
- Is just Prioritization Enough ?
- Set the limit for Each Priority
- Setting the priority and PBR is good enough ?
- Who should care about the logical channel prioritization ?
- RRC Parameters
- The Procedure as 36.321 States It
- Reference
As shown here, there are multiples of Logical Channel Data which has data to be transmitted through Physical data pipe. As you can see here, Physical Pipe is single and relatively limited resource and it can be transmitted only once in a TTI (NOTE : You may say the physical pipe can also be a multiples considering multiple codewords carrier aggregation, but logically with the view point of MAC scheduler it can be considered as a single / aggregated pipe). If there are not so much data waiting in each of the logical channel buffer, there wouldn't much things to worry about, but if there are large amount of data waiting in every logical channels and those data cannot be packed into the single transmission of physical pipe, the problem would happen. The scheduler should decide which of the data should be transmitted right away and which of the data should wait for next chance. It means the scheduler should set a certain priorities for each of the logica channel and this process/rule is called Logical Channel Prioritization.
NOTE : To be honest, I didn't have much understanding on this topic before I write this note. The first big picture that I got about this topic was from this excellent blog. I just tried to understand this blog and write/illustrate in my own words and imagenation. I strongly recommend the readers to read the through the blog.

Do Prioritization
Then what would be the solution to handle those competing situation ? As briefly mentioned above, the simplest idea is to set the priority for each of Logical channels (In real impelmentation, this priority for each logical channel is set by RRC message as shown in RRC Parameter).
Step 1 : Let's assume that there are four Logical channels with a lot of data to be transmitted and the priority is set as shown below. (NOTE : Low Priority value indicaters High Priority..meaning that priority 0 is the highest priority and priority 3 is the lowest priority in this example).

Step 2 : Pack the data into the physical pipe based on Priority. In this case, the whole data of each logical channel get packed into the physical pipe based on priority as shown below. In this example, the physical pipe got full when Logical Channel 0,1,2 are packed into the physical pipe and Logical channel 3 should wait for next chance.

Step 3 : (If there is no other data flowing into Logical Channel 0,1,2), Logical Channel 3 would get the chance to be packed into the physical pipe at next transmission as shown below.

36.321 clause 5.4.3.1 states the rule the pictures illustrate. RRC controls the scheduling of uplink data by signalling a priority for each logical channel, and an increasing priority value indicates a lower priority level. The listing further down this page gives the range as 1 to 16, so 1 is the strongest and 16 the weakest. The diagrams above number their channels from 0, which shortens the example without reversing the order.
The procedure runs per grant rather than per bearer. The clause applies it when a new transmission is performed, so the whole decision is remade every time an uplink grant arrives. Between grants the UE stores only the counters described further down.
A lower number means a stronger claim : 36.321 clause 5.4.3.1 says an increasing priority value indicates a lower priority level.The configured range is 1 to 16 : 36.331 gives priority that range, so the 0 to 3 in the diagrams is an illustration rather than a configuration.The decision is remade on every grant : the clause applies the procedure when a new transmission is performed.Priority alone is a strict order : the strongest channel with data takes what it needs before the next one is looked at.
Is just Prioritization Enough ?
Would just prioritization setting be enough for the issues that we have ? In most cases this would work. However, there can be some corner cases where setting the prioritzation cause another problem. Let's think of a specific case as illustrated below. At TTI 1, all the data with prioity 0,1,2 got packed into the PHY/MAC packet.. but at the same time new data flow into the logical channel buffer as shown below.

What would happen to LoCh 3 at the next TTI ? Would it get the chance to get packed into PHY/MAC packet ? Still No because there are higher priority data waiting in the buffer even at the next TTI. What would happen this kind of situation repeats ? That is, what would happen to low priority channel data if high priority data keep flowing into the logical channel buffer ? If we pack the data into PHY/MAC pipe only based on priority, the low priority channel would never get a chance to transmit the data. This kind of situation is called Starvation.
The specification agrees that priority alone is not enough, and it answers in a way worth knowing. It does not cap the strong channel. 36.321 clause 5.4.3.1 instead guarantees the weak one a first round, and priority then decides whatever capacity is left.
One smaller rule in the same clause settles a case the diagrams do not cover. Logical channels configured with equal priority should be served equally, so two channels at the same priority split the remaining room rather than competing for it.
Starvation is the problem the clause solves : a channel whose buffer is never reached sends nothing, however long it waits.The answer is a guaranteed round, not a cap : 36.321 clause 5.4.3.1 gives every channel a first round before priority decides the rest.Equal priorities share : the clause says logical channels configured with equal priority should be served equally.Nothing here limits a strong channel : once the guaranteed round is done, the strongest channel with data is served first again.
Set the limit for Each Priority
What would be the solution for the starvation problem mentioned above ? One simple solution is to set some limit for each logical channel to push the data into the PHY/MAC pipe at once. In this way, even the lower priority channel would get higher chance to push its data into the PHY/MAC pipe even when high priority buffer is not completely empty as illustrated below.

NOTE : PBR in this diagram stands for prioritisedBitRate which is configured by RRC.
36.321 clause 5.4.3.1 keeps that limit in one variable per channel. The MAC entity maintains a variable Bj for each logical channel j. Bj starts at zero when the logical channel is established, and it is incremented by the product of PBR and the TTI duration for every TTI. The value can never exceed the bucket size, and the bucket size of a logical channel is equal to PBR multiplied by BSD.
Two configured numbers therefore do different jobs. The prioritised bit rate is a rate and fixes how fast the allowance accumulates. The bucket size duration is a time and fixes how much allowance may be saved up. The diagram below follows one channel through both.
The bucket is a quantity, not a rate. PBR sets how fast it fills and BSD sets how full it may get. A channel that has been quiet therefore carries a larger allowance into the next grant than one that has just been served.
Bj is the limit, held per channel : 36.321 clause 5.4.3.1 gives every logical channel its own variable.PBR fills the bucket : Bj grows by PBR multiplied by the TTI duration in every TTI.BSD sizes the bucket : the cap is PBR multiplied by BSD, and Bj is clipped to it.A quiet channel accumulates : an idle channel reaches the cap and arrives at the next grant with a full allowance.kBps0 and infinity are both configurable : 36.331 offers a prioritised bit rate of zero at one end and infinity at the other, which turns the allowance off or removes the limit.
Setting the priority and PBR is good enough ?
Definately it would be good enough for most of the case, but there always can be some corner case to cause difficulties (issues). Let's assume that data keep flowing into the high priority logical channels. In this case, even we set PBR, it is likely that low priority channel suffer from starvation.
To resolve this kind of issues, we put another restriction for the prioritization. It is a kind of timing (time duration) restriction for a logical channel to maintain the given priority. There is a kind of count-down timer for each logical channel and everytime a logical channel transmit a data with the size of PBR, the timer get descreased by 1. Once the count down timer become negative, it should give up its turn to transmit the data even if it has high priority. This timer prevent a channel to permanently monopolize the PHY/MAC pipe even if it has high priority and has some data to send all the time.
This count-down timer is configured by the RRC parameter : bucketSizeDuration.
36.321 clause 5.4.3.1 keeps the same accounting in Bj rather than in a separate timer. Step 2 of the procedure decrements Bj by the total size of the MAC SDUs served to that logical channel in Step 1. The amount subtracted is therefore the number of bytes actually sent, and NOTE 1 in the clause adds that Bj can be negative.
An empty bucket does not silence a channel, and this is the part most often misread. Step 1 skips that channel, because Step 1 serves only the channels with Bj greater than zero. Step 3 then serves every allowed channel again in strict decreasing priority order regardless of the value of Bj. A strong channel with an empty bucket still takes whatever capacity Step 1 left unused.
The guarantee runs one way. Each channel is promised its PBR share before priority decides anything, and nothing in the clause promises a weak channel more than that. A grant large enough to satisfy every bucket is distributed by priority exactly as it would have been without PBR at all.
Step 2 subtracts bytes, not ticks : Bj falls by the total size of the MAC SDUs served in Step 1.Bj is allowed to go negative : NOTE 1 in 36.321 clause 5.4.3.1 says so explicitly.An empty bucket costs a channel Step 1 only : Step 3 serves every allowed channel again, whatever Bj reads.PBR is a floor and never a ceiling : it guarantees a share and does not cap the channel that has priority.A large grant hides the whole mechanism : when the grant covers every bucket, the outcome is the plain priority order.
Who should care about the logical channel prioritization ?
Who is controlling the logical channel priotization ? The question has a documented answer rather than a conventional one, and 36.321 settles it in a table instead of a procedure. The row to look at is the Logical Channel prioritisation row, and the columns either side of it matter as much as the row does.
In terms of 3GPP specification shown below, Logical Channel Priotisation is for UE Uplink and is part of MAC layer functionality.
< 36.321-Table 4.4-1: MAC function location and link direction association. >

The highlighted row in the table above is worth reading across in full. Logical Channel prioritisation carries a mark under UE, under Uplink and under Sidelink tx. The eNB column is empty, so the network configures the inputs through RRC and the UE runs the procedure. 36.321 v19.3.0 still marks the row exactly that way.
One row above it is a different function with a confusingly similar name. Priority handling between logical channels of one MAC entity carries its marks under eNB, Downlink and Uplink. That one belongs to the network scheduler, and it decides between channels the network itself is serving rather than inside the UE.
Logical Channel prioritisation is a UE function : 36.321 Table 4.4-1 marks it under UE and leaves the eNB column empty.It covers uplink and sidelink transmission : the same row is marked under Uplink and under Sidelink tx.The network still sets the inputs : priority, prioritisedBitRate and bucketSizeDuration all arrive by RRC.A neighbouring row is a different function : priority handling between logical channels of one MAC entity is marked under eNB, Downlink and Uplink.The screenshot is still current : the same table in 36.321 v19.3.0 reads the same way.
RRC Parameters
As explained above, there were a few parameters for the operation of logical channel priotization : Priority, PBR(PriotisedBitRate), BSD(bucketSizeDuration). All of these are configured by RRC message as shown below.
Following is based on
LogicalChannelConfig ::= SEQUENCE {
ul-SpecificParameters SEQUENCE {
priority INTEGER (1..16),
prioritisedBitRate ENUMERATED {
kBps0, kBps8, kBps16, kBps32, kBps64, kBps128,
kBps256, infinity, kBps512-v1020, kBps1024-v1020,
kBps2048-v1020, spare5, spare4, spare3, spare2,
spare1},
bucketSizeDuration ENUMERATED {
ms50, ms100, ms150, ms300, ms500, ms1000, spare2,
spare1},
logicalChannelGroup INTEGER (0..3) OPTIONAL -- Need OR
} OPTIONAL, -- Cond UL
...,
[[ logicalChannelSR-Mask-r9 ENUMERATED {setup} OPTIONAL -- Cond SRmask
]],
[[ logicalChannelSR-Prohibit-r12 BOOLEAN OPTIONAL -- Need ON
]],
[[ laa-UL-Allowed-r14 BOOLEAN OPTIONAL, -- Need ON
bitRateQueryProhibitTimer-r14 ENUMERATED {
s0, s0dot4, s0dot8, s1dot6, s3, s6, s12,
s30} OPTIONAL --Need OR
]],
[[ allowedTTI-Lengths-r15 CHOICE {
release NULL,
setup SEQUENCE {
shortTTI-r15 BOOLEAN,
subframeTTI-r15 BOOLEAN
}
} OPTIONAL, -- Need ON
logicalChannelSR-Restriction-r15 CHOICE {
release NULL,
setup ENUMERATED {spucch, pucch}
} OPTIONAL, -- Need ON
channelAccessPriority-r15 CHOICE {
release NULL,
setup INTEGER (1..4)
} OPTIONAL, -- Need ON
lch-CellRestriction-r15 BIT STRING (SIZE (maxServCell-r13)) OPTIONAL -- Need ON
]],
[[
bitRateMultiplier-r16 ENUMERATED {x40, x70, x100, x200} OPTIONAL -- Need OR
]],
[[
allowedHARQ-Mode-r18 ENUMERATED {harqModeA, harqModeB} OPTIONAL -- Need OR
]]
}
The three parameters named above sit in the first half of the structure and have not moved since. Everything after the extension marker arrived later, and three of those later fields are read by the prioritization procedure itself rather than by anything else.
The first of them is allowedTTI-Lengths-r15. 36.321 clause 5.4.3.1 allocates resources only to the logical channels allowed to transmit using the TTI length of the grant. The field therefore decides which channels are candidates before any priority is compared. The second is lch-CellRestriction-r15, whose named cells count as restricted once carrier aggregation duplication is active.
The third is laa-UL-Allowed-r14, and it works on a different axis. On serving cells operating according to Frame Structure Type 3, only the logical channels carrying that field are considered at all. A channel can therefore be the highest priority one on the UE and still be invisible to a particular grant.
The first three fields are the ones the page has been about : priority, prioritisedBitRate and bucketSizeDuration, all inside ul-SpecificParameters.logicalChannelGroup is in the same structure : it is the buffer status reporting group and not part of the prioritization order.allowedTTI-Lengths-r15 filters the candidates : only channels allowed on the grant's TTI length are considered.lch-CellRestriction-r15 restricts by cell : 36.321 clause 5.4.3.1 applies it when carrier aggregation duplication is active.laa-UL-Allowed-r14 restricts by frame structure : on Frame Structure Type 3 only channels carrying it are considered.
The Procedure as 36.321 States It
Everything above is the reasoning. The clause itself is three numbered steps and a list, and reading them in order settles several questions the diagrams leave open.
36.321 clause 5.4.3.1 runs the three steps against one uplink grant. Step 1 allocates resources to all the allowed logical channels with Bj greater than zero, in decreasing priority order. Step 2 decrements Bj by the total size of the MAC SDUs served in Step 1. Step 3 serves every allowed logical channel again in strict decreasing priority order, regardless of the value of Bj, until either the data or the grant is exhausted.
A prioritised bit rate of infinity changes Step 1 alone. The clause then allocates resources for all the data available on that logical channel before meeting the PBR of the lower priority channels. The guaranteed round for everything below it waits until that channel is empty.
Data is not the only thing being ordered. The same clause sets a relative priority across MAC control elements and data together. The list starts with a control element for C-RNTI, or data from UL-CCCH. The control elements for BSR and for power headroom rank above ordinary data, and a BSR included for padding ranks last of all. Data from any logical channel sits low in that list, which is why a small grant can carry control signalling and almost nothing else.
Step 1 serves the buckets : allowed channels with Bj greater than zero, in decreasing priority order.Step 2 subtracts what was served : Bj drops by the total size of the MAC SDUs served in Step 1.Step 3 uses whatever is left : strict decreasing priority, regardless of Bj, until the data or the grant runs out.infinity suspends the guarantee below it : that channel takes all its data in Step 1 before any lower priority PBR is met.Control elements outrank ordinary data : 36.321 clause 5.4.3.1 puts BSR and power headroom above data from any logical channel, and padding BSR last.NB-IoT skips the bucket entirely : the clause says prioritisedBitRate, bucketSizeDuration and Steps 1 and 2 do not apply there.
Reference
The blog entry below is the one the note credits at the top. The two specifications after it carry the procedure and the parameters that this pass checked every added claim against.
[1] 36.321 : 3GPP - E-UTRA; Medium Access Control, v19.3.0. Clause 5.4.3.1 is the whole Logical Channel Prioritization procedure, including Bj, the bucket size and the three steps. Table 4.4-1 places the function on the UE for uplink and sidelink transmission.
[2] 36.331 : 3GPP - E-UTRA; Radio Resource Control, v19.3.0. LogicalChannelConfig carries priority, prioritisedBitRate and bucketSizeDuration, and the release extension groups listed above it.