diff --git a/ostd/src/bus/mmio/common_device.rs b/ostd/src/bus/mmio/common_device.rs index 0e67702a..068b0994 100644 --- a/ostd/src/bus/mmio/common_device.rs +++ b/ostd/src/bus/mmio/common_device.rs @@ -5,19 +5,9 @@ use int_to_c_enum::TryFromInt; use log::info; -use super::VIRTIO_MMIO_MAGIC; -use crate::{ - io::IoMem, - mm::{ - paddr_to_vaddr, - page_prop::{CachePolicy, PageFlags}, - Paddr, VmIoOnce, - }, - trap::IrqLine, - Error, Result, -}; +use crate::{io::IoMem, mm::VmIoOnce, trap::IrqLine, Error, Result}; -/// MMIO Common device. +/// A MMIO common device. #[derive(Debug)] pub struct MmioCommonDevice { io_mem: IoMem, @@ -25,68 +15,48 @@ pub struct MmioCommonDevice { } impl MmioCommonDevice { - pub(super) fn new(paddr: Paddr, handle: IrqLine) -> Self { - // TODO: Implement universal access to MMIO devices since we are temporarily - // using specific virtio device as implementation of CommonDevice. + pub(super) fn new(io_mem: IoMem, irq: IrqLine) -> Self { + debug_assert!(mmio_check_magic(&io_mem)); - // Read magic value - // SAFETY: It only read the value and judge if the magic value fit 0x74726976 - unsafe { - debug_assert_eq!(*(paddr_to_vaddr(paddr) as *const u32), VIRTIO_MMIO_MAGIC); - } - // SAFETY: This range is virtio-mmio device space. - let io_mem = unsafe { - IoMem::new( - paddr..paddr + 0x200, - PageFlags::RW, - CachePolicy::Uncacheable, - ) - }; - let res = Self { - io_mem, - irq: handle, - }; + let this = Self { io_mem, irq }; info!( - "[Virtio]: Found Virtio mmio device, device id:{:?}, irq number:{:?}", - res.read_device_id().unwrap(), - res.irq.num() + "[Virtio]: Found MMIO device at {:#x}, device ID {}, IRQ number {}", + this.io_mem.paddr(), + this.read_device_id().unwrap(), + this.irq.num(), ); - res + + this } - /// Base address - pub fn address(&self) -> Paddr { - self.io_mem.paddr() - } - - /// Grants access to the MMIO + /// Returns a reference to the I/O memory. pub fn io_mem(&self) -> &IoMem { &self.io_mem } - /// Device ID + /// Reads the device ID from the I/O memory. pub fn read_device_id(&self) -> Result { - self.io_mem.read_once::(8) + mmio_read_device_id(&self.io_mem) } - /// Version of the MMIO device. + /// Reads the version number from the I/O memory. pub fn read_version(&self) -> Result { - VirtioMmioVersion::try_from(self.io_mem.read_once::(4)?) + VirtioMmioVersion::try_from(mmio_read_version(&self.io_mem)?) .map_err(|_| Error::InvalidArgs) } - /// Interrupt line + /// Returns an immutable reference to the IRQ line. pub fn irq(&self) -> &IrqLine { &self.irq } - /// Mutable Interrupt line + /// Returns a mutable reference to the IRQ line. pub fn irq_mut(&mut self) -> &mut IrqLine { &mut self.irq } } -/// Virtio MMIO version +/// Virtio MMIO version. #[derive(Debug, Clone, Copy, TryFromInt, PartialEq, Eq, PartialOrd, Ord)] #[repr(u32)] pub enum VirtioMmioVersion { @@ -95,3 +65,20 @@ pub enum VirtioMmioVersion { /// Modern Modern = 2, } + +const OFFSET_TO_MAGIC: usize = 0; +const OFFSET_TO_VERSION: usize = 4; +const OFFSET_TO_DEVICE_ID: usize = 8; + +pub(super) fn mmio_check_magic(io_mem: &IoMem) -> bool { + const MAGIC_VALUE: u32 = 0x74726976; + io_mem + .read_once::(OFFSET_TO_MAGIC) + .is_ok_and(|val| val == MAGIC_VALUE) +} +fn mmio_read_version(io_mem: &IoMem) -> Result { + io_mem.read_once(OFFSET_TO_VERSION) +} +pub(super) fn mmio_read_device_id(io_mem: &IoMem) -> Result { + io_mem.read_once(OFFSET_TO_DEVICE_ID) +} diff --git a/ostd/src/bus/mmio/mod.rs b/ostd/src/bus/mmio/mod.rs index b927b8aa..ce3ea9f0 100644 --- a/ostd/src/bus/mmio/mod.rs +++ b/ostd/src/bus/mmio/mod.rs @@ -1,76 +1,113 @@ // SPDX-License-Identifier: MPL-2.0 -#![expect(dead_code)] - //! Virtio over MMIO +use bus::MmioBus; + +use crate::sync::SpinLock; + pub mod bus; pub mod common_device; -use alloc::vec::Vec; -use core::ops::Range; - -use log::debug; - -use self::bus::MmioBus; -use crate::{ - bus::mmio::common_device::MmioCommonDevice, mm::paddr_to_vaddr, sync::SpinLock, trap::IrqLine, -}; - -const VIRTIO_MMIO_MAGIC: u32 = 0x74726976; - -/// MMIO bus instance +/// The MMIO bus instance. pub static MMIO_BUS: SpinLock = SpinLock::new(MmioBus::new()); -static IRQS: SpinLock> = SpinLock::new(Vec::new()); pub(crate) fn init() { #[cfg(target_arch = "x86_64")] - { - crate::arch::if_tdx_enabled!({ - // SAFETY: - // This is safe because we are ensuring that the address range 0xFEB0_0000 to 0xFEB0_4000 is valid before this operation. - // The address range is page-aligned and falls within the MMIO range, which is a requirement for the `unprotect_gpa_range` function. - // We are also ensuring that we are only unprotecting four pages. - // Therefore, we are not causing any undefined behavior or violating any of the requirements of the `unprotect_gpa_range` function. - unsafe { - crate::arch::tdx_guest::unprotect_gpa_range(0xFEB0_0000, 4).unwrap(); - } - }); - // FIXME: The address 0xFEB0_0000 is obtained from an instance of microvm, and it may not work in other architecture. - iter_range(0xFEB0_0000..0xFEB0_4000); - } + x86_probe(); } #[cfg(target_arch = "x86_64")] -fn iter_range(range: Range) { - debug!("[Virtio]: Iter MMIO range:{:x?}", range); - let mut current = range.end; - let mut lock = MMIO_BUS.lock(); - let io_apics = crate::arch::kernel::IO_APIC.get().unwrap(); - let is_ioapic2 = io_apics.len() == 2; - let mut io_apic = if is_ioapic2 { - io_apics.get(1).unwrap().lock() - } else { - io_apics.first().unwrap().lock() - }; - let mut device_count = 0; - while current > range.start { - current -= 0x100; - // SAFETY: It only read the value and judge if the magic value fit 0x74726976 - let magic = unsafe { core::ptr::read_volatile(paddr_to_vaddr(current) as *const u32) }; - if magic == VIRTIO_MMIO_MAGIC { - // SAFETY: It only read the device id - let device_id = unsafe { *(paddr_to_vaddr(current + 8) as *const u32) }; - device_count += 1; - if device_id == 0 { - continue; - } - let handle = IrqLine::alloc().unwrap(); - // If has two IOApic, then start: 24 (0 in IOApic2), end 47 (23 in IOApic2) - // If one IOApic, then start: 16, end 23 - io_apic.enable(24 - device_count, handle.clone()).unwrap(); - let device = MmioCommonDevice::new(current, handle); - lock.register_mmio_device(device); +fn x86_probe() { + use common_device::{mmio_check_magic, mmio_read_device_id, MmioCommonDevice}; + use log::debug; + + use crate::{io::IoMem, trap::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_IRQ_BASE: u8 = 16; + const QEMU_IOAPIC1_NUM_TRANS: u8 = 8; + // https://github.com/qemu/qemu/blob/3c5a5e213e5f08fbfe70728237f7799ac70f5b99/hw/i386/microvm.c#L192 + const QEMU_IOAPIC2_IRQ_BASE: u8 = 0; + const QEMU_IOAPIC2_NUM_TRANS: u8 = 24; + + let mut mmio_bus = MMIO_BUS.lock(); + + let io_apics = crate::arch::kernel::IO_APIC.get(); + let (mut ioapic, irq_base, num_trans) = match io_apics { + Some(io_apic_vec) if io_apic_vec.len() == 1 => ( + io_apic_vec[0].lock(), + QEMU_IOAPIC1_IRQ_BASE, + QEMU_IOAPIC1_NUM_TRANS, + ), + Some(io_apic_vec) if io_apic_vec.len() >= 2 => ( + io_apic_vec[1].lock(), + QEMU_IOAPIC2_IRQ_BASE, + QEMU_IOAPIC2_NUM_TRANS, + ), + Some(_) | None => { + 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; + 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 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 irq_line = if let Ok(irq_line) = IrqLine::alloc() + && ioapic.enable(irq_base + index, irq_line.clone()).is_ok() + { + irq_line + } 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); } }