Skip to content

feat: selection policy in UART virtualizer#4802

Open
frihetselsker wants to merge 1 commit into
tock:masterfrom
OxidosAutomotive:virt/selection_policy
Open

feat: selection policy in UART virtualizer#4802
frihetselsker wants to merge 1 commit into
tock:masterfrom
OxidosAutomotive:virt/selection_policy

Conversation

@frihetselsker

@frihetselsker frihetselsker commented Apr 22, 2026

Copy link
Copy Markdown

Pull Request Overview

This pull request fixes the fairness problem of virtual devices. So far it only implements it for the UART Mux, but if approved, the same fix will be easily implemented for the rest of the affected Muxes.

The Fairness Problem

When searching for the next operation, multiplexers (except the Alarm) will always iterate the devices list from beginning to end. This means that if one device always has a job and it was inserted earlier, it will always be picked.

The following examples illustrates the problem for the UART Mux. If debug!() is called frequently (for example in the UartMux), the DebugConsole has a monopoly over the UART Multiplexer, as it will be the only devices chosen to print.

insertion_first

Solution

We introduce the SelectionPolicy trait that provides Muxes with the device selection logic. It is a generic type and should not add any overhead, neither in speed, neither in code space.

We define two policies:

  • InsertionFirstPolicy that behaves like Muxes used to behave so far, it iterates the list from the beginning to the end. This provides backward compatibility.
  • RoundRobinPolicy that iterates the devices list in a round robin manner. Using this policy with the previous debug! examples allows all device to print.
round_robin

This solution offers a structural change that gives users the freedom to choose the policy they want. By default, the UART Mux uses the InsertionFirstPolicy and there are no changes required in the code.

Users will now able to implement their own selection policies for the UART Mux.

Testing Strategy

This pull request was tested by using a custom kernel of qemu_rv32_virt with UART peripheral, and three equivalent applications that try to use printf() simultaneously.

TODO or Help Wanted

Feedback

Documentation Updated

  • Updated the relevant files in /docs, or no updates are required.

We are unsure of the proper placement of the documentation in the README file. Help is needed here as well.

Formatting

  • Ran make prepush.

AI Use

  • The PR description details my use of AI in the production of the
    code in this PR, if any, and I have manually checked and
    personally certify the entire contents of this PR.

No AI was used for this PR.

@lschuermann

lschuermann commented Apr 22, 2026

Copy link
Copy Markdown
Member

Thanks @frihetselsker, priority and queue managers in virtualizers is not something we've considered much so far. Before we dive into the code, may I ask to do revisit the "AI Use" section of the PR comment and, if applicable, detail whether and how you used Gen-AI to help with this PR? It's poorly worded right now, but we require checking this box even if you did not use any of these tools currently (which #4792 proposes to change).

@bradjc

bradjc commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

This is a long standing issue, and it's great you are looking at it.

I do wonder, however, why a board would want the uart mux to prioritize apps in the order they are in the processes array (e.g., InsertionFirst) rather than round robin. Is InsertionFirst just for backwards compatibility and to not change current behavior, while still allowing RoundRobin? Or is there a specific use case you have in mind for wanting to use InsertionFirst or both for different virtualizers?

I guess what I'm asking, is would it be better to just have a round robin iterator tool and just switch all capsules to use that rather than apps.each directly.

@alexandruradovici

alexandruradovici commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

This is a long standing issue, and it's great you are looking at it.

I do wonder, however, why a board would want the uart mux to prioritize apps in the order they are in the processes array (e.g., InsertionFirst) rather than round robin. Is InsertionFirst just for backwards compatibility and to not change current behavior, while still allowing RoundRobin? Or is there a specific use case you have in mind for wanting to use InsertionFirst or both for different virtualizers?

I guess what I'm asking, is would it be better to just have a round robin iterator tool and just switch all capsules to use that rather than apps.each directly.

This is not related to applications, but to the mux that serves capsules. For the UART specifically, the console capsule handles the applications order, the mux handles several capsules that require the UART.

InsertionFirst is only for backward compatibility, as some downstream users may rely on this, and refers to the order in which the VirtualUart devices were added to the mux. Each capsule usually has one VirtualUart, applications all share the console's VirtualUart

@bradjc

bradjc commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

This is not related to applications, but to the mux that serves capsules. For the UART specifically, the console capsule handles the applications order, the mux handles several capsules that require the UART.

Oh, right. A board could strategically assign its virtual devices in the mux, I suppose, a little bit more effectively than processes in the process array.

InsertionFirst is only for backward compatibility, as some downstream users may rely on this, and refers to the order in which the VirtualUart devices were added to the mux. Each capsule usually has one VirtualUart, applications all share the console's VirtualUart

Personally, I don't think we ever guaranteed this behavior and I think virtualized devices should not have any guarantee about their priority. So I'm find changing the behavior.

@alexandruradovici

Copy link
Copy Markdown
Contributor

Personally, I don't think we ever guaranteed this behavior and I think virtualized devices should not have any guarantee about their priority. So I'm find changing the behavior.

Even if we delete the InsertionFirst and make RoundRobin the default, I think keeping the idea that users can configure the mux selection behaviour is valuable.

@bradjc

bradjc commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Personally, I don't think we ever guaranteed this behavior and I think virtualized devices should not have any guarantee about their priority. So I'm find changing the behavior.

Even if we delete the InsertionFirst and make RoundRobin the default, I think keeping the idea that users can configure the mux selection behaviour is valuable.

Can you elaborate on that? If whoever created the first mux had implemented a generic round robin iterator that all other muxes copied, I'm dubious that anyone would now push to have the option to prioritize the first thing added to the mux.

@frihetselsker

Copy link
Copy Markdown
Author

Thanks @frihetselsker, priority and queue managers in virtualizers is not something we've considered much so far. Before we dive into the code, may I ask to do revisit the "AI Use" section of the PR comment and, if applicable, detail whether and how you used Gen-AI to help with this PR? It's poorly worded right now, but we require checking this box even if you did not use any of these tools currently (which #4792 proposes to change).

Sorry, I didn't check because I wasn't sure what to do since I hadn't used AI to write this code.

@alexandruradovici

alexandruradovici commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Personally, I don't think we ever guaranteed this behavior and I think virtualized devices should not have any guarantee about their priority. So I'm find changing the behavior.

Even if we delete the InsertionFirst and make RoundRobin the default, I think keeping the idea that users can configure the mux selection behaviour is valuable.

Can you elaborate on that? If whoever created the first mux had implemented a generic round robin iterator that all other muxes copied, I'm dubious that anyone would now push to have the option to prioritize the first thing added to the mux.

The issue is more generic than having the first thing added to the mux. The idea is to allow downstream users to control the way in which the muxes select the next device. For instance, we may have capsules controlling hardware that have different priorities based on the state in which a device is. For a car, they would have to behave differently when the car is in PARK than when the car is in DRIVE. Adding the SelectionPolicy trait allows this.

Whether we should use the RoundRobin as the default implementation is another discussion. If breaking downstream behaviour is not a problem, as this was never documented, then I think we can drop the InsertionFirst.

Comment thread capsules/core/src/virtualizers/selection_policy.rs
@bradjc

bradjc commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

The issue is more generic than having the first thing added to the mux. The idea is to allow downstream users to control the way in which the muxes select the next device. For instance, we may have capsules controlling hardware that have different priorities based on the state in which a device is. For a car, they would have to behave differently when the car is in PARK than when the car is in DRIVE. Adding the SelectionPolicy trait allows this.

I'm coming around to the idea of having the capability to implement dynamic list selection policies as useful. I think we should think through the trait.

@bradjc bradjc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to move forward with this approach, and we can just do the switch to round robin.

Hopefully board main.rs files don't need to change.

We do need to fix the CI test.

Comment on lines +18 to +28
/// Default policy which selects the first ready device starting from the beginning of the list.
pub struct InsertionFirstPolicy;

impl<I> SelectionPolicy<I> for InsertionFirstPolicy {
fn select<It>(&self, mut it: It, ready: impl Fn(&I) -> bool) -> Option<I>
where
It: Iterator<Item = I>,
{
it.find(|node| ready(node))
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this.

Comment on lines +53 to +59
impl Default for RoundRobinPolicy {
fn default() -> Self {
Self {
last_access_position: Cell::new(0),
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't need this.

pub const RX_BUF_LEN: usize = 64;

pub struct MuxUart<'a> {
pub struct MuxUart<'a, P: SelectionPolicy<&'a UartDevice<'a, P>> = InsertionFirstPolicy> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub struct MuxUart<'a, P: SelectionPolicy<&'a UartDevice<'a, P>> = InsertionFirstPolicy> {
pub struct MuxUart<'a, P: SelectionPolicy<&'a UartDevice<'a, P>>> {

Comment on lines +217 to +224
/// Create a new Mux with the [`InsertionFirstPolicy`] selection policy.
pub fn new(
uart: &'a dyn uart::Uart<'a>,
buffer: &'static mut [u8],
speed: u32,
) -> MuxUart<'a, InsertionFirstPolicy> {
MuxUart {
uart,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Create a new Mux with the [`InsertionFirstPolicy`] selection policy.
pub fn new(
uart: &'a dyn uart::Uart<'a>,
buffer: &'static mut [u8],
speed: u32,
) -> MuxUart<'a, InsertionFirstPolicy> {
MuxUart {
uart,
pub fn new(
uart: &'a dyn uart::Uart<'a>,
buffer: &'static mut [u8],
speed: u32,
) -> Self {
Self {
uart,

///
/// For the default implementation, please refer to [`MuxUart::new`] function
/// which uses [`InsertionFirstPolicy`] selection policy.
pub fn new_with_policy(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need.

$P
)
}};
// By default, if no selection policy is provided we will use the `InsertionFirstPolicy`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default to round robin.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants