diff --git a/src/arch/riscv64/kernel/devicetree.rs b/src/arch/riscv64/kernel/devicetree.rs index 45380af9da..71b00378a6 100644 --- a/src/arch/riscv64/kernel/devicetree.rs +++ b/src/arch/riscv64/kernel/devicetree.rs @@ -1,4 +1,11 @@ +#[cfg(all(feature = "tcp", not(feature = "pci")))] +use core::ptr::NonNull; + use fdt::Fdt; +#[cfg(all(feature = "tcp", not(feature = "pci")))] +use virtio_spec::mmio::{DeviceRegisterVolatileFieldAccess, DeviceRegisters}; +#[cfg(all(feature = "tcp", not(feature = "pci")))] +use volatile::VolatileRef; #[cfg(feature = "gem-net")] use crate::arch::mm::VirtAddr; @@ -9,11 +16,11 @@ use crate::arch::riscv64::kernel::mmio::MmioDriver; use crate::arch::riscv64::mm::{paging, PhysAddr}; #[cfg(feature = "gem-net")] use crate::drivers::net::gem; +#[cfg(all(feature = "tcp", not(feature = "pci")))] +use crate::drivers::virtio::transport::mmio::DevId; #[cfg(all(feature = "tcp", not(feature = "pci"), not(feature = "gem-net")))] use crate::drivers::virtio::transport::mmio::{self as mmio_virtio, VirtioDriver}; #[cfg(all(feature = "tcp", not(feature = "pci")))] -use crate::drivers::virtio::transport::mmio::{DevId, MmioRegisterLayout}; -#[cfg(all(feature = "tcp", not(feature = "pci")))] use crate::kernel::mmio::register_driver; static mut PLATFORM_MODEL: Model = Model::Unknown; @@ -184,32 +191,30 @@ pub fn init_drivers() { ); // Verify the first register value to find out if this is really an MMIO magic-value. - let mmio = &mut *(virtio_region.starting_address as *mut MmioRegisterLayout); + let ptr = virtio_region.starting_address as *mut DeviceRegisters; + let mmio = VolatileRef::new(NonNull::new(ptr).unwrap()); - let magic = mmio.get_magic_value(); - let version = mmio.get_version(); + let magic = mmio.as_ptr().magic_value().read().to_ne(); + let version = mmio.as_ptr().version().read().to_ne(); const MMIO_MAGIC_VALUE: u32 = 0x74726976; if magic != MMIO_MAGIC_VALUE { - error!("It's not a MMIO-device at {:#X}", mmio as *const _ as usize); + error!("It's not a MMIO-device at {mmio:p}"); } if version != 2 { warn!("Found a leagacy device, which isn't supported"); } else { // We found a MMIO-device (whose 512-bit address in this structure). - trace!("Found a MMIO-device at {:#X}", mmio as *const _ as usize); + trace!("Found a MMIO-device at {mmio:p}"); // Verify the device-ID to find the network card - let id = mmio.get_device_id(); + let id = DevId::from(mmio.as_ptr().device_id().read().to_ne()); if id != DevId::VIRTIO_DEV_ID_NET { - debug!( - "It's not a network card at {:#X}", - mmio as *const _ as usize - ); + debug!("It's not a network card at {mmio:p}"); } else { - info!("Found network card at {:#X}", mmio as *const _ as usize); + info!("Found network card at {mmio:p}"); // crate::arch::mm::physicalmem::reserve( // PhysAddr::from(current_address.align_down(BasePageSize::SIZE as usize)), diff --git a/src/arch/x86_64/kernel/mmio.rs b/src/arch/x86_64/kernel/mmio.rs index 70f8681d29..95329b6177 100644 --- a/src/arch/x86_64/kernel/mmio.rs +++ b/src/arch/x86_64/kernel/mmio.rs @@ -1,9 +1,12 @@ use alloc::string::String; use alloc::vec::Vec; +use core::ptr::NonNull; use core::{ptr, str}; use align_address::Align; use hermit_sync::{without_interrupts, InterruptTicketMutex}; +use virtio_spec::mmio::{DeviceRegisterVolatileFieldAccess, DeviceRegisters}; +use volatile::VolatileRef; use crate::arch::x86_64::mm::paging::{ BasePageSize, PageSize, PageTableEntryFlags, PageTableEntryFlagsExt, @@ -11,7 +14,7 @@ use crate::arch::x86_64::mm::paging::{ use crate::arch::x86_64::mm::{paging, PhysAddr}; use crate::drivers::net::virtio_net::VirtioNetDriver; use crate::drivers::virtio::transport::mmio as mmio_virtio; -use crate::drivers::virtio::transport::mmio::{DevId, MmioRegisterLayout, VirtioDriver}; +use crate::drivers::virtio::transport::mmio::{DevId, VirtioDriver}; use crate::env; pub const MAGIC_VALUE: u32 = 0x74726976; @@ -36,9 +39,40 @@ impl MmioDriver { } } +unsafe fn check_ptr(ptr: *mut u8) -> Option> { + // Verify the first register value to find out if this is really an MMIO magic-value. + let mmio = unsafe { VolatileRef::new(NonNull::new(ptr.cast::()).unwrap()) }; + + let magic = mmio.as_ptr().magic_value().read().to_ne(); + let version = mmio.as_ptr().version().read().to_ne(); + + if magic != MAGIC_VALUE { + trace!("It's not a MMIO-device at {mmio:p}"); + return None; + } + + if version != 2 { + trace!("Found a legacy device, which isn't supported"); + return None; + } + + // We found a MMIO-device (whose 512-bit address in this structure). + trace!("Found a MMIO-device at {mmio:p}"); + + // Verify the device-ID to find the network card + let id = DevId::from(mmio.as_ptr().device_id().read().to_ne()); + + if id != DevId::VIRTIO_DEV_ID_NET { + trace!("It's not a network card at {mmio:p}"); + return None; + } + + Some(mmio) +} + fn check_linux_args( linux_mmio: &'static [String], -) -> Result<(&'static mut MmioRegisterLayout, u8), &'static str> { +) -> Result<(VolatileRef<'static, DeviceRegisters>, u8), &'static str> { let virtual_address = crate::arch::mm::virtualmem::allocate(BasePageSize::SIZE as usize).unwrap(); @@ -66,37 +100,12 @@ fn check_linux_args( flags, ); - // Verify the first register value to find out if this is really an MMIO magic-value. - let mmio = unsafe { - &mut *(ptr::with_exposed_provenance_mut::( - virtual_address.as_usize() - | (current_address & (BasePageSize::SIZE as usize - 1)), - )) - }; - - let magic = mmio.get_magic_value(); - let version = mmio.get_version(); - - if magic != MAGIC_VALUE { - trace!("It's not a MMIO-device at {mmio:p}"); - continue; - } - - if version != 2 { - trace!("Found a legacy device, which isn't supported"); - continue; - } - - // We found a MMIO-device (whose 512-bit address in this structure). - trace!("Found a MMIO-device at {mmio:p}"); - - // Verify the device-ID to find the network card - let id = mmio.get_device_id(); - - if id != DevId::VIRTIO_DEV_ID_NET { - trace!("It's not a network card at {mmio:p}"); + let addr = virtual_address.as_usize() + | (current_address & (BasePageSize::SIZE as usize - 1)); + let ptr = ptr::with_exposed_provenance_mut(addr); + let Some(mmio) = (unsafe { check_ptr(ptr) }) else { continue; - } + }; crate::arch::mm::physicalmem::reserve( PhysAddr::from(current_address.align_down(BasePageSize::SIZE as usize)), @@ -117,7 +126,7 @@ fn check_linux_args( Err("Network card not found!") } -fn guess_device() -> Result<(&'static mut MmioRegisterLayout, u8), &'static str> { +fn guess_device() -> Result<(VolatileRef<'static, DeviceRegisters>, u8), &'static str> { // Trigger page mapping in the first iteration! let mut current_page = 0; let virtual_address = @@ -144,36 +153,12 @@ fn guess_device() -> Result<(&'static mut MmioRegisterLayout, u8), &'static str> current_page = current_address / BasePageSize::SIZE as usize; } - // Verify the first register value to find out if this is really an MMIO magic-value. - let mmio = unsafe { - &mut *(ptr::with_exposed_provenance_mut::( - virtual_address.as_usize() | (current_address & (BasePageSize::SIZE as usize - 1)), - )) - }; - - let magic = mmio.get_magic_value(); - let version = mmio.get_version(); - - if magic != MAGIC_VALUE { - trace!("It's not a MMIO-device at {mmio:p}"); + let addr = + virtual_address.as_usize() | (current_address & (BasePageSize::SIZE as usize - 1)); + let ptr = ptr::with_exposed_provenance_mut(addr); + let Some(mmio) = (unsafe { check_ptr(ptr) }) else { continue; - } - - if version != 2 { - trace!("Found a legacy device, which isn't supported"); - continue; - } - - // We found a MMIO-device (whose 512-bit address in this structure). - trace!("Found a MMIO-device at {mmio:p}"); - - // Verify the device-ID to find the network card - let id = mmio.get_device_id(); - - if id != DevId::VIRTIO_DEV_ID_NET { - trace!("It's not a network card at {mmio:p}"); - continue; - } + }; info!("Found network card at {mmio:p}"); @@ -193,7 +178,7 @@ fn guess_device() -> Result<(&'static mut MmioRegisterLayout, u8), &'static str> /// Tries to find the network device within the specified address range. /// Returns a reference to it within the Ok() if successful or an Err() on failure. -fn detect_network() -> Result<(&'static mut MmioRegisterLayout, u8), &'static str> { +fn detect_network() -> Result<(VolatileRef<'static, DeviceRegisters>, u8), &'static str> { let linux_mmio = env::mmio(); if !linux_mmio.is_empty() { diff --git a/src/drivers/net/virtio_mmio.rs b/src/drivers/net/virtio_mmio.rs index f99e33a7dd..674e434372 100644 --- a/src/drivers/net/virtio_mmio.rs +++ b/src/drivers/net/virtio_mmio.rs @@ -4,17 +4,18 @@ use alloc::rc::Rc; use alloc::vec::Vec; -use core::ptr; use core::ptr::read_volatile; use core::str::FromStr; use core::sync::atomic::{fence, Ordering}; use smoltcp::phy::ChecksumCapabilities; +use virtio_spec::mmio::{DeviceRegisterVolatileFieldAccess, DeviceRegisters}; +use volatile::VolatileRef; use crate::drivers::net::virtio_net::constants::Status; use crate::drivers::net::virtio_net::{CtrlQueue, NetDevCfg, RxQueues, TxQueues, VirtioNetDriver}; use crate::drivers::virtio::error::{VirtioError, VirtioNetError}; -use crate::drivers::virtio::transport::mmio::{ComCfg, IsrStatus, MmioRegisterLayout, NotifCfg}; +use crate::drivers::virtio::transport::mmio::{ComCfg, IsrStatus, NotifCfg}; use crate::drivers::virtio::virtqueue::Virtq; /// Virtio's network device configuration structure. @@ -110,18 +111,25 @@ impl NetDevCfgRaw { impl VirtioNetDriver { pub fn new( dev_id: u16, - registers: &'static mut MmioRegisterLayout, + mut registers: VolatileRef<'static, DeviceRegisters>, irq: u8, ) -> Result { - let dev_cfg_raw: &'static NetDevCfgRaw = - unsafe { &*(ptr::with_exposed_provenance(ptr::from_ref(registers).addr() + 0xFC)) }; + let dev_cfg_raw: &'static NetDevCfgRaw = unsafe { + &*registers + .borrow_mut() + .as_mut_ptr() + .config_generation() + .as_raw_ptr() + .cast::() + .as_ptr() + }; let dev_cfg = NetDevCfg { raw: dev_cfg_raw, dev_id, features: virtio_spec::net::F::empty(), }; - let isr_stat = IsrStatus::new(registers); - let notif_cfg = NotifCfg::new(registers); + let isr_stat = IsrStatus::new(registers.borrow_mut()); + let notif_cfg = NotifCfg::new(registers.borrow_mut()); let mtu = if let Some(my_mtu) = hermit_var!("HERMIT_MTU") { u16::from_str(&my_mtu).unwrap() @@ -159,7 +167,7 @@ impl VirtioNetDriver { /// [VirtioNetDriver](structs.virtionetdriver.html) or an [VirtioError](enums.virtioerror.html). pub fn init( dev_id: u16, - registers: &'static mut MmioRegisterLayout, + registers: VolatileRef<'static, DeviceRegisters>, irq_no: u8, ) -> Result { if let Ok(mut drv) = VirtioNetDriver::new(dev_id, registers, irq_no) { diff --git a/src/drivers/virtio/transport/mmio.rs b/src/drivers/virtio/transport/mmio.rs index 4915dd7f4d..9a75eeb8ad 100644 --- a/src/drivers/virtio/transport/mmio.rs +++ b/src/drivers/virtio/transport/mmio.rs @@ -3,11 +3,14 @@ //! The module contains ... #![allow(dead_code)] -use core::ptr; use core::ptr::{read_volatile, write_volatile}; use core::sync::atomic::{fence, Ordering}; +use virtio_spec::mmio::{ + DeviceRegisterVolatileFieldAccess, DeviceRegisterVolatileWideFieldAccess, DeviceRegisters, +}; use virtio_spec::DeviceStatus; +use volatile::VolatileRef; #[cfg(any(feature = "tcp", feature = "udp"))] use crate::arch::kernel::interrupts::*; @@ -59,32 +62,49 @@ impl From for DevId { } pub struct VqCfgHandler<'a> { - vq_index: u32, - raw: &'a mut MmioRegisterLayout, + vq_index: u16, + raw: VolatileRef<'a, DeviceRegisters>, } impl<'a> VqCfgHandler<'a> { + // TODO: Create type for queue selected invariant to get rid of `self.select_queue()` everywhere. + fn select_queue(&mut self) { + self.raw + .as_mut_ptr() + .queue_sel() + .write(self.vq_index.into()) + } + /// Sets the size of a given virtqueue. In case the provided size exceeds the maximum allowed /// size, the size is set to this maximum instead. Else size is set to the provided value. /// /// Returns the set size in form of a `u16`. pub fn set_vq_size(&mut self, size: u16) -> u16 { - self.raw - .set_queue_size(self.vq_index, size as u32) - .try_into() - .unwrap() + self.select_queue(); + let ptr = self.raw.as_mut_ptr(); + + let num_max = ptr.queue_num_max().read().to_ne(); + let size = size.min(num_max); + ptr.queue_num().write(size.into()); + size } pub fn set_ring_addr(&mut self, addr: PhysAddr) { - self.raw.set_ring_addr(self.vq_index, addr); + self.select_queue(); + + self.raw.as_mut_ptr().queue_desc().write(addr.0.into()); } pub fn set_drv_ctrl_addr(&mut self, addr: PhysAddr) { - self.raw.set_drv_ctrl_addr(self.vq_index, addr); + self.select_queue(); + + self.raw.as_mut_ptr().queue_driver().write(addr.0.into()); } pub fn set_dev_ctrl_addr(&mut self, addr: PhysAddr) { - self.raw.set_dev_ctrl_addr(self.vq_index, addr); + self.select_queue(); + + self.raw.as_mut_ptr().queue_device().write(addr.0.into()); } pub fn notif_off(&mut self) -> u16 { @@ -93,7 +113,9 @@ impl<'a> VqCfgHandler<'a> { } pub fn enable_queue(&mut self) { - self.raw.enable_queue(self.vq_index); + self.select_queue(); + + self.raw.as_mut_ptr().queue_ready().write(true); } } @@ -103,9 +125,8 @@ impl<'a> VqCfgHandler<'a> { /// Provides a safe API for the raw structure and allows interaction with the device via /// the structure. pub struct ComCfg { - // References the raw structure in PCI memory space. Is static as - // long as the device is present, which is mandatory in order to let this code work. - com_cfg: &'static mut MmioRegisterLayout, + // FIXME: remove 'static lifetime + com_cfg: VolatileRef<'static, DeviceRegisters>, /// Preferences of the device for this config. From 1 (highest) to 2^7-1 (lowest) rank: u8, @@ -113,7 +134,7 @@ pub struct ComCfg { // Public Interface of ComCfg impl ComCfg { - pub fn new(raw: &'static mut MmioRegisterLayout, rank: u8) -> Self { + pub fn new(raw: VolatileRef<'static, DeviceRegisters>, rank: u8) -> Self { ComCfg { com_cfg: raw, rank } } @@ -122,86 +143,77 @@ impl ComCfg { /// /// INFO: The queue size is automatically bounded by constant `src::config:VIRTIO_MAX_QUEUE_SIZE`. pub fn select_vq(&mut self, index: u16) -> Option> { - if self.com_cfg.get_max_queue_size(u32::from(index)) == 0 { + if self.get_max_queue_size(index) == 0 { None } else { Some(VqCfgHandler { - vq_index: index as u32, - raw: self.com_cfg, + vq_index: index, + raw: self.com_cfg.borrow_mut(), }) } } - pub fn get_max_queue_size(&mut self, sel: u32) -> u32 { - self.com_cfg.get_max_queue_size(sel) + pub fn get_max_queue_size(&mut self, sel: u16) -> u16 { + let ptr = self.com_cfg.as_mut_ptr(); + ptr.queue_sel().write(sel.into()); + ptr.queue_num_max().read().to_ne() } - pub fn get_queue_ready(&mut self, sel: u32) -> bool { - self.com_cfg.get_queue_ready(sel) + pub fn get_queue_ready(&mut self, sel: u16) -> bool { + let ptr = self.com_cfg.as_mut_ptr(); + ptr.queue_sel().write(sel.into()); + ptr.queue_ready().read() } /// Returns the device status field. pub fn dev_status(&self) -> u8 { - unsafe { read_volatile(&self.com_cfg.status).try_into().unwrap() } + self.com_cfg.as_ptr().status().read().bits() } /// Resets the device status field to zero. pub fn reset_dev(&mut self) { - unsafe { - write_volatile( - &mut self.com_cfg.status, - DeviceStatus::empty().bits().into(), - ); - } + self.com_cfg + .as_mut_ptr() + .status() + .write(DeviceStatus::empty()); } /// Sets the device status field to FAILED. /// A driver MUST NOT initialize and use the device any further after this. /// A driver MAY use the device again after a proper reset of the device. pub fn set_failed(&mut self) { - unsafe { - write_volatile(&mut self.com_cfg.status, DeviceStatus::FAILED.bits().into()); - } + self.com_cfg + .as_mut_ptr() + .status() + .write(DeviceStatus::FAILED); } /// Sets the ACKNOWLEDGE bit in the device status field. This indicates, the /// OS has notived the device pub fn ack_dev(&mut self) { - unsafe { - let status = - DeviceStatus::from_bits(read_volatile(&self.com_cfg.status) as u8).unwrap(); - write_volatile( - &mut self.com_cfg.status, - (status | DeviceStatus::ACKNOWLEDGE).bits().into(), - ); - } + self.com_cfg + .as_mut_ptr() + .status() + .update(|status| status | DeviceStatus::ACKNOWLEDGE); } /// Sets the DRIVER bit in the device status field. This indicates, the OS /// know how to run this device. pub fn set_drv(&mut self) { - unsafe { - let status = - DeviceStatus::from_bits(read_volatile(&self.com_cfg.status) as u8).unwrap(); - write_volatile( - &mut self.com_cfg.status, - (status | DeviceStatus::DRIVER).bits().into(), - ); - } + self.com_cfg + .as_mut_ptr() + .status() + .update(|status| status | DeviceStatus::DRIVER); } /// Sets the FEATURES_OK bit in the device status field. /// /// Drivers MUST NOT accept new features after this step. pub fn features_ok(&mut self) { - unsafe { - let status = - DeviceStatus::from_bits(read_volatile(&self.com_cfg.status) as u8).unwrap(); - write_volatile( - &mut self.com_cfg.status, - (status | DeviceStatus::FEATURES_OK).bits().into(), - ); - } + self.com_cfg + .as_mut_ptr() + .status() + .update(|status| status | DeviceStatus::FEATURES_OK); } /// In order to correctly check feature negotiaten, this function @@ -211,39 +223,81 @@ impl ComCfg { /// Re-reads device status to ensure the FEATURES_OK bit is still set: /// otherwise, the device does not support our subset of features and the device is unusable. pub fn check_features(&self) -> bool { - unsafe { - let status = - DeviceStatus::from_bits(read_volatile(&self.com_cfg.status) as u8).unwrap(); - status.contains(DeviceStatus::FEATURES_OK) - } + self.com_cfg + .as_ptr() + .status() + .read() + .contains(DeviceStatus::FEATURES_OK) } /// Sets the DRIVER_OK bit in the device status field. /// /// After this call, the device is "live"! pub fn drv_ok(&mut self) { - unsafe { - let status = - DeviceStatus::from_bits(read_volatile(&self.com_cfg.status) as u8).unwrap(); - write_volatile( - &mut self.com_cfg.status, - (status | DeviceStatus::DRIVER_OK).bits().into(), - ); - } + self.com_cfg + .as_mut_ptr() + .status() + .update(|status| status | DeviceStatus::DRIVER_OK); } /// Returns the features offered by the device. pub fn dev_features(&mut self) -> virtio_spec::F { - self.com_cfg.dev_features() + let ptr = self.com_cfg.as_mut_ptr(); + + // Indicate device to show high 32 bits in device_feature field. + // See Virtio specification v1.1. - 4.1.4.3 + ptr.device_features_sel().write(1.into()); + + // read high 32 bits of device features + let mut device_features = u64::from(ptr.device_features().read().to_ne()) << 32; + + // Indicate device to show low 32 bits in device_feature field. + // See Virtio specification v1.1. - 4.1.4.3 + ptr.device_features_sel().write(0.into()); + + // read low 32 bits of device features + device_features |= u64::from(ptr.device_features().read().to_ne()); + + virtio_spec::F::from_bits_retain(u128::from(device_features).into()) } /// Write selected features into driver_select field. pub fn set_drv_features(&mut self, features: virtio_spec::F) { - self.com_cfg.set_drv_features(features); + let ptr = self.com_cfg.as_mut_ptr(); + + let features = features.bits().to_ne() as u64; + let high: u32 = (features >> 32) as u32; + let low: u32 = features as u32; + + // Indicate to device that driver_features field shows low 32 bits. + // See Virtio specification v1.1. - 4.1.4.3 + ptr.driver_features_sel().write(0.into()); + + // write low 32 bits of device features + ptr.driver_features().write(low.into()); + + // Indicate to device that driver_features field shows high 32 bits. + // See Virtio specification v1.1. - 4.1.4.3 + ptr.driver_features_sel().write(1.into()); + + // write high 32 bits of device features + ptr.driver_features().write(high.into()); } pub fn print_information(&mut self) { - self.com_cfg.print_information(); + let ptr = self.com_cfg.as_ptr(); + + infoheader!(" MMIO RREGISTER LAYOUT INFORMATION "); + + infoentry!("Device version", "{:#X}", ptr.version().read()); + infoentry!("Device ID", "{:?}", ptr.device_id().read()); + infoentry!("Vendor ID", "{:#X}", ptr.vendor_id().read()); + infoentry!("Device Features", "{:#X}", self.dev_features()); + let ptr = self.com_cfg.as_ptr(); + infoentry!("Interrupt status", "{:#X}", ptr.interrupt_status().read()); + infoentry!("Device status", "{:#X}", ptr.status().read()); + + infofooter!(); } } @@ -255,8 +309,13 @@ pub struct NotifCfg { } impl NotifCfg { - pub fn new(registers: &mut MmioRegisterLayout) -> Self { - let raw = ptr::from_mut(&mut registers.queue_notify); + pub fn new(mut registers: VolatileRef<'_, DeviceRegisters>) -> Self { + let raw = registers + .as_mut_ptr() + .queue_notify() + .as_raw_ptr() + .as_ptr() + .cast(); NotifCfg { queue_notify: raw } } @@ -332,8 +391,12 @@ pub struct IsrStatus { } impl IsrStatus { - pub fn new(registers: &mut MmioRegisterLayout) -> Self { - let ptr = ptr::from_mut(&mut registers.interrupt_status); + pub fn new(mut registers: VolatileRef<'_, DeviceRegisters>) -> Self { + let ptr = registers + .as_mut_ptr() + .interrupt_status() + .as_raw_ptr() + .as_ptr(); let raw: &'static mut IsrStatusRaw = unsafe { &mut *(ptr as *mut IsrStatusRaw) }; IsrStatus { raw } @@ -374,12 +437,12 @@ pub(crate) enum VirtioDriver { #[allow(unused_variables)] pub(crate) fn init_device( - registers: &'static mut MmioRegisterLayout, + registers: VolatileRef<'static, DeviceRegisters>, irq_no: u8, ) -> Result { let dev_id: u16 = 0; - if registers.version == 0x1 { + if registers.as_ptr().version().read().to_ne() == 0x1 { error!("Legacy interface isn't supported!"); return Err(DriverError::InitVirtioDevFail( VirtioError::DevNotSupported(dev_id), @@ -387,7 +450,7 @@ pub(crate) fn init_device( } // Verify the device-ID to find the network card - match registers.device_id { + match registers.as_ptr().device_id().read().to_ne().into() { #[cfg(any(feature = "tcp", feature = "udp"))] DevId::VIRTIO_DEV_ID_NET => { match VirtioNetDriver::init(dev_id, registers, irq_no) { @@ -406,11 +469,8 @@ pub(crate) fn init_device( } } } - _ => { - error!( - "Device with id {:?} is currently not supported!", - registers.device_id - ); + device_id => { + error!("Device with id {device_id:?} is currently not supported!"); // Return Driver error inidacting device is not supported Err(DriverError::InitVirtioDevFail( VirtioError::DevNotSupported(dev_id), @@ -418,209 +478,3 @@ pub(crate) fn init_device( } } } - -/// The Layout of MMIO Device -#[repr(C, align(4))] -pub struct MmioRegisterLayout { - magic_value: u32, - version: u32, - device_id: DevId, - vendor_id: u32, - - device_features: u32, - device_features_sel: u32, - _reserved0: [u32; 2], - driver_features: u32, - driver_features_sel: u32, - - guest_page_size: u32, // legacy only - _reserved1: u32, - - queue_sel: u32, - queue_num_max: u32, - queue_num: u32, - queue_align: u32, // legacy only - queue_pfn: u32, // legacy only - queue_ready: u32, // non-legacy only - _reserved2: [u32; 2], - queue_notify: u32, - _reserved3: [u32; 3], - - interrupt_status: u32, - interrupt_ack: u32, - _reserved4: [u32; 2], - - status: u32, - _reserved5: [u32; 3], - - queue_desc_low: u32, // non-legacy only - queue_desc_high: u32, // non-legacy only - _reserved6: [u32; 2], - queue_driver_low: u32, // non-legacy only - queue_driver_high: u32, // non-legacy only - _reserved7: [u32; 2], - queue_device_low: u32, // non-legacy only - queue_device_high: u32, // non-legacy only - _reserved8: [u32; 21], - - config_generation: u32, // non-legacy only - config: [u32; 3], -} - -impl MmioRegisterLayout { - pub fn get_magic_value(&self) -> u32 { - unsafe { read_volatile(&self.magic_value) } - } - - pub fn get_version(&self) -> u32 { - unsafe { read_volatile(&self.version) } - } - - pub fn get_device_id(&self) -> DevId { - unsafe { read_volatile(&self.device_id) } - } - - pub fn enable_queue(&mut self, sel: u32) { - unsafe { - write_volatile(&mut self.queue_sel, sel); - write_volatile(&mut self.queue_ready, 1u32); - } - } - - pub fn get_max_queue_size(&mut self, sel: u32) -> u32 { - unsafe { - write_volatile(&mut self.queue_sel, sel); - read_volatile(&self.queue_num_max) - } - } - - pub fn set_queue_size(&mut self, sel: u32, size: u32) -> u32 { - unsafe { - write_volatile(&mut self.queue_sel, sel); - - let num_max = read_volatile(&self.queue_num_max); - - if num_max >= size { - write_volatile(&mut self.queue_num, size); - size - } else { - write_volatile(&mut self.queue_num, num_max); - num_max - } - } - } - - pub fn set_ring_addr(&mut self, sel: u32, addr: PhysAddr) { - unsafe { - write_volatile(&mut self.queue_sel, sel); - write_volatile(&mut self.queue_desc_low, addr.as_u64() as u32); - write_volatile(&mut self.queue_desc_high, (addr.as_u64() >> 32) as u32); - } - } - - pub fn set_drv_ctrl_addr(&mut self, sel: u32, addr: PhysAddr) { - unsafe { - write_volatile(&mut self.queue_sel, sel); - write_volatile(&mut self.queue_driver_low, addr.as_u64() as u32); - write_volatile(&mut self.queue_driver_high, (addr.as_u64() >> 32) as u32); - } - } - - pub fn set_dev_ctrl_addr(&mut self, sel: u32, addr: PhysAddr) { - unsafe { - write_volatile(&mut self.queue_sel, sel); - write_volatile(&mut self.queue_device_low, addr.as_u64() as u32); - write_volatile(&mut self.queue_device_high, (addr.as_u64() >> 32) as u32); - } - } - - pub fn get_queue_ready(&mut self, sel: u32) -> bool { - unsafe { - write_volatile(&mut self.queue_sel, sel); - read_volatile(&self.queue_ready) != 0 - } - } - - pub fn dev_features(&mut self) -> virtio_spec::F { - // Indicate device to show high 32 bits in device_feature field. - // See Virtio specification v1.1. - 4.1.4.3 - unsafe { - write_volatile(&mut self.device_features_sel, 1u32); - - // read high 32 bits of device features - let mut device_features = u64::from(read_volatile(&self.device_features)) << 32; - - // Indicate device to show low 32 bits in device_feature field. - // See Virtio specification v1.1. - 4.1.4.3 - write_volatile(&mut self.device_features_sel, 0u32); - - // read low 32 bits of device features - device_features |= u64::from(read_volatile(&self.device_features)); - - virtio_spec::F::from_bits_retain(u128::from(device_features).into()) - } - } - - /// Write selected features into driver_select field. - pub fn set_drv_features(&mut self, features: virtio_spec::F) { - let features = features.bits().to_ne() as u64; - let high: u32 = (features >> 32) as u32; - let low: u32 = features as u32; - - unsafe { - // Indicate to device that driver_features field shows low 32 bits. - // See Virtio specification v1.1. - 4.1.4.3 - write_volatile(&mut self.driver_features_sel, 0u32); - - // write low 32 bits of device features - write_volatile(&mut self.driver_features, low); - - // Indicate to device that driver_features field shows high 32 bits. - // See Virtio specification v1.1. - 4.1.4.3 - write_volatile(&mut self.driver_features_sel, 1u32); - - // write high 32 bits of device features - write_volatile(&mut self.driver_features, high); - } - } - - pub fn get_config(&mut self) -> [u32; 3] { - // see Virtio specification v1.1 - 2.4.1 - unsafe { - loop { - let before = read_volatile(&self.config_generation); - fence(Ordering::SeqCst); - let config = read_volatile(&self.config); - fence(Ordering::SeqCst); - let after = read_volatile(&self.config_generation); - fence(Ordering::SeqCst); - - if before == after { - return config; - } - } - } - } - - pub fn print_information(&mut self) { - infoheader!(" MMIO RREGISTER LAYOUT INFORMATION "); - - infoentry!("Device version", "{:#X}", self.get_version()); - infoentry!("Device ID", "{:?}", unsafe { - read_volatile(&self.device_id) - }); - infoentry!("Vendor ID", "{:#X}", unsafe { - read_volatile(&self.vendor_id) - }); - infoentry!("Device Features", "{:#X}", self.dev_features()); - infoentry!("Interrupt status", "{:#X}", unsafe { - read_volatile(&self.interrupt_status) - }); - infoentry!("Device status", "{:#X}", unsafe { - read_volatile(&self.status) - }); - infoentry!("Configuration space", "{:#X?}", self.get_config()); - - infofooter!(); - } -} diff --git a/virtio-spec/src/lib.rs b/virtio-spec/src/lib.rs index cea4366df1..c0704fadac 100644 --- a/virtio-spec/src/lib.rs +++ b/virtio-spec/src/lib.rs @@ -8,15 +8,15 @@ #[macro_use] mod bitflags; #[macro_use] -mod volatile; +pub mod volatile; mod features; +pub mod mmio; pub mod net; pub mod pci; pub use endian_num::{be128, be16, be32, be64, le128, le16, le32, le64}; pub use self::features::{FeatureBits, F}; -pub use self::volatile::WideVolatilePtr; pub mod fs { //! File System Device diff --git a/virtio-spec/src/mmio.rs b/virtio-spec/src/mmio.rs new file mode 100644 index 0000000000..52138f977b --- /dev/null +++ b/virtio-spec/src/mmio.rs @@ -0,0 +1,530 @@ +//! Definitions for Virtio over MMIO. + +use core::mem; + +use endian_num::{le16, le32}; +use volatile::access::{ReadOnly, ReadWrite, RestrictAccess, WriteOnly}; +use volatile::VolatilePtr; + +use crate::volatile::{OveralignedVolatilePtr, WideVolatilePtr}; +use crate::DeviceStatus; + +/// MMIO Device Registers +#[repr(transparent)] +pub struct DeviceRegisters([le32; 0x100 / mem::size_of::()]); + +macro_rules! field_fn { + ( + $(#[doc = $doc:literal])* + #[doc(alias = $alias:literal)] + #[access($Access:ty)] + $field:ident: le32, + ) => { + $(#[doc = $doc])* + #[doc(alias = $alias)] + fn $field(self) -> VolatilePtr<'a, le32, A::Restricted> + where + A: RestrictAccess<$Access>; + }; + ( + $(#[doc = $doc:literal])* + #[doc(alias = $alias:literal)] + #[access($Access:ty)] + $field:ident: $T:ty, + ) => { + $(#[doc = $doc])* + #[doc(alias = $alias)] + fn $field(self) -> OveralignedVolatilePtr<'a, $T, le32, A::Restricted> + where + A: RestrictAccess<$Access>; + }; +} + +macro_rules! field_impl { + ( + #[offset($offset:literal)] + #[access($Access:ty)] + $field:ident: le32, + ) => { + fn $field(self) -> VolatilePtr<'a, le32, A::Restricted> + where + A: RestrictAccess<$Access>, + { + unsafe { + self.map(|ptr| ptr.cast::().byte_add($offset)) + .restrict() + } + } + }; + ( + #[offset($offset:literal)] + #[access($Access:ty)] + $field:ident: $T:ty, + ) => { + fn $field(self) -> OveralignedVolatilePtr<'a, $T, le32, A::Restricted> + where + A: RestrictAccess<$Access>, + { + let ptr = unsafe { self.map(|ptr| ptr.cast::().byte_add($offset)) }; + OveralignedVolatilePtr::new(ptr.restrict()) + } + }; +} + +macro_rules! device_register_impl { + ( + $(#[doc = $outer_doc:literal])* + pub struct DeviceRegisters { + $( + $(#[doc = $doc:literal])* + #[doc(alias = $alias:literal)] + #[offset($offset:literal)] + #[access($Access:ty)] + $field:ident: $T:ident, + )* + } + ) => { + $(#[doc = $outer_doc])* + pub trait DeviceRegisterVolatileFieldAccess<'a, A> { + $( + field_fn! { + $(#[doc = $doc])* + #[doc(alias = $alias)] + #[access($Access)] + $field: $T, + } + )* + } + + impl<'a, A> DeviceRegisterVolatileFieldAccess<'a, A> for VolatilePtr<'a, DeviceRegisters, A> { + $( + field_impl! { + #[offset($offset)] + #[access($Access)] + $field: $T, + } + )* + } + }; +} + +device_register_impl! { + /// MMIO Device Registers + pub struct DeviceRegisters { + /// Magic Value + /// + /// 0x74726976 + /// (a Little Endian equivalent of the “virt” string). + #[doc(alias = "MagicValue")] + #[offset(0x000)] + #[access(ReadOnly)] + magic_value: le32, + + /// Device version number + /// + /// 0x2. + /// + ///
+ /// + /// Legacy devices (see _Virtio Transport Options / Virtio Over MMIO / Legacy interface_) used 0x1. + /// + ///
+ #[doc(alias = "Version")] + #[offset(0x004)] + #[access(ReadOnly)] + version: le32, + + /// Virtio Subsystem Device ID + /// + /// See _Device Types_ for possible values. + /// Value zero (0x0) is used to + /// define a system memory map with placeholder devices at static, + /// well known addresses, assigning functions to them depending + /// on user's needs. + #[doc(alias = "DeviceID")] + #[offset(0x008)] + #[access(ReadOnly)] + device_id: le32, + + /// Virtio Subsystem Vendor ID + #[doc(alias = "VendorID")] + #[offset(0x00c)] + #[access(ReadOnly)] + vendor_id: le32, + + /// Flags representing features the device supports + /// + /// Reading from this register returns 32 consecutive flag bits, + /// the least significant bit depending on the last value written to + /// `DeviceFeaturesSel`. Access to this register returns + /// bits `DeviceFeaturesSel`*32 to (`DeviceFeaturesSel`*32)+31, eg. + /// feature bits 0 to 31 if `DeviceFeaturesSel` is set to 0 and + /// features bits 32 to 63 if `DeviceFeaturesSel` is set to 1. + /// Also see _Basic Facilities of a Virtio Device / Feature Bits_. + #[doc(alias = "DeviceFeatures")] + #[offset(0x010)] + #[access(ReadOnly)] + device_features: le32, + + /// Device (host) features word selection. + /// + /// Writing to this register selects a set of 32 device feature bits + /// accessible by reading from `DeviceFeatures`. + #[doc(alias = "DeviceFeaturesSel")] + #[offset(0x014)] + #[access(WriteOnly)] + device_features_sel: le32, + + /// Flags representing device features understood and activated by the driver + /// + /// Writing to this register sets 32 consecutive flag bits, the least significant + /// bit depending on the last value written to `DriverFeaturesSel`. + /// Access to this register sets bits `DriverFeaturesSel`*32 + /// to (`DriverFeaturesSel`*32)+31, eg. feature bits 0 to 31 if + /// `DriverFeaturesSel` is set to 0 and features bits 32 to 63 if + /// `DriverFeaturesSel` is set to 1. Also see _Basic Facilities of a Virtio Device / Feature Bits_. + #[doc(alias = "DriverFeatures")] + #[offset(0x020)] + #[access(WriteOnly)] + driver_features: le32, + + /// Activated (guest) features word selection + /// + /// Writing to this register selects a set of 32 activated feature + /// bits accessible by writing to `DriverFeatures`. + #[doc(alias = "DriverFeaturesSel")] + #[offset(0x024)] + #[access(WriteOnly)] + driver_features_sel: le32, + + /// Virtual queue index + /// + /// Writing to this register selects the virtual queue that the + /// following operations on `QueueNumMax`, `QueueNum`, `QueueReady`, + /// `QueueDescLow`, `QueueDescHigh`, `QueueDriverlLow`, `QueueDriverHigh`, + /// `QueueDeviceLow`, `QueueDeviceHigh` and `QueueReset` apply to. The index + /// number of the first queue is zero (0x0). + #[doc(alias = "QueueSel")] + #[offset(0x030)] + #[access(WriteOnly)] + queue_sel: le16, + + /// Maximum virtual queue size + /// + /// Reading from the register returns the maximum size (number of + /// elements) of the queue the device is ready to process or + /// zero (0x0) if the queue is not available. This applies to the + /// queue selected by writing to `QueueSel`. + #[doc(alias = "QueueNumMax")] + #[offset(0x034)] + #[access(ReadOnly)] + queue_num_max: le16, + + /// Virtual queue size + /// + /// Queue size is the number of elements in the queue. + /// Writing to this register notifies the device what size of the + /// queue the driver will use. This applies to the queue selected by + /// writing to `QueueSel`. + #[doc(alias = "QueueNum")] + #[offset(0x038)] + #[access(WriteOnly)] + queue_num: le16, + + /// Virtual queue ready bit + /// + /// Writing one (0x1) to this register notifies the device that it can + /// execute requests from this virtual queue. Reading from this register + /// returns the last value written to it. Both read and write + /// accesses apply to the queue selected by writing to `QueueSel`. + #[doc(alias = "QueueReady")] + #[offset(0x044)] + #[access(ReadWrite)] + queue_ready: bool, + + /// Queue notifier + /// + /// Writing a value to this register notifies the device that + /// there are new buffers to process in a queue. + /// + /// When VIRTIO_F_NOTIFICATION_DATA has not been negotiated, + /// the value written is the queue index. + /// + /// When VIRTIO_F_NOTIFICATION_DATA has been negotiated, + /// the `Notification data` value has the following format: + /// + /// ```c + /// le32 { + /// vqn : 16; + /// next_off : 15; + /// next_wrap : 1; + /// }; + /// ``` + /// + /// See _Virtqueues / Driver notifications_ + /// for the definition of the components. + #[doc(alias = "QueueNotify")] + #[offset(0x050)] + #[access(WriteOnly)] + queue_notify: le32, + + /// Interrupt status + /// + /// Reading from this register returns a bit mask of events that + /// caused the device interrupt to be asserted. + /// The following events are possible: + /// + /// - Used Buffer Notification + /// - bit 0 - the interrupt was asserted + /// because the device has used a buffer + /// in at least one of the active virtual queues. + /// - Configuration Change Notification + /// - bit 1 - the interrupt was + /// asserted because the configuration of the device has changed. + #[doc(alias = "InterruptStatus")] + #[offset(0x060)] + #[access(ReadOnly)] + interrupt_status: le32, + + /// Interrupt acknowledge + /// + /// Writing a value with bits set as defined in `InterruptStatus` + /// to this register notifies the device that events causing + /// the interrupt have been handled. + #[doc(alias = "InterruptACK")] + #[offset(0x064)] + #[access(WriteOnly)] + interrupt_ack: le32, + + /// Device status + /// + /// Reading from this register returns the current device status + /// flags. + /// Writing non-zero values to this register sets the status flags, + /// indicating the driver progress. Writing zero (0x0) to this + /// register triggers a device reset. + /// See also p. _Virtio Transport Options / Virtio Over MMIO / MMIO-specific Initialization And Device Operation / Device Initialization_. + #[doc(alias = "Status")] + #[offset(0x070)] + #[access(ReadWrite)] + status: DeviceStatus, + + /// Virtual queue's Descriptor Area 64 bit long physical address + /// + /// Writing to these two registers (lower 32 bits of the address + /// to `QueueDescLow`, higher 32 bits to `QueueDescHigh`) notifies + /// the device about location of the Descriptor Area of the queue + /// selected by writing to `QueueSel` register. + #[doc(alias = "QueueDescLow")] + #[offset(0x080)] + #[access(WriteOnly)] + queue_desc_low: le32, + + /// Virtual queue's Descriptor Area 64 bit long physical address + /// + /// Writing to these two registers (lower 32 bits of the address + /// to `QueueDescLow`, higher 32 bits to `QueueDescHigh`) notifies + /// the device about location of the Descriptor Area of the queue + /// selected by writing to `QueueSel` register. + #[doc(alias = "QueueDescHigh")] + #[offset(0x084)] + #[access(WriteOnly)] + queue_desc_high: le32, + + /// Virtual queue's Driver Area 64 bit long physical address + /// + /// Writing to these two registers (lower 32 bits of the address + /// to `QueueDriverLow`, higher 32 bits to `QueueDriverHigh`) notifies + /// the device about location of the Driver Area of the queue + /// selected by writing to `QueueSel`. + #[doc(alias = "QueueDriverLow")] + #[offset(0x090)] + #[access(WriteOnly)] + queue_driver_low: le32, + + /// Virtual queue's Driver Area 64 bit long physical address + /// + /// Writing to these two registers (lower 32 bits of the address + /// to `QueueDriverLow`, higher 32 bits to `QueueDriverHigh`) notifies + /// the device about location of the Driver Area of the queue + /// selected by writing to `QueueSel`. + #[doc(alias = "QueueDriverHigh")] + #[offset(0x094)] + #[access(WriteOnly)] + queue_driver_high: le32, + + /// Virtual queue's Device Area 64 bit long physical address + /// + /// Writing to these two registers (lower 32 bits of the address + /// to `QueueDeviceLow`, higher 32 bits to `QueueDeviceHigh`) notifies + /// the device about location of the Device Area of the queue + /// selected by writing to `QueueSel`. + #[doc(alias = "QueueDeviceLow")] + #[offset(0x0a0)] + #[access(WriteOnly)] + queue_device_low: le32, + + /// Virtual queue's Device Area 64 bit long physical address + /// + /// Writing to these two registers (lower 32 bits of the address + /// to `QueueDeviceLow`, higher 32 bits to `QueueDeviceHigh`) notifies + /// the device about location of the Device Area of the queue + /// selected by writing to `QueueSel`. + #[doc(alias = "QueueDeviceHigh")] + #[offset(0x0a4)] + #[access(WriteOnly)] + queue_device_high: le32, + + /// Shared memory id + /// + /// Writing to this register selects the shared memory region _Basic Facilities of a Virtio Device / Shared Memory Regions_ + /// following operations on `SHMLenLow`, `SHMLenHigh`, + /// `SHMBaseLow` and `SHMBaseHigh` apply to. + #[doc(alias = "SHMSel")] + #[offset(0x0ac)] + #[access(WriteOnly)] + shm_sel: le32, + + /// Shared memory region 64 bit long length + /// + /// These registers return the length of the shared memory + /// region in bytes, as defined by the device for the region selected by + /// the `SHMSel` register. The lower 32 bits of the length + /// are read from `SHMLenLow` and the higher 32 bits from + /// `SHMLenHigh`. Reading from a non-existent + /// region (i.e. where the ID written to `SHMSel` is unused) + /// results in a length of -1. + #[doc(alias = "SHMLenLow")] + #[offset(0x0b0)] + #[access(ReadOnly)] + shm_len_low: le32, + + /// Shared memory region 64 bit long length + /// + /// These registers return the length of the shared memory + /// region in bytes, as defined by the device for the region selected by + /// the `SHMSel` register. The lower 32 bits of the length + /// are read from `SHMLenLow` and the higher 32 bits from + /// `SHMLenHigh`. Reading from a non-existent + /// region (i.e. where the ID written to `SHMSel` is unused) + /// results in a length of -1. + #[doc(alias = "SHMLenHigh")] + #[offset(0x0b4)] + #[access(ReadOnly)] + shm_len_high: le32, + + /// Shared memory region 64 bit long physical address + /// + /// The driver reads these registers to discover the base address + /// of the region in physical address space. This address is + /// chosen by the device (or other part of the VMM). + /// The lower 32 bits of the address are read from `SHMBaseLow` + /// with the higher 32 bits from `SHMBaseHigh`. Reading + /// from a non-existent region (i.e. where the ID written to + /// `SHMSel` is unused) results in a base address of + /// 0xffffffffffffffff. + #[doc(alias = "SHMBaseLow")] + #[offset(0x0b8)] + #[access(ReadOnly)] + shm_base_low: le32, + + /// Shared memory region 64 bit long physical address + /// + /// The driver reads these registers to discover the base address + /// of the region in physical address space. This address is + /// chosen by the device (or other part of the VMM). + /// The lower 32 bits of the address are read from `SHMBaseLow` + /// with the higher 32 bits from `SHMBaseHigh`. Reading + /// from a non-existent region (i.e. where the ID written to + /// `SHMSel` is unused) results in a base address of + /// 0xffffffffffffffff. + #[doc(alias = "SHMBaseHigh")] + #[offset(0x0bc)] + #[access(ReadOnly)] + shm_base_high: le32, + + /// Virtual queue reset bit + /// + /// If VIRTIO_F_RING_RESET has been negotiated, writing one (0x1) to this + /// register selectively resets the queue. Both read and write accesses + /// apply to the queue selected by writing to `QueueSel`. + #[doc(alias = "QueueReset")] + #[offset(0x0c0)] + #[access(ReadWrite)] + queue_reset: le32, + + /// Configuration atomicity value + /// + /// Reading from this register returns a value describing a version of the device-specific configuration space (see `Config`). + /// The driver can then access the configuration space and, when finished, read `ConfigGeneration` again. + /// If no part of the configuration space has changed between these two `ConfigGeneration` reads, the returned values are identical. + /// If the values are different, the configuration space accesses were not atomic and the driver has to perform the operations again. + /// See also _Basic Facilities of a Virtio Device / Device Configuration Space_. + #[doc(alias = "ConfigGeneration")] + #[offset(0x0fc)] + #[access(ReadOnly)] + config_generation: le32, + } +} + +impl_wide_field_access! { + /// MMIO Device Registers + pub trait DeviceRegisterVolatileWideFieldAccess<'a, A>: DeviceRegisters { + /// Virtual queue's Descriptor Area 64 bit long physical address + /// + /// Writing to these two registers (lower 32 bits of the address + /// to `QueueDescLow`, higher 32 bits to `QueueDescHigh`) notifies + /// the device about location of the Descriptor Area of the queue + /// selected by writing to `QueueSel` register. + #[doc(alias = "QueueDesc")] + #[access(WriteOnly)] + queue_desc: queue_desc_low, queue_desc_high; + + /// Virtual queue's Driver Area 64 bit long physical address + /// + /// Writing to these two registers (lower 32 bits of the address + /// to `QueueDriverLow`, higher 32 bits to `QueueDriverHigh`) notifies + /// the device about location of the Driver Area of the queue + /// selected by writing to `QueueSel`. + #[doc(alias = "QueueDriver")] + #[access(WriteOnly)] + queue_driver: queue_driver_low, queue_driver_high; + + /// Virtual queue's Device Area 64 bit long physical address + /// + /// Writing to these two registers (lower 32 bits of the address + /// to `QueueDeviceLow`, higher 32 bits to `QueueDeviceHigh`) notifies + /// the device about location of the Device Area of the queue + /// selected by writing to `QueueSel`. + #[doc(alias = "QueueDevice")] + #[access(WriteOnly)] + queue_device: queue_device_low, queue_device_high; + + /// Shared memory region 64 bit long length + /// + /// These registers return the length of the shared memory + /// region in bytes, as defined by the device for the region selected by + /// the `SHMSel` register. The lower 32 bits of the length + /// are read from `SHMLenLow` and the higher 32 bits from + /// `SHMLenHigh`. Reading from a non-existent + /// region (i.e. where the ID written to `SHMSel` is unused) + /// results in a length of -1. + #[doc(alias = "SHMLen")] + #[access(ReadOnly)] + shm_len: shm_len_low, shm_len_high; + + /// Shared memory region 64 bit long physical address + /// + /// The driver reads these registers to discover the base address + /// of the region in physical address space. This address is + /// chosen by the device (or other part of the VMM). + /// The lower 32 bits of the address are read from `SHMBaseLow` + /// with the higher 32 bits from `SHMBaseHigh`. Reading + /// from a non-existent region (i.e. where the ID written to + /// `SHMSel` is unused) results in a base address of + /// 0xffffffffffffffff. + #[doc(alias = "SHMBase")] + #[access(ReadOnly)] + shm_base: shm_base_low, shm_base_high; + } +} diff --git a/virtio-spec/src/pci.rs b/virtio-spec/src/pci.rs index f73d67ab23..f361bee7ff 100644 --- a/virtio-spec/src/pci.rs +++ b/virtio-spec/src/pci.rs @@ -3,7 +3,8 @@ use volatile::access::{ReadOnly, ReadWrite, RestrictAccess}; use volatile::{VolatileFieldAccess, VolatilePtr}; -use crate::{le16, le32, DeviceStatus, WideVolatilePtr}; +use crate::volatile::WideVolatilePtr; +use crate::{le16, le32, DeviceStatus}; /// Common configuration structure /// @@ -127,12 +128,15 @@ impl_wide_field_access! { /// Common configuration structure pub trait CommonCfgVolatileWideFieldAccess<'a, A>: CommonCfg { /// The driver writes the physical address of Device Area here. See section _Basic Facilities of a Virtio Device / Virtqueues_. + #[access(ReadWrite)] queue_desc: queue_desc_low, queue_desc_high; /// The driver writes the physical address of Device Area here. See section _Basic Facilities of a Virtio Device / Virtqueues_. + #[access(ReadWrite)] queue_driver: queue_driver_low, queue_driver_high; /// The driver writes the physical address of Device Area here. See section _Basic Facilities of a Virtio Device / Virtqueues_. + #[access(ReadWrite)] queue_device: queue_device_low, queue_device_high; } } diff --git a/virtio-spec/src/volatile.rs b/virtio-spec/src/volatile.rs index 025aeda6b7..d5d9757d32 100644 --- a/virtio-spec/src/volatile.rs +++ b/virtio-spec/src/volatile.rs @@ -1,19 +1,35 @@ //! Volatile Pointer Types. +use core::marker::PhantomData; + use volatile::access::{Readable, Writable}; use volatile::VolatilePtr; -use crate::{be32, be64, le32, le64}; +use crate::{be32, be64, le16, le32, le64, DeviceStatus}; /// A wide volatile pointer for 64-bit fields. /// /// In virtio, 64-bit fields are to be treated as two 32-bit fields, with low 32 bit part followed by the high 32 bit part. /// This type mimics [`VolatilePtr`], and allows easy access to 64-bit fields. -pub struct WideVolatilePtr<'a, T, A> { +pub struct WideVolatilePtr<'a, T, A> +where + T: ?Sized, +{ low: VolatilePtr<'a, T, A>, high: VolatilePtr<'a, T, A>, } +impl<'a, T, A> Copy for WideVolatilePtr<'a, T, A> where T: ?Sized {} + +impl<'a, T, A> Clone for WideVolatilePtr<'a, T, A> +where + T: ?Sized, +{ + fn clone(&self) -> Self { + *self + } +} + impl<'a, T, A> WideVolatilePtr<'a, T, A> { /// Creates a new wide volatile pointer from pointers to the low and to the high part. pub fn from_low_high(low: VolatilePtr<'a, T, A>, high: VolatilePtr<'a, T, A>) -> Self { @@ -45,6 +61,17 @@ impl<'a, A> WideVolatilePtr<'a, le32, A> { self.low.write(low); self.high.write(high); } + + /// Updates the contained value using the given closure and volatile instructions. + /// + /// See [`VolatilePtr::update`]. + pub fn update(self, f: impl FnOnce(le64) -> le64) + where + A: Readable + Writable, + { + let new = f(self.read()); + self.write(new); + } } impl<'a, A> WideVolatilePtr<'a, be32, A> { @@ -71,6 +98,17 @@ impl<'a, A> WideVolatilePtr<'a, be32, A> { self.low.write(low); self.high.write(high); } + + /// Updates the contained value using the given closure and volatile instructions. + /// + /// See [`VolatilePtr::update`]. + pub fn update(self, f: impl FnOnce(be64) -> be64) + where + A: Readable + Writable, + { + let new = f(self.read()); + self.write(new); + } } macro_rules! impl_wide_field_access { @@ -78,7 +116,9 @@ macro_rules! impl_wide_field_access { $(#[$outer:meta])* $vis:vis trait $Trait:ident<'a, A>: $T:ty { $( - $(#[$inner:meta])* + $(#[doc = $doc:literal])* + $(#[doc(alias = $alias:literal)])? + #[access($Access:ty)] $field:ident: $field_low:ident, $field_high:ident; )* } @@ -86,10 +126,11 @@ macro_rules! impl_wide_field_access { $(#[$outer])* $vis trait $Trait<'a, A> { $( - $(#[$inner])* + $(#[doc = $doc])* + $(#[doc(alias = $alias)])? fn $field(self) -> WideVolatilePtr<'a, le32, A::Restricted> where - A: RestrictAccess; + A: RestrictAccess<$Access>; )* } @@ -97,7 +138,7 @@ macro_rules! impl_wide_field_access { $( fn $field(self) -> WideVolatilePtr<'a, le32, A::Restricted> where - A: RestrictAccess, + A: RestrictAccess<$Access>, { WideVolatilePtr::from_low_high(self.$field_low(), self.$field_high()) } @@ -105,3 +146,139 @@ macro_rules! impl_wide_field_access { } }; } + +/// An overaligned volatile pointer for fields that require wider access operations. +/// +/// In virtio, some fields require wider access operations than their type indicate, such as for [`mmio::DeviceRegisters`]. +/// +/// [`mmio::DeviceRegisters`]: crate::mmio::DeviceRegisters +pub struct OveralignedVolatilePtr<'a, T, F, A> +where + T: ?Sized, + F: ?Sized, +{ + ptr: VolatilePtr<'a, F, A>, + ty: PhantomData>, +} + +impl<'a, T, F, A> Copy for OveralignedVolatilePtr<'a, T, F, A> +where + T: ?Sized, + F: ?Sized, +{ +} + +impl<'a, T, F, A> Clone for OveralignedVolatilePtr<'a, T, F, A> +where + T: ?Sized, + F: ?Sized, +{ + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T, F, A> OveralignedVolatilePtr<'a, T, F, A> +where + T: OveralignedField, + F: Copy, +{ + /// Creates a new overaligned volatile pointer. + pub fn new(ptr: VolatilePtr<'a, F, A>) -> Self { + Self { + ptr, + ty: PhantomData, + } + } + + /// Performs a volatile read of the contained value. + /// + /// See [`VolatilePtr::read`]. + pub fn read(self) -> T + where + A: Readable, + { + T::from_field(self.ptr.read()) + } + + /// Performs a volatile write, setting the contained value to the given `value`. + /// + /// See [`VolatilePtr::write`]. + pub fn write(self, value: T) + where + A: Writable, + { + self.ptr.write(value.into_field()) + } + + /// Updates the contained value using the given closure and volatile instructions. + /// + /// See [`VolatilePtr::update`]. + pub fn update(self, f: impl FnOnce(T) -> T) + where + A: Readable + Writable, + { + let new = f(self.read()); + self.write(new); + } +} + +/// A trait for fields that can be accessed via [`OveralignedVolatilePtr`]. +pub trait OveralignedField: private::Sealed { + /// Converts to this type from the overaligned field. + fn from_field(field: F) -> Self; + + /// Converts this type into the overaligned field. + fn into_field(self) -> F; +} + +impl OveralignedField for le16 { + fn from_field(field: le32) -> Self { + field.try_into().unwrap() + } + + fn into_field(self) -> le32 { + self.into() + } +} + +impl OveralignedField for bool { + fn from_field(field: le32) -> Self { + field.to_ne() == 1 + } + + fn into_field(self) -> le32 { + le32::from_ne(self as u32) + } +} + +impl OveralignedField for u8 { + fn from_field(field: le32) -> Self { + field.to_ne().try_into().unwrap() + } + + fn into_field(self) -> le32 { + le32::from_ne(self.into()) + } +} + +impl OveralignedField for DeviceStatus { + fn from_field(field: le32) -> Self { + Self::from_bits_retain(u8::from_field(field)) + } + + fn into_field(self) -> le32 { + self.bits().into_field() + } +} + +mod private { + use crate::{le16, le32, DeviceStatus}; + + pub trait Sealed {} + + impl Sealed for bool {} + impl Sealed for u8 {} + impl Sealed for le16 {} + impl Sealed for DeviceStatus {} +}