Add VirtIO device probing code on RISC-V platforms
This commit is contained in:
parent
39a541fdeb
commit
3353e53577
|
|
@ -0,0 +1,8 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
// TODO: Add `MappedIrqLine` support for Loongarch.
|
||||
pub(super) use ostd::irq::IrqLine as MappedIrqLine;
|
||||
|
||||
pub(super) fn probe_for_device() {
|
||||
unimplemented!()
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
pub(super) use ostd::arch::irq::MappedIrqLine;
|
||||
use ostd::arch::{
|
||||
boot::DEVICE_TREE,
|
||||
irq::{InterruptSourceInFdt, IRQ_CHIP},
|
||||
};
|
||||
|
||||
pub(super) fn probe_for_device() {
|
||||
// The device tree parsing logic here assumed a Linux-compatible device
|
||||
// tree.
|
||||
// Reference: <https://www.kernel.org/doc/Documentation/devicetree/bindings/virtio/mmio.txt>.
|
||||
let device_tree = DEVICE_TREE.get().unwrap();
|
||||
let mmio_nodes = device_tree.all_nodes().filter(|node| {
|
||||
node.compatible().is_some_and(|compatibles| {
|
||||
compatibles
|
||||
.all()
|
||||
.any(|compatible| compatible == "virtio,mmio")
|
||||
})
|
||||
});
|
||||
mmio_nodes.for_each(|node| {
|
||||
let interrupt_source_in_fdt = InterruptSourceInFdt {
|
||||
interrupt: node.interrupts().unwrap().next().unwrap() as u32,
|
||||
interrupt_parent: node
|
||||
.property("interrupt-parent")
|
||||
.and_then(|prop| prop.as_usize())
|
||||
.unwrap() as u32,
|
||||
};
|
||||
let mmio_region = node.reg().unwrap().next().unwrap();
|
||||
let mmio_start = mmio_region.starting_address as usize;
|
||||
let mmio_end = mmio_start + mmio_region.size.unwrap();
|
||||
|
||||
let _ = super::try_register_mmio_device(mmio_start..mmio_end, |irq_line| {
|
||||
IRQ_CHIP
|
||||
.get()
|
||||
.unwrap()
|
||||
.map_fdt_pin_to(interrupt_source_in_fdt, irq_line)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use log::debug;
|
||||
pub(super) use ostd::arch::irq::MappedIrqLine;
|
||||
use ostd::arch::irq::IRQ_CHIP;
|
||||
|
||||
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
|
||||
|
||||
// Constants from QEMU MicroVM. We should remove them as they're QEMU's implementation details.
|
||||
//
|
||||
// https://github.com/qemu/qemu/blob/3c5a5e213e5f08fbfe70728237f7799ac70f5b99/hw/i386/microvm.c#L201
|
||||
const QEMU_MMIO_BASE: usize = 0xFEB0_0000;
|
||||
const QEMU_MMIO_SIZE: usize = 512;
|
||||
// https://github.com/qemu/qemu/blob/3c5a5e213e5f08fbfe70728237f7799ac70f5b99/hw/i386/microvm.c#L196
|
||||
const QEMU_IOAPIC1_GSI_BASE: u32 = 16;
|
||||
const QEMU_IOAPIC1_NUM_TRANS: u32 = 8;
|
||||
// https://github.com/qemu/qemu/blob/3c5a5e213e5f08fbfe70728237f7799ac70f5b99/hw/i386/microvm.c#L192
|
||||
const QEMU_IOAPIC2_GSI_BASE: u32 = 24;
|
||||
const QEMU_IOAPIC2_NUM_TRANS: u32 = 24;
|
||||
|
||||
let irq_chip = IRQ_CHIP.get().unwrap();
|
||||
let (gsi_base, num_trans) = match irq_chip.count_io_apics() {
|
||||
1 => (QEMU_IOAPIC1_GSI_BASE, QEMU_IOAPIC1_NUM_TRANS),
|
||||
2.. => (QEMU_IOAPIC2_GSI_BASE, QEMU_IOAPIC2_NUM_TRANS),
|
||||
0 => {
|
||||
debug!("[Virtio]: Skip MMIO detection because there are no I/O APICs");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
for index in 0..num_trans {
|
||||
let mmio_base = QEMU_MMIO_BASE + (index as usize) * QEMU_MMIO_SIZE;
|
||||
match super::try_register_mmio_device(mmio_base..(mmio_base + QEMU_MMIO_SIZE), |irq_line| {
|
||||
irq_chip.map_gsi_pin_to(irq_line, gsi_base + index)
|
||||
}) {
|
||||
Err(e) if e.is_fatal() => break,
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MmioRegisterError {
|
||||
/// Returns `true` if it should terminate a linear MMIO scan.
|
||||
fn is_fatal(self) -> bool {
|
||||
matches!(self, Self::MmioUnavailable | Self::MagicMismatch)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,11 +2,6 @@
|
|||
|
||||
//! MMIO bus.
|
||||
|
||||
#![cfg_attr(
|
||||
any(target_arch = "riscv64", target_arch = "loongarch64"),
|
||||
expect(dead_code)
|
||||
)]
|
||||
|
||||
use alloc::{collections::VecDeque, fmt::Debug, sync::Arc, vec::Vec};
|
||||
|
||||
use log::{debug, error};
|
||||
|
|
|
|||
|
|
@ -2,19 +2,8 @@
|
|||
|
||||
//! MMIO device common definitions or functions.
|
||||
|
||||
#![cfg_attr(
|
||||
any(target_arch = "riscv64", target_arch = "loongarch64"),
|
||||
expect(dead_code)
|
||||
)]
|
||||
|
||||
use int_to_c_enum::TryFromInt;
|
||||
use log::info;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use ostd::arch::irq::MappedIrqLine;
|
||||
#[cfg(target_arch = "riscv64")] // TODO: Add `MappedIrqLine` support for RISC-V.
|
||||
use ostd::irq::IrqLine as MappedIrqLine;
|
||||
#[cfg(target_arch = "loongarch64")] // TODO: Add `MappedIrqLine` support for Loongarch.
|
||||
use ostd::irq::IrqLine as MappedIrqLine;
|
||||
use ostd::{
|
||||
io::IoMem,
|
||||
irq::IrqLine,
|
||||
|
|
@ -22,6 +11,8 @@ use ostd::{
|
|||
Error, Result,
|
||||
};
|
||||
|
||||
use super::arch::MappedIrqLine;
|
||||
|
||||
/// A MMIO common device.
|
||||
#[derive(Debug)]
|
||||
pub struct MmioCommonDevice {
|
||||
|
|
|
|||
|
|
@ -2,9 +2,20 @@
|
|||
|
||||
//! Virtio over MMIO
|
||||
|
||||
use bus::MmioBus;
|
||||
use ostd::sync::SpinLock;
|
||||
use core::ops::Range;
|
||||
|
||||
use bus::MmioBus;
|
||||
use log::debug;
|
||||
use ostd::{io::IoMem, irq::IrqLine, sync::SpinLock};
|
||||
|
||||
use crate::transport::mmio::bus::common_device::{
|
||||
mmio_check_magic, mmio_read_device_id, MmioCommonDevice,
|
||||
};
|
||||
|
||||
#[cfg_attr(target_arch = "x86_64", path = "arch/x86.rs")]
|
||||
#[cfg_attr(target_arch = "riscv64", path = "arch/riscv.rs")]
|
||||
#[cfg_attr(target_arch = "loongarch64", path = "arch/loongarch.rs")]
|
||||
pub mod arch;
|
||||
#[expect(clippy::module_inception)]
|
||||
pub(super) mod bus;
|
||||
pub(super) mod common_device;
|
||||
|
|
@ -20,90 +31,80 @@ pub(super) fn init() {
|
|||
// Currently, virtio-mmio devices need to acquire sub-page MMIO regions,
|
||||
// which are not supported by `IoMem::acquire` in the TDX environment.
|
||||
} else {
|
||||
x86_probe();
|
||||
arch::probe_for_device();
|
||||
});
|
||||
#[cfg(not(target_arch = "x86_64"))]
|
||||
arch::probe_for_device();
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn x86_probe() {
|
||||
use common_device::{mmio_check_magic, mmio_read_device_id, MmioCommonDevice};
|
||||
use log::debug;
|
||||
use ostd::{arch::irq::IRQ_CHIP, io::IoMem, irq::IrqLine};
|
||||
|
||||
// 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
|
||||
|
||||
// Constants from QEMU MicroVM. We should remove them as they're QEMU's implementation details.
|
||||
//
|
||||
// https://github.com/qemu/qemu/blob/3c5a5e213e5f08fbfe70728237f7799ac70f5b99/hw/i386/microvm.c#L201
|
||||
const QEMU_MMIO_BASE: usize = 0xFEB0_0000;
|
||||
const QEMU_MMIO_SIZE: usize = 512;
|
||||
// https://github.com/qemu/qemu/blob/3c5a5e213e5f08fbfe70728237f7799ac70f5b99/hw/i386/microvm.c#L196
|
||||
const QEMU_IOAPIC1_GSI_BASE: u32 = 16;
|
||||
const QEMU_IOAPIC1_NUM_TRANS: u32 = 8;
|
||||
// https://github.com/qemu/qemu/blob/3c5a5e213e5f08fbfe70728237f7799ac70f5b99/hw/i386/microvm.c#L192
|
||||
const QEMU_IOAPIC2_GSI_BASE: u32 = 24;
|
||||
const QEMU_IOAPIC2_NUM_TRANS: u32 = 24;
|
||||
|
||||
let mut mmio_bus = MMIO_BUS.lock();
|
||||
|
||||
let irq_chip = IRQ_CHIP.get().unwrap();
|
||||
let (gsi_base, num_trans) = match irq_chip.count_io_apics() {
|
||||
1 => (QEMU_IOAPIC1_GSI_BASE, QEMU_IOAPIC1_NUM_TRANS),
|
||||
2.. => (QEMU_IOAPIC2_GSI_BASE, QEMU_IOAPIC2_NUM_TRANS),
|
||||
0 => {
|
||||
debug!("[Virtio]: Skip MMIO detection because there are no I/O APICs");
|
||||
return;
|
||||
}
|
||||
/// Try to validate a potential VirtIO-MMIO device, map it to an IRQ line, and
|
||||
/// register it as a VirtIO-MMIO device.
|
||||
///
|
||||
/// Returns `Ok(())` if the device was registered, or a specific
|
||||
/// `MmioRegisterError` otherwise.
|
||||
#[cfg_attr(target_arch = "loongarch64", expect(unused))]
|
||||
fn try_register_mmio_device<F>(
|
||||
mmio_range: Range<usize>,
|
||||
map_irq_line: F,
|
||||
) -> Result<(), MmioRegisterError>
|
||||
where
|
||||
F: FnOnce(IrqLine) -> ostd::Result<arch::MappedIrqLine>,
|
||||
{
|
||||
let start_addr = mmio_range.start;
|
||||
let Ok(io_mem) = IoMem::acquire(mmio_range) else {
|
||||
debug!(
|
||||
"[Virtio]: Abort MMIO detection at {:#x} because the MMIO address is not available",
|
||||
start_addr
|
||||
);
|
||||
return Err(MmioRegisterError::MmioUnavailable);
|
||||
};
|
||||
|
||||
for index in 0..num_trans {
|
||||
let mmio_base = QEMU_MMIO_BASE + (index as usize) * QEMU_MMIO_SIZE;
|
||||
let Ok(io_mem) = IoMem::acquire(mmio_base..(mmio_base + QEMU_MMIO_SIZE)) else {
|
||||
debug!(
|
||||
"[Virtio]: Abort MMIO detection at {:#x} because the MMIO address is not available",
|
||||
mmio_base
|
||||
);
|
||||
break;
|
||||
};
|
||||
// We now check the requirements specified in Virtual I/O Device (VIRTIO) Version 1.3,
|
||||
// Section 4.2.2.2 Driver Requirements: MMIO Device Register Layout.
|
||||
|
||||
// We now check the the rquirements specified in Virtual I/O Device (VIRTIO) Version 1.3,
|
||||
// Section 4.2.2.2 Driver Requirements: MMIO Device Register Layout.
|
||||
|
||||
// "The driver MUST ignore a device with MagicValue which is not 0x74726976, although it
|
||||
// MAY report an error."
|
||||
if !mmio_check_magic(&io_mem) {
|
||||
debug!(
|
||||
"[Virtio]: Abort MMIO detection at {:#x} because the magic number does not match",
|
||||
mmio_base
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: "The driver MUST ignore a device with Version which is not 0x2, although it MAY
|
||||
// report an error."
|
||||
|
||||
// "The driver MUST ignore a device with DeviceID 0x0, but MUST NOT report any error."
|
||||
match mmio_read_device_id(&io_mem) {
|
||||
Err(_) | Ok(0) => continue,
|
||||
Ok(_) => (),
|
||||
}
|
||||
|
||||
let Ok(irq_line) = IrqLine::alloc()
|
||||
.and_then(|irq_line| irq_chip.map_gsi_pin_to(irq_line, gsi_base + index))
|
||||
else {
|
||||
debug!(
|
||||
"[Virtio]: Ignore MMIO device at {:#x} because its IRQ line is not available",
|
||||
mmio_base
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
let device = MmioCommonDevice::new(io_mem, irq_line);
|
||||
mmio_bus.register_mmio_device(device);
|
||||
// "The driver MUST ignore a device with MagicValue which is not 0x74726976, although it
|
||||
// MAY report an error."
|
||||
if !mmio_check_magic(&io_mem) {
|
||||
debug!(
|
||||
"[Virtio]: Abort MMIO detection at {:#x} because the magic number does not match",
|
||||
start_addr
|
||||
);
|
||||
return Err(MmioRegisterError::MagicMismatch);
|
||||
}
|
||||
|
||||
// TODO: "The driver MUST ignore a device with Version which is not 0x2, although it MAY
|
||||
// report an error."
|
||||
|
||||
// "The driver MUST ignore a device with DeviceID 0x0, but MUST NOT report any error."
|
||||
match mmio_read_device_id(&io_mem) {
|
||||
Err(_) | Ok(0) => {
|
||||
return Err(MmioRegisterError::NoDevice);
|
||||
}
|
||||
Ok(_) => {}
|
||||
}
|
||||
|
||||
let Ok(mapped_irq_line) = IrqLine::alloc().and_then(map_irq_line) else {
|
||||
debug!(
|
||||
"[Virtio]: Ignore MMIO device at {:#x} because its IRQ line is not available",
|
||||
start_addr
|
||||
);
|
||||
return Err(MmioRegisterError::IrqUnavailable);
|
||||
};
|
||||
|
||||
let device = MmioCommonDevice::new(io_mem, mapped_irq_line);
|
||||
MMIO_BUS.lock().register_mmio_device(device);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum MmioRegisterError {
|
||||
/// MMIO region not available.
|
||||
MmioUnavailable,
|
||||
/// Not a VirtIO-MMIO slot.
|
||||
MagicMismatch,
|
||||
/// No device present.
|
||||
NoDevice,
|
||||
/// IRQ line not available.
|
||||
IrqUnavailable,
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue