From 6fba62fdfab4fa4a7482be92071bd704b336acf9 Mon Sep 17 00:00:00 2001 From: Yuke Peng Date: Tue, 20 Sep 2022 21:26:11 -0700 Subject: [PATCH] finish pci virtio block device driver --- src/kxos-boot/src/main.rs | 4 +- src/kxos-frame/src/config.rs | 4 - src/kxos-frame/src/drivers/mod.rs | 2 + src/kxos-frame/src/drivers/msix.rs | 71 +++++ src/kxos-frame/src/drivers/pci.rs | 24 +- .../src/drivers/virtio/block/mod.rs | 157 ++++++++++ .../src/drivers/virtio/block/virtio_blk.rs | 186 +++++++++++ src/kxos-frame/src/drivers/virtio/mod.rs | 158 ++++++++++ src/kxos-frame/src/drivers/virtio/queue.rs | 289 ++++++++++++++++++ src/kxos-frame/src/error.rs | 1 + src/kxos-frame/src/lib.rs | 2 + src/kxos-frame/src/mm/mod.rs | 1 + src/kxos-frame/src/trap/irq.rs | 17 +- src/kxos-frame/src/trap/mod.rs | 2 +- 14 files changed, 898 insertions(+), 20 deletions(-) create mode 100644 src/kxos-frame/src/drivers/msix.rs create mode 100644 src/kxos-frame/src/drivers/virtio/block/mod.rs create mode 100644 src/kxos-frame/src/drivers/virtio/block/virtio_blk.rs create mode 100644 src/kxos-frame/src/drivers/virtio/mod.rs create mode 100644 src/kxos-frame/src/drivers/virtio/queue.rs diff --git a/src/kxos-boot/src/main.rs b/src/kxos-boot/src/main.rs index 7ce7da49..7690bb66 100644 --- a/src/kxos-boot/src/main.rs +++ b/src/kxos-boot/src/main.rs @@ -89,8 +89,8 @@ fn create_fs_image(path: &Path) -> anyhow::Result { .write(true) .create(true) .open(fs_img_path.as_str())?; - // 64MiB - f.set_len(1 * 1024 * 1024).unwrap(); + // 16MiB + f.set_len(16 * 1024 * 1024).unwrap(); Ok(format!( "file={},if=none,format=raw,id=x0", fs_img_path.as_str() diff --git a/src/kxos-frame/src/config.rs b/src/kxos-frame/src/config.rs index b024bec7..99cb14c4 100644 --- a/src/kxos-frame/src/config.rs +++ b/src/kxos-frame/src/config.rs @@ -12,10 +12,6 @@ pub const ENTRY_COUNT: usize = 512; pub const PAGE_SIZE: usize = 0x1000; pub const PAGE_SIZE_BITS: usize = 0xc; -pub const MEM_START: usize = 0x8000_0000; - -pub const TRAMPOLINE: usize = usize::MAX - PAGE_SIZE + 1; -pub const TRAP_CONTEXT_BASE: usize = TRAMPOLINE - PAGE_SIZE; pub const KVA_START: usize = (usize::MAX) << PAGE_SIZE_BITS; diff --git a/src/kxos-frame/src/drivers/mod.rs b/src/kxos-frame/src/drivers/mod.rs index 8d34b152..96ca07e1 100644 --- a/src/kxos-frame/src/drivers/mod.rs +++ b/src/kxos-frame/src/drivers/mod.rs @@ -1,4 +1,6 @@ +pub(crate) mod msix; mod pci; +pub mod virtio; pub fn init() { pci::init(); diff --git a/src/kxos-frame/src/drivers/msix.rs b/src/kxos-frame/src/drivers/msix.rs new file mode 100644 index 00000000..df4914a7 --- /dev/null +++ b/src/kxos-frame/src/drivers/msix.rs @@ -0,0 +1,71 @@ +use alloc::vec::Vec; +use pci::*; + +use crate::{mm, trap::NOT_USING_IRQ_NUMBER}; + +use super::pci::*; + +#[derive(Debug)] +#[repr(C)] +pub struct CapabilityMSIXData { + pub cap_ptr: u16, + pub table_size: u16, + pub table: Vec, + /// pointing to the Pending Table + pub pba_addr: u64, +} + +#[derive(Debug)] +pub struct MSIXEntry { + pub table_entry: &'static mut MSIXTableEntry, + pub allocate_irq: u8, +} + +#[derive(Debug)] +#[repr(C)] +pub struct MSIXTableEntry { + pub msg_addr: u32, + pub msg_upper_addr: u32, + pub msg_data: u32, + pub vector_control: u32, +} + +impl CapabilityMSIXData { + pub unsafe fn handle(loc: Location, cap_ptr: u16) -> Self { + let ops = &PortOpsImpl; + let am = CSpaceAccessMethod::IO; + let message_control = am.read16(ops, loc, cap_ptr + 2); + let table_info = am.read32(ops, loc, cap_ptr + 4); + let pba_info = am.read32(ops, loc, cap_ptr + 8); + let table_size = table_info & (0b11_1111_1111); + let mut cap = Self { + cap_ptr: cap_ptr, + table_size: table_size as u16, + table: Vec::new(), + pba_addr: mm::phys_to_virt( + (pba_info / 8 + am.read32(ops, loc, PCI_BAR + ((pba_info & 0b111) as u16) * 4)) + as usize, + ) as u64, + }; + let mut table_addr = mm::phys_to_virt( + (table_info / 8 + am.read32(ops, loc, PCI_BAR + ((table_info & 0b111) as u16) * 4)) + as usize, + ); + for i in 0..table_size { + let entry = &mut *(table_addr as *const usize as *mut MSIXTableEntry); + entry.msg_addr = 0xFEE0_0000; + // allocate irq number + let irq_number = NOT_USING_IRQ_NUMBER.exclusive_access().pop().unwrap(); + entry.msg_data = irq_number as u32; + entry.vector_control = 0; + cap.table.push(MSIXEntry { + table_entry: entry, + allocate_irq: irq_number, + }); + table_addr += 32; + } + // enable MSI-X + am.write8(ops, loc, cap_ptr, 0b1000_0000); + cap + } +} diff --git a/src/kxos-frame/src/drivers/pci.rs b/src/kxos-frame/src/drivers/pci.rs index b9fbadd1..f57b740f 100644 --- a/src/kxos-frame/src/drivers/pci.rs +++ b/src/kxos-frame/src/drivers/pci.rs @@ -1,20 +1,19 @@ -use crate::*; +use crate::{drivers::virtio, trap::NOT_USING_IRQ_NUMBER, *}; use pci::*; -const PCI_COMMAND: u16 = 0x04; -const PCI_CAP_PTR: u16 = 0x34; -const PCI_INTERRUPT_LINE: u16 = 0x3c; -const PCI_INTERRUPT_PIN: u16 = 0x3d; +pub(crate) const PCI_COMMAND: u16 = 0x04; +pub(crate) const PCI_BAR: u16 = 0x10; +pub(crate) const PCI_CAP_PTR: u16 = 0x34; +pub(crate) const PCI_INTERRUPT_LINE: u16 = 0x3c; +pub(crate) const PCI_INTERRUPT_PIN: u16 = 0x3d; -const PCI_MSI_CTRL_CAP: u16 = 0x00; -const PCI_MSI_ADDR: u16 = 0x04; -const PCI_MSI_UPPER_ADDR: u16 = 0x08; -const PCI_MSI_DATA_32: u16 = 0x08; -const PCI_MSI_DATA_64: u16 = 0x0C; +pub(crate) const PCI_MSIX_CTRL_CAP: u16 = 0x00; +pub(crate) const PCI_MSIX_TABLE: u16 = 0x04; +pub(crate) const PCI_MSIX_PBA: u16 = 0x08; -const PCI_CAP_ID_MSI: u8 = 0x05; +pub(crate) const PCI_CAP_ID_MSI: u8 = 0x05; -struct PortOpsImpl; +pub(crate) struct PortOpsImpl; impl PortOps for PortOpsImpl { unsafe fn read8(&self, port: u16) -> u8 { @@ -57,6 +56,7 @@ pub fn init() { { // virtio block device mass storage info!("found virtio pci block device"); + virtio::block::init(dev); } } } diff --git a/src/kxos-frame/src/drivers/virtio/block/mod.rs b/src/kxos-frame/src/drivers/virtio/block/mod.rs new file mode 100644 index 00000000..18a02e15 --- /dev/null +++ b/src/kxos-frame/src/drivers/virtio/block/mod.rs @@ -0,0 +1,157 @@ +mod virtio_blk; + +use core::{any::Any, mem::transmute}; + +use crate::prelude::*; +use crate::{ + cell::Cell, + drivers::pci::{PortOpsImpl, PCI_BAR}, + info, mm, + trap::{IrqCallbackHandle, IrqLine, TrapFrame}, +}; +use pci::{CSpaceAccessMethod, Location, PCIDevice}; + +use super::AsBuf; + +pub const BLK_SIZE: usize = 512; + +pub trait BlockDevice: Send + Sync + Any { + fn read_block(&self, block_id: usize, buf: &mut [u8]) -> Result<()>; + fn write_block(&self, block_id: usize, buf: &[u8]) -> Result<()>; + fn handle_irq(&self); +} + +pub static BLOCK_DEVICE: Cell> = unsafe { + transmute(&0 as *const _ as *const virtio_blk::VirtIOBlock as *const dyn BlockDevice) +}; + +static mut BLOCK_DEVICE_IRQ_CALLBACK_LIST: Vec = Vec::new(); + +pub fn init(loc: PCIDevice) { + let dev = virtio_blk::VirtIOBlock::new(loc); + unsafe { + (BLOCK_DEVICE.get() as *mut Arc).write(Arc::new(dev)); + } +} + +#[repr(u8)] +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +pub enum RespStatus { + /// Ok. + Ok = 0, + /// IoErr. + IoErr = 1, + /// Unsupported yet. + Unsupported = 2, + /// Not ready. + _NotReady = 3, +} + +#[derive(Debug)] +#[repr(C)] +pub struct VirtioBLKConfig { + pub capacity: u64, + pub size_max: u64, + pub geometry: VirtioBLKGeometry, + pub blk_size: u32, + pub topology: VirtioBLKTopology, + pub writeback: u8, + pub unused0: [u8; 3], + pub max_discard_sectors: u32, + pub max_discard_seg: u32, + pub discard_sector_alignment: u32, + pub max_write_zeroes_sectors: u32, + pub max_write_zeroes_seg: u32, + pub write_zeros_may_unmap: u8, + pub unused1: [u8; 3], +} + +#[repr(C)] +#[derive(Debug)] +struct BlkReq { + type_: ReqType, + reserved: u32, + sector: u64, +} + +/// Response of a VirtIOBlk request. +#[repr(C)] +#[derive(Debug)] +pub struct BlkResp { + pub status: RespStatus, +} + +#[repr(u32)] +#[derive(Debug)] +enum ReqType { + In = 0, + Out = 1, + Flush = 4, + Discard = 11, + WriteZeroes = 13, +} + +#[derive(Debug)] +#[repr(C)] +pub struct VirtioBLKGeometry { + pub cylinders: u16, + pub heads: u8, + pub sectors: u8, +} + +#[derive(Debug)] +#[repr(C)] +pub struct VirtioBLKTopology { + pub physical_block_exp: u8, + pub alignment_offset: u8, + pub min_io_size: u16, + pub opt_io_size: u32, +} + +impl VirtioBLKConfig { + pub unsafe fn new(loc: Location, cap_ptr: u16) -> &'static mut Self { + let ops = &PortOpsImpl; + let am = CSpaceAccessMethod::IO; + let bar = am.read8(ops, loc, cap_ptr + 4); + let offset = am.read32(ops, loc, cap_ptr + 8); + let bar_address = am.read32(ops, loc, PCI_BAR + bar as u16 * 4) & (!(0b1111)); + &mut *(mm::phys_to_virt(bar_address as usize + offset as usize) as *const usize + as *mut Self) + } +} + +impl Default for BlkResp { + fn default() -> Self { + BlkResp { + status: RespStatus::_NotReady, + } + } +} + +unsafe impl AsBuf for BlkReq {} +unsafe impl AsBuf for BlkResp {} + +pub fn read_block(block_id: usize, buf: &mut [u8]) -> Result<()> { + BLOCK_DEVICE.get().read_block(block_id, buf) +} + +pub fn write_block(block_id: usize, buf: &[u8]) -> Result<()> { + BLOCK_DEVICE.get().write_block(block_id, buf) +} + +#[allow(unused)] +fn block_device_test() { + let block_device = BLOCK_DEVICE.clone(); + let mut write_buffer = [0u8; 512]; + let mut read_buffer = [0u8; 512]; + info!("test:{:x}", write_buffer.as_ptr() as usize); + for i in 0..512 { + for byte in write_buffer.iter_mut() { + *byte = i as u8; + } + block_device.write_block(i as usize, &write_buffer); + block_device.read_block(i as usize, &mut read_buffer); + assert_eq!(write_buffer, read_buffer); + } + info!("block device test passed!"); +} diff --git a/src/kxos-frame/src/drivers/virtio/block/virtio_blk.rs b/src/kxos-frame/src/drivers/virtio/block/virtio_blk.rs new file mode 100644 index 00000000..094230d0 --- /dev/null +++ b/src/kxos-frame/src/drivers/virtio/block/virtio_blk.rs @@ -0,0 +1,186 @@ +use core::hint::spin_loop; + +use alloc::collections::BTreeMap; +use pci::{CSpaceAccessMethod, PCIDevice}; + +use crate::{ + drivers::{ + msix::CapabilityMSIXData, + pci::*, + virtio::{block::*, queue::VirtQueue, *}, + }, + info, + task::Task, + zero, Error, +}; + +use super::BlockDevice; + +pub struct VirtIOBlock { + // virtio_blk: Cell>, + common_cfg: &'static mut VitrioPciCommonCfg, + dev_cfg: &'static mut VirtioBLKConfig, + queue: Cell, + tasks: BTreeMap>, + irq_callback: IrqCallbackHandle, +} + +impl BlockDevice for VirtIOBlock { + fn read_block(&self, block_id: usize, buf: &mut [u8]) -> Result<()> { + assert_eq!(buf.len(), BLK_SIZE); + let req = BlkReq { + type_: ReqType::In, + reserved: 0, + sector: block_id as u64, + }; + let mut resp = BlkResp::default(); + let queue = self.queue.get(); + queue + .add(&[req.as_buf()], &[buf, resp.as_buf_mut()]) + .expect("add queue failed"); + queue.notify(); + while !queue.can_pop() { + spin_loop(); + } + queue.pop_used().expect("pop used failed"); + match resp.status { + RespStatus::Ok => Ok(()), + _ => Err(Error::IoError), + } + } + /// it is blocking now + fn write_block(&self, block_id: usize, buf: &[u8]) -> Result<()> { + assert_eq!(buf.len(), BLK_SIZE); + let req = BlkReq { + type_: ReqType::Out, + reserved: 0, + sector: block_id as u64, + }; + let mut resp = BlkResp::default(); + let queue = self.queue.get(); + queue + .add(&[req.as_buf(), buf], &[resp.as_buf_mut()]) + .expect("add queue failed"); + queue.notify(); + while !queue.can_pop() { + spin_loop(); + } + queue.pop_used().expect("pop used failed"); + match resp.status { + RespStatus::Ok => Ok(()), + _ => Err(Error::IoError), + } + } + fn handle_irq(&self) { + info!("handle irq in block device!"); + } +} + +impl VirtIOBlock { + pub fn new(dev: PCIDevice) -> Self { + fn handle_block_device(frame: TrapFrame) { + BLOCK_DEVICE.get().handle_irq() + } + let (msix, common_cfg, dev_cfg, cap_offset, notify_off_multiplier); + unsafe { + (msix, common_cfg, dev_cfg, cap_offset, notify_off_multiplier) = Self::enable(dev.loc) + }; + common_cfg.device_status = DeviceStatus::ACKNOWLEDGE.bits(); + common_cfg.device_status = DeviceStatus::DRIVER.bits(); + common_cfg.device_status = DeviceStatus::FEATURES_OK.bits(); + let queue = VirtQueue::new(common_cfg, 0, 16, cap_offset, notify_off_multiplier) + .expect("error creating virtqueue"); + common_cfg.queue_enable = 1; + common_cfg.device_status = DeviceStatus::DRIVER_OK.bits(); + let mut tasks = BTreeMap::new(); + let channels = queue.size(); + for i in 0..channels { + tasks.insert(i, None); + } + let msix_entry = msix + .table + .get(common_cfg.queue_msix_vector as usize) + .unwrap(); + // register interrupt + let irq_number = msix_entry.allocate_irq; + let irq; + unsafe { + irq = IrqLine::acquire(irq_number); + } + let blk = Self { + common_cfg, + dev_cfg, + queue: Cell::new(queue), + tasks: tasks, + irq_callback: irq.on_active(handle_block_device), + }; + blk + } + + /// Enable the pci device and virtio MSIX + /// need to activate the specific device + /// return the msix, virtio pci common cfg, virtio block device config, + /// the virtual address of cap.offset and notify_off_multiplier + unsafe fn enable( + loc: Location, + ) -> ( + CapabilityMSIXData, + &'static mut VitrioPciCommonCfg, + &'static mut VirtioBLKConfig, + usize, + u32, + ) { + let ops = &PortOpsImpl; + let am = CSpaceAccessMethod::IO; + + // 23 and lower are used, use 22-27 + static mut MSI_IRQ: u32 = 23; + let mut cap_ptr = am.read8(ops, loc, PCI_CAP_PTR) as u16; + let mut msix = zero(); + let mut init = false; + let mut common_cfg = zero(); + let mut dev_cfg = zero(); + let mut notify_off_multiplier: u32 = 0; + let mut cap_offset: usize = 0; + while cap_ptr > 0 { + let cap_vndr = am.read8(ops, loc, cap_ptr); + match cap_vndr { + 9 => { + let cap = PciVirtioCapability::handle(loc, cap_ptr); + match cap.cfg { + CFGType::COMMON(x) => { + common_cfg = x; + } + CFGType::NOTIFY(x) => { + let bar = cap.bar; + let bar_address = + am.read32(ops, loc, PCI_BAR + bar as u16 * 4) & (!(0b1111)); + cap_offset = mm::phys_to_virt((bar_address + cap.offset) as usize); + notify_off_multiplier = x; + } + CFGType::DEVICE(dev) => { + match dev { + VirtioDeviceCFG::Block(x) => dev_cfg = x, + _ => { + panic!("wrong device while initalize virtio block device") + } + }; + } + _ => {} + }; + } + 17 => { + msix = CapabilityMSIXData::handle(loc, cap_ptr); + init = true; + } + _ => panic!("unsupport capability, id:{}", cap_vndr), + }; + cap_ptr = am.read8(ops, loc, cap_ptr + 1) as u16; + } + if !init { + panic!("PCI Virtio Block Device initalize incomplete, not found msix"); + } + common_cfg.queue_msix_vector = 0; + (msix, common_cfg, dev_cfg, cap_offset, notify_off_multiplier) + } +} diff --git a/src/kxos-frame/src/drivers/virtio/mod.rs b/src/kxos-frame/src/drivers/virtio/mod.rs new file mode 100644 index 00000000..8bf5cdbc --- /dev/null +++ b/src/kxos-frame/src/drivers/virtio/mod.rs @@ -0,0 +1,158 @@ +use core::mem::size_of; + +use crate::mm; +use bitflags::bitflags; +use pci::{CSpaceAccessMethod, Location}; + +use self::block::VirtioBLKConfig; + +use super::pci::*; + +pub(crate) mod block; +pub(crate) mod queue; + +pub(crate) const PCI_VIRTIO_CAP_COMMON_CFG: u8 = 1; +pub(crate) const PCI_VIRTIO_CAP_NOTIFY_CFG: u8 = 2; +pub(crate) const PCI_VIRTIO_CAP_ISR_CFG: u8 = 3; +pub(crate) const PCI_VIRTIO_CAP_DEVICE_CFG: u8 = 4; +pub(crate) const PCI_VIRTIO_CAP_PCI_CFG: u8 = 5; + +bitflags! { + /// The device status field. + pub(crate) struct DeviceStatus: u8 { + /// 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; + } +} + +#[derive(Debug)] +#[repr(C)] +pub(crate) struct VitrioPciCommonCfg { + device_feature_select: u32, + device_feature: u32, + driver_feature_select: u32, + driver_feature: u32, + config_msix_vector: u16, + num_queues: u16, + device_status: u8, + config_generation: u8, + + queue_select: u16, + queue_size: u16, + queue_msix_vector: u16, + queue_enable: u16, + queue_notify_off: u16, + queue_desc: u64, + queue_driver: u64, + queue_device: u64, +} + +#[derive(Debug)] +enum CFGType { + COMMON(&'static mut VitrioPciCommonCfg), + NOTIFY(u32), + ISR, + DEVICE(VirtioDeviceCFG), + PCI, +} + +#[derive(Debug)] +enum VirtioDeviceCFG { + Network, + Block(&'static mut VirtioBLKConfig), + Console, + Entropy, + TraditionalMemoryBalloon, + ScsiHost, + GPU, + Input, + Crypto, + Socket, +} + +#[derive(Debug)] +struct PciVirtioCapability { + pub cap_vndr: u8, + pub cap_ptr: u16, + pub cap_len: u8, + pub cfg_type: u8, + pub cfg: CFGType, + pub bar: u8, + pub offset: u32, + pub length: u32, +} + +impl VitrioPciCommonCfg { + pub unsafe fn new(loc: Location, cap_ptr: u16) -> &'static mut Self { + let ops = &PortOpsImpl; + let am = CSpaceAccessMethod::IO; + let bar = am.read8(ops, loc, cap_ptr + 4); + let offset = am.read32(ops, loc, cap_ptr + 8); + let bar_address = am.read32(ops, loc, PCI_BAR + bar as u16 * 4) & (!(0b1111)); + &mut *(mm::phys_to_virt(bar_address as usize + offset as usize) as *const usize + as *mut Self) + } +} + +impl PciVirtioCapability { + pub unsafe fn handle(loc: Location, cap_ptr: u16) -> Self { + let ops = &PortOpsImpl; + let am = CSpaceAccessMethod::IO; + let cap_vndr = am.read8(ops, loc, cap_ptr); + let cap_next = am.read8(ops, loc, cap_ptr + 1); + let cap_len = am.read8(ops, loc, cap_ptr + 2); + let cfg_type = am.read8(ops, loc, cap_ptr + 3); + let cfg = match cfg_type { + PCI_VIRTIO_CAP_COMMON_CFG => CFGType::COMMON(VitrioPciCommonCfg::new(loc, cap_ptr)), + PCI_VIRTIO_CAP_NOTIFY_CFG => CFGType::NOTIFY(am.read32(ops, loc, cap_ptr + 16)), + PCI_VIRTIO_CAP_ISR_CFG => CFGType::ISR, + PCI_VIRTIO_CAP_DEVICE_CFG => { + CFGType::DEVICE(VirtioDeviceCFG::Block(VirtioBLKConfig::new(loc, cap_ptr))) + } + PCI_VIRTIO_CAP_PCI_CFG => CFGType::PCI, + _ => panic!("unsupport cfg, cfg_type:{}", cfg_type), + }; + let cap = PciVirtioCapability { + cap_vndr: cap_vndr, + cap_ptr: cap_ptr, + cap_len: cap_len, + cfg_type: cfg_type, + cfg: cfg, + bar: am.read8(ops, loc, cap_ptr + 4), + offset: am.read32(ops, loc, cap_ptr + 8), + length: am.read32(ops, loc, cap_ptr + 12), + }; + cap + } +} + +/// Convert a struct into a byte buffer. +unsafe trait AsBuf: Sized { + fn as_buf(&self) -> &[u8] { + unsafe { core::slice::from_raw_parts(self as *const _ as _, size_of::()) } + } + fn as_buf_mut(&mut self) -> &mut [u8] { + unsafe { core::slice::from_raw_parts_mut(self as *mut _ as _, size_of::()) } + } +} diff --git a/src/kxos-frame/src/drivers/virtio/queue.rs b/src/kxos-frame/src/drivers/virtio/queue.rs new file mode 100644 index 00000000..c1caa35a --- /dev/null +++ b/src/kxos-frame/src/drivers/virtio/queue.rs @@ -0,0 +1,289 @@ +//! FIXME: use Volatile +use crate::mm::address::align_up; +use core::mem::size_of; +use core::slice; +use core::sync::atomic::{fence, Ordering}; + +use super::*; +#[derive(Debug)] +pub enum QueueError { + InvalidArgs, + BufferTooSmall, + NotReady, + AlreadyUsed, +} + +#[derive(Debug)] +#[repr(C)] +pub struct QueueNotify { + notify: u32, +} + +/// The mechanism for bulk data transport on virtio devices. +/// +/// Each device can have zero or more virtqueues. +#[derive(Debug)] +pub(crate) struct VirtQueue { + /// Descriptor table + desc: &'static mut [Descriptor], + /// Available ring + avail: &'static mut AvailRing, + /// Used ring + used: &'static mut UsedRing, + /// point to notify address + notify: &'static mut QueueNotify, + + /// The index of queue + queue_idx: u32, + /// The size of the queue. + /// + /// This is both the number of descriptors, and the number of slots in the available and used + /// rings. + queue_size: u16, + /// The number of used queues. + num_used: u16, + /// The head desc index of the free list. + free_head: u16, + avail_idx: u16, + last_used_idx: u16, +} + +impl VirtQueue { + /// Create a new VirtQueue. + pub fn new( + cfg: &mut VitrioPciCommonCfg, + idx: usize, + size: u16, + cap_offset: usize, + notify_off_multiplier: u32, + ) -> Result { + if !size.is_power_of_two() || cfg.queue_size < size { + return Err(QueueError::InvalidArgs); + } + let layout = VirtQueueLayout::new(size); + // Allocate contiguous pages. + + cfg.queue_select = idx as u16; + cfg.queue_size = size; + + let desc = unsafe { + slice::from_raw_parts_mut( + mm::phys_to_virt(cfg.queue_desc as usize) as *mut Descriptor, + size as usize, + ) + }; + let avail = + unsafe { &mut *(mm::phys_to_virt(cfg.queue_driver as usize) as *mut AvailRing) }; + let used = unsafe { &mut *(mm::phys_to_virt(cfg.queue_device as usize) as *mut UsedRing) }; + let notify = unsafe { + &mut *((cap_offset + notify_off_multiplier as usize * idx) as *mut QueueNotify) + }; + // Link descriptors together. + for i in 0..(size - 1) { + desc[i as usize].next = i + 1; + } + + Ok(VirtQueue { + desc, + avail, + used, + notify, + queue_size: size, + queue_idx: idx as u32, + num_used: 0, + free_head: 0, + avail_idx: 0, + last_used_idx: 0, + }) + } + + /// Add buffers to the virtqueue, return a token. + /// + /// Ref: linux virtio_ring.c virtqueue_add + pub fn add(&mut self, inputs: &[&[u8]], outputs: &[&mut [u8]]) -> Result { + if inputs.is_empty() && outputs.is_empty() { + return Err(QueueError::InvalidArgs); + } + if inputs.len() + outputs.len() + self.num_used as usize > self.queue_size as usize { + return Err(QueueError::BufferTooSmall); + } + + // allocate descriptors from free list + let head = self.free_head; + let mut last = self.free_head; + for input in inputs.iter() { + let desc = &mut self.desc[self.free_head as usize]; + desc.set_buf(input); + desc.flags = DescFlags::NEXT; + last = self.free_head; + self.free_head = desc.next; + } + for output in outputs.iter() { + let desc = &mut self.desc[self.free_head as usize]; + desc.set_buf(output); + desc.flags = DescFlags::NEXT | DescFlags::WRITE; + last = self.free_head; + self.free_head = desc.next; + } + // set last_elem.next = NULL + { + let desc = &mut self.desc[last as usize]; + let mut flags = desc.flags; + flags.remove(DescFlags::NEXT); + desc.flags = flags; + } + self.num_used += (inputs.len() + outputs.len()) as u16; + + let avail_slot = self.avail_idx & (self.queue_size - 1); + self.avail.ring[avail_slot as usize] = head; + + // write barrier + fence(Ordering::SeqCst); + + // increase head of avail ring + self.avail_idx = self.avail_idx.wrapping_add(1); + self.avail.idx = self.avail_idx; + Ok(head) + } + + /// Whether there is a used element that can pop. + pub fn can_pop(&self) -> bool { + self.last_used_idx != self.used.idx + } + + /// The number of free descriptors. + pub fn available_desc(&self) -> usize { + (self.queue_size - self.num_used) as usize + } + + /// Recycle descriptors in the list specified by head. + /// + /// This will push all linked descriptors at the front of the free list. + fn recycle_descriptors(&mut self, mut head: u16) { + let origin_free_head = self.free_head; + self.free_head = head; + loop { + let desc = &mut self.desc[head as usize]; + let flags = desc.flags; + self.num_used -= 1; + if flags.contains(DescFlags::NEXT) { + head = desc.next; + } else { + desc.next = origin_free_head; + return; + } + } + } + + /// Get a token from device used buffers, return (token, len). + /// + /// Ref: linux virtio_ring.c virtqueue_get_buf_ctx + pub fn pop_used(&mut self) -> Result<(u16, u32), QueueError> { + if !self.can_pop() { + return Err(QueueError::NotReady); + } + // read barrier + fence(Ordering::SeqCst); + + let last_used_slot = self.last_used_idx & (self.queue_size - 1); + let index = self.used.ring[last_used_slot as usize].id as u16; + let len = self.used.ring[last_used_slot as usize].len; + + self.recycle_descriptors(index); + self.last_used_idx = self.last_used_idx.wrapping_add(1); + + Ok((index, len)) + } + + /// Return size of the queue. + pub fn size(&self) -> u16 { + self.queue_size + } + + pub fn notify(&mut self) { + self.notify.notify = 0 + } +} + +/// The inner layout of a VirtQueue. +/// +/// Ref: 2.6.2 Legacy Interfaces: A Note on Virtqueue Layout +struct VirtQueueLayout { + avail_offset: usize, + used_offset: usize, + size: usize, +} + +impl VirtQueueLayout { + fn new(queue_size: u16) -> Self { + assert!( + queue_size.is_power_of_two(), + "queue size should be a power of 2" + ); + let queue_size = queue_size as usize; + let desc = size_of::() * queue_size; + let avail = size_of::() * (3 + queue_size); + let used = size_of::() * 3 + size_of::() * queue_size; + VirtQueueLayout { + avail_offset: desc, + used_offset: align_up(desc + avail), + size: align_up(desc + avail) + align_up(used), + } + } +} + +#[repr(C, align(16))] +#[derive(Debug)] +struct Descriptor { + addr: u64, + len: u32, + flags: DescFlags, + next: u16, +} + +impl Descriptor { + fn set_buf(&mut self, buf: &[u8]) { + self.addr = mm::virt_to_phys(buf.as_ptr() as usize) as u64; + self.len = buf.len() as u32; + } +} + +bitflags! { + /// Descriptor flags + struct DescFlags: u16 { + const NEXT = 1; + const WRITE = 2; + const INDIRECT = 4; + } +} + +/// The driver uses the available ring to offer buffers to the device: +/// each ring entry refers to the head of a descriptor chain. +/// It is only written by the driver and read by the device. +#[repr(C)] +#[derive(Debug)] +struct AvailRing { + flags: u16, + /// A driver MUST NOT decrement the idx. + idx: u16, + ring: [u16; 32], // actual size: queue_size + used_event: u16, // unused +} + +/// The used ring is where the device returns buffers once it is done with them: +/// it is only written to by the device, and read by the driver. +#[repr(C)] +#[derive(Debug)] +struct UsedRing { + flags: u16, + idx: u16, + ring: [UsedElem; 32], // actual size: queue_size + avail_event: u16, // unused +} + +#[repr(C)] +#[derive(Debug)] +struct UsedElem { + id: u32, + len: u32, +} diff --git a/src/kxos-frame/src/error.rs b/src/kxos-frame/src/error.rs index 0f4d9021..e4a0cb11 100644 --- a/src/kxos-frame/src/error.rs +++ b/src/kxos-frame/src/error.rs @@ -5,4 +5,5 @@ pub enum Error { NoMemory, PageFault, AccessDenied, + IoError, } diff --git a/src/kxos-frame/src/lib.rs b/src/kxos-frame/src/lib.rs index 771b7ded..fb48d731 100644 --- a/src/kxos-frame/src/lib.rs +++ b/src/kxos-frame/src/lib.rs @@ -41,6 +41,8 @@ use bootloader::{ }; use trap::{IrqCallbackHandle, IrqLine, TrapFrame}; +pub use self::drivers::virtio::block::{read_block, write_block}; + static mut IRQ_CALLBACK_LIST: Vec = Vec::new(); #[cfg(not(feature = "serial_print"))] diff --git a/src/kxos-frame/src/mm/mod.rs b/src/kxos-frame/src/mm/mod.rs index be548c59..bd48aa7d 100644 --- a/src/kxos-frame/src/mm/mod.rs +++ b/src/kxos-frame/src/mm/mod.rs @@ -10,6 +10,7 @@ use address::PhysAddr; use address::VirtAddr; pub use self::{frame_allocator::*, memory_set::*, page_table::*}; +pub(crate) use address::{phys_to_virt, virt_to_phys}; bitflags::bitflags! { /// Possible flags for a page table entry. diff --git a/src/kxos-frame/src/trap/irq.rs b/src/kxos-frame/src/trap/irq.rs index 10a46d50..eef15d8a 100644 --- a/src/kxos-frame/src/trap/irq.rs +++ b/src/kxos-frame/src/trap/irq.rs @@ -1,9 +1,24 @@ -use crate::prelude::*; +use crate::{prelude::*, sync::up::UPSafeCell}; use super::TrapFrame; use lazy_static::lazy_static; use spin::{Mutex, MutexGuard}; +lazy_static! { + /// The IRQ numbers which are not using + /// FIXME: using alloc, dealloc instead of letting user use push and pop method. + pub static ref NOT_USING_IRQ_NUMBER:UPSafeCell> = unsafe {UPSafeCell::new({ + let mut vector = Vec::new(); + for i in 31..256{ + vector.push(i as u8); + } + for i in 22..28{ + vector.push(i as u8); + } + vector + })}; +} + lazy_static! { pub static ref IRQ_LIST: Vec = { let mut list: Vec = Vec::new(); diff --git a/src/kxos-frame/src/trap/mod.rs b/src/kxos-frame/src/trap/mod.rs index 113a3b90..5f48cc35 100644 --- a/src/kxos-frame/src/trap/mod.rs +++ b/src/kxos-frame/src/trap/mod.rs @@ -1,7 +1,7 @@ mod handler; mod irq; -pub use self::irq::{IrqCallbackHandle, IrqLine}; +pub use self::irq::{IrqCallbackHandle, IrqLine, NOT_USING_IRQ_NUMBER}; use core::mem::size_of_val; use crate::{x86_64_util::*, *};