|
@@ -0,0 +1,266 @@
|
|
|
+use crate::PAGE_SIZE;
|
|
|
+use bitflags::*;
|
|
|
+use volatile::{ReadOnly, Volatile, WriteOnly};
|
|
|
+
|
|
|
+/// MMIO Device Legacy Register Interface.
|
|
|
+///
|
|
|
+/// Ref: 4.2.4 Legacy interface
|
|
|
+#[repr(C)]
|
|
|
+#[derive(Debug)]
|
|
|
+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() == 0x74726976 && self.version.read() == 1 && self.device_id.read() != 0
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 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 u8 {
|
|
|
+ (self as *const _ as usize + CONFIG_SPACE_OFFSET) as _
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+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;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+pub const CONFIG_SPACE_OFFSET: usize = 0x100;
|