1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
//! Simple VirtIO Block
//!
//! ## Background
//! ### Simple VirtIO Block device specification
//! ### 1. Introduction
//! This describes the specifications of the "Simple VirtIO Block" device.
//! The purpose of this documentations is to provide the basic and concrete informations
//! for building svirtb-device and svirtb-driver.
//!
//! #### 1.1 Terminology
//! The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT",
//! "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted
//! as described in [RFC2119](http://www.ietf.org/rfc/rfc2119.txt).
//!
//! #### 1.2 Structure Specification
//! Device and In-memory structure layouts are documented using the C struct syntax.
//! All structure MUST not have any additional padding.
//!
//! For the integer data types, the following conventions are used:
//! * u8, u16, u32, u64: An unsigned integer of the specified length in bits.
//! * le8, le16, le32, le64: An unsigned integer of the specified length in bits, in little-endian byte order.
//!
//! ### 2. Basic Facilities of a Simple VirtIO Block
//! A simple virtIO block is discovered and identified by a mmio struct [`VirtIoMmioHeader`]. Mmio field (header) consists of the following parts:
//! ```C
//! struct VirtIoMmioHeader {
//! le32 status;
//! le32 queue_size;
//! le32 queue_addr_hi;
//! le32 queue_addr_lo;
//! le32 queue_head;
//! le32 queue_tail;
//! }
//! ```
//! * status: Status of the device
//! * queue_size: Size of the virtqueue
//! * queue_addr_hi: Upper 32bit of the virtqueue physical address
//! * queue_addr_lo: Lower 32bit of the virtqueue physical address
//! * queue_head: Head of the ring buffer. Driver update the tail of the queue. Device must not update the field.
//! * queue_tail: Tail of the ring buffer. Device update the tail of the queue. Driver must not update the field.
//!
//! #### 2.1 Device status field
//! During device initialization by a driver, the driver follows the sequence of steps specified in [`3`](#3-device-initialization).
//! The device status field provides a simple low-level indication of the completed steps of this sequence.
//! It's most useful to imagine it hooked up to traffic lights on the console indicating the status of each device.
//! The following status code are defined (listed below in the order in which they would be typically set):
//! ```C
//! le32 MAGIC = 0x74726976
//! le32 DRIVER_OK = 0
//! le32 READY = 1
//! le32 RESET = 2
//! ```
//! * MAGIC : Indicates that the region of the mmio is used for svirtb. Device sets the fields when initialization.
//! * DRIVER_OK : Indicates that the driver is ready to initiate the svirtb.
//! * READY : Indicates that the device is ready to be used by the svirtb device driver. From this points, svirtb device is activated.
//! * RESET : Reset the svirtb connection and start over.
//!
//! #### 2.1.1 Driver Requirements: Device Status Field
//! The driver MUST update device status, setting bits to indicate the steps of the driver initialization
//! in [`3`](#3-device-initialization). The driver MUST NOT set a device status to MAGIC value.
//! The driver MUST fill the necessary fields of the ring buffer configuration space
//! (size, queue_addr_low and queue_addr_high) after driver sets the DRIVER_OK status bit.
//!
//! #### 2.1.2 Device Requirements: Device Status Field
//! The device MUST initialize device status to 0x74726976 upon start and reset.
//!
//! The driver MUST enable READY after it validate the queue address and size negotiated
//! with the virtio driver.
//! The device SHOULD set RESET when it enters an error state that a reset is needed.
//!
//! #### 2.2 Ring buffer
//! The mechanisms for data transport on svirtb is ring buffer.
//! Driver makes requests available to device by enqueue an entry or multiple entries to the ring buffer and
//! write the index to the doorbell (ring_buffer_head).
//! Device executes the requests and, when complete, update the lastly consume index to the ring_buffer_tail.
//!
//! #### 2.2.1 Ring buffer entry
//! Each entry of the ring buffer is consists of the four parts.
//! ```C
//! struct VirtQueueEntry {
//! le64 addr;
//! le64 size;
//! le64 sector;
//! le32 cmd;
//! }
//! ```
//! * addr: Physical address of the buffer
//! * size: size of the buffer
//! * sector: sector of the vritual disk
//! * cmd: indicates the command. 0 is read, and 1 is write.
//!
//! ### 3. Device Initialization
//! The driver MUST follow this sequence to initialize a device:
//! 1. Check the magic exists in status field.
//! 2. Write the DRIVER_OK to the status field.
//! 3. Check whether status field is still DRIVER_OK.
//! 4. Perform ring buffer configuration; reading and writing device's ring buffer configuration space.
//! 5. Set the status field to READY.
//! 6. Check whether status field is READY. At this point the device is "live".
//!
//! The driver MUST NOT send any buffer available notifications to the device before setting READY.
//!
//! ### 4. virtio over mmio
//! For simplicity of the implementation, the mmio region of the simple virtIO block always located on the 0xcafe0000.
//!
//! The layout of the mmio area is follow:
//! ```C
//! struct svirtb {
//! le32 status;
//! le32 queue_size;
//! le32 queue_addr_hi;
//! le32 queue_addr_lo;
//! le32 queue_head;
//! le32 queue_tail;
//! }
//! ```
//! * status: Device status bits. Reading from this register returns the current device status flags. Initialized with magic by device - 0x74726976 (a Little Endian equivalent of the 'virt' string).
//! * queue_size: Writing to this register notifies the device what size of the queue the driver will use (select the length of the queue).
//! * queue_addr_hi: high part of ring buffer physical address (32-63 bits)
//! * queue_addr_lo: low part of ring buffer physical address (0-31 bits)
//! * queue_head: head index of the ring buffer
//! * queue_tail: tail index of the ring buffer
//!
//! ## Tasks
//! In this project, you are required to implement the device part (backend driver) of Simple Virtio Block Device.
//! You can get [`VirtQueue`] by calling [`VirtQueue::new_from_raw_ptr`].
//! After that, you can access [`VirtQueueEntry`] through an struct called [`VirtQueueFetcher`].
//! You can utilize [`VirtQueueFetcher`] to implement this project.
//!
//! [`VirtQueue`]: crate::virtio::virt_queue::VirtQueue::new_from_raw_ptr
//! [`VirtQueueEntry`]: crate::virtio::virt_queue::VirtQueueEntry
//! [`VirtQueueFetcher`]: crate::virtio::virt_queue::VirtQueueFetcher
//!
use crate::virtio::{
virt_queue::{VirtQueue, VirtQueueEntry, VirtQueueEntryCmd},
VirtIoMmioHeader, VirtIoStatus,
};
use alloc::{boxed::Box, sync::Arc};
use core::mem::size_of;
use keos::{
fs::{file_system, File},
mm::Page,
sync::SpinLock,
};
use kev::{
vcpu::{GenericVCpuState, VmexitResult},
vm::Gpa,
Probe, VmError,
};
use project3::{
ept::EptMappingError,
keos_vm::pager::KernelVmPager,
vmexit::mmio::{self, MmioInfo, MmioRegion},
};
pub struct SimpleVirtioBlockDevInner {
status: VirtIoStatus,
virt_queue: Option<VirtQueue<&'static [VirtQueueEntry]>>,
file_system: File,
}
pub struct SimpleVirtIoBlockDev {
inner: Arc<SpinLock<SimpleVirtioBlockDevInner>>,
}
impl SimpleVirtIoBlockDev {
pub fn new() -> Self {
let this = SimpleVirtioBlockDevInner {
status: VirtIoStatus::MAGIC,
virt_queue: None,
file_system: file_system().unwrap().open("disk_file").unwrap(),
};
Self {
inner: Arc::new(SpinLock::new(this)),
}
}
pub fn attach(
&self,
pager: &mut KernelVmPager,
mmio_ctl: &mut mmio::Controller,
) -> Result<(), EptMappingError> {
todo!()
}
}
impl mmio::MmioHandler for SimpleVirtIoBlockDev {
fn region(&self) -> MmioRegion {
MmioRegion {
start: Gpa::new(0xcafe0000).unwrap(),
end: Gpa::new(0xcafe0000 + size_of::<VirtIoMmioHeader>()).unwrap(),
}
}
fn handle(
&mut self,
p: &dyn Probe,
info: MmioInfo,
generic_vcpu_state: &mut GenericVCpuState,
) -> Result<VmexitResult, VmError> {
if let mmio::Direction::Write32 { dst, src } = info.direction {
todo!()
} else {
Ok(VmexitResult::Ok)
}
}
}