Support virtio-mmio devices from kernel command line

This commit is contained in:
Wang Yue 2026-06-16 23:26:31 +08:00 committed by Tate, Hongliang Tian
parent e68b9218d4
commit cf9c093d01
8 changed files with 222 additions and 16 deletions

1
Cargo.lock generated
View File

@ -408,6 +408,7 @@ version = "0.1.0"
dependencies = [
"aster-bigtcp",
"aster-block",
"aster-cmdline",
"aster-console",
"aster-fuse",
"aster-input",

View File

@ -35,6 +35,28 @@ console=ttyS0
console=ttyS0 console=hvc0
```
### `virtio_mmio.device`
Register a VirtIO-MMIO device from the kernel command line.
This parameter may be specified multiple times.
Format:
```text
virtio_mmio.device=<size>@<base>:<irq>[:<id>]
```
Notes:
- `size` and `base` may be decimal or hexadecimal with a `0x` prefix.
- `size` may use `K`, `M`, `G`, or `T` suffixes.
- `irq` must be nonzero.
- The optional `id` field is accepted for Linux compatibility but ignored.
Examples:
```text
virtio_mmio.device=0x200@0x5950f000:10
virtio_mmio.device=1K@0x1001e000:74
```
## Asterinas-specific
### `ostd.log_level`

View File

@ -127,7 +127,7 @@ macro_rules! define_kv_param_early {
///
/// The stored value type `S::Value` must implement
/// [`crate::parse::ParseRepeatableParamValue`]. This crate provides a default
/// implementation for `Vec<T>` where `T: FromStr`.
/// implementation for `Vec<T>` where `T: ParseParamValue`.
///
/// # Examples
///

View File

@ -86,13 +86,10 @@ impl<T: FromStr> ParseParamValue for T {
}
}
/// A `Vec<T>` where `T: FromStr` can be a repeatable parameter.
impl<T: FromStr> ParseRepeatableParamValue for Vec<T> {
/// A `Vec<T>` where `T: ParseParamValue` can be a repeatable parameter.
impl<T: ParseParamValue> ParseRepeatableParamValue for Vec<T> {
fn parse_all(values: &[&str]) -> Result<Self, ParamError> {
values
.iter()
.map(|v| v.parse().map_err(|_| ParamError::InvalidValue))
.collect()
values.iter().map(|value| T::parse_param(value)).collect()
}
}

View File

@ -6,7 +6,7 @@
//! command lines so users of this framework don't need to rewrite them.
use alloc::vec::Vec;
use core::num::NonZeroU32;
use core::num::{NonZeroU32, NonZeroUsize};
use crate::parse::{ParamError, ParseParamValue};
@ -208,6 +208,113 @@ fn parse_u32(s: &str) -> Result<u32, ParamError> {
s.parse::<u32>().map_err(|_| ParamError::InvalidValue)
}
/// Linux-style MMIO device descriptor.
///
/// This type parses values in the form `<size>@<base>:<irq>[:<id>]`.
/// `size` supports binary suffixes (`K`, `M`, `G`, and `T`), while `base`,
/// `irq`, and `id` may be decimal or hexadecimal with a `0x` prefix.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct MmioDevice {
base: usize,
size: NonZeroUsize,
irq: NonZeroU32,
id: Option<u32>,
}
impl MmioDevice {
/// Returns the base address of the MMIO region.
pub fn base(&self) -> usize {
self.base
}
/// Returns the size of the MMIO region.
pub fn size(&self) -> NonZeroUsize {
self.size
}
/// Returns the interrupt line described by the command-line value.
pub fn irq(&self) -> NonZeroU32 {
self.irq
}
/// Returns the optional device ID.
pub fn id(&self) -> Option<u32> {
self.id
}
}
impl ParseParamValue for MmioDevice {
fn parse_param(value: &str) -> Result<Self, ParamError> {
parse_mmio_device(value).ok_or(ParamError::InvalidValue)
}
}
fn parse_mmio_device(value: &str) -> Option<MmioDevice> {
let (size, rest) = value.split_once('@')?;
let mut rest_segments = rest.split(':');
let base = parse_usize_with_hex_prefix(rest_segments.next()?)?;
let irq = NonZeroU32::new(parse_u32_with_hex_prefix(rest_segments.next()?)?)?;
let id = match rest_segments.next() {
Some(device_id) => Some(parse_u32_with_hex_prefix(device_id)?),
None => None,
};
if rest_segments.next().is_some() {
return None;
}
let size = NonZeroUsize::new(parse_size(size)?)?;
Some(MmioDevice {
base,
size,
irq,
id,
})
}
fn parse_size(value: &str) -> Option<usize> {
let (number, shift) = match value.as_bytes().last()? {
b'k' | b'K' => (&value[..value.len() - 1], 10),
b'm' | b'M' => (&value[..value.len() - 1], 20),
b'g' | b'G' => (&value[..value.len() - 1], 30),
b't' | b'T' => (&value[..value.len() - 1], 40),
_ => (value, 0),
};
parse_usize_with_hex_prefix(number)?.checked_mul(1usize.checked_shl(shift)?)
}
fn parse_usize_with_hex_prefix(value: &str) -> Option<usize> {
if value.is_empty() {
return None;
}
if let Some(value) = value
.strip_prefix("0x")
.or_else(|| value.strip_prefix("0X"))
{
usize::from_str_radix(value, 16).ok()
} else {
value.parse().ok()
}
}
fn parse_u32_with_hex_prefix(value: &str) -> Option<u32> {
if value.is_empty() {
return None;
}
if let Some(value) = value
.strip_prefix("0x")
.or_else(|| value.strip_prefix("0X"))
{
u32::from_str_radix(value, 16).ok()
} else {
value.parse().ok()
}
}
/// Linux-style metric-suffixed u64 value.
///
/// Supports binary multiples (KiB-style):
@ -259,6 +366,36 @@ mod test {
use super::*;
#[ktest]
fn mmio_device_parse_ok() {
let dev = MmioDevice::parse_param("0x200@0x5950f000:10").unwrap();
assert_eq!(dev.base(), 0x5950_f000);
assert_eq!(dev.size().get(), 0x200);
assert_eq!(dev.irq().get(), 10);
assert_eq!(dev.id(), None);
let dev = MmioDevice::parse_param("1K@0x1001e000:74:2").unwrap();
assert_eq!(dev.base(), 0x1001_e000);
assert_eq!(dev.size().get(), 1024);
assert_eq!(dev.irq().get(), 74);
assert_eq!(dev.id(), Some(2));
}
#[ktest]
fn mmio_device_parse_err() {
for value in [
"",
"0x200@0x1000",
"0x200@0x1000:0",
"0@0x1000:1",
"1K@0x1000:1:2:3",
"1Z@0x1000:1",
"virtio_mmio.device=0x200@0x1000:1",
] {
assert!(MmioDevice::parse_param(value).is_err());
}
}
#[ktest]
fn metric_u64_parse_ok() {
assert_eq!(MetricU64::parse_param("0").unwrap(), MetricU64(0));

View File

@ -8,6 +8,7 @@ edition.workspace = true
[dependencies]
aster-bigtcp.workspace = true
aster-block.workspace = true
aster-cmdline.workspace = true
aster-console.workspace = true
aster-fuse.workspace = true
aster-input.workspace = true

View File

@ -1,17 +1,65 @@
// SPDX-License-Identifier: MPL-2.0
use alloc::vec::Vec;
use aster_cmdline::types::MmioDevice;
pub(super) use ostd::arch::irq::MappedIrqLine;
use ostd::{arch::irq::IRQ_CHIP, debug};
use ostd::{arch::irq::IRQ_CHIP, debug, info, warn};
use spin::Once;
use crate::transport::mmio::bus::MmioRegisterError;
pub(super) fn probe_for_device() {
// TODO: The correct method for detecting VirtIO-MMIO devices on x86_64 systems is to parse the
// kernel command line if ACPI tables are absent [1], or the ACPI SSDT if ACPI tables are
// present [2]. Neither of them is supported for now. This function's approach of blindly
// scanning the MMIO region is only a workaround.
// [1]: https://github.com/torvalds/linux/blob/0ff41df1cb268fc69e703a08a57ee14ae967d0ca/drivers/virtio/virtio_mmio.c#L733
// [2]: https://github.com/torvalds/linux/blob/0ff41df1cb268fc69e703a08a57ee14ae967d0ca/drivers/virtio/virtio_mmio.c#L840
probe_from_kernel_cmdline();
probe_from_microvm_constants();
}
static VIRTIO_MMIO_CMDLINE_DEVICES: Once<Vec<MmioDevice>> = Once::new();
aster_cmdline::define_repeatable_kv_param!("virtio_mmio.device", VIRTIO_MMIO_CMDLINE_DEVICES);
/// Probes Linux-compatible `virtio_mmio.device=<size>@<base>:<irq>[:<id>]` parameters.
///
/// This format follows Linux's `virtio_mmio.device` kernel parameter.
fn probe_from_kernel_cmdline() {
let Some(devices) = VIRTIO_MMIO_CMDLINE_DEVICES.get() else {
return;
};
let irq_chip = IRQ_CHIP.get().unwrap();
for device in devices {
info!(
"Probe MMIO command-line device: base={:#x}, size={:#x}, irq={}",
device.base(),
device.size().get(),
device.irq().get()
);
let Some(mmio_end) = device.base().checked_add(device.size().get()) else {
warn!(
"Ignore MMIO command-line device at {:#x} because its range overflows",
device.base()
);
continue;
};
if let Err(err) = super::try_register_mmio_device(device.base()..mmio_end, |irq_line| {
irq_chip.map_gsi_pin_to(irq_line, device.irq().get())
}) {
warn!(
"Ignore MMIO command-line device at {:#x} due to an error ({:?})",
device.base(),
err,
);
}
}
}
fn probe_from_microvm_constants() {
// TODO: If ACPI tables are present, the correct method for detecting VirtIO-MMIO
// devices is to parse the ACPI SSDT [1]. It is not supported yet, so we fall
// back to blindly scanning QEMU MicroVM's fixed MMIO window as a workaround.
// [1]: https://github.com/torvalds/linux/blob/0ff41df1cb268fc69e703a08a57ee14ae967d0ca/drivers/virtio/virtio_mmio.c#L840
// Constants from QEMU MicroVM. We should remove them as they're QEMU's implementation details.
//

View File

@ -97,7 +97,7 @@ where
Ok(())
}
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Debug)]
enum MmioRegisterError {
/// MMIO region not available.
MmioUnavailable,