diff --git a/kernel/comps/virtio/src/transport/mmio/bus/arch/loongarch.rs b/kernel/comps/virtio/src/transport/mmio/bus/arch/loongarch.rs new file mode 100644 index 000000000..f5e355bbf --- /dev/null +++ b/kernel/comps/virtio/src/transport/mmio/bus/arch/loongarch.rs @@ -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!() +} diff --git a/kernel/comps/virtio/src/transport/mmio/bus/arch/riscv.rs b/kernel/comps/virtio/src/transport/mmio/bus/arch/riscv.rs new file mode 100644 index 000000000..e947566ed --- /dev/null +++ b/kernel/comps/virtio/src/transport/mmio/bus/arch/riscv.rs @@ -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: . + 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) + }); + }); +} diff --git a/kernel/comps/virtio/src/transport/mmio/bus/arch/x86.rs b/kernel/comps/virtio/src/transport/mmio/bus/arch/x86.rs new file mode 100644 index 000000000..92e5df9cb --- /dev/null +++ b/kernel/comps/virtio/src/transport/mmio/bus/arch/x86.rs @@ -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) + } +} diff --git a/kernel/comps/virtio/src/transport/mmio/bus/bus.rs b/kernel/comps/virtio/src/transport/mmio/bus/bus.rs index 0f6f2508c..ab455528c 100644 --- a/kernel/comps/virtio/src/transport/mmio/bus/bus.rs +++ b/kernel/comps/virtio/src/transport/mmio/bus/bus.rs @@ -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}; diff --git a/kernel/comps/virtio/src/transport/mmio/bus/common_device.rs b/kernel/comps/virtio/src/transport/mmio/bus/common_device.rs index 53c057ac8..1ad211804 100644 --- a/kernel/comps/virtio/src/transport/mmio/bus/common_device.rs +++ b/kernel/comps/virtio/src/transport/mmio/bus/common_device.rs @@ -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 { diff --git a/kernel/comps/virtio/src/transport/mmio/bus/mod.rs b/kernel/comps/virtio/src/transport/mmio/bus/mod.rs index 5788436c2..f00984669 100644 --- a/kernel/comps/virtio/src/transport/mmio/bus/mod.rs +++ b/kernel/comps/virtio/src/transport/mmio/bus/mod.rs @@ -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( + mmio_range: Range, + map_irq_line: F, +) -> Result<(), MmioRegisterError> +where + F: FnOnce(IrqLine) -> ostd::Result, +{ + 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, }