Support virtio-mmio devices from kernel command line
This commit is contained in:
parent
e68b9218d4
commit
cf9c093d01
|
|
@ -408,6 +408,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"aster-bigtcp",
|
||||
"aster-block",
|
||||
"aster-cmdline",
|
||||
"aster-console",
|
||||
"aster-fuse",
|
||||
"aster-input",
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
///
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum MmioRegisterError {
|
||||
/// MMIO region not available.
|
||||
MmioUnavailable,
|
||||
|
|
|
|||
Loading…
Reference in New Issue