From 0c7df54513163441d0837fe410858c9ccf8f0cf8 Mon Sep 17 00:00:00 2001 From: Yuke Peng Date: Sat, 28 Oct 2023 20:55:44 +0800 Subject: [PATCH] Impl virtio-mmio transport --- framework/jinux-frame/src/bus/mmio/mod.rs | 1 + services/comps/virtio/src/device/block/mod.rs | 4 +- .../comps/virtio/src/device/input/device.rs | 4 +- services/comps/virtio/src/lib.rs | 21 +- services/comps/virtio/src/queue.rs | 97 ++++-- .../comps/virtio/src/transport/mmio/device.rs | 291 ++++++++++++++++++ .../comps/virtio/src/transport/mmio/driver.rs | 46 +++ .../comps/virtio/src/transport/mmio/layout.rs | 143 +++++++++ .../comps/virtio/src/transport/mmio/mod.rs | 18 ++ .../virtio/src/transport/mmio/multiplex.rs | 80 +++++ services/comps/virtio/src/transport/mod.rs | 12 +- .../comps/virtio/src/transport/pci/device.rs | 27 +- .../comps/virtio/src/transport/pci/driver.rs | 15 +- 13 files changed, 700 insertions(+), 59 deletions(-) create mode 100644 services/comps/virtio/src/transport/mmio/device.rs create mode 100644 services/comps/virtio/src/transport/mmio/driver.rs create mode 100644 services/comps/virtio/src/transport/mmio/layout.rs create mode 100644 services/comps/virtio/src/transport/mmio/mod.rs create mode 100644 services/comps/virtio/src/transport/mmio/multiplex.rs diff --git a/framework/jinux-frame/src/bus/mmio/mod.rs b/framework/jinux-frame/src/bus/mmio/mod.rs index ef40bdb53..252cca282 100644 --- a/framework/jinux-frame/src/bus/mmio/mod.rs +++ b/framework/jinux-frame/src/bus/mmio/mod.rs @@ -19,6 +19,7 @@ const VIRTIO_MMIO_MAGIC: u32 = 0x74726976; pub static MMIO_BUS: SpinLock = SpinLock::new(MmioBus::new()); static IRQS: SpinLock> = SpinLock::new(Vec::new()); + pub fn init() { // 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); diff --git a/services/comps/virtio/src/device/block/mod.rs b/services/comps/virtio/src/device/block/mod.rs index 3a892f93f..fd6b5cefb 100644 --- a/services/comps/virtio/src/device/block/mod.rs +++ b/services/comps/virtio/src/device/block/mod.rs @@ -14,19 +14,19 @@ pub static DEVICE_NAME: &str = "Virtio-Block"; bitflags! { /// features for virtio block device pub(crate) struct BlkFeatures : u64{ + const BARRIER = 1 << 0; const SIZE_MAX = 1 << 1; const SEG_MAX = 1 << 2; const GEOMETRY = 1 << 4; const RO = 1 << 5; const BLK_SIZE = 1 << 6; + const SCSI = 1 << 7; const FLUSH = 1 << 9; const TOPOLOGY = 1 << 10; const CONFIG_WCE = 1 << 11; const DISCARD = 1 << 13; const WRITE_ZEROES = 1 << 14; - } - } #[repr(C)] diff --git a/services/comps/virtio/src/device/input/device.rs b/services/comps/virtio/src/device/input/device.rs index 73e1705ad..4132ae0bb 100644 --- a/services/comps/virtio/src/device/input/device.rs +++ b/services/comps/virtio/src/device/input/device.rs @@ -185,7 +185,9 @@ impl jinux_input::InputDevice for InputDevice { fn handle_irq(&self) -> Option<()> { // one interrupt may contains serval input, so it should loop loop { - let event = self.pop_pending_event()?; + let Some(event) = self.pop_pending_event() else { + return Some(()); + }; let dtype = match Decoder::decode( event.event_type as usize, event.code as usize, diff --git a/services/comps/virtio/src/lib.rs b/services/comps/virtio/src/lib.rs index d685dbff1..4a8397d91 100644 --- a/services/comps/virtio/src/lib.rs +++ b/services/comps/virtio/src/lib.rs @@ -16,7 +16,7 @@ use device::{ VirtioDeviceType, }; use log::{error, warn}; -use transport::{pci::VIRTIO_PCI_DRIVER, DeviceStatus}; +use transport::{mmio::VIRTIO_MMIO_DRIVER, pci::VIRTIO_PCI_DRIVER, DeviceStatus}; use crate::transport::VirtioTransport; @@ -28,8 +28,7 @@ mod transport; fn virtio_component_init() -> Result<(), ComponentInitError> { // Find all devices and register them to the corresponding crate transport::init(); - let pci_driver = VIRTIO_PCI_DRIVER.get().unwrap(); - while let Some(mut transport) = pci_driver.pop_device_tranport() { + while let Some(mut transport) = pop_device_transport() { // Reset device transport.set_device_status(DeviceStatus::empty()).unwrap(); // Set to acknowledge @@ -45,7 +44,6 @@ fn virtio_component_init() -> Result<(), ComponentInitError> { DeviceStatus::ACKNOWLEDGE | DeviceStatus::DRIVER | DeviceStatus::FEATURES_OK, ) .unwrap(); - let transport = Box::new(transport); let device_type = transport.device_type(); let res = match transport.device_type() { VirtioDeviceType::Block => BlockDevice::init(transport), @@ -66,9 +64,18 @@ fn virtio_component_init() -> Result<(), ComponentInitError> { Ok(()) } -fn negotiate_features(transport: &mut dyn VirtioTransport) { - let features = transport.device_features(); +fn pop_device_transport() -> Option> { + if let Some(device) = VIRTIO_PCI_DRIVER.get().unwrap().pop_device_transport() { + return Some(Box::new(device)); + } + if let Some(device) = VIRTIO_MMIO_DRIVER.get().unwrap().pop_device_transport() { + return Some(Box::new(device)); + } + None +} +fn negotiate_features(transport: &mut Box) { + let features = transport.device_features(); let mask = ((1u64 << 24) - 1) | (((1u64 << 24) - 1) << 50); let device_specified_features = features & mask; let device_support_features = match transport.device_type() { @@ -105,5 +112,7 @@ bitflags! { const ORDER_PLATFORM = 1 << 36; const SR_IOV = 1 << 37; const NOTIFICATION_DATA = 1 << 38; + const NOTIF_CONFIG_DATA = 1 << 39; + const RING_RESET = 1 << 40; } } diff --git a/services/comps/virtio/src/queue.rs b/services/comps/virtio/src/queue.rs index b2d6c488c..cf9a8c635 100644 --- a/services/comps/virtio/src/queue.rs +++ b/services/comps/virtio/src/queue.rs @@ -4,11 +4,14 @@ use crate::transport::VirtioTransport; use alloc::vec::Vec; use bitflags::bitflags; -use core::sync::atomic::{fence, Ordering}; +use core::{ + mem::size_of, + sync::atomic::{fence, Ordering}, +}; use jinux_frame::{ io_mem::IoMem, offset_of, - vm::{VmAllocOptions, VmFrame, VmFrameVec}, + vm::{HasPaddr, VmAllocOptions, VmFrame, VmFrameVec}, }; use jinux_rights::{Dup, TRightSet, TRights, Write}; use jinux_util::{field_ptr, safe_ptr::SafePtr}; @@ -66,40 +69,73 @@ impl VirtQueue { return Err(QueueError::InvalidArgs); } - let descriptor_ptr: SafePtr = SafePtr::new( - VmFrameVec::allocate(VmAllocOptions::new(1).uninit(false).can_dma(true)) - .unwrap() - .pop() - .unwrap(), - 0, - ); - let avail_ring_ptr: SafePtr = SafePtr::new( - VmFrameVec::allocate(VmAllocOptions::new(1).uninit(false).can_dma(true)) - .unwrap() - .pop() - .unwrap(), - 0, - ); - let used_ring_ptr: SafePtr = SafePtr::new( - VmFrameVec::allocate(VmAllocOptions::new(1).uninit(false).can_dma(true)) - .unwrap() - .pop() - .unwrap(), - 0, - ); + let (descriptor_ptr, avail_ring_ptr, used_ring_ptr) = if transport.is_legacy_version() { + // FIXME: How about pci legacy? + // Currently, we use one VmFrame to place the descriptors and avaliable rings, one VmFrame to place used rings + // because the virtio-mmio legacy required the address to be continuous. The max queue size is 128. + if size > 128 { + return Err(QueueError::InvalidArgs); + } + let desc_size = size_of::() * size as usize; + + let (page1, page2) = { + let mut continue_pages = VmFrameVec::allocate( + VmAllocOptions::new(2) + .uninit(false) + .can_dma(true) + .is_contiguous(true), + ) + .unwrap(); + let page1 = continue_pages.pop().unwrap(); + let page2 = continue_pages.pop().unwrap(); + if page1.paddr() > page2.paddr() { + (page2, page1) + } else { + (page1, page2) + } + }; + let desc_frame_ptr: SafePtr = SafePtr::new(page1, 0); + let mut avail_frame_ptr: SafePtr = desc_frame_ptr.clone().cast(); + avail_frame_ptr.byte_add(desc_size); + let used_frame_ptr: SafePtr = SafePtr::new(page2, 0); + (desc_frame_ptr, avail_frame_ptr, used_frame_ptr) + } else { + if size > 256 { + return Err(QueueError::InvalidArgs); + } + ( + SafePtr::new( + VmFrameVec::allocate(VmAllocOptions::new(1).uninit(false).can_dma(true)) + .unwrap() + .pop() + .unwrap(), + 0, + ), + SafePtr::new( + VmFrameVec::allocate(VmAllocOptions::new(1).uninit(false).can_dma(true)) + .unwrap() + .pop() + .unwrap(), + 0, + ), + SafePtr::new( + VmFrameVec::allocate(VmAllocOptions::new(1).uninit(false).can_dma(true)) + .unwrap() + .pop() + .unwrap(), + 0, + ), + ) + }; debug!("queue_desc start paddr:{:x?}", descriptor_ptr.paddr()); debug!("queue_driver start paddr:{:x?}", avail_ring_ptr.paddr()); debug!("queue_device start paddr:{:x?}", used_ring_ptr.paddr()); - transport - .set_queue(idx, size, &descriptor_ptr, &avail_ring_ptr, &used_ring_ptr) - .unwrap(); - let mut descs = Vec::with_capacity(size as usize); - descs.push(descriptor_ptr); + descs.push(descriptor_ptr.clone()); for i in 0..size as usize { let mut desc = descs.get(i).unwrap().clone(); - desc.offset(1); + desc.add(1); descs.push(desc); } @@ -112,6 +148,9 @@ impl VirtQueue { field_ptr!(&avail_ring_ptr, AvailRing, flags) .write(&(0u16)) .unwrap(); + transport + .set_queue(idx, size, &descriptor_ptr, &avail_ring_ptr, &used_ring_ptr) + .unwrap(); Ok(VirtQueue { descs, avail: avail_ring_ptr, diff --git a/services/comps/virtio/src/transport/mmio/device.rs b/services/comps/virtio/src/transport/mmio/device.rs new file mode 100644 index 000000000..3d7ab6200 --- /dev/null +++ b/services/comps/virtio/src/transport/mmio/device.rs @@ -0,0 +1,291 @@ +use alloc::{boxed::Box, sync::Arc}; +use core::mem::size_of; +use jinux_frame::{ + bus::mmio::{ + bus::MmioDevice, + device::{MmioCommonDevice, VirtioMmioVersion}, + }, + config::PAGE_SIZE, + io_mem::IoMem, + offset_of, + sync::RwLock, + trap::IrqCallbackFunction, + vm::VmFrame, +}; +use jinux_rights::{ReadOp, WriteOp}; +use jinux_util::{field_ptr, safe_ptr::SafePtr}; +use log::warn; + +use crate::{ + queue::{AvailRing, Descriptor, UsedRing}, + transport::{DeviceStatus, VirtioTransport, VirtioTransportError}, + VirtioDeviceType, +}; + +use super::{layout::VirtioMmioLayout, multiplex::MultiplexIrq}; + +#[derive(Debug)] +pub struct VirtioMmioDevice { + device_id: u32, +} + +#[derive(Debug)] +pub struct VirtioMmioTransport { + layout: SafePtr, + device: Arc, + common_device: jinux_frame::bus::mmio::device::MmioCommonDevice, + multiplex: Arc>, +} + +impl MmioDevice for VirtioMmioDevice { + fn device_id(&self) -> u32 { + self.device_id + } +} + +impl MmioDevice for VirtioMmioTransport { + fn device_id(&self) -> u32 { + self.common_device.device_id() + } +} + +impl VirtioMmioTransport { + pub(super) fn mmio_device(&self) -> &Arc { + &self.device + } + + pub(super) fn new(device: MmioCommonDevice) -> Self { + let irq = device.irq().clone(); + let layout = SafePtr::new(device.io_mem().clone(), 0); + let device_id = device.device_id(); + let (interrupt_ack, interrupt_status) = { + let interrupt_ack_offset = offset_of!(VirtioMmioLayout, interrupt_ack); + let interrupt_status_offset = offset_of!(VirtioMmioLayout, interrupt_status); + let mut interrupt_ack = layout.clone(); + interrupt_ack.byte_add(interrupt_ack_offset as usize); + let mut interrupt_status = layout.clone(); + interrupt_status.byte_add(interrupt_status_offset as usize); + ( + interrupt_ack.cast::().restrict::(), + interrupt_status.cast::().restrict::(), + ) + }; + let device = Self { + layout, + common_device: device, + multiplex: MultiplexIrq::new(irq, interrupt_ack, interrupt_status), + device: Arc::new(VirtioMmioDevice { device_id }), + }; + if device.common_device.version() == VirtioMmioVersion::Legacy { + field_ptr!(&device.layout, VirtioMmioLayout, legacy_guest_page_size) + .write(&(PAGE_SIZE as u32)) + .unwrap(); + } + device + } +} + +impl VirtioTransport for VirtioMmioTransport { + fn device_type(&self) -> VirtioDeviceType { + VirtioDeviceType::try_from(self.common_device.device_id() as u8).unwrap() + } + + fn set_queue( + &mut self, + idx: u16, + queue_size: u16, + descriptor_ptr: &SafePtr, + driver_ptr: &SafePtr, + device_ptr: &SafePtr, + ) -> Result<(), VirtioTransportError> { + field_ptr!(&self.layout, VirtioMmioLayout, queue_sel) + .write(&(idx as u32)) + .unwrap(); + + let queue_num_max: u32 = field_ptr!(&self.layout, VirtioMmioLayout, queue_num_max) + .read() + .unwrap(); + + if queue_size as u32 > queue_num_max { + warn!("Set queue failed, queue size is bigger than maximum virtual queue size."); + return Err(VirtioTransportError::InvalidArgs); + } + + let descriptor_paddr = descriptor_ptr.paddr(); + let driver_paddr = driver_ptr.paddr(); + let device_paddr = device_ptr.paddr(); + + field_ptr!(&self.layout, VirtioMmioLayout, queue_num) + .write(&(queue_size as u32)) + .unwrap(); + + match self.common_device.version() { + VirtioMmioVersion::Legacy => { + // The area should be continuous + assert_eq!( + driver_paddr - descriptor_paddr, + size_of::() * queue_size as usize + ); + // Descriptor paddr should align + assert_eq!(descriptor_paddr % PAGE_SIZE, 0); + let pfn = (descriptor_paddr / PAGE_SIZE) as u32; + field_ptr!(&self.layout, VirtioMmioLayout, legacy_queue_align) + .write(&(PAGE_SIZE as u32)) + .unwrap(); + field_ptr!(&self.layout, VirtioMmioLayout, legacy_queue_pfn) + .write(&pfn) + .unwrap(); + } + VirtioMmioVersion::Modern => { + field_ptr!(&self.layout, VirtioMmioLayout, queue_desc_low) + .write(&(descriptor_paddr as u32)) + .unwrap(); + field_ptr!(&self.layout, VirtioMmioLayout, queue_desc_high) + .write(&((descriptor_paddr >> 32) as u32)) + .unwrap(); + + field_ptr!(&self.layout, VirtioMmioLayout, queue_driver_low) + .write(&(driver_paddr as u32)) + .unwrap(); + field_ptr!(&self.layout, VirtioMmioLayout, queue_driver_high) + .write(&((driver_paddr >> 32) as u32)) + .unwrap(); + + field_ptr!(&self.layout, VirtioMmioLayout, queue_device_low) + .write(&(device_paddr as u32)) + .unwrap(); + field_ptr!(&self.layout, VirtioMmioLayout, queue_device_high) + .write(&((device_paddr >> 32) as u32)) + .unwrap(); + // enable queue + field_ptr!(&self.layout, VirtioMmioLayout, queue_sel) + .write(&(idx as u32)) + .unwrap(); + field_ptr!(&self.layout, VirtioMmioLayout, queue_ready) + .write(&1u32) + .unwrap(); + } + }; + Ok(()) + } + + fn get_notify_ptr(&self, _idx: u16) -> Result, VirtioTransportError> { + let offset = offset_of!(VirtioMmioLayout, queue_notify) as usize; + Ok(SafePtr::new(self.common_device.io_mem().clone(), offset)) + } + + fn num_queues(&self) -> u16 { + // We use the field `queue_num_max` to get queue size. + // If the queue is not exists, the field should be zero + let mut num_queues = 0; + const MAX_QUEUES: u32 = 512; + while num_queues < MAX_QUEUES { + field_ptr!(&self.layout, VirtioMmioLayout, queue_sel) + .write(&num_queues) + .unwrap(); + if field_ptr!(&self.layout, VirtioMmioLayout, queue_num_max) + .read() + .unwrap() + == 0u32 + { + return num_queues as u16; + } + num_queues += 1; + } + todo!() + } + + fn device_config_memory(&self) -> IoMem { + // offset: 0x100~0x200 + let mut io_mem = self.common_device.io_mem().clone(); + let paddr = io_mem.paddr(); + io_mem.resize((paddr + 0x100)..(paddr + 0x200)).unwrap(); + io_mem + } + + fn device_features(&self) -> u64 { + // select low + field_ptr!(&self.layout, VirtioMmioLayout, device_features_select) + .write(&0u32) + .unwrap(); + let device_feature_low = field_ptr!(&self.layout, VirtioMmioLayout, device_features) + .read() + .unwrap(); + // select high + field_ptr!(&self.layout, VirtioMmioLayout, device_features_select) + .write(&1u32) + .unwrap(); + let device_feature_high = field_ptr!(&self.layout, VirtioMmioLayout, device_features) + .read() + .unwrap() as u64; + device_feature_high << 32 | device_feature_low as u64 + } + + fn set_driver_features(&mut self, features: u64) -> Result<(), VirtioTransportError> { + let low = features as u32; + let high = (features >> 32) as u32; + field_ptr!(&self.layout, VirtioMmioLayout, driver_features_select) + .write(&0u32) + .unwrap(); + field_ptr!(&self.layout, VirtioMmioLayout, driver_features) + .write(&low) + .unwrap(); + field_ptr!(&self.layout, VirtioMmioLayout, driver_features_select) + .write(&1u32) + .unwrap(); + field_ptr!(&self.layout, VirtioMmioLayout, driver_features) + .write(&high) + .unwrap(); + Ok(()) + } + + fn device_status(&self) -> DeviceStatus { + DeviceStatus::from_bits( + field_ptr!(&self.layout, VirtioMmioLayout, status) + .read() + .unwrap() as u8, + ) + .unwrap() + } + + fn set_device_status(&mut self, status: DeviceStatus) -> Result<(), VirtioTransportError> { + field_ptr!(&self.layout, VirtioMmioLayout, status) + .write(&(status.bits() as u32)) + .unwrap(); + Ok(()) + } + + fn is_legacy_version(&self) -> bool { + self.common_device.version() == VirtioMmioVersion::Legacy + } + + fn max_queue_size(&self, idx: u16) -> Result { + field_ptr!(&self.layout, VirtioMmioLayout, queue_sel) + .write(&(idx as u32)) + .unwrap(); + Ok(field_ptr!(&self.layout, VirtioMmioLayout, queue_num_max) + .read() + .unwrap() as u16) + } + + fn register_queue_callback( + &mut self, + _index: u16, + func: Box, + single_interrupt: bool, + ) -> Result<(), VirtioTransportError> { + if single_interrupt { + return Err(VirtioTransportError::NotEnoughResources); + } + self.multiplex.write().register_queue_callback(func); + Ok(()) + } + + fn register_cfg_callback( + &mut self, + func: Box, + ) -> Result<(), VirtioTransportError> { + self.multiplex.write().register_cfg_callback(func); + Ok(()) + } +} diff --git a/services/comps/virtio/src/transport/mmio/driver.rs b/services/comps/virtio/src/transport/mmio/driver.rs new file mode 100644 index 000000000..1e6c428a7 --- /dev/null +++ b/services/comps/virtio/src/transport/mmio/driver.rs @@ -0,0 +1,46 @@ +use alloc::{sync::Arc, vec::Vec}; +use jinux_frame::{ + bus::{ + mmio::{ + bus::{MmioDevice, MmioDriver}, + device::MmioCommonDevice, + }, + BusProbeError, + }, + sync::SpinLock, +}; + +use super::device::VirtioMmioTransport; + +#[derive(Debug)] +pub struct VirtioMmioDriver { + devices: SpinLock>, +} + +impl VirtioMmioDriver { + pub fn num_devices(&self) -> usize { + self.devices.lock().len() + } + + pub fn pop_device_transport(&self) -> Option { + self.devices.lock().pop() + } + + pub(super) fn new() -> Self { + VirtioMmioDriver { + devices: SpinLock::new(Vec::new()), + } + } +} + +impl MmioDriver for VirtioMmioDriver { + fn probe( + &self, + device: MmioCommonDevice, + ) -> Result, (BusProbeError, MmioCommonDevice)> { + let device = VirtioMmioTransport::new(device); + let mmio_device = device.mmio_device().clone(); + self.devices.lock().push(device); + Ok(mmio_device) + } +} diff --git a/services/comps/virtio/src/transport/mmio/layout.rs b/services/comps/virtio/src/transport/mmio/layout.rs new file mode 100644 index 000000000..cc3a56f11 --- /dev/null +++ b/services/comps/virtio/src/transport/mmio/layout.rs @@ -0,0 +1,143 @@ +use core::fmt::Debug; + +use pod::Pod; + +#[derive(Clone, Copy, Pod)] +#[repr(C)] +pub struct VirtioMmioLayout { + /// Magic value: 0x74726976. **Read-only** + pub magic_value: u32, + /// Device version. 1 => Legacy, 2 => Normal. **Read-only** + pub version: u32, + /// Virtio Subsystem Device ID. **Read-only** + pub device_id: u32, + /// Virtio Subsystem Vendor ID. **Read-only** + pub vendor_id: u32, + + /// Flags representing features the device supports. + /// Bits 0-31 if `device_features_sel`is 0, + /// bits 32-63 if `device_features_sel` is 1. + /// **Read-only** + pub device_features: u32, + /// **Write-only** + pub device_features_select: u32, + + __r1: [u8; 8], + + /// Flags representing device features understood and activated by the driver. + /// Bits 0-31 if `driver_features_sel`is 0, + /// bits 32-63 if `driver_features_sel` is 1. + /// **Write-only** + pub driver_features: u32, + /// **Write-only** + pub driver_features_select: u32, + + /// Guest page size. + /// + /// The driver writes the guest page size in bytes + /// to the register during initialization, before any queues are used. + /// + /// This value should be a power of 2 and is used by the device to + /// calculate the Guest address of the first queue page (see legacy_queue_pfn). + /// **Write-only** + pub legacy_guest_page_size: u32, + + __r2: [u8; 4], + + /// Selected queue. **Write-only** + pub queue_sel: u32, + /// Maximum virtual queue size. **Read-only** + pub queue_num_max: u32, + /// Virtual queue size. **Write-only** + pub queue_num: u32, + + pub legacy_queue_align: u32, + + pub legacy_queue_pfn: u32, + + /// Virtual queue ready bit. + /// + /// Write 1 to notifies the device that it can execute requests from this virtual queue. + /// **Read-Write** + pub queue_ready: u32, + + __r3: [u8; 8], + + /// Queue notifier. + /// + /// Writing a value to this register notifies the device + /// that there are new buffers to process in a queue. **Write-only** + pub queue_notify: u32, + + __r4: [u8; 12], + + /// Interrupt status. + /// + /// bit0 => Used Buffer Notification; + /// bit1 => Configuration Change Notification + /// **Read-only** + pub interrupt_status: u32, + /// Interrupt acknowledge. **Write-only** + pub interrupt_ack: u32, + + __r5: [u8; 8], + + /// Device status. **Read-Write** + pub status: u32, + + __r6: [u8; 12], + + /// Virtual queue’s Descriptor Area 64 bit long physical address. **Write-only** + pub queue_desc_low: u32, + pub queue_desc_high: u32, + + __r7: [u8; 8], + + /// Virtual queue’s Driver Area 64 bit long physical address. **Write-only** + pub queue_driver_low: u32, + pub queue_driver_high: u32, + + __r8: [u8; 8], + + /// Virtual queue’s Device Area 64 bit long physical address. **Write-only** + pub queue_device_low: u32, + pub queue_device_high: u32, + + __r9: [u8; 84], + + /// Configuration atomicity value. **Read-only** + pub config_generation: u32, +} + +impl Debug for VirtioMmioLayout { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("VirtioMmioLayout") + .field("magic_value", &self.magic_value) + .field("version", &self.version) + .field("device_id", &self.device_id) + .field("vendor_id", &self.vendor_id) + .field("device_features", &self.device_features) + .field("device_features_sel", &self.device_features_select) + .field("driver_features", &self.driver_features) + .field("driver_features_sel", &self.driver_features_select) + .field("legacy_guest_page_size", &self.legacy_guest_page_size) + .field("queue_sel", &self.queue_sel) + .field("queue_num_max", &self.queue_num_max) + .field("queue_num", &self.queue_num) + .field("legacy_queue_align", &self.legacy_queue_align) + .field("legacy_queue_pfn", &self.legacy_queue_pfn) + .field("queue_ready", &self.queue_ready) + .field("queue_notify", &self.queue_notify) + .field("interrupt_status", &self.interrupt_status) + .field("interrupt_ack", &self.interrupt_ack) + .field("status", &self.status) + .field("queue_desc_low", &self.queue_desc_low) + .field("queue_desc_high", &self.queue_desc_high) + .field("queue_driver_low", &self.queue_driver_low) + .field("queue_driver_high", &self.queue_driver_high) + .field("queue_device_low", &self.queue_device_low) + .field("queue_device_high", &self.queue_device_high) + .field("config_generation", &self.config_generation) + .finish() + } +} diff --git a/services/comps/virtio/src/transport/mmio/mod.rs b/services/comps/virtio/src/transport/mmio/mod.rs new file mode 100644 index 000000000..be15263ef --- /dev/null +++ b/services/comps/virtio/src/transport/mmio/mod.rs @@ -0,0 +1,18 @@ +use alloc::sync::Arc; +use jinux_frame::bus::mmio::MMIO_BUS; +use spin::Once; + +use self::driver::VirtioMmioDriver; + +pub mod device; +pub mod driver; +pub mod layout; +pub mod multiplex; + +pub static VIRTIO_MMIO_DRIVER: Once> = Once::new(); +pub fn virtio_mmio_init() { + VIRTIO_MMIO_DRIVER.call_once(|| Arc::new(VirtioMmioDriver::new())); + MMIO_BUS + .lock() + .register_driver(VIRTIO_MMIO_DRIVER.get().unwrap().clone()); +} diff --git a/services/comps/virtio/src/transport/mmio/multiplex.rs b/services/comps/virtio/src/transport/mmio/multiplex.rs new file mode 100644 index 000000000..845635643 --- /dev/null +++ b/services/comps/virtio/src/transport/mmio/multiplex.rs @@ -0,0 +1,80 @@ +use core::fmt::Debug; + +use alloc::{boxed::Box, sync::Arc, vec::Vec}; +use jinux_frame::{ + io_mem::IoMem, + sync::RwLock, + trap::{IrqCallbackFunction, IrqLine, TrapFrame}, +}; +use jinux_rights::{ReadOp, TRightSet, WriteOp}; +use jinux_util::safe_ptr::SafePtr; + +/// Multiplexing Irqs. The two interrupt types (configuration space change and queue interrupt) +/// of the virtio-mmio device share the same IRQ, so `MultiplexIrq` are used to distinguish them. +/// Besides, virtio-mmio requires ack_interrupt after interrupt is handled. +pub struct MultiplexIrq { + irq: IrqLine, + queue_callbacks: Vec>, + cfg_callbacks: Vec>, + interrupt_ack: SafePtr>, + interrupt_status: SafePtr>, +} + +impl MultiplexIrq { + pub fn new( + irq: IrqLine, + interrupt_ack: SafePtr>, + interrupt_status: SafePtr>, + ) -> Arc> { + let irq = Arc::new(RwLock::new(Self { + irq, + queue_callbacks: Vec::new(), + cfg_callbacks: Vec::new(), + interrupt_ack, + interrupt_status, + })); + // Holding a weak reference to prevent memory leakage due to + // circular reference. + let weak = Arc::downgrade(&irq); + let mut lock = irq.write(); + let callback = move |trap_frame: &TrapFrame| { + let Some(multiplex_irq) = weak.upgrade() else { + return; + }; + let irq = multiplex_irq.read(); + let interrupt_status = irq.interrupt_status.read().unwrap(); + let callbacks = if interrupt_status & 0x01 == 1 { + // Used buffer notification + &irq.queue_callbacks + } else { + // Configuration Change Notification + &irq.cfg_callbacks + }; + for callback in callbacks.iter() { + callback.call((trap_frame,)); + } + irq.interrupt_ack.write(&interrupt_status).unwrap(); + }; + lock.irq.on_active(callback); + drop(lock); + irq + } + + pub fn register_queue_callback(&mut self, func: Box) { + self.queue_callbacks.push(func); + } + + pub fn register_cfg_callback(&mut self, func: Box) { + self.cfg_callbacks.push(func); + } +} + +impl Debug for MultiplexIrq { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("MultiplexIrq") + .field("irq", &self.irq) + .field("interrupt_ack", &self.interrupt_ack) + .field("interrupt_status", &self.interrupt_status) + .finish() + } +} diff --git a/services/comps/virtio/src/transport/mod.rs b/services/comps/virtio/src/transport/mod.rs index 439123dff..53eff9e36 100644 --- a/services/comps/virtio/src/transport/mod.rs +++ b/services/comps/virtio/src/transport/mod.rs @@ -1,7 +1,7 @@ use core::fmt::Debug; use alloc::boxed::Box; -use jinux_frame::{io_mem::IoMem, trap::TrapFrame, vm::VmFrame}; +use jinux_frame::{io_mem::IoMem, trap::IrqCallbackFunction, vm::VmFrame}; use jinux_util::safe_ptr::SafePtr; use crate::{ @@ -9,8 +9,9 @@ use crate::{ VirtioDeviceType, }; -use self::pci::virtio_pci_init; +use self::{mmio::virtio_mmio_init, pci::virtio_pci_init}; +pub mod mmio; pub mod pci; /// The transport of virtio device. Virtio device can use this transport to: @@ -72,6 +73,8 @@ pub trait VirtioTransport: Sync + Send + Debug { /// after it add buffers into the corresponding virtqueue. fn get_notify_ptr(&self, idx: u16) -> Result, VirtioTransportError>; + fn is_legacy_version(&self) -> bool; + // ====================Device interrupt APIs===================== /// Register queue interrupt callback. The transport will try to allocate single IRQ line if @@ -79,14 +82,14 @@ pub trait VirtioTransport: Sync + Send + Debug { fn register_queue_callback( &mut self, index: u16, - func: Box, + func: Box, single_interrupt: bool, ) -> Result<(), VirtioTransportError>; /// Register configuration space change interrupt callback. fn register_cfg_callback( &mut self, - func: Box, + func: Box, ) -> Result<(), VirtioTransportError>; } @@ -128,4 +131,5 @@ bitflags::bitflags! { pub fn init() { virtio_pci_init(); + virtio_mmio_init(); } diff --git a/services/comps/virtio/src/transport/pci/device.rs b/services/comps/virtio/src/transport/pci/device.rs index 391c170fa..13132451f 100644 --- a/services/comps/virtio/src/transport/pci/device.rs +++ b/services/comps/virtio/src/transport/pci/device.rs @@ -1,13 +1,13 @@ use jinux_frame::{ - bus::pci::{ - bus::{PciDevice, PciDriverProbeError}, - capability::CapabilityData, - common_device::PciCommonDevice, - PciDeviceId, + bus::{ + pci::{ + bus::PciDevice, capability::CapabilityData, common_device::PciCommonDevice, PciDeviceId, + }, + BusProbeError, }, io_mem::IoMem, offset_of, - trap::TrapFrame, + trap::IrqCallbackFunction, vm::VmFrame, }; @@ -206,7 +206,7 @@ impl VirtioTransport for VirtioPciTransport { fn register_queue_callback( &mut self, index: u16, - func: Box, + func: Box, single_interrupt: bool, ) -> Result<(), VirtioTransportError> { if index >= self.num_queues() { @@ -237,12 +237,17 @@ impl VirtioTransport for VirtioPciTransport { fn register_cfg_callback( &mut self, - func: Box, + func: Box, ) -> Result<(), VirtioTransportError> { let (_, irq) = self.msix_manager.config_msix_irq(); irq.on_active(func); Ok(()) } + + fn is_legacy_version(&self) -> bool { + // TODO: Support legacy version + false + } } impl VirtioPciTransport { @@ -253,7 +258,7 @@ impl VirtioPciTransport { #[allow(clippy::result_large_err)] pub(super) fn new( common_device: PciCommonDevice, - ) -> Result { + ) -> Result { let device_type = match common_device.device_id().device_id { 0x1000 => VirtioDeviceType::Network, 0x1001 => VirtioDeviceType::Block, @@ -268,7 +273,7 @@ impl VirtioPciTransport { "Unrecognized virtio-pci device id:{:x?}", common_device.device_id().device_id ); - return Err((PciDriverProbeError::ConfigurationSpaceError, common_device)); + return Err((BusProbeError::ConfigurationSpaceError, common_device)); } let id = id - 0x1040; match VirtioDeviceType::try_from(id as u8) { @@ -278,7 +283,7 @@ impl VirtioPciTransport { "Unrecognized virtio-pci device id:{:x?}", common_device.device_id().device_id ); - return Err((PciDriverProbeError::ConfigurationSpaceError, common_device)); + return Err((BusProbeError::ConfigurationSpaceError, common_device)); } } } diff --git a/services/comps/virtio/src/transport/pci/driver.rs b/services/comps/virtio/src/transport/pci/driver.rs index 6d2ef5889..7f6cf9baa 100644 --- a/services/comps/virtio/src/transport/pci/driver.rs +++ b/services/comps/virtio/src/transport/pci/driver.rs @@ -1,8 +1,11 @@ use alloc::{sync::Arc, vec::Vec}; use jinux_frame::{ - bus::pci::{ - bus::{PciDevice, PciDriver, PciDriverProbeError}, - common_device::PciCommonDevice, + bus::{ + pci::{ + bus::{PciDevice, PciDriver}, + common_device::PciCommonDevice, + }, + BusProbeError, }, sync::SpinLock, }; @@ -19,7 +22,7 @@ impl VirtioPciDriver { self.devices.lock().len() } - pub fn pop_device_tranport(&self) -> Option { + pub fn pop_device_transport(&self) -> Option { self.devices.lock().pop() } @@ -34,10 +37,10 @@ impl PciDriver for VirtioPciDriver { fn probe( &self, device: PciCommonDevice, - ) -> Result, (PciDriverProbeError, PciCommonDevice)> { + ) -> Result, (BusProbeError, PciCommonDevice)> { const VIRTIO_DEVICE_VENDOR_ID: u16 = 0x1af4; if device.device_id().vendor_id != VIRTIO_DEVICE_VENDOR_ID { - return Err((PciDriverProbeError::DeviceNotMatch, device)); + return Err((BusProbeError::DeviceNotMatch, device)); } let transport = VirtioPciTransport::new(device)?; let device = transport.pci_device().clone();