Browse Source

Merge pull request #14 from qwandor/transport

Factor out trait for transport, and add support for modern MMIO transport
Yiren Zhang 2 years ago
parent
commit
95f35b0015
13 changed files with 723 additions and 470 deletions
  1. 15 1
      examples/riscv/Makefile
  2. 32 20
      examples/riscv/src/main.rs
  3. 16 15
      src/blk.rs
  4. 18 12
      src/console.rs
  5. 15 13
      src/gpu.rs
  6. 0 5
      src/hal.rs
  7. 0 357
      src/header.rs
  8. 13 11
      src/input.rs
  9. 3 2
      src/lib.rs
  10. 15 13
      src/net.rs
  11. 33 21
      src/queue.rs
  12. 435 0
      src/transport/mmio.rs
  13. 128 0
      src/transport/mod.rs

+ 15 - 1
examples/riscv/Makefile

@@ -36,12 +36,26 @@ header:
 clean:
 	cargo clean
 
+qemu-legacy: kernel $(img)
+	qemu-system-$(arch) \
+		-machine virt \
+		-serial mon:stdio \
+		-bios default \
+		-kernel $(kernel) \
+		-drive file=$(img),if=none,format=raw,id=x0 \
+		-device virtio-blk-device,drive=x0 \
+		-device virtio-gpu-device \
+		-device virtio-mouse-device \
+		# -netdev type=tap,id=net0,script=no,downscript=no \
+		# -device virtio-net-device,netdev=net0
+
 qemu: kernel $(img)
 	qemu-system-$(arch) \
 		-machine virt \
 		-serial mon:stdio \
 		-bios default \
 		-kernel $(kernel) \
+		-global virtio-mmio.force-legacy=false \
 		-drive file=$(img),if=none,format=raw,id=x0 \
 		-device virtio-blk-device,drive=x0 \
 		-device virtio-gpu-device \
@@ -52,4 +66,4 @@ qemu: kernel $(img)
 $(img):
 	dd if=/dev/zero of=$@ bs=512 count=32
 
-run: build qemu
+run: build qemu-legacy qemu

+ 32 - 20
examples/riscv/src/main.rs

@@ -6,6 +6,7 @@ extern crate alloc;
 extern crate opensbi_rt;
 
 use alloc::vec;
+use core::ptr::NonNull;
 use device_tree::util::SliceRead;
 use device_tree::{DeviceTree, Node};
 use log::{info, warn, LevelFilter};
@@ -55,25 +56,35 @@ fn virtio_probe(node: &Node) {
         let size = reg.as_slice().read_be_u64(8).unwrap();
         let vaddr = paddr;
         info!("walk dt addr={:#x}, size={:#x}", paddr, size);
-        let header = unsafe { &mut *(vaddr as *mut VirtIOHeader) };
-        info!(
-            "Detected virtio device with vendor id {:#X}, device type {:?}",
-            header.vendor_id(),
-            header.device_type(),
-        );
         info!("Device tree node {:?}", node);
-        match header.device_type() {
-            DeviceType::Block => virtio_blk(header),
-            DeviceType::GPU => virtio_gpu(header),
-            DeviceType::Input => virtio_input(header),
-            DeviceType::Network => virtio_net(header),
-            t => warn!("Unrecognized virtio device: {:?}", t),
+        let header = NonNull::new(vaddr as *mut VirtIOHeader).unwrap();
+        match unsafe { MmioTransport::new(header) } {
+            Err(e) => warn!("Error creating VirtIO MMIO transport: {}", e),
+            Ok(transport) => {
+                info!(
+                    "Detected virtio MMIO device with vendor id {:#X}, device type {:?}, version {:?}",
+                    transport.vendor_id(),
+                    transport.device_type(),
+                    transport.version(),
+                );
+                virtio_device(transport);
+            }
         }
     }
 }
 
-fn virtio_blk(header: &'static mut VirtIOHeader) {
-    let mut blk = VirtIOBlk::<HalImpl>::new(header).expect("failed to create blk driver");
+fn virtio_device(transport: impl Transport) {
+    match transport.device_type() {
+        DeviceType::Block => virtio_blk(transport),
+        DeviceType::GPU => virtio_gpu(transport),
+        DeviceType::Input => virtio_input(transport),
+        DeviceType::Network => virtio_net(transport),
+        t => warn!("Unrecognized virtio device: {:?}", t),
+    }
+}
+
+fn virtio_blk<T: Transport>(transport: T) {
+    let mut blk = VirtIOBlk::<HalImpl, T>::new(transport).expect("failed to create blk driver");
     let mut input = vec![0xffu8; 512];
     let mut output = vec![0; 512];
     for i in 0..32 {
@@ -87,8 +98,8 @@ fn virtio_blk(header: &'static mut VirtIOHeader) {
     info!("virtio-blk test finished");
 }
 
-fn virtio_gpu(header: &'static mut VirtIOHeader) {
-    let mut gpu = VirtIOGpu::<HalImpl>::new(header).expect("failed to create gpu driver");
+fn virtio_gpu<T: Transport>(transport: T) {
+    let mut gpu = VirtIOGpu::<HalImpl, T>::new(transport).expect("failed to create gpu driver");
     let fb = gpu.setup_framebuffer().expect("failed to get fb");
     for y in 0..768 {
         for x in 0..1024 {
@@ -102,9 +113,10 @@ fn virtio_gpu(header: &'static mut VirtIOHeader) {
     info!("virtio-gpu test finished");
 }
 
-fn virtio_input(header: &'static mut VirtIOHeader) {
+fn virtio_input<T: Transport>(transport: T) {
     //let mut event_buf = [0u64; 32];
-    let mut _input = VirtIOInput::<HalImpl>::new(header).expect("failed to create input driver");
+    let mut _input =
+        VirtIOInput::<HalImpl, T>::new(transport).expect("failed to create input driver");
     // loop {
     //     input.ack_interrupt().expect("failed to ack");
     //     info!("mouse: {:?}", input.mouse_xy());
@@ -112,8 +124,8 @@ fn virtio_input(header: &'static mut VirtIOHeader) {
     // TODO: handle external interrupt
 }
 
-fn virtio_net(header: &'static mut VirtIOHeader) {
-    let mut net = VirtIONet::<HalImpl>::new(header).expect("failed to create net driver");
+fn virtio_net<T: Transport>(transport: T) {
+    let mut net = VirtIONet::<HalImpl, T>::new(transport).expect("failed to create net driver");
     let mut buf = [0u8; 0x100];
     let len = net.recv(&mut buf).expect("failed to recv");
     info!("recv: {:?}", &buf[..len]);

+ 16 - 15
src/blk.rs

@@ -1,6 +1,6 @@
 use super::*;
-use crate::header::VirtIOHeader;
 use crate::queue::VirtQueue;
+use crate::transport::Transport;
 use bitflags::*;
 use core::hint::spin_loop;
 use log::*;
@@ -10,16 +10,16 @@ use volatile::Volatile;
 ///
 /// Read and write requests (and other exotic requests) are placed in the queue,
 /// and serviced (probably out of order) by the device except where noted.
-pub struct VirtIOBlk<'a, H: Hal> {
-    header: &'static mut VirtIOHeader,
+pub struct VirtIOBlk<'a, H: Hal, T: Transport> {
+    transport: T,
     queue: VirtQueue<'a, H>,
     capacity: usize,
 }
 
-impl<H: Hal> VirtIOBlk<'_, H> {
+impl<H: Hal, T: Transport> VirtIOBlk<'_, H, T> {
     /// Create a new VirtIO-Blk driver.
-    pub fn new(header: &'static mut VirtIOHeader) -> Result<Self> {
-        header.begin_init(|features| {
+    pub fn new(mut transport: T) -> Result<Self> {
+        transport.begin_init(|features| {
             let features = BlkFeature::from_bits_truncate(features);
             info!("device features: {:?}", features);
             // negotiate these flags only
@@ -28,18 +28,19 @@ impl<H: Hal> VirtIOBlk<'_, H> {
         });
 
         // read configuration space
-        let config = unsafe { &mut *(header.config_space() as *mut BlkConfig) };
+        let config_space = transport.config_space().cast::<BlkConfig>();
+        let config = unsafe { config_space.as_ref() };
         info!("config: {:?}", config);
         info!(
             "found a block device of size {}KB",
             config.capacity.read() / 2
         );
 
-        let queue = VirtQueue::new(header, 0, 16)?;
-        header.finish_init();
+        let queue = VirtQueue::new(&mut transport, 0, 16)?;
+        transport.finish_init();
 
         Ok(VirtIOBlk {
-            header,
+            transport,
             queue,
             capacity: config.capacity.read() as usize,
         })
@@ -47,7 +48,7 @@ impl<H: Hal> VirtIOBlk<'_, H> {
 
     /// Acknowledge interrupt.
     pub fn ack_interrupt(&mut self) -> bool {
-        self.header.ack_interrupt()
+        self.transport.ack_interrupt()
     }
 
     /// Read a block.
@@ -60,7 +61,7 @@ impl<H: Hal> VirtIOBlk<'_, H> {
         };
         let mut resp = BlkResp::default();
         self.queue.add(&[req.as_buf()], &[buf, resp.as_buf_mut()])?;
-        self.header.notify(0);
+        self.transport.notify(0);
         while !self.queue.can_pop() {
             spin_loop();
         }
@@ -112,7 +113,7 @@ impl<H: Hal> VirtIOBlk<'_, H> {
             sector: block_id as u64,
         };
         let token = self.queue.add(&[req.as_buf()], &[buf, resp.as_buf_mut()])?;
-        self.header.notify(0);
+        self.transport.notify(0);
         Ok(token)
     }
 
@@ -126,7 +127,7 @@ impl<H: Hal> VirtIOBlk<'_, H> {
         };
         let mut resp = BlkResp::default();
         self.queue.add(&[req.as_buf(), buf], &[resp.as_buf_mut()])?;
-        self.header.notify(0);
+        self.transport.notify(0);
         while !self.queue.can_pop() {
             spin_loop();
         }
@@ -167,7 +168,7 @@ impl<H: Hal> VirtIOBlk<'_, H> {
             sector: block_id as u64,
         };
         let token = self.queue.add(&[req.as_buf(), buf], &[resp.as_buf_mut()])?;
-        self.header.notify(0);
+        self.transport.notify(0);
         Ok(token)
     }
 

+ 18 - 12
src/console.rs

@@ -1,5 +1,6 @@
 use super::*;
 use crate::queue::VirtQueue;
+use crate::transport::Transport;
 use bitflags::*;
 use core::{fmt, hint::spin_loop};
 use log::*;
@@ -7,11 +8,12 @@ use volatile::{ReadOnly, WriteOnly};
 
 const QUEUE_RECEIVEQ_PORT_0: usize = 0;
 const QUEUE_TRANSMITQ_PORT_0: usize = 1;
+const QUEUE_SIZE: u16 = 2;
 
 /// Virtio console. Only one single port is allowed since ``alloc'' is disabled.
 /// Emergency and cols/rows unimplemented.
-pub struct VirtIOConsole<'a, H: Hal> {
-    header: &'static mut VirtIOHeader,
+pub struct VirtIOConsole<'a, H: Hal, T: Transport> {
+    transport: T,
     receiveq: VirtQueue<'a, H>,
     transmitq: VirtQueue<'a, H>,
     queue_buf_dma: DMA<H>,
@@ -20,24 +22,25 @@ pub struct VirtIOConsole<'a, H: Hal> {
     pending_len: usize,
 }
 
-impl<H: Hal> VirtIOConsole<'_, H> {
+impl<H: Hal, T: Transport> VirtIOConsole<'_, H, T> {
     /// Create a new VirtIO-Console driver.
-    pub fn new(header: &'static mut VirtIOHeader) -> Result<Self> {
-        header.begin_init(|features| {
+    pub fn new(mut transport: T) -> Result<Self> {
+        transport.begin_init(|features| {
             let features = Features::from_bits_truncate(features);
             info!("Device features {:?}", features);
             let supported_features = Features::empty();
             (features & supported_features).bits()
         });
-        let config = unsafe { &mut *(header.config_space() as *mut Config) };
+        let config_space = transport.config_space().cast::<Config>();
+        let config = unsafe { config_space.as_ref() };
         info!("Config: {:?}", config);
-        let receiveq = VirtQueue::new(header, QUEUE_RECEIVEQ_PORT_0, 2)?;
-        let transmitq = VirtQueue::new(header, QUEUE_TRANSMITQ_PORT_0, 2)?;
+        let receiveq = VirtQueue::new(&mut transport, QUEUE_RECEIVEQ_PORT_0, QUEUE_SIZE)?;
+        let transmitq = VirtQueue::new(&mut transport, QUEUE_TRANSMITQ_PORT_0, QUEUE_SIZE)?;
         let queue_buf_dma = DMA::new(1)?;
         let queue_buf_rx = unsafe { &mut queue_buf_dma.as_buf()[0..] };
-        header.finish_init();
+        transport.finish_init();
         let mut console = VirtIOConsole {
-            header,
+            transport,
             receiveq,
             transmitq,
             queue_buf_dma,
@@ -48,13 +51,15 @@ impl<H: Hal> VirtIOConsole<'_, H> {
         console.poll_retrieve()?;
         Ok(console)
     }
+
     fn poll_retrieve(&mut self) -> Result<()> {
         self.receiveq.add(&[], &[self.queue_buf_rx])?;
         Ok(())
     }
+
     /// Acknowledge interrupt.
     pub fn ack_interrupt(&mut self) -> Result<bool> {
-        let ack = self.header.ack_interrupt();
+        let ack = self.transport.ack_interrupt();
         if !ack {
             return Ok(false);
         }
@@ -83,11 +88,12 @@ impl<H: Hal> VirtIOConsole<'_, H> {
         }
         Ok(Some(ch))
     }
+
     /// Put a char onto the device.
     pub fn send(&mut self, chr: u8) -> Result<()> {
         let buf: [u8; 1] = [chr];
         self.transmitq.add(&[&buf], &[])?;
-        self.header.notify(QUEUE_TRANSMITQ_PORT_0 as u32);
+        self.transport.notify(QUEUE_TRANSMITQ_PORT_0 as u32);
         while !self.transmitq.can_pop() {
             spin_loop();
         }

+ 15 - 13
src/gpu.rs

@@ -1,5 +1,6 @@
 use super::*;
 use crate::queue::VirtQueue;
+use crate::transport::Transport;
 use bitflags::*;
 use core::{fmt, hint::spin_loop};
 use log::*;
@@ -12,8 +13,8 @@ use volatile::{ReadOnly, Volatile, WriteOnly};
 /// a gpu with 3D support on the host machine.
 /// In 2D mode the virtio-gpu device provides support for ARGB Hardware cursors
 /// and multiple scanouts (aka heads).
-pub struct VirtIOGpu<'a, H: Hal> {
-    header: &'static mut VirtIOHeader,
+pub struct VirtIOGpu<'a, H: Hal, T: Transport> {
+    transport: T,
     rect: Rect,
     /// DMA area of frame buffer.
     frame_buffer_dma: Option<DMA<H>>,
@@ -31,10 +32,10 @@ pub struct VirtIOGpu<'a, H: Hal> {
     queue_buf_recv: &'a mut [u8],
 }
 
-impl<H: Hal> VirtIOGpu<'_, H> {
+impl<H: Hal, T: Transport> VirtIOGpu<'_, H, T> {
     /// Create a new VirtIO-Gpu driver.
-    pub fn new(header: &'static mut VirtIOHeader) -> Result<Self> {
-        header.begin_init(|features| {
+    pub fn new(mut transport: T) -> Result<Self> {
+        transport.begin_init(|features| {
             let features = Features::from_bits_truncate(features);
             info!("Device features {:?}", features);
             let supported_features = Features::empty();
@@ -42,20 +43,21 @@ impl<H: Hal> VirtIOGpu<'_, H> {
         });
 
         // read configuration space
-        let config = unsafe { &mut *(header.config_space() as *mut Config) };
+        let config_space = transport.config_space().cast::<Config>();
+        let config = unsafe { config_space.as_ref() };
         info!("Config: {:?}", config);
 
-        let control_queue = VirtQueue::new(header, QUEUE_TRANSMIT, 2)?;
-        let cursor_queue = VirtQueue::new(header, QUEUE_CURSOR, 2)?;
+        let control_queue = VirtQueue::new(&mut transport, QUEUE_TRANSMIT, 2)?;
+        let cursor_queue = VirtQueue::new(&mut transport, QUEUE_CURSOR, 2)?;
 
         let queue_buf_dma = DMA::new(2)?;
         let queue_buf_send = unsafe { &mut queue_buf_dma.as_buf()[..PAGE_SIZE] };
         let queue_buf_recv = unsafe { &mut queue_buf_dma.as_buf()[PAGE_SIZE..] };
 
-        header.finish_init();
+        transport.finish_init();
 
         Ok(VirtIOGpu {
-            header,
+            transport,
             frame_buffer_dma: None,
             cursor_buffer_dma: None,
             rect: Rect::default(),
@@ -69,7 +71,7 @@ impl<H: Hal> VirtIOGpu<'_, H> {
 
     /// Acknowledge interrupt.
     pub fn ack_interrupt(&mut self) -> bool {
-        self.header.ack_interrupt()
+        self.transport.ack_interrupt()
     }
 
     /// Get the resolution (width, height).
@@ -161,7 +163,7 @@ impl<H: Hal> VirtIOGpu<'_, H> {
         }
         self.control_queue
             .add(&[self.queue_buf_send], &[self.queue_buf_recv])?;
-        self.header.notify(QUEUE_TRANSMIT as u32);
+        self.transport.notify(QUEUE_TRANSMIT as u32);
         while !self.control_queue.can_pop() {
             spin_loop();
         }
@@ -175,7 +177,7 @@ impl<H: Hal> VirtIOGpu<'_, H> {
             (self.queue_buf_send.as_mut_ptr() as *mut Req).write(req);
         }
         self.cursor_queue.add(&[self.queue_buf_send], &[])?;
-        self.header.notify(QUEUE_CURSOR as u32);
+        self.transport.notify(QUEUE_CURSOR as u32);
         while !self.cursor_queue.can_pop() {
             spin_loop();
         }

+ 0 - 5
src/hal.rs

@@ -39,11 +39,6 @@ impl<H: Hal> DMA<H> {
         H::phys_to_virt(self.paddr)
     }
 
-    /// Returns the physical page frame number.
-    pub fn pfn(&self) -> u32 {
-        (self.paddr >> 12) as u32
-    }
-
     /// Convert to a buffer
     pub unsafe fn as_buf(&self) -> &'static mut [u8] {
         core::slice::from_raw_parts_mut(self.vaddr() as _, PAGE_SIZE * self.pages as usize)

+ 0 - 357
src/header.rs

@@ -1,357 +0,0 @@
-use crate::PAGE_SIZE;
-use bitflags::*;
-use volatile::{ReadOnly, Volatile, WriteOnly};
-
-const MAGIC_VALUE: u32 = 0x7472_6976;
-
-/// MMIO Device Legacy Register Interface.
-///
-/// Ref: 4.2.4 Legacy interface
-#[repr(C)]
-pub struct VirtIOHeader {
-    /// Magic value
-    magic: ReadOnly<u32>,
-
-    /// Device version number
-    ///
-    /// Legacy device returns value 0x1.
-    version: ReadOnly<u32>,
-
-    /// Virtio Subsystem Device ID
-    device_id: ReadOnly<u32>,
-
-    /// Virtio Subsystem Vendor ID
-    vendor_id: ReadOnly<u32>,
-
-    /// Flags representing features the device supports
-    device_features: ReadOnly<u32>,
-
-    /// Device (host) features word selection
-    device_features_sel: WriteOnly<u32>,
-
-    /// Reserved
-    __r1: [ReadOnly<u32>; 2],
-
-    /// Flags representing device features understood and activated by the driver
-    driver_features: WriteOnly<u32>,
-
-    /// Activated (guest) features word selection
-    driver_features_sel: WriteOnly<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 QueuePFN).
-    guest_page_size: WriteOnly<u32>,
-
-    /// Reserved
-    __r2: ReadOnly<u32>,
-
-    /// Virtual queue index
-    ///
-    /// Writing to this register selects the virtual queue that the following
-    /// operations on the QueueNumMax, QueueNum, QueueAlign and QueuePFN
-    /// registers apply to. The index number of the first queue is zero (0x0).
-    queue_sel: WriteOnly<u32>,
-
-    /// Maximum virtual queue size
-    ///
-    /// Reading from the register returns the maximum size 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 and is
-    /// allowed only when QueuePFN is set to zero (0x0), so when the queue is
-    /// not actively used.
-    queue_num_max: ReadOnly<u32>,
-
-    /// 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.
-    queue_num: WriteOnly<u32>,
-
-    /// Used Ring alignment in the virtual queue
-    ///
-    /// Writing to this register notifies the device about alignment boundary
-    /// of the Used Ring in bytes. This value should be a power of 2 and
-    /// applies to the queue selected by writing to QueueSel.
-    queue_align: WriteOnly<u32>,
-
-    /// Guest physical page number of the virtual queue
-    ///
-    /// Writing to this register notifies the device about location of the
-    /// virtual queue in the Guest’s physical address space. This value is
-    /// the index number of a page starting with the queue Descriptor Table.
-    /// Value zero (0x0) means physical address zero (0x00000000) and is illegal.
-    /// When the driver stops using the queue it writes zero (0x0) to this
-    /// register. Reading from this register returns the currently used page
-    /// number of the queue, therefore a value other than zero (0x0) means that
-    /// the queue is in use. Both read and write accesses apply to the queue
-    /// selected by writing to QueueSel.
-    queue_pfn: Volatile<u32>,
-
-    /// new interface only
-    queue_ready: Volatile<u32>,
-
-    /// Reserved
-    __r3: [ReadOnly<u32>; 2],
-
-    /// Queue notifier
-    queue_notify: WriteOnly<u32>,
-
-    /// Reserved
-    __r4: [ReadOnly<u32>; 3],
-
-    /// Interrupt status
-    interrupt_status: ReadOnly<u32>,
-
-    /// Interrupt acknowledge
-    interrupt_ack: WriteOnly<u32>,
-
-    /// Reserved
-    __r5: [ReadOnly<u32>; 2],
-
-    /// 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 OS/driver progress. Writing zero (0x0) to this register
-    /// triggers a device reset. The device sets QueuePFN to zero (0x0) for
-    /// all queues in the device. Also see 3.1 Device Initialization.
-    status: Volatile<DeviceStatus>,
-
-    /// Reserved
-    __r6: [ReadOnly<u32>; 3],
-
-    // new interface only since here
-    queue_desc_low: WriteOnly<u32>,
-    queue_desc_high: WriteOnly<u32>,
-
-    /// Reserved
-    __r7: [ReadOnly<u32>; 2],
-
-    queue_avail_low: WriteOnly<u32>,
-    queue_avail_high: WriteOnly<u32>,
-
-    /// Reserved
-    __r8: [ReadOnly<u32>; 2],
-
-    queue_used_low: WriteOnly<u32>,
-    queue_used_high: WriteOnly<u32>,
-
-    /// Reserved
-    __r9: [ReadOnly<u32>; 21],
-
-    config_generation: ReadOnly<u32>,
-}
-
-impl VirtIOHeader {
-    /// Verify a valid header.
-    pub fn verify(&self) -> bool {
-        self.magic.read() == MAGIC_VALUE && self.version.read() == 1 && self.device_id.read() != 0
-    }
-
-    /// Get the device type.
-    pub fn device_type(&self) -> DeviceType {
-        match self.device_id.read() {
-            x @ 1..=13 | x @ 16..=24 => unsafe { core::mem::transmute(x as u8) },
-            _ => DeviceType::Invalid,
-        }
-    }
-
-    /// Get the vendor ID.
-    pub fn vendor_id(&self) -> u32 {
-        self.vendor_id.read()
-    }
-
-    /// Begin initializing the device.
-    ///
-    /// Ref: virtio 3.1.1 Device Initialization
-    pub fn begin_init(&mut self, negotiate_features: impl FnOnce(u64) -> u64) {
-        self.status.write(DeviceStatus::ACKNOWLEDGE);
-        self.status.write(DeviceStatus::DRIVER);
-
-        let features = self.read_device_features();
-        self.write_driver_features(negotiate_features(features));
-        self.status.write(DeviceStatus::FEATURES_OK);
-
-        self.guest_page_size.write(PAGE_SIZE as u32);
-    }
-
-    /// Finish initializing the device.
-    pub fn finish_init(&mut self) {
-        self.status.write(DeviceStatus::DRIVER_OK);
-    }
-
-    /// Read device features.
-    fn read_device_features(&mut self) -> u64 {
-        self.device_features_sel.write(0); // device features [0, 32)
-        let mut device_features_bits = self.device_features.read().into();
-        self.device_features_sel.write(1); // device features [32, 64)
-        device_features_bits += (self.device_features.read() as u64) << 32;
-        device_features_bits
-    }
-
-    /// Write device features.
-    fn write_driver_features(&mut self, driver_features: u64) {
-        self.driver_features_sel.write(0); // driver features [0, 32)
-        self.driver_features.write(driver_features as u32);
-        self.driver_features_sel.write(1); // driver features [32, 64)
-        self.driver_features.write((driver_features >> 32) as u32);
-    }
-
-    /// Set queue.
-    pub fn queue_set(&mut self, queue: u32, size: u32, align: u32, pfn: u32) {
-        self.queue_sel.write(queue);
-        self.queue_num.write(size);
-        self.queue_align.write(align);
-        self.queue_pfn.write(pfn);
-    }
-
-    /// Get guest physical page number of the virtual queue.
-    pub fn queue_physical_page_number(&mut self, queue: u32) -> u32 {
-        self.queue_sel.write(queue);
-        self.queue_pfn.read()
-    }
-
-    /// Whether the queue is in used.
-    pub fn queue_used(&mut self, queue: u32) -> bool {
-        self.queue_physical_page_number(queue) != 0
-    }
-
-    /// Get the max size of queue.
-    pub fn max_queue_size(&self) -> u32 {
-        self.queue_num_max.read()
-    }
-
-    /// Notify device.
-    pub fn notify(&mut self, queue: u32) {
-        self.queue_notify.write(queue);
-    }
-
-    /// Acknowledge interrupt and return true if success.
-    pub fn ack_interrupt(&mut self) -> bool {
-        let interrupt = self.interrupt_status.read();
-        if interrupt != 0 {
-            self.interrupt_ack.write(interrupt);
-            true
-        } else {
-            false
-        }
-    }
-
-    /// Get the pointer to config space (at offset 0x100)
-    pub fn config_space(&self) -> *mut u64 {
-        (self as *const _ as usize + CONFIG_SPACE_OFFSET) as _
-    }
-
-    /// Constructs a fake virtio header for use in unit tests.
-    #[cfg(test)]
-    pub fn make_fake_header(
-        device_id: u32,
-        vendor_id: u32,
-        device_features: u32,
-        queue_num_max: u32,
-    ) -> Self {
-        Self {
-            magic: ReadOnly::new(MAGIC_VALUE),
-            version: ReadOnly::new(1),
-            device_id: ReadOnly::new(device_id),
-            vendor_id: ReadOnly::new(vendor_id),
-            device_features: ReadOnly::new(device_features),
-            device_features_sel: WriteOnly::default(),
-            __r1: Default::default(),
-            driver_features: Default::default(),
-            driver_features_sel: Default::default(),
-            guest_page_size: Default::default(),
-            __r2: Default::default(),
-            queue_sel: Default::default(),
-            queue_num_max: ReadOnly::new(queue_num_max),
-            queue_num: Default::default(),
-            queue_align: Default::default(),
-            queue_pfn: Default::default(),
-            queue_ready: Default::default(),
-            __r3: Default::default(),
-            queue_notify: Default::default(),
-            __r4: Default::default(),
-            interrupt_status: Default::default(),
-            interrupt_ack: Default::default(),
-            __r5: Default::default(),
-            status: Volatile::new(DeviceStatus::empty()),
-            __r6: Default::default(),
-            queue_desc_low: Default::default(),
-            queue_desc_high: Default::default(),
-            __r7: Default::default(),
-            queue_avail_low: Default::default(),
-            queue_avail_high: Default::default(),
-            __r8: Default::default(),
-            queue_used_low: Default::default(),
-            queue_used_high: Default::default(),
-            __r9: Default::default(),
-            config_generation: Default::default(),
-        }
-    }
-}
-
-bitflags! {
-    /// The device status field.
-    struct DeviceStatus: u32 {
-        /// Indicates that the guest OS has found the device and recognized it
-        /// as a valid virtio device.
-        const ACKNOWLEDGE = 1;
-
-        /// Indicates that the guest OS knows how to drive the device.
-        const DRIVER = 2;
-
-        /// Indicates that something went wrong in the guest, and it has given
-        /// up on the device. This could be an internal error, or the driver
-        /// didn’t like the device for some reason, or even a fatal error
-        /// during device operation.
-        const FAILED = 128;
-
-        /// Indicates that the driver has acknowledged all the features it
-        /// understands, and feature negotiation is complete.
-        const FEATURES_OK = 8;
-
-        /// Indicates that the driver is set up and ready to drive the device.
-        const DRIVER_OK = 4;
-
-        /// Indicates that the device has experienced an error from which it
-        /// can’t recover.
-        const DEVICE_NEEDS_RESET = 64;
-    }
-}
-
-const CONFIG_SPACE_OFFSET: usize = 0x100;
-
-/// Types of virtio devices.
-#[repr(u8)]
-#[derive(Debug, Eq, PartialEq)]
-#[allow(missing_docs)]
-pub enum DeviceType {
-    Invalid = 0,
-    Network = 1,
-    Block = 2,
-    Console = 3,
-    EntropySource = 4,
-    MemoryBallooning = 5,
-    IoMemory = 6,
-    Rpmsg = 7,
-    ScsiHost = 8,
-    _9P = 9,
-    Mac80211 = 10,
-    RprocSerial = 11,
-    VirtioCAIF = 12,
-    MemoryBalloon = 13,
-    GPU = 16,
-    Timer = 17,
-    Input = 18,
-    Socket = 19,
-    Crypto = 20,
-    SignalDistributionModule = 21,
-    Pstore = 22,
-    IOMMU = 23,
-    Memory = 24,
-}

+ 13 - 11
src/input.rs

@@ -1,4 +1,5 @@
 use super::*;
+use crate::transport::Transport;
 use alloc::boxed::Box;
 use bitflags::*;
 use log::*;
@@ -9,18 +10,18 @@ use volatile::{ReadOnly, WriteOnly};
 /// An instance of the virtio device represents one such input device.
 /// Device behavior mirrors that of the evdev layer in Linux,
 /// making pass-through implementations on top of evdev easy.
-pub struct VirtIOInput<'a, H: Hal> {
-    header: &'static mut VirtIOHeader,
+pub struct VirtIOInput<'a, H: Hal, T: Transport> {
+    transport: T,
     event_queue: VirtQueue<'a, H>,
     status_queue: VirtQueue<'a, H>,
     event_buf: Box<[InputEvent; 32]>,
 }
 
-impl<'a, H: Hal> VirtIOInput<'a, H> {
+impl<H: Hal, T: Transport> VirtIOInput<'_, H, T> {
     /// Create a new VirtIO-Input driver.
-    pub fn new(header: &'static mut VirtIOHeader) -> Result<Self> {
+    pub fn new(mut transport: T) -> Result<Self> {
         let mut event_buf = Box::new([InputEvent::default(); QUEUE_SIZE]);
-        header.begin_init(|features| {
+        transport.begin_init(|features| {
             let features = Feature::from_bits_truncate(features);
             info!("Device features: {:?}", features);
             // negotiate these flags only
@@ -28,17 +29,17 @@ impl<'a, H: Hal> VirtIOInput<'a, H> {
             (features & supported_features).bits()
         });
 
-        let mut event_queue = VirtQueue::new(header, QUEUE_EVENT, QUEUE_SIZE as u16)?;
-        let status_queue = VirtQueue::new(header, QUEUE_STATUS, QUEUE_SIZE as u16)?;
+        let mut event_queue = VirtQueue::new(&mut transport, QUEUE_EVENT, QUEUE_SIZE as u16)?;
+        let status_queue = VirtQueue::new(&mut transport, QUEUE_STATUS, QUEUE_SIZE as u16)?;
         for (i, event) in event_buf.as_mut().iter_mut().enumerate() {
             let token = event_queue.add(&[], &[event.as_buf_mut()])?;
             assert_eq!(token, i as u16);
         }
 
-        header.finish_init();
+        transport.finish_init();
 
         Ok(VirtIOInput {
-            header,
+            transport,
             event_queue,
             status_queue,
             event_buf,
@@ -47,7 +48,7 @@ impl<'a, H: Hal> VirtIOInput<'a, H> {
 
     /// Acknowledge interrupt and process events.
     pub fn ack_interrupt(&mut self) -> bool {
-        self.header.ack_interrupt()
+        self.transport.ack_interrupt()
     }
 
     /// Pop the pending event.
@@ -70,7 +71,8 @@ impl<'a, H: Hal> VirtIOInput<'a, H> {
         subsel: u8,
         out: &mut [u8],
     ) -> u8 {
-        let config = unsafe { &mut *(self.header.config_space() as *mut Config) };
+        let mut config_space = self.transport.config_space().cast::<Config>();
+        let config = unsafe { config_space.as_mut() };
         config.select.write(select as u8);
         config.subsel.write(subsel);
         let size = config.size.read();

+ 3 - 2
src/lib.rs

@@ -11,19 +11,20 @@ mod blk;
 mod console;
 mod gpu;
 mod hal;
-mod header;
 mod input;
 mod net;
 mod queue;
+mod transport;
 
 pub use self::blk::{BlkResp, RespStatus, VirtIOBlk};
 pub use self::console::VirtIOConsole;
 pub use self::gpu::VirtIOGpu;
 pub use self::hal::{Hal, PhysAddr, VirtAddr};
-pub use self::header::*;
 pub use self::input::{InputConfigSelect, InputEvent, VirtIOInput};
 pub use self::net::VirtIONet;
 use self::queue::VirtQueue;
+pub use self::transport::mmio::{MmioError, MmioTransport, MmioVersion, VirtIOHeader};
+pub use self::transport::{DeviceStatus, DeviceType, Transport};
 use core::mem::size_of;
 use hal::*;
 

+ 15 - 13
src/net.rs

@@ -1,6 +1,7 @@
 use core::mem::{size_of, MaybeUninit};
 
 use super::*;
+use crate::transport::Transport;
 use bitflags::*;
 use core::hint::spin_loop;
 use log::*;
@@ -13,35 +14,36 @@ use volatile::{ReadOnly, Volatile};
 /// Empty buffers are placed in one virtqueue for receiving packets, and
 /// outgoing packets are enqueued into another for transmission in that order.
 /// A third command queue is used to control advanced filtering features.
-pub struct VirtIONet<'a, H: Hal> {
-    header: &'static mut VirtIOHeader,
+pub struct VirtIONet<'a, H: Hal, T: Transport> {
+    transport: T,
     mac: EthernetAddress,
     recv_queue: VirtQueue<'a, H>,
     send_queue: VirtQueue<'a, H>,
 }
 
-impl<H: Hal> VirtIONet<'_, H> {
+impl<H: Hal, T: Transport> VirtIONet<'_, H, T> {
     /// Create a new VirtIO-Net driver.
-    pub fn new(header: &'static mut VirtIOHeader) -> Result<Self> {
-        header.begin_init(|features| {
+    pub fn new(mut transport: T) -> Result<Self> {
+        transport.begin_init(|features| {
             let features = Features::from_bits_truncate(features);
             info!("Device features {:?}", features);
             let supported_features = Features::MAC | Features::STATUS;
             (features & supported_features).bits()
         });
         // read configuration space
-        let config = unsafe { &mut *(header.config_space() as *mut Config) };
+        let config_space = transport.config_space().cast::<Config>();
+        let config = unsafe { config_space.as_ref() };
         let mac = config.mac.read();
         debug!("Got MAC={:?}, status={:?}", mac, config.status.read());
 
         let queue_num = 2; // for simplicity
-        let recv_queue = VirtQueue::new(header, QUEUE_RECEIVE, queue_num)?;
-        let send_queue = VirtQueue::new(header, QUEUE_TRANSMIT, queue_num)?;
+        let recv_queue = VirtQueue::new(&mut transport, QUEUE_RECEIVE, queue_num)?;
+        let send_queue = VirtQueue::new(&mut transport, QUEUE_TRANSMIT, queue_num)?;
 
-        header.finish_init();
+        transport.finish_init();
 
         Ok(VirtIONet {
-            header,
+            transport,
             mac,
             recv_queue,
             send_queue,
@@ -50,7 +52,7 @@ impl<H: Hal> VirtIONet<'_, H> {
 
     /// Acknowledge interrupt.
     pub fn ack_interrupt(&mut self) -> bool {
-        self.header.ack_interrupt()
+        self.transport.ack_interrupt()
     }
 
     /// Get MAC address.
@@ -73,7 +75,7 @@ impl<H: Hal> VirtIONet<'_, H> {
         let mut header = MaybeUninit::<Header>::uninit();
         let header_buf = unsafe { (*header.as_mut_ptr()).as_buf_mut() };
         self.recv_queue.add(&[], &[header_buf, buf])?;
-        self.header.notify(QUEUE_RECEIVE as u32);
+        self.transport.notify(QUEUE_RECEIVE as u32);
         while !self.recv_queue.can_pop() {
             spin_loop();
         }
@@ -87,7 +89,7 @@ impl<H: Hal> VirtIONet<'_, H> {
     pub fn send(&mut self, buf: &[u8]) -> Result {
         let header = unsafe { MaybeUninit::<Header>::zeroed().assume_init() };
         self.send_queue.add(&[header.as_buf(), buf], &[])?;
-        self.header.notify(QUEUE_TRANSMIT as u32);
+        self.transport.notify(QUEUE_TRANSMIT as u32);
         while !self.send_queue.can_pop() {
             spin_loop();
         }

+ 33 - 21
src/queue.rs

@@ -3,7 +3,7 @@ use core::slice;
 use core::sync::atomic::{fence, Ordering};
 
 use super::*;
-use crate::header::VirtIOHeader;
+use crate::transport::Transport;
 use bitflags::*;
 
 use volatile::Volatile;
@@ -39,18 +39,24 @@ pub struct VirtQueue<'a, H: Hal> {
 
 impl<H: Hal> VirtQueue<'_, H> {
     /// Create a new VirtQueue.
-    pub fn new(header: &mut VirtIOHeader, idx: usize, size: u16) -> Result<Self> {
-        if header.queue_used(idx as u32) {
+    pub fn new<T: Transport>(transport: &mut T, idx: usize, size: u16) -> Result<Self> {
+        if transport.queue_used(idx as u32) {
             return Err(Error::AlreadyUsed);
         }
-        if !size.is_power_of_two() || header.max_queue_size() < size as u32 {
+        if !size.is_power_of_two() || transport.max_queue_size() < size as u32 {
             return Err(Error::InvalidParam);
         }
         let layout = VirtQueueLayout::new(size);
         // Allocate contiguous pages.
         let dma = DMA::new(layout.size / PAGE_SIZE)?;
 
-        header.queue_set(idx as u32, size as u32, PAGE_SIZE as u32, dma.pfn());
+        transport.queue_set(
+            idx as u32,
+            size as u32,
+            dma.paddr(),
+            dma.paddr() + layout.avail_offset,
+            dma.paddr() + layout.used_offset,
+        );
 
         let desc =
             unsafe { slice::from_raw_parts_mut(dma.vaddr() as *mut Descriptor, size as usize) };
@@ -209,7 +215,7 @@ impl VirtQueueLayout {
 
 #[repr(C, align(16))]
 #[derive(Debug)]
-struct Descriptor {
+pub(crate) struct Descriptor {
     addr: Volatile<u64>,
     len: Volatile<u32>,
     flags: Volatile<DescFlags>,
@@ -267,49 +273,54 @@ struct UsedElem {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::hal::fake::FakeHal;
-    use core::mem::zeroed;
+    use crate::{hal::fake::FakeHal, transport::mmio::MODERN_VERSION};
+    use core::ptr::NonNull;
 
     #[test]
     fn invalid_queue_size() {
-        let mut header = unsafe { zeroed() };
+        let mut header = VirtIOHeader::make_fake_header(MODERN_VERSION, 1, 0, 0, 4);
+        let mut transport = unsafe { MmioTransport::new(NonNull::from(&mut header)) }.unwrap();
         // Size not a power of 2.
         assert_eq!(
-            VirtQueue::<FakeHal>::new(&mut header, 0, 3).unwrap_err(),
+            VirtQueue::<FakeHal>::new(&mut transport, 0, 3).unwrap_err(),
             Error::InvalidParam
         );
     }
 
     #[test]
     fn queue_too_big() {
-        let mut header = VirtIOHeader::make_fake_header(0, 0, 0, 4);
+        let mut header = VirtIOHeader::make_fake_header(MODERN_VERSION, 1, 0, 0, 4);
+        let mut transport = unsafe { MmioTransport::new(NonNull::from(&mut header)) }.unwrap();
         assert_eq!(
-            VirtQueue::<FakeHal>::new(&mut header, 0, 5).unwrap_err(),
+            VirtQueue::<FakeHal>::new(&mut transport, 0, 5).unwrap_err(),
             Error::InvalidParam
         );
     }
 
     #[test]
     fn queue_already_used() {
-        let mut header = VirtIOHeader::make_fake_header(0, 0, 0, 4);
-        VirtQueue::<FakeHal>::new(&mut header, 0, 4).unwrap();
+        let mut header = VirtIOHeader::make_fake_header(MODERN_VERSION, 1, 0, 0, 4);
+        let mut transport = unsafe { MmioTransport::new(NonNull::from(&mut header)) }.unwrap();
+        VirtQueue::<FakeHal>::new(&mut transport, 0, 4).unwrap();
         assert_eq!(
-            VirtQueue::<FakeHal>::new(&mut header, 0, 4).unwrap_err(),
+            VirtQueue::<FakeHal>::new(&mut transport, 0, 4).unwrap_err(),
             Error::AlreadyUsed
         );
     }
 
     #[test]
     fn add_empty() {
-        let mut header = VirtIOHeader::make_fake_header(0, 0, 0, 4);
-        let mut queue = VirtQueue::<FakeHal>::new(&mut header, 0, 4).unwrap();
+        let mut header = VirtIOHeader::make_fake_header(MODERN_VERSION, 1, 0, 0, 4);
+        let mut transport = unsafe { MmioTransport::new(NonNull::from(&mut header)) }.unwrap();
+        let mut queue = VirtQueue::<FakeHal>::new(&mut transport, 0, 4).unwrap();
         assert_eq!(queue.add(&[], &[]).unwrap_err(), Error::InvalidParam);
     }
 
     #[test]
     fn add_too_big() {
-        let mut header = VirtIOHeader::make_fake_header(0, 0, 0, 4);
-        let mut queue = VirtQueue::<FakeHal>::new(&mut header, 0, 4).unwrap();
+        let mut header = VirtIOHeader::make_fake_header(MODERN_VERSION, 1, 0, 0, 4);
+        let mut transport = unsafe { MmioTransport::new(NonNull::from(&mut header)) }.unwrap();
+        let mut queue = VirtQueue::<FakeHal>::new(&mut transport, 0, 4).unwrap();
         assert_eq!(queue.available_desc(), 4);
         assert_eq!(
             queue
@@ -321,8 +332,9 @@ mod tests {
 
     #[test]
     fn add_buffers() {
-        let mut header = VirtIOHeader::make_fake_header(0, 0, 0, 4);
-        let mut queue = VirtQueue::<FakeHal>::new(&mut header, 0, 4).unwrap();
+        let mut header = VirtIOHeader::make_fake_header(MODERN_VERSION, 1, 0, 0, 4);
+        let mut transport = unsafe { MmioTransport::new(NonNull::from(&mut header)) }.unwrap();
+        let mut queue = VirtQueue::<FakeHal>::new(&mut transport, 0, 4).unwrap();
         assert_eq!(queue.size(), 4);
         assert_eq!(queue.available_desc(), 4);
 

+ 435 - 0
src/transport/mmio.rs

@@ -0,0 +1,435 @@
+use super::{DeviceStatus, DeviceType, Transport};
+use crate::{align_up, queue::Descriptor, PhysAddr, PAGE_SIZE};
+use core::{
+    convert::{TryFrom, TryInto},
+    fmt::{self, Display, Formatter},
+    mem::size_of,
+    ptr::NonNull,
+};
+use volatile::{ReadOnly, Volatile, WriteOnly};
+
+const MAGIC_VALUE: u32 = 0x7472_6976;
+pub(crate) const LEGACY_VERSION: u32 = 1;
+pub(crate) const MODERN_VERSION: u32 = 2;
+const CONFIG_SPACE_OFFSET: usize = 0x100;
+
+/// The version of the VirtIO MMIO transport supported by a device.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[repr(u32)]
+pub enum MmioVersion {
+    /// Legacy MMIO transport with page-based addressing.
+    Legacy = LEGACY_VERSION,
+    /// Modern MMIO transport.
+    Modern = MODERN_VERSION,
+}
+
+impl TryFrom<u32> for MmioVersion {
+    type Error = MmioError;
+
+    fn try_from(version: u32) -> Result<Self, Self::Error> {
+        match version {
+            LEGACY_VERSION => Ok(Self::Legacy),
+            MODERN_VERSION => Ok(Self::Modern),
+            _ => Err(MmioError::UnsupportedVersion(version)),
+        }
+    }
+}
+
+impl From<MmioVersion> for u32 {
+    fn from(version: MmioVersion) -> Self {
+        match version {
+            MmioVersion::Legacy => LEGACY_VERSION,
+            MmioVersion::Modern => MODERN_VERSION,
+        }
+    }
+}
+
+/// An error encountered initialising a VirtIO MMIO transport.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum MmioError {
+    /// The header doesn't start with the expected magic value 0x74726976.
+    BadMagic(u32),
+    /// The header reports a version number that is neither 1 (legacy) nor 2 (modern).
+    UnsupportedVersion(u32),
+    /// The header reports a device ID of 0.
+    ZeroDeviceId,
+}
+
+impl Display for MmioError {
+    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+        match self {
+            Self::BadMagic(magic) => write!(
+                f,
+                "Invalid magic value {:#010x} (expected 0x74726976).",
+                magic
+            ),
+            Self::UnsupportedVersion(version) => {
+                write!(f, "Unsupported Virtio MMIO version {}.", version)
+            }
+            Self::ZeroDeviceId => write!(f, "Device ID was zero."),
+        }
+    }
+}
+
+/// MMIO Device Register Interface, both legacy and modern.
+///
+/// Ref: 4.2.2 MMIO Device Register Layout and 4.2.4 Legacy interface
+#[repr(C)]
+pub struct VirtIOHeader {
+    /// Magic value
+    magic: ReadOnly<u32>,
+
+    /// Device version number
+    ///
+    /// Legacy device returns value 0x1.
+    version: ReadOnly<u32>,
+
+    /// Virtio Subsystem Device ID
+    device_id: ReadOnly<u32>,
+
+    /// Virtio Subsystem Vendor ID
+    vendor_id: ReadOnly<u32>,
+
+    /// Flags representing features the device supports
+    device_features: ReadOnly<u32>,
+
+    /// Device (host) features word selection
+    device_features_sel: WriteOnly<u32>,
+
+    /// Reserved
+    __r1: [ReadOnly<u32>; 2],
+
+    /// Flags representing device features understood and activated by the driver
+    driver_features: WriteOnly<u32>,
+
+    /// Activated (guest) features word selection
+    driver_features_sel: WriteOnly<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 QueuePFN).
+    legacy_guest_page_size: WriteOnly<u32>,
+
+    /// Reserved
+    __r2: ReadOnly<u32>,
+
+    /// Virtual queue index
+    ///
+    /// Writing to this register selects the virtual queue that the following
+    /// operations on the QueueNumMax, QueueNum, QueueAlign and QueuePFN
+    /// registers apply to. The index number of the first queue is zero (0x0).
+    queue_sel: WriteOnly<u32>,
+
+    /// Maximum virtual queue size
+    ///
+    /// Reading from the register returns the maximum size 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 and is
+    /// allowed only when QueuePFN is set to zero (0x0), so when the queue is
+    /// not actively used.
+    queue_num_max: ReadOnly<u32>,
+
+    /// 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.
+    queue_num: WriteOnly<u32>,
+
+    /// Used Ring alignment in the virtual queue
+    ///
+    /// Writing to this register notifies the device about alignment boundary
+    /// of the Used Ring in bytes. This value should be a power of 2 and
+    /// applies to the queue selected by writing to QueueSel.
+    legacy_queue_align: WriteOnly<u32>,
+
+    /// Guest physical page number of the virtual queue
+    ///
+    /// Writing to this register notifies the device about location of the
+    /// virtual queue in the Guest’s physical address space. This value is
+    /// the index number of a page starting with the queue Descriptor Table.
+    /// Value zero (0x0) means physical address zero (0x00000000) and is illegal.
+    /// When the driver stops using the queue it writes zero (0x0) to this
+    /// register. Reading from this register returns the currently used page
+    /// number of the queue, therefore a value other than zero (0x0) means that
+    /// the queue is in use. Both read and write accesses apply to the queue
+    /// selected by writing to QueueSel.
+    legacy_queue_pfn: Volatile<u32>,
+
+    /// new interface only
+    queue_ready: Volatile<u32>,
+
+    /// Reserved
+    __r3: [ReadOnly<u32>; 2],
+
+    /// Queue notifier
+    queue_notify: WriteOnly<u32>,
+
+    /// Reserved
+    __r4: [ReadOnly<u32>; 3],
+
+    /// Interrupt status
+    interrupt_status: ReadOnly<u32>,
+
+    /// Interrupt acknowledge
+    interrupt_ack: WriteOnly<u32>,
+
+    /// Reserved
+    __r5: [ReadOnly<u32>; 2],
+
+    /// 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 OS/driver progress. Writing zero (0x0) to this register
+    /// triggers a device reset. The device sets QueuePFN to zero (0x0) for
+    /// all queues in the device. Also see 3.1 Device Initialization.
+    status: Volatile<DeviceStatus>,
+
+    /// Reserved
+    __r6: [ReadOnly<u32>; 3],
+
+    // new interface only since here
+    queue_desc_low: WriteOnly<u32>,
+    queue_desc_high: WriteOnly<u32>,
+
+    /// Reserved
+    __r7: [ReadOnly<u32>; 2],
+
+    queue_driver_low: WriteOnly<u32>,
+    queue_driver_high: WriteOnly<u32>,
+
+    /// Reserved
+    __r8: [ReadOnly<u32>; 2],
+
+    queue_device_low: WriteOnly<u32>,
+    queue_device_high: WriteOnly<u32>,
+
+    /// Reserved
+    __r9: [ReadOnly<u32>; 21],
+
+    config_generation: ReadOnly<u32>,
+}
+
+impl VirtIOHeader {
+    /// Constructs a fake VirtIO header for use in unit tests.
+    #[cfg(test)]
+    pub fn make_fake_header(
+        version: u32,
+        device_id: u32,
+        vendor_id: u32,
+        device_features: u32,
+        queue_num_max: u32,
+    ) -> Self {
+        Self {
+            magic: ReadOnly::new(MAGIC_VALUE),
+            version: ReadOnly::new(version),
+            device_id: ReadOnly::new(device_id),
+            vendor_id: ReadOnly::new(vendor_id),
+            device_features: ReadOnly::new(device_features),
+            device_features_sel: WriteOnly::default(),
+            __r1: Default::default(),
+            driver_features: Default::default(),
+            driver_features_sel: Default::default(),
+            legacy_guest_page_size: Default::default(),
+            __r2: Default::default(),
+            queue_sel: Default::default(),
+            queue_num_max: ReadOnly::new(queue_num_max),
+            queue_num: Default::default(),
+            legacy_queue_align: Default::default(),
+            legacy_queue_pfn: Default::default(),
+            queue_ready: Default::default(),
+            __r3: Default::default(),
+            queue_notify: Default::default(),
+            __r4: Default::default(),
+            interrupt_status: Default::default(),
+            interrupt_ack: Default::default(),
+            __r5: Default::default(),
+            status: Volatile::new(DeviceStatus::empty()),
+            __r6: Default::default(),
+            queue_desc_low: Default::default(),
+            queue_desc_high: Default::default(),
+            __r7: Default::default(),
+            queue_driver_low: Default::default(),
+            queue_driver_high: Default::default(),
+            __r8: Default::default(),
+            queue_device_low: Default::default(),
+            queue_device_high: Default::default(),
+            __r9: Default::default(),
+            config_generation: Default::default(),
+        }
+    }
+}
+
+/// MMIO Device Register Interface.
+///
+/// Ref: 4.2.2 MMIO Device Register Layout and 4.2.4 Legacy interface
+#[derive(Debug)]
+pub struct MmioTransport {
+    header: NonNull<VirtIOHeader>,
+    version: MmioVersion,
+}
+
+impl MmioTransport {
+    /// Constructs a new VirtIO MMIO transport, or returns an error if the header reports an
+    /// unsupported version.
+    ///
+    /// # Safety
+    /// `header` must point to a properly aligned valid VirtIO MMIO region, which must remain valid
+    /// for the lifetime of the transport that is returned.
+    pub unsafe fn new(header: NonNull<VirtIOHeader>) -> Result<Self, MmioError> {
+        let magic = header.as_ref().magic.read();
+        if magic != MAGIC_VALUE {
+            return Err(MmioError::BadMagic(magic));
+        }
+        if header.as_ref().device_id.read() == 0 {
+            return Err(MmioError::ZeroDeviceId);
+        }
+        let version = header.as_ref().version.read().try_into()?;
+        Ok(Self { header, version })
+    }
+
+    /// Gets the version of the VirtIO MMIO transport.
+    pub fn version(&self) -> MmioVersion {
+        self.version
+    }
+
+    /// Gets the vendor ID.
+    pub fn vendor_id(&self) -> u32 {
+        self.header().vendor_id.read()
+    }
+
+    fn header(&self) -> &VirtIOHeader {
+        unsafe { self.header.as_ref() }
+    }
+
+    fn header_mut(&mut self) -> &mut VirtIOHeader {
+        unsafe { self.header.as_mut() }
+    }
+}
+
+impl Transport for MmioTransport {
+    fn device_type(&self) -> DeviceType {
+        match self.header().device_id.read() {
+            x @ 1..=13 | x @ 16..=24 => unsafe { core::mem::transmute(x as u8) },
+            _ => DeviceType::Invalid,
+        }
+    }
+
+    fn read_device_features(&mut self) -> u64 {
+        let header = self.header_mut();
+        header.device_features_sel.write(0); // device features [0, 32)
+        let mut device_features_bits = header.device_features.read().into();
+        header.device_features_sel.write(1); // device features [32, 64)
+        device_features_bits += (header.device_features.read() as u64) << 32;
+        device_features_bits
+    }
+
+    fn write_driver_features(&mut self, driver_features: u64) {
+        let header = self.header_mut();
+        header.driver_features_sel.write(0); // driver features [0, 32)
+        header.driver_features.write(driver_features as u32);
+        header.driver_features_sel.write(1); // driver features [32, 64)
+        header.driver_features.write((driver_features >> 32) as u32);
+    }
+
+    fn max_queue_size(&self) -> u32 {
+        self.header().queue_num_max.read()
+    }
+
+    fn notify(&mut self, queue: u32) {
+        self.header_mut().queue_notify.write(queue);
+    }
+
+    fn set_status(&mut self, status: DeviceStatus) {
+        self.header_mut().status.write(status);
+    }
+
+    fn set_guest_page_size(&mut self, guest_page_size: u32) {
+        match self.version {
+            MmioVersion::Legacy => {
+                self.header_mut()
+                    .legacy_guest_page_size
+                    .write(guest_page_size);
+            }
+            MmioVersion::Modern => {
+                // No-op, modern devices don't care.
+            }
+        }
+    }
+
+    fn queue_set(
+        &mut self,
+        queue: u32,
+        size: u32,
+        descriptors: PhysAddr,
+        driver_area: PhysAddr,
+        device_area: PhysAddr,
+    ) {
+        match self.version {
+            MmioVersion::Legacy => {
+                assert_eq!(
+                    driver_area - descriptors,
+                    size_of::<Descriptor>() * size as usize
+                );
+                assert_eq!(
+                    device_area - descriptors,
+                    align_up(
+                        size_of::<Descriptor>() * size as usize
+                            + size_of::<u16>() * (size as usize + 3)
+                    )
+                );
+                let align = PAGE_SIZE as u32;
+                let pfn = (descriptors / PAGE_SIZE) as u32;
+                assert_eq!(pfn as usize * PAGE_SIZE, descriptors);
+                self.header_mut().queue_sel.write(queue);
+                self.header_mut().queue_num.write(size);
+                self.header_mut().legacy_queue_align.write(align);
+                self.header_mut().legacy_queue_pfn.write(pfn);
+            }
+            MmioVersion::Modern => {
+                self.header_mut().queue_sel.write(queue);
+                self.header_mut().queue_num.write(size);
+                self.header_mut().queue_desc_low.write(descriptors as u32);
+                self.header_mut()
+                    .queue_desc_high
+                    .write((descriptors >> 32) as u32);
+                self.header_mut().queue_driver_low.write(driver_area as u32);
+                self.header_mut()
+                    .queue_driver_high
+                    .write((driver_area >> 32) as u32);
+                self.header_mut().queue_device_low.write(device_area as u32);
+                self.header_mut()
+                    .queue_device_high
+                    .write((device_area >> 32) as u32);
+                self.header_mut().queue_ready.write(1);
+            }
+        }
+    }
+
+    fn queue_used(&mut self, queue: u32) -> bool {
+        self.header_mut().queue_sel.write(queue);
+        match self.version {
+            MmioVersion::Legacy => self.header().legacy_queue_pfn.read() != 0,
+            MmioVersion::Modern => self.header().queue_ready.read() != 0,
+        }
+    }
+
+    fn ack_interrupt(&mut self) -> bool {
+        let header = self.header_mut();
+        let interrupt = header.interrupt_status.read();
+        if interrupt != 0 {
+            header.interrupt_ack.write(interrupt);
+            true
+        } else {
+            false
+        }
+    }
+
+    fn config_space(&self) -> NonNull<u64> {
+        NonNull::new((self.header.as_ptr() as usize + CONFIG_SPACE_OFFSET) as _).unwrap()
+    }
+}

+ 128 - 0
src/transport/mod.rs

@@ -0,0 +1,128 @@
+pub mod mmio;
+
+use crate::{PhysAddr, PAGE_SIZE};
+use bitflags::bitflags;
+use core::ptr::NonNull;
+
+/// A VirtIO transport layer.
+pub trait Transport {
+    /// Gets the device type.
+    fn device_type(&self) -> DeviceType;
+
+    /// Reads device features.
+    fn read_device_features(&mut self) -> u64;
+
+    /// Writes device features.
+    fn write_driver_features(&mut self, driver_features: u64);
+
+    /// Gets the max size of queue.
+    fn max_queue_size(&self) -> u32;
+
+    /// Notifies the given queue on the device.
+    fn notify(&mut self, queue: u32);
+
+    /// Sets the device status.
+    fn set_status(&mut self, status: DeviceStatus);
+
+    /// Sets the guest page size.
+    fn set_guest_page_size(&mut self, guest_page_size: u32);
+
+    /// Sets up the given queue.
+    fn queue_set(
+        &mut self,
+        queue: u32,
+        size: u32,
+        descriptors: PhysAddr,
+        driver_area: PhysAddr,
+        device_area: PhysAddr,
+    );
+
+    /// Returns whether the queue is in use, i.e. has a nonzero PFN or is marked as ready.
+    fn queue_used(&mut self, queue: u32) -> bool;
+
+    /// Acknowledges an interrupt.
+    ///
+    /// Returns true on success.
+    fn ack_interrupt(&mut self) -> bool;
+
+    /// Begins initializing the device.
+    ///
+    /// Ref: virtio 3.1.1 Device Initialization
+    fn begin_init(&mut self, negotiate_features: impl FnOnce(u64) -> u64) {
+        self.set_status(DeviceStatus::ACKNOWLEDGE);
+        self.set_status(DeviceStatus::DRIVER);
+
+        let features = self.read_device_features();
+        self.write_driver_features(negotiate_features(features));
+        self.set_status(DeviceStatus::FEATURES_OK);
+
+        self.set_guest_page_size(PAGE_SIZE as u32);
+    }
+
+    /// Finishes initializing the device.
+    fn finish_init(&mut self) {
+        self.set_status(DeviceStatus::DRIVER_OK);
+    }
+
+    /// Gets the pointer to the config space.
+    fn config_space(&self) -> NonNull<u64>;
+}
+
+bitflags! {
+    /// The device status field.
+    pub struct DeviceStatus: u32 {
+        /// Indicates that the guest OS has found the device and recognized it
+        /// as a valid virtio device.
+        const ACKNOWLEDGE = 1;
+
+        /// Indicates that the guest OS knows how to drive the device.
+        const DRIVER = 2;
+
+        /// Indicates that something went wrong in the guest, and it has given
+        /// up on the device. This could be an internal error, or the driver
+        /// didn’t like the device for some reason, or even a fatal error
+        /// during device operation.
+        const FAILED = 128;
+
+        /// Indicates that the driver has acknowledged all the features it
+        /// understands, and feature negotiation is complete.
+        const FEATURES_OK = 8;
+
+        /// Indicates that the driver is set up and ready to drive the device.
+        const DRIVER_OK = 4;
+
+        /// Indicates that the device has experienced an error from which it
+        /// can’t recover.
+        const DEVICE_NEEDS_RESET = 64;
+    }
+}
+
+/// Types of virtio devices.
+#[repr(u8)]
+#[derive(Debug, Eq, PartialEq)]
+#[allow(missing_docs)]
+pub enum DeviceType {
+    Invalid = 0,
+    Network = 1,
+    Block = 2,
+    Console = 3,
+    EntropySource = 4,
+    MemoryBallooning = 5,
+    IoMemory = 6,
+    Rpmsg = 7,
+    ScsiHost = 8,
+    _9P = 9,
+    Mac80211 = 10,
+    RprocSerial = 11,
+    VirtioCAIF = 12,
+    MemoryBalloon = 13,
+    GPU = 16,
+    Timer = 17,
+    Input = 18,
+    Socket = 19,
+    Crypto = 20,
+    SignalDistributionModule = 21,
+    Pstore = 22,
+    IOMMU = 23,
+    Memory = 24,
+}