virtiod: split into virtio-core and virtiod
Signed-off-by: Anhad Singh <andypython@protonmail.com>
This commit is contained in:
Generated
+13
-1
@@ -1904,12 +1904,23 @@ dependencies = [
|
||||
"rusttype",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "virtio-core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags 2.3.2",
|
||||
"log",
|
||||
"pcid",
|
||||
"redox_syscall 0.3.5",
|
||||
"static_assertions",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "virtiod"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.3.2",
|
||||
"block-io-wrapper",
|
||||
"log",
|
||||
"partitionlib",
|
||||
@@ -1920,6 +1931,7 @@ dependencies = [
|
||||
"redox_syscall 0.3.5",
|
||||
"static_assertions",
|
||||
"thiserror",
|
||||
"virtio-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "virtio-core"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Anhad Singh <andypython@protonmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
static_assertions = "1.1.0"
|
||||
bitflags = "2.3.2"
|
||||
redox_syscall = "0.3"
|
||||
log = "0.4"
|
||||
thiserror = "1.0.40"
|
||||
|
||||
pcid = { path = "../pcid" }
|
||||
@@ -0,0 +1,9 @@
|
||||
#![feature(int_roundings)]
|
||||
|
||||
pub mod spec;
|
||||
pub mod transport;
|
||||
pub mod utils;
|
||||
|
||||
mod probe;
|
||||
|
||||
pub use probe::{probe_device, MSIX_PRIMARY_VECTOR, Device};
|
||||
@@ -0,0 +1,271 @@
|
||||
use std::fs::File;
|
||||
use std::ptr::NonNull;
|
||||
use std::sync::Arc;
|
||||
|
||||
use pcid_interface::irq_helpers::{allocate_single_interrupt_vector, read_bsp_apic_id};
|
||||
use pcid_interface::msi::x86_64 as x86_64_msix;
|
||||
use pcid_interface::msi::x86_64::DeliveryMode;
|
||||
use pcid_interface::msi::{MsixCapability, MsixTableEntry};
|
||||
use pcid_interface::*;
|
||||
|
||||
use syscall::{Io, PHYSMAP_NO_CACHE, PHYSMAP_WRITE};
|
||||
|
||||
use crate::spec::*;
|
||||
use crate::transport::{Error, StandardTransport};
|
||||
use crate::utils::VolatileCell;
|
||||
|
||||
pub struct Device<'a> {
|
||||
pub transport: Arc<StandardTransport<'a>>,
|
||||
pub device_space: *const u8,
|
||||
pub irq_handle: File,
|
||||
pub isr: &'a VolatileCell<u32>,
|
||||
}
|
||||
|
||||
struct MsixInfo {
|
||||
pub virt_table_base: NonNull<MsixTableEntry>,
|
||||
pub virt_pba_base: NonNull<u64>,
|
||||
pub capability: MsixCapability,
|
||||
}
|
||||
|
||||
impl MsixInfo {
|
||||
pub unsafe fn table_entry_pointer_unchecked(&mut self, k: usize) -> &mut MsixTableEntry {
|
||||
&mut *self.virt_table_base.as_ptr().add(k)
|
||||
}
|
||||
|
||||
pub fn table_entry_pointer(&mut self, k: usize) -> &mut MsixTableEntry {
|
||||
assert!(k < self.capability.table_size() as usize);
|
||||
unsafe { self.table_entry_pointer_unchecked(k) }
|
||||
}
|
||||
}
|
||||
|
||||
static_assertions::const_assert_eq!(std::mem::size_of::<MsixTableEntry>(), 16);
|
||||
|
||||
pub const MSIX_PRIMARY_VECTOR: u16 = 0;
|
||||
|
||||
fn enable_msix(pcid_handle: &mut PcidServerHandle) -> Result<File, Error> {
|
||||
let pci_config = pcid_handle.fetch_config()?;
|
||||
|
||||
// Extended message signaled interrupts.
|
||||
let capability = match pcid_handle.feature_info(PciFeature::MsiX)? {
|
||||
PciFeatureInfo::MsiX(capability) => capability,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let table_size = capability.table_size();
|
||||
let table_base = capability.table_base_pointer(pci_config.func.bars);
|
||||
let table_min_length = table_size * 16;
|
||||
let pba_min_length = table_size.div_ceil(8);
|
||||
|
||||
let pba_base = capability.pba_base_pointer(pci_config.func.bars);
|
||||
|
||||
let bir = capability.table_bir() as usize;
|
||||
let bar = pci_config.func.bars[bir];
|
||||
let bar_size = pci_config.func.bar_sizes[bir] as u64;
|
||||
|
||||
let bar_ptr = match bar {
|
||||
PciBar::Memory32(ptr) => ptr.into(),
|
||||
PciBar::Memory64(ptr) => ptr,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let address = unsafe {
|
||||
syscall::physmap(
|
||||
bar_ptr as usize,
|
||||
bar_size as usize,
|
||||
PHYSMAP_WRITE | PHYSMAP_NO_CACHE,
|
||||
)?
|
||||
};
|
||||
|
||||
// Ensure that the table and PBA are be within the BAR.
|
||||
{
|
||||
let bar_range = bar_ptr..bar_ptr + bar_size;
|
||||
assert!(bar_range.contains(&(table_base as u64 + table_min_length as u64)));
|
||||
assert!(bar_range.contains(&(pba_base as u64 + pba_min_length as u64)));
|
||||
}
|
||||
|
||||
let virt_table_base = ((table_base - bar_ptr as usize) + address) as *mut MsixTableEntry;
|
||||
let virt_pba_base = ((pba_base - bar_ptr as usize) + address) as *mut u64;
|
||||
|
||||
let mut info = MsixInfo {
|
||||
virt_table_base: NonNull::new(virt_table_base).unwrap(),
|
||||
virt_pba_base: NonNull::new(virt_pba_base).unwrap(),
|
||||
capability,
|
||||
};
|
||||
|
||||
// Allocate the primary MSI vector.
|
||||
let interrupt_handle = {
|
||||
let table_entry_pointer = info.table_entry_pointer(MSIX_PRIMARY_VECTOR as usize);
|
||||
|
||||
let destination_id = read_bsp_apic_id().expect("virtio_core: `read_bsp_apic_id()` failed");
|
||||
let lapic_id = u8::try_from(destination_id).unwrap();
|
||||
|
||||
let rh = false;
|
||||
let dm = false;
|
||||
let addr = x86_64_msix::message_address(lapic_id, rh, dm);
|
||||
|
||||
let (vector, interrupt_handle) = allocate_single_interrupt_vector(destination_id)
|
||||
.unwrap()
|
||||
.expect("virtio_core: interrupt vector exhaustion");
|
||||
|
||||
let msg_data = x86_64_msix::message_data_edge_triggered(DeliveryMode::Fixed, vector);
|
||||
|
||||
table_entry_pointer.addr_lo.write(addr);
|
||||
table_entry_pointer.addr_hi.write(0);
|
||||
table_entry_pointer.msg_data.write(msg_data);
|
||||
table_entry_pointer
|
||||
.vec_ctl
|
||||
.writef(MsixTableEntry::VEC_CTL_MASK_BIT, false);
|
||||
|
||||
interrupt_handle
|
||||
};
|
||||
|
||||
pcid_handle.enable_feature(PciFeature::MsiX)?;
|
||||
|
||||
log::info!("virtio: using MSI-X (interrupt_handle={interrupt_handle:?})");
|
||||
Ok(interrupt_handle)
|
||||
}
|
||||
|
||||
/// VirtIO Device Probe
|
||||
///
|
||||
/// ## Device State
|
||||
/// After this function, the device has been successfully reseted and is ready for use.
|
||||
///
|
||||
/// The caller is required to do the following:
|
||||
/// * Negotiate the device and driver supported features (finialize via [`StandardTransport::finalize_features`])
|
||||
/// * Create the device specific virtio queues (via [`StandardTransport::setup_queue`])
|
||||
/// * Finally start the device (via [`StandardTransport::run_device`]). At this point, the device
|
||||
/// is alive.
|
||||
///
|
||||
/// ## Panics
|
||||
/// This function panics if the device is not a virtio device.
|
||||
pub fn probe_device<'a>(pcid_handle: &mut PcidServerHandle) -> Result<Device<'a>, Error> {
|
||||
let pci_config = pcid_handle.fetch_config()?;
|
||||
let pci_header = pcid_handle.fetch_header()?;
|
||||
|
||||
assert_eq!(
|
||||
pci_config.func.venid, 6900,
|
||||
"virtio_core::probe_device: not a virtio device"
|
||||
);
|
||||
|
||||
let mut common_addr = None;
|
||||
let mut notify_addr = None;
|
||||
let mut isr_addr = None;
|
||||
let mut device_addr = None;
|
||||
|
||||
for capability in pcid_handle
|
||||
.get_capabilities()?
|
||||
.iter()
|
||||
.filter_map(|capability| {
|
||||
if let Capability::Vendor(vendor) = capability {
|
||||
Some(vendor)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
{
|
||||
// SAFETY: We have verified that the length of the data is correct.
|
||||
let capability = unsafe { &*(capability.data.as_ptr() as *const PciCapability) };
|
||||
|
||||
match capability.cfg_type {
|
||||
CfgType::Common | CfgType::Notify | CfgType::Isr | CfgType::Device => {}
|
||||
_ => continue,
|
||||
}
|
||||
|
||||
let bar = pci_header.get_bar(capability.bar as usize);
|
||||
let addr = match bar {
|
||||
PciBar::Memory32(addr) => addr as usize,
|
||||
PciBar::Memory64(addr) => addr as usize,
|
||||
|
||||
_ => unreachable!("virtio: unsupported bar type: {bar:?}"),
|
||||
};
|
||||
|
||||
let address = unsafe {
|
||||
syscall::physmap(
|
||||
addr + capability.offset as usize,
|
||||
capability.length as usize,
|
||||
PHYSMAP_WRITE | PHYSMAP_NO_CACHE,
|
||||
)?
|
||||
};
|
||||
|
||||
match capability.cfg_type {
|
||||
CfgType::Common => {
|
||||
debug_assert!(common_addr.is_none());
|
||||
common_addr = Some(address);
|
||||
}
|
||||
|
||||
CfgType::Notify => {
|
||||
debug_assert!(notify_addr.is_none());
|
||||
|
||||
// SAFETY: The capability type is `Notify`, so its safe to access
|
||||
// the `notify_multiplier` field.
|
||||
let multiplier = unsafe { capability.notify_multiplier() };
|
||||
notify_addr = Some((address, multiplier));
|
||||
}
|
||||
|
||||
CfgType::Isr => {
|
||||
debug_assert!(isr_addr.is_none());
|
||||
isr_addr = Some(address);
|
||||
}
|
||||
|
||||
CfgType::Device => {
|
||||
debug_assert!(device_addr.is_none());
|
||||
device_addr = Some(address);
|
||||
}
|
||||
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
log::info!("virtio: {capability:?}");
|
||||
}
|
||||
|
||||
let common_addr = common_addr.ok_or(Error::InCapable(CfgType::Common))?;
|
||||
let (notify_addr, notify_multiplier) = notify_addr.ok_or(Error::InCapable(CfgType::Notify))?;
|
||||
let isr_addr = isr_addr.ok_or(Error::InCapable(CfgType::Isr))?;
|
||||
let device_addr = device_addr.ok_or(Error::InCapable(CfgType::Device))?;
|
||||
|
||||
assert!(
|
||||
notify_multiplier != 0,
|
||||
"virtio-core::device_probe: device uses the same Queue Notify addresses for all queues"
|
||||
);
|
||||
|
||||
let common = unsafe { &mut *(common_addr as *mut CommonCfg) };
|
||||
let device_space = unsafe { &mut *(device_addr as *mut u8) };
|
||||
let isr = unsafe { &*(isr_addr as *mut VolatileCell<u32>) };
|
||||
|
||||
// Reset the device.
|
||||
common.device_status.set(DeviceStatusFlags::empty());
|
||||
// Upon reset, the device must initialize device status to 0.
|
||||
assert_eq!(common.device_status.get(), DeviceStatusFlags::empty());
|
||||
log::info!("virtio: successfully reseted the device");
|
||||
|
||||
// XXX: According to the virtio specification v1.2, setting the ACKNOWLEDGE and DRIVER bits
|
||||
// in `device_status` is required to be done in two steps.
|
||||
common
|
||||
.device_status
|
||||
.set(common.device_status.get() | DeviceStatusFlags::ACKNOWLEDGE);
|
||||
|
||||
common
|
||||
.device_status
|
||||
.set(common.device_status.get() | DeviceStatusFlags::DRIVER);
|
||||
|
||||
// Setup interrupts.
|
||||
let all_pci_features = pcid_handle.fetch_all_features()?;
|
||||
let has_msix = all_pci_features
|
||||
.iter()
|
||||
.any(|(feature, _)| feature.is_msix());
|
||||
|
||||
// According to the virtio specification, the device REQUIRED to support MSI-X.
|
||||
assert!(has_msix, "virtio: device does not support MSI-X");
|
||||
let irq_handle = enable_msix(pcid_handle)?;
|
||||
|
||||
log::info!("virtio: using standard PCI transport");
|
||||
|
||||
let transport = StandardTransport::new(common, notify_addr as *const u8, notify_multiplier);
|
||||
|
||||
Ok(Device {
|
||||
transport,
|
||||
device_space,
|
||||
irq_handle,
|
||||
isr,
|
||||
})
|
||||
}
|
||||
@@ -1,28 +1,8 @@
|
||||
//! https://docs.oasis-open.org/virtio/virtio/v1.1/virtio-v1.1.html
|
||||
|
||||
#![feature(int_roundings)]
|
||||
|
||||
use crate::utils::{IncompleteArrayField, VolatileCell};
|
||||
use static_assertions::const_assert_eq;
|
||||
|
||||
pub mod transport;
|
||||
pub mod utils;
|
||||
|
||||
use utils::{IncompleteArrayField, VolatileCell};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("capability {0:?} not found")]
|
||||
InCapable(CfgType),
|
||||
#[error("failed to map memory")]
|
||||
Physmap,
|
||||
#[error("failed to allocate an interrupt vector")]
|
||||
ExhaustedInt,
|
||||
#[error("syscall error")]
|
||||
SyscallError(syscall::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[repr(u8)]
|
||||
pub enum CfgType {
|
||||
@@ -225,9 +205,9 @@ pub struct UsedRingExtra {
|
||||
|
||||
// ======== Utils ========
|
||||
pub struct Buffer {
|
||||
buffer: usize,
|
||||
size: usize,
|
||||
flags: DescriptorFlags,
|
||||
pub(crate) buffer: usize,
|
||||
pub(crate) size: usize,
|
||||
pub(crate) flags: DescriptorFlags,
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
@@ -1,7 +1,6 @@
|
||||
use crate::utils::align;
|
||||
use crate::*;
|
||||
use crate::spec::*;
|
||||
use crate::utils::{align, VolatileCell};
|
||||
|
||||
use pcid_interface::PciHeader;
|
||||
use syscall::{Dma, PHYSMAP_WRITE};
|
||||
|
||||
use core::mem::size_of;
|
||||
@@ -10,6 +9,28 @@ use core::sync::atomic::{fence, AtomicU16, Ordering};
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("syscall failed")]
|
||||
SyscallError(syscall::Error),
|
||||
#[error("pcid client handle error")]
|
||||
PcidClientHandle(pcid_interface::PcidClientHandleError),
|
||||
#[error("the device is incapable of {0:?}")]
|
||||
InCapable(CfgType),
|
||||
}
|
||||
|
||||
impl From<pcid_interface::PcidClientHandleError> for Error {
|
||||
fn from(value: pcid_interface::PcidClientHandleError) -> Self {
|
||||
Self::PcidClientHandle(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<syscall::Error> for Error {
|
||||
fn from(value: syscall::Error) -> Self {
|
||||
Self::SyscallError(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the queue part sizes in bytes.
|
||||
///
|
||||
/// ## Reference
|
||||
@@ -167,7 +188,7 @@ pub struct Available<'a> {
|
||||
}
|
||||
|
||||
impl<'a> Available<'a> {
|
||||
pub fn new(queue_size: usize) -> anyhow::Result<Self> {
|
||||
pub fn new(queue_size: usize) -> Result<Self, Error> {
|
||||
let (_, size, _) = queue_part_sizes(queue_size);
|
||||
let size = size.next_multiple_of(syscall::PAGE_SIZE); // align to page size
|
||||
|
||||
@@ -229,7 +250,7 @@ pub struct Used<'a> {
|
||||
}
|
||||
|
||||
impl<'a> Used<'a> {
|
||||
pub fn new(queue_size: usize) -> anyhow::Result<Self> {
|
||||
pub fn new(queue_size: usize) -> Result<Self, Error> {
|
||||
let (_, _, size) = queue_part_sizes(queue_size);
|
||||
let size = size.next_multiple_of(syscall::PAGE_SIZE); // align to page size
|
||||
|
||||
@@ -286,7 +307,6 @@ impl Drop for Used<'_> {
|
||||
}
|
||||
|
||||
pub struct StandardTransport<'a> {
|
||||
header: PciHeader,
|
||||
common: Mutex<&'a mut CommonCfg>,
|
||||
notify: *const u8,
|
||||
notify_mul: u32,
|
||||
@@ -296,14 +316,8 @@ pub struct StandardTransport<'a> {
|
||||
}
|
||||
|
||||
impl<'a> StandardTransport<'a> {
|
||||
pub fn new(
|
||||
header: PciHeader,
|
||||
common: &'a mut CommonCfg,
|
||||
notify: *const u8,
|
||||
notify_mul: u32,
|
||||
) -> Arc<Self> {
|
||||
pub fn new(common: &'a mut CommonCfg, notify: *const u8, notify_mul: u32) -> Arc<Self> {
|
||||
Arc::new_cyclic(|sref| Self {
|
||||
header,
|
||||
common: Mutex::new(common),
|
||||
notify,
|
||||
notify_mul,
|
||||
@@ -362,7 +376,7 @@ impl<'a> StandardTransport<'a> {
|
||||
.set(status | DeviceStatusFlags::DRIVER_OK);
|
||||
}
|
||||
|
||||
pub fn setup_queue(&self, vector: u16) -> anyhow::Result<Arc<Queue<'a>>> {
|
||||
pub fn setup_queue(&self, vector: u16) -> Result<Arc<Queue<'a>>, Error> {
|
||||
let mut common = self.common.lock().unwrap();
|
||||
|
||||
let queue_index = self.queue_index.fetch_add(1, Ordering::SeqCst);
|
||||
+2
-2
@@ -5,11 +5,10 @@ edition = "2021"
|
||||
authors = ["Anhad Singh <andypython@protonmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
static_assertions = "1.1.0"
|
||||
bitflags = "2.3.2"
|
||||
anyhow = "1.0.71"
|
||||
log = "0.4"
|
||||
thiserror = "1.0.40"
|
||||
static_assertions = "1.1.0"
|
||||
|
||||
redox-daemon = "0.1"
|
||||
redox-log = "0.1"
|
||||
@@ -19,3 +18,4 @@ partitionlib = { git = "https://gitlab.redox-os.org/redox-os/partitionlib.git" }
|
||||
|
||||
block-io-wrapper = { path = "../block-io-wrapper" }
|
||||
pcid = { path = "../pcid" }
|
||||
virtio-core = { path = "../virtio-core" }
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
## Generic
|
||||
- [x] Reset the device.
|
||||
- [x] Set the ACKNOWLEDGE status bit: the guest OS has noticed the device.
|
||||
- [x] Set the DRIVER status bit: the guest OS knows how to drive the device.
|
||||
- [x] Setup Interrupts
|
||||
|
||||
## Driver Specific
|
||||
- [x] Read device feature bits, and write the subset of feature bits understood by the OS and driver to the device. During this step the driver MAY read (but MUST NOT write) the device-specific configuration fields to check that it can support the device before accepting it.
|
||||
- [x] Set the FEATURES_OK status bit. The driver MUST NOT accept new feature bits after this step.
|
||||
- [x] Re-read device status to ensure the FEATURES_OK bit is still set: otherwise, the device does not support our subset of features and the device is unusable.
|
||||
- [x] Perform device-specific setup, including discovery of virtqueues for the device, optional per-bus setup, reading and possibly writing the device’s virtio configuration space, and population of virtqueues.
|
||||
- [x] Set the DRIVER_OK status bit. At this point the device is “live”.
|
||||
|
||||
## XXX
|
||||
- [ ] Mark the deamon as ready.
|
||||
|
||||
## Drivers
|
||||
- [ ] `virtio-blk` (in-progress)
|
||||
- [ ] `virtio-net`
|
||||
- [ ] `virtio-gpu`
|
||||
+32
-267
@@ -1,8 +1,6 @@
|
||||
#![deny(trivial_numeric_casts, unused_allocation)]
|
||||
#![feature(int_roundings)]
|
||||
|
||||
use core::ptr::NonNull;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::{Read, Write};
|
||||
@@ -10,22 +8,30 @@ use std::os::fd::{AsRawFd, FromRawFd, RawFd};
|
||||
|
||||
use static_assertions::const_assert_eq;
|
||||
|
||||
use virtiod::transport::StandardTransport;
|
||||
use virtiod::*;
|
||||
|
||||
use pcid_interface::irq_helpers::{allocate_single_interrupt_vector, read_bsp_apic_id};
|
||||
use pcid_interface::msi::x86_64 as x86_64_msix;
|
||||
use pcid_interface::msi::x86_64::DeliveryMode;
|
||||
use pcid_interface::msi::{MsixCapability, MsixTableEntry};
|
||||
use pcid_interface::*;
|
||||
use virtio_core::spec::*;
|
||||
|
||||
use event::EventQueue;
|
||||
use syscall::{Io, Packet, SchemeBlockMut, PHYSMAP_NO_CACHE, PHYSMAP_WRITE};
|
||||
use syscall::{Packet, SchemeBlockMut};
|
||||
|
||||
use virtiod::utils::VolatileCell;
|
||||
use virtio_core::utils::VolatileCell;
|
||||
|
||||
mod scheme;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("capability {0:?} not found")]
|
||||
InCapable(CfgType),
|
||||
#[error("failed to map memory")]
|
||||
Physmap,
|
||||
#[error("failed to allocate an interrupt vector")]
|
||||
ExhaustedInt,
|
||||
#[error("syscall error")]
|
||||
SyscallError(syscall::Error),
|
||||
}
|
||||
|
||||
pub fn main() -> anyhow::Result<()> {
|
||||
#[cfg(target_os = "redox")]
|
||||
setup_logging();
|
||||
@@ -74,110 +80,6 @@ fn setup_logging() {
|
||||
log::info!("virtiod: enabled logger");
|
||||
}
|
||||
|
||||
struct MsixInfo {
|
||||
pub virt_table_base: NonNull<MsixTableEntry>,
|
||||
pub virt_pba_base: NonNull<u64>,
|
||||
pub capability: MsixCapability,
|
||||
}
|
||||
|
||||
impl MsixInfo {
|
||||
pub unsafe fn table_entry_pointer_unchecked(&mut self, k: usize) -> &mut MsixTableEntry {
|
||||
&mut *self.virt_table_base.as_ptr().add(k)
|
||||
}
|
||||
|
||||
pub fn table_entry_pointer(&mut self, k: usize) -> &mut MsixTableEntry {
|
||||
assert!(k < self.capability.table_size() as usize);
|
||||
unsafe { self.table_entry_pointer_unchecked(k) }
|
||||
}
|
||||
}
|
||||
|
||||
const_assert_eq!(std::mem::size_of::<MsixTableEntry>(), 16);
|
||||
|
||||
const MSIX_PRIMARY_VECTOR: u16 = 0;
|
||||
|
||||
fn enable_msix(pcid_handle: &mut PcidServerHandle) -> anyhow::Result<File> {
|
||||
let pci_config = pcid_handle.fetch_config()?;
|
||||
|
||||
// Extended message signaled interrupts.
|
||||
let capability = match pcid_handle.feature_info(PciFeature::MsiX)? {
|
||||
PciFeatureInfo::MsiX(capability) => capability,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let table_size = capability.table_size();
|
||||
let table_base = capability.table_base_pointer(pci_config.func.bars);
|
||||
let table_min_length = table_size * 16;
|
||||
let pba_min_length = table_size.div_ceil(8);
|
||||
|
||||
let pba_base = capability.pba_base_pointer(pci_config.func.bars);
|
||||
|
||||
let bir = capability.table_bir() as usize;
|
||||
let bar = pci_config.func.bars[bir];
|
||||
let bar_size = pci_config.func.bar_sizes[bir] as u64;
|
||||
|
||||
let bar_ptr = match bar {
|
||||
PciBar::Memory32(ptr) => ptr.into(),
|
||||
PciBar::Memory64(ptr) => ptr,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let address = unsafe {
|
||||
syscall::physmap(
|
||||
bar_ptr as usize,
|
||||
bar_size as usize,
|
||||
PHYSMAP_WRITE | PHYSMAP_NO_CACHE,
|
||||
)
|
||||
.map_err(|_| Error::Physmap)?
|
||||
};
|
||||
|
||||
// Ensure that the table and PBA are be within the BAR.
|
||||
{
|
||||
let bar_range = bar_ptr..bar_ptr + bar_size;
|
||||
assert!(bar_range.contains(&(table_base as u64 + table_min_length as u64)));
|
||||
assert!(bar_range.contains(&(pba_base as u64 + pba_min_length as u64)));
|
||||
}
|
||||
|
||||
let virt_table_base = ((table_base - bar_ptr as usize) + address) as *mut MsixTableEntry;
|
||||
let virt_pba_base = ((pba_base - bar_ptr as usize) + address) as *mut u64;
|
||||
|
||||
let mut info = MsixInfo {
|
||||
virt_table_base: NonNull::new(virt_table_base).unwrap(),
|
||||
virt_pba_base: NonNull::new(virt_pba_base).unwrap(),
|
||||
capability,
|
||||
};
|
||||
|
||||
// Allocate the primary MSI vector.
|
||||
let interrupt_handle = {
|
||||
let table_entry_pointer = info.table_entry_pointer(MSIX_PRIMARY_VECTOR as usize);
|
||||
|
||||
let destination_id = read_bsp_apic_id()?;
|
||||
let lapic_id = u8::try_from(destination_id).unwrap();
|
||||
|
||||
let rh = false;
|
||||
let dm = false;
|
||||
let addr = x86_64_msix::message_address(lapic_id, rh, dm);
|
||||
|
||||
let (vector, interrupt_handle) =
|
||||
allocate_single_interrupt_vector(destination_id)?.ok_or(Error::ExhaustedInt)?;
|
||||
|
||||
let msg_data = x86_64_msix::message_data_edge_triggered(DeliveryMode::Fixed, vector);
|
||||
|
||||
table_entry_pointer.addr_lo.write(addr);
|
||||
table_entry_pointer.addr_hi.write(0);
|
||||
table_entry_pointer.msg_data.write(msg_data);
|
||||
table_entry_pointer
|
||||
.vec_ctl
|
||||
.writef(MsixTableEntry::VEC_CTL_MASK_BIT, false);
|
||||
|
||||
interrupt_handle
|
||||
};
|
||||
|
||||
pcid_handle.enable_feature(PciFeature::MsiX)?;
|
||||
|
||||
log::info!("virtio: using MSI-X (interrupt_handle={interrupt_handle:?})");
|
||||
Ok(interrupt_handle)
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct BlockGeometry {
|
||||
pub cylinders: VolatileCell<u16>,
|
||||
@@ -224,148 +126,35 @@ const_assert_eq!(core::mem::size_of::<BlockVirtRequest>(), 16);
|
||||
|
||||
fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> {
|
||||
let mut pcid_handle = PcidServerHandle::connect_default()?;
|
||||
let pci_config = pcid_handle.fetch_config()?;
|
||||
let pci_header = pcid_handle.fetch_header()?;
|
||||
|
||||
// Double check that we have the right device.
|
||||
//
|
||||
// 0x1001 - virtio-blk
|
||||
let pci_config = pcid_handle.fetch_config()?;
|
||||
|
||||
assert_eq!(pci_config.func.devid, 0x1001);
|
||||
log::info!("virtiod: found `virtio-blk` device");
|
||||
|
||||
let mut common_addr = None;
|
||||
let mut notify_addr = None;
|
||||
let mut isr_addr = None;
|
||||
let mut device_addr = None;
|
||||
let mut device = virtio_core::probe_device(&mut pcid_handle)?;
|
||||
device.transport.finalize_features();
|
||||
|
||||
for capability in pcid_handle
|
||||
.get_capabilities()?
|
||||
.iter()
|
||||
.filter_map(|capability| {
|
||||
if let Capability::Vendor(vendor) = capability {
|
||||
Some(vendor)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
{
|
||||
// SAFETY: We have verified that the length of the data is correct.
|
||||
let capability = unsafe { &*(capability.data.as_ptr() as *const PciCapability) };
|
||||
|
||||
match capability.cfg_type {
|
||||
CfgType::Common | CfgType::Notify | CfgType::Isr | CfgType::Device => {}
|
||||
_ => continue,
|
||||
}
|
||||
|
||||
let bar = pci_header.get_bar(capability.bar as usize);
|
||||
let addr = match bar {
|
||||
PciBar::Memory32(addr) => addr as usize,
|
||||
PciBar::Memory64(addr) => addr as usize,
|
||||
|
||||
_ => unreachable!("virtio: unsupported bar type: {bar:?}"),
|
||||
};
|
||||
|
||||
let address = unsafe {
|
||||
syscall::physmap(
|
||||
addr + capability.offset as usize,
|
||||
capability.length as usize,
|
||||
PHYSMAP_WRITE | PHYSMAP_NO_CACHE,
|
||||
)
|
||||
.map_err(|_| Error::Physmap)?
|
||||
};
|
||||
|
||||
match capability.cfg_type {
|
||||
CfgType::Common => {
|
||||
debug_assert!(common_addr.is_none());
|
||||
common_addr = Some(address);
|
||||
}
|
||||
|
||||
CfgType::Notify => {
|
||||
debug_assert!(notify_addr.is_none());
|
||||
|
||||
// SAFETY: The capability type is `Notify`, so its safe to access
|
||||
// the `notify_multiplier` field.
|
||||
let multiplier = unsafe { capability.notify_multiplier() };
|
||||
notify_addr = Some((address, multiplier));
|
||||
}
|
||||
|
||||
CfgType::Isr => {
|
||||
debug_assert!(isr_addr.is_none());
|
||||
isr_addr = Some(address);
|
||||
}
|
||||
|
||||
CfgType::Device => {
|
||||
debug_assert!(device_addr.is_none());
|
||||
device_addr = Some(address);
|
||||
}
|
||||
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
log::info!("virtio: {capability:?}");
|
||||
}
|
||||
|
||||
let common_addr = common_addr.ok_or(Error::InCapable(CfgType::Common))?;
|
||||
let (notify_addr, notify_multiplier) = notify_addr.ok_or(Error::InCapable(CfgType::Notify))?;
|
||||
let isr_addr = isr_addr.ok_or(Error::InCapable(CfgType::Isr))?;
|
||||
let device_addr = device_addr.ok_or(Error::InCapable(CfgType::Device))?;
|
||||
|
||||
assert!(
|
||||
notify_multiplier != 0,
|
||||
"virtio: device uses the same Queue Notify addresses for all queues"
|
||||
);
|
||||
|
||||
let common = unsafe { &mut *(common_addr as *mut CommonCfg) };
|
||||
let device_space = unsafe { &mut *(device_addr as *mut BlockDeviceConfig) };
|
||||
let isr = unsafe { &*(isr_addr as *mut VolatileCell<u32>) };
|
||||
|
||||
// Reset the device.
|
||||
common.device_status.set(DeviceStatusFlags::empty());
|
||||
// Upon reset, the device must initialize device status to 0.
|
||||
assert_eq!(common.device_status.get(), DeviceStatusFlags::empty());
|
||||
log::info!("virtio: successfully reseted the device");
|
||||
|
||||
// XXX: According to the virtio specification v1.2, setting the ACKNOWLEDGE and DRIVER bits
|
||||
// in `device_status` is required to be done in two steps.
|
||||
common
|
||||
.device_status
|
||||
.set(common.device_status.get() | DeviceStatusFlags::ACKNOWLEDGE);
|
||||
|
||||
common
|
||||
.device_status
|
||||
.set(common.device_status.get() | DeviceStatusFlags::DRIVER);
|
||||
|
||||
// Setup interrupts.
|
||||
let all_pci_features = pcid_handle.fetch_all_features()?;
|
||||
let has_msix = all_pci_features
|
||||
.iter()
|
||||
.any(|(feature, _)| feature.is_msix());
|
||||
|
||||
// According to the virtio specification, the device REQUIRED to support MSI-X.
|
||||
assert!(has_msix, "virtio: device does not support MSI-X");
|
||||
let mut irq_handle = enable_msix(&mut pcid_handle)?;
|
||||
|
||||
log::info!("virtio: using standard PCI transport");
|
||||
|
||||
let transport = StandardTransport::new(
|
||||
pci_header,
|
||||
common,
|
||||
notify_addr as *const u8,
|
||||
notify_multiplier,
|
||||
);
|
||||
transport.finalize_features();
|
||||
|
||||
let queue = transport.setup_queue(MSIX_PRIMARY_VECTOR)?;
|
||||
let queue = device
|
||||
.transport
|
||||
.setup_queue(virtio_core::MSIX_PRIMARY_VECTOR)?;
|
||||
let queue_copy = queue.clone();
|
||||
|
||||
let device_space = unsafe { &mut *(device.device_space as *mut BlockDeviceConfig) };
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let mut event_queue = EventQueue::<usize>::new().unwrap();
|
||||
let mut progress_head = 0;
|
||||
|
||||
event_queue
|
||||
.add(
|
||||
irq_handle.as_raw_fd(),
|
||||
device.irq_handle.as_raw_fd(),
|
||||
move |_| -> Result<Option<usize>, io::Error> {
|
||||
// Read from ISR to acknowledge the interrupt.
|
||||
let _isr = isr.get() as usize;
|
||||
let _isr = device.isr.get() as usize;
|
||||
|
||||
let mut inner = queue_copy.inner.lock().unwrap();
|
||||
let used_head = inner.used.head_index();
|
||||
@@ -394,7 +183,7 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> {
|
||||
drop(inner);
|
||||
|
||||
let mut buf = [0u8; 8];
|
||||
irq_handle.read(&mut buf)?;
|
||||
device.irq_handle.read(&mut buf)?;
|
||||
// Acknowledge the interrupt.
|
||||
// irq_handle.write(&buf)?;
|
||||
Ok(None)
|
||||
@@ -408,7 +197,7 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> {
|
||||
});
|
||||
|
||||
// At this point the device is alive!
|
||||
transport.run_device();
|
||||
device.transport.run_device();
|
||||
|
||||
log::info!(
|
||||
"virtio-blk: disk size: {} sectors and block size of {} bytes",
|
||||
@@ -444,30 +233,6 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> {
|
||||
.write(&mut packet)
|
||||
.expect("ahcid: failed to read disk scheme");
|
||||
}
|
||||
|
||||
// for _ in 0..3 {
|
||||
// let req = syscall::Dma::new(BlockVirtRequest {
|
||||
// ty: BlockRequestTy::In,
|
||||
// reserved: 0,
|
||||
// sector: 0,
|
||||
// })
|
||||
// .unwrap();
|
||||
|
||||
// let result = syscall::Dma::new([0u8; 512]).unwrap();
|
||||
// let status = syscall::Dma::new(u8::MAX).unwrap();
|
||||
|
||||
// let chain = ChainBuilder::new()
|
||||
// .chain(Buffer::new(&req).flags(DescriptorFlags::NEXT))
|
||||
// .chain(Buffer::new(&result).flags(DescriptorFlags::WRITE_ONLY | DescriptorFlags::NEXT))
|
||||
// .chain(Buffer::new(&status).flags(DescriptorFlags::WRITE_ONLY))
|
||||
// .build();
|
||||
|
||||
// queue.send(chain);
|
||||
|
||||
// log::info!("{}", event_queue.run()?);
|
||||
// log::info!("command status: {}", *status);
|
||||
// log::info!("data: {:?}", result.as_ref());
|
||||
// }
|
||||
}
|
||||
|
||||
fn daemon_runner(redox_daemon: redox_daemon::Daemon) -> ! {
|
||||
|
||||
@@ -10,10 +10,9 @@ use std::sync::Arc;
|
||||
use partitionlib::LogicalBlockSize;
|
||||
use partitionlib::PartitionTable;
|
||||
use syscall::*;
|
||||
use virtiod::transport::Queue;
|
||||
use virtiod::Buffer;
|
||||
use virtiod::ChainBuilder;
|
||||
use virtiod::DescriptorFlags;
|
||||
|
||||
use virtio_core::spec::{Buffer, ChainBuilder, DescriptorFlags};
|
||||
use virtio_core::transport::Queue;
|
||||
|
||||
use crate::BlockDeviceConfig;
|
||||
use crate::BlockRequestTy;
|
||||
@@ -28,6 +27,8 @@ trait BlkExtension {
|
||||
/// XXX: Reads only one block despite the size of the output buffer. Use [`BlkExtension::write`] instead.
|
||||
fn write_block(&self, block: u64, block_bytes: &[u8]) -> usize;
|
||||
|
||||
// FIXME(andypython): The following two methods can be done asynchronously (i.e, send all of the commands at once
|
||||
// and wait for the result in the end).
|
||||
fn read(&self, block: u64, block_bytes: &mut [u8]) -> usize {
|
||||
let sectors = block_bytes.len() / BLK_SIZE as usize;
|
||||
|
||||
@@ -72,8 +73,6 @@ impl BlkExtension for Queue<'_> {
|
||||
|
||||
let size = core::cmp::min(block_bytes.len(), result.len());
|
||||
block_bytes[..size].copy_from_slice(&result.as_slice()[..size]);
|
||||
std::thread::yield_now();
|
||||
|
||||
size
|
||||
}
|
||||
|
||||
@@ -103,7 +102,6 @@ impl BlkExtension for Queue<'_> {
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
|
||||
std::thread::yield_now();
|
||||
block_bytes.len()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user