Merge branch 'core' into 'master'

virtio: add `virtio-net` drivers

See merge request redox-os/drivers!95
This commit is contained in:
Jeremy Soller
2023-06-30 12:44:17 +00:00
21 changed files with 1221 additions and 718 deletions
Generated
+52 -2
View File
@@ -354,6 +354,16 @@ dependencies = [
"crossbeam-utils 0.8.15",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-utils 0.8.15",
]
[[package]]
name = "crossbeam-utils"
version = "0.7.2"
@@ -1905,23 +1915,63 @@ dependencies = [
]
[[package]]
name = "virtiod"
name = "virtio-blkd"
version = "0.1.0"
dependencies = [
"anyhow",
"bitflags 2.3.2",
"block-io-wrapper",
"futures",
"log",
"partitionlib",
"pcid",
"redox-daemon",
"redox-log",
"redox_syscall 0.3.5",
"static_assertions",
"thiserror",
"virtio-core",
]
[[package]]
name = "virtio-core"
version = "0.1.0"
dependencies = [
"bitflags 2.3.2",
"crossbeam-queue",
"futures",
"log",
"pcid",
"redox-log",
"redox_event",
"redox_syscall 0.3.5",
"static_assertions",
"thiserror",
]
[[package]]
name = "virtio-gpud"
version = "0.1.0"
dependencies = [
"log",
"pcid",
"redox-daemon",
"virtio-core",
]
[[package]]
name = "virtio-netd"
version = "0.1.0"
dependencies = [
"futures",
"log",
"netutils",
"pcid",
"redox-daemon",
"redox_syscall 0.3.5",
"static_assertions",
"virtio-core",
]
[[package]]
name = "vte"
version = "0.3.3"
+4 -1
View File
@@ -23,7 +23,10 @@ members = [
"usbhidd",
"usbscsid",
"virtiod",
"virtio-blkd",
"virtio-netd",
"virtio-gpud",
"virtio-core",
]
[profile.release]
+1 -1
View File
@@ -24,7 +24,7 @@ These are the currently implemented devices/hardware interfaces.
- usbctl - USB control (incomplete).
- usbhidd - USB HID (incomplete).
- usbscsid - USB SCSI (incomplete).
- virtiod - VirtIO (incomplete) (`virtio-blk`).
- virtio-* - VirtIO (incomplete) (`virtio-blk`, `virtio-net`, `virtio-gpu`).
## Contributing to Drivers
Executable
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/bash
pushd virtio-core
cargo fmt
popd
pushd virtio-netd
cargo fmt
popd
pushd virtio-blkd
cargo fmt
popd
+22 -4
View File
@@ -23,12 +23,30 @@ subclass = 8
command = ["nvmed"]
use_channel = true
# virtiod
# -------------- virtio -------------- #
[[drivers]]
name = "VirtIO block storage"
name = "virtio-blk"
class = 1
subclass = 0
# VirtIO PCI vendor identifier.
vendor = 6900
command = ["virtiod"]
device = 4097
command = ["virtio-blkd"]
use_channel = true
[[drivers]]
name = "virtio-net"
class = 2
subclass = 0
vendor = 6900
device = 4096
command = ["virtio-netd"]
use_channel = true
[[drivers]]
name = "virtio-gpu"
class = 3
subclass = 0
vendor = 6900
device = 4176
command = ["virtio-gpud"]
use_channel = true
@@ -1,21 +1,21 @@
[package]
name = "virtiod"
name = "virtio-blkd"
version = "0.1.0"
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"
futures = { version = "0.3.28", features = ["executor"] }
redox-daemon = "0.1"
redox-log = "0.1"
redox_syscall = "0.3"
redox_event = { git = "https://gitlab.redox-os.org/redox-os/event.git" }
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" }
+144
View File
@@ -0,0 +1,144 @@
#![deny(trivial_numeric_casts, unused_allocation)]
#![feature(int_roundings, async_fn_in_trait)]
use std::fs::File;
use std::io::{Read, Write};
use std::os::fd::{FromRawFd, RawFd};
use static_assertions::const_assert_eq;
use pcid_interface::*;
use virtio_core::spec::*;
use syscall::{Packet, SchemeBlockMut};
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")]
virtio_core::utils::setup_logging(log::LevelFilter::Trace, "virtio-blkd");
redox_daemon::Daemon::new(daemon_runner).expect("virtio-core: failed to daemonize");
}
#[repr(C)]
pub struct BlockGeometry {
pub cylinders: VolatileCell<u16>,
pub heads: VolatileCell<u8>,
pub sectors: VolatileCell<u8>,
}
#[repr(C)]
pub struct BlockDeviceConfig {
capacity: VolatileCell<u64>,
pub size_max: VolatileCell<u32>,
pub seq_max: VolatileCell<u32>,
pub geometry: BlockGeometry,
blk_size: VolatileCell<u32>,
}
impl BlockDeviceConfig {
/// Returns the capacity of the block device in bytes.
pub fn capacity(&self) -> u64 {
self.capacity.get()
}
pub fn block_size(&self) -> u32 {
self.blk_size.get()
}
}
#[repr(u32)]
pub enum BlockRequestTy {
In = 0,
Out = 1,
}
const_assert_eq!(core::mem::size_of::<BlockRequestTy>(), 4);
#[repr(C)]
pub struct BlockVirtRequest {
pub ty: BlockRequestTy,
pub reserved: u32,
pub sector: u64,
}
const_assert_eq!(core::mem::size_of::<BlockVirtRequest>(), 16);
fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> {
let mut pcid_handle = PcidServerHandle::connect_default()?;
// 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!("virtio-blk: initiating startup sequence :^)");
let device = virtio_core::probe_device(&mut pcid_handle)?;
device.transport.finalize_features();
let queue = device
.transport
.setup_queue(virtio_core::MSIX_PRIMARY_VECTOR, &device.irq_handle)?;
let device_space = unsafe { &mut *(device.device_space as *mut BlockDeviceConfig) };
// At this point the device is alive!
device.transport.run_device();
log::info!(
"virtio-blk: disk size: {} sectors and block size of {} bytes",
device_space.capacity.get(),
device_space.blk_size.get()
);
let mut name = pci_config.func.name();
name.push_str("_virtio_blk");
let scheme_name = format!("disk/{}", name);
let socket_fd = syscall::open(
&format!(":{}", scheme_name),
syscall::O_RDWR | syscall::O_CREAT | syscall::O_CLOEXEC,
)
.map_err(Error::SyscallError)?;
let mut socket_file = unsafe { File::from_raw_fd(socket_fd as RawFd) };
let mut scheme = scheme::DiskScheme::new(queue, device_space);
deamon.ready().expect("virtio-blkd: failed to deamonize");
loop {
let mut packet = Packet::default();
socket_file
.read(&mut packet)
.expect("virtio-blkd: failed to read disk scheme");
let packey = scheme.handle(&mut packet);
packet.a = packey.unwrap();
socket_file
.write(&mut packet)
.expect("virtio-blkd: failed to read disk scheme");
}
}
fn daemon_runner(redox_daemon: redox_daemon::Daemon) -> ! {
deamon(redox_daemon).unwrap();
unreachable!();
}
@@ -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;
@@ -22,31 +21,12 @@ use crate::BlockVirtRequest;
const BLK_SIZE: u64 = 512;
trait BlkExtension {
/// XXX: Reads only one block despite the size of the output buffer. Use [`BlkExtension::read`] instead.
fn read_block(&self, block: u64, block_bytes: &mut [u8]) -> usize;
/// 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;
fn read(&self, block: u64, block_bytes: &mut [u8]) -> usize {
let sectors = block_bytes.len() / BLK_SIZE as usize;
(0..sectors)
.map(|i| self.read_block(block + i as u64, &mut block_bytes[i * BLK_SIZE as usize..]))
.sum()
}
fn write(&self, block: u64, block_bytes: &[u8]) -> usize {
let sectors = block_bytes.len() / BLK_SIZE as usize;
(0..sectors)
.map(|i| self.write_block(block + i as u64, &block_bytes[i * BLK_SIZE as usize..]))
.sum()
}
async fn read(&self, block: u64, target: &mut [u8]) -> usize;
async fn write(&self, block: u64, target: &[u8]) -> usize;
}
impl BlkExtension for Queue<'_> {
fn read_block(&self, block: u64, block_bytes: &mut [u8]) -> usize {
async fn read(&self, block: u64, target: &mut [u8]) -> usize {
let req = syscall::Dma::new(BlockVirtRequest {
ty: BlockRequestTy::In,
reserved: 0,
@@ -54,30 +34,24 @@ impl BlkExtension for Queue<'_> {
})
.unwrap();
let result = syscall::Dma::new([0u8; 512]).unwrap();
let result = unsafe { syscall::Dma::<[u8]>::zeroed_unsized(target.len()) }.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(&req))
.chain(Buffer::new_unsized(&result).flags(DescriptorFlags::WRITE_ONLY))
.chain(Buffer::new(&status).flags(DescriptorFlags::WRITE_ONLY))
.build();
self.send(chain);
// XXX: Subtract 1 because the of status byte.
let written = self.send(chain).await as usize - 1;
assert_eq!(*status, 0);
// FIXME: interrupts are for a reason
while *status != 0 {
core::hint::spin_loop();
}
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
target[..written].copy_from_slice(&result);
written
}
fn write_block(&self, block: u64, block_bytes: &[u8]) -> usize {
async fn write(&self, block: u64, target: &[u8]) -> usize {
let req = syscall::Dma::new(BlockVirtRequest {
ty: BlockRequestTy::Out,
reserved: 0,
@@ -85,26 +59,21 @@ impl BlkExtension for Queue<'_> {
})
.unwrap();
let mut result = syscall::Dma::new([0u8; 512]).unwrap();
result.copy_from_slice(&block_bytes[..512]);
let mut result = unsafe { syscall::Dma::<[u8]>::zeroed_unsized(target.len()) }.unwrap();
result.copy_from_slice(target.as_ref());
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::NEXT))
.chain(Buffer::new(&req))
.chain(Buffer::new_sized(&result, target.len()))
.chain(Buffer::new(&status).flags(DescriptorFlags::WRITE_ONLY))
.build();
self.send(chain);
self.send(chain).await as usize;
assert_eq!(*status, 0);
// FIXME: interrupts are for a reason
while *status != 0 {
core::hint::spin_loop();
}
std::thread::yield_now();
block_bytes.len()
target.len()
}
}
@@ -164,26 +133,16 @@ impl<'a> DiskScheme<'a> {
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(&req))
.chain(Buffer::new(&result).flags(DescriptorFlags::WRITE_ONLY))
.chain(Buffer::new(&status).flags(DescriptorFlags::WRITE_ONLY))
.build();
self.scheme.queue.send(chain);
// FIXME: interrupts are for a reason
while *status != 0 {
core::hint::spin_loop();
}
futures::executor::block_on(self.scheme.queue.send(chain));
assert_eq!(*status, 0);
let size = core::cmp::min(block_bytes.len(), result.len());
block_bytes[..size].copy_from_slice(&result.as_slice()[..size]);
std::thread::yield_now();
Ok(())
};
@@ -333,7 +292,7 @@ impl<'a> SchemeBlockMut for DiskScheme<'a> {
let abs_block = part.start_lba + rel_block;
let count = self.queue.read(abs_block, buf);
let count = futures::executor::block_on(self.queue.read(abs_block, buf));
*offset += count;
Ok(Some(count))
}
@@ -341,7 +300,9 @@ impl<'a> SchemeBlockMut for DiskScheme<'a> {
Handle::Disk { ref mut offset } => {
let block_size = self.cfg.block_size();
let count = self.queue.read((*offset as u64) / block_size as u64, buf);
let count = futures::executor::block_on(
self.queue.read((*offset as u64) / block_size as u64, buf),
);
*offset += count;
Ok(Some(count))
}
@@ -352,7 +313,9 @@ impl<'a> SchemeBlockMut for DiskScheme<'a> {
match *self.handles.get_mut(&id).ok_or(Error::new(EBADF))? {
Handle::Disk { ref mut offset } => {
let block_size = self.cfg.block_size();
let count = self.queue.write((*offset as u64) / block_size as u64, buf);
let count = futures::executor::block_on(
self.queue.write((*offset as u64) / block_size as u64, buf),
);
*offset += count;
Ok(Some(count))
+19
View File
@@ -0,0 +1,19 @@
[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"
futures = { version = "0.3.28", features = ["executor"] }
crossbeam-queue = "0.3.8"
redox-log = "0.1"
redox_event = { git = "https://gitlab.redox-os.org/redox-os/event.git" }
pcid = { path = "../pcid" }
+9
View File
@@ -0,0 +1,9 @@
#![feature(int_roundings)]
pub mod spec;
pub mod transport;
pub mod utils;
mod probe;
pub use probe::{probe_device, Device, MSIX_PRIMARY_VECTOR};
+279
View File
@@ -0,0 +1,279 @@
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, align_down};
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 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 mut info = MsixInfo {
virt_table_base: NonNull::new(virt_table_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 will have 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`]). This is *required* to be done
/// before starting the device.
/// * 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 {
let addr = addr + capability.offset as usize;
// XXX: physmap() requires the address to be page aligned.
let aligned_addr = align_down(addr);
let offset = addr - aligned_addr;
let size = offset + capability.length as usize;
let addr = syscall::physmap(
aligned_addr,
size,
PHYSMAP_WRITE | PHYSMAP_NO_CACHE,
)?;
addr + offset
};
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::trace!("virtio-core::device-probe: {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,
})
}
+68 -32
View File
@@ -1,28 +1,10 @@
//! https://docs.oasis-open.org/virtio/virtio/v1.1/virtio-v1.1.html
#![feature(int_roundings)]
use std::sync::atomic::{AtomicU16, AtomicU32, AtomicU64, Ordering};
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 {
@@ -136,21 +118,48 @@ bitflags::bitflags! {
#[repr(C)]
pub struct Descriptor {
/// Address (guest-physical).
pub address: u64,
pub address: AtomicU64,
/// Size of the descriptor.
pub size: u32,
pub flags: DescriptorFlags,
pub size: AtomicU32,
flags: AtomicU16,
/// Index of next desciptor in chain.
pub next: u16,
pub next: AtomicU16,
}
const_assert_eq!(core::mem::size_of::<Descriptor>(), 16);
impl Descriptor {
pub fn set_addr(&self, addr: u64) {
self.address.store(addr, Ordering::SeqCst)
}
pub fn set_size(&self, size: u32) {
self.size.store(size, Ordering::SeqCst)
}
pub fn set_next(&self, next: Option<u16>) {
self.next.store(next.unwrap_or_default(), Ordering::SeqCst)
}
pub fn set_flags(&self, flags: DescriptorFlags) {
self.flags.store(flags.bits(), Ordering::SeqCst)
}
pub fn next(&self) -> u16 {
self.next.load(Ordering::SeqCst)
}
pub fn flags(&self) -> DescriptorFlags {
DescriptorFlags::from_bits_truncate(self.flags.load(Ordering::SeqCst))
}
}
/// This indicates compliance with the version 1 VirtIO specification.
///
/// See `6.1 Driver Requirements: Reserved Feature Bits` section of the VirtIO
/// specification for more information.
pub const VIRTIO_F_VERSION_1: u32 = 32;
pub const VIRTIO_NET_F_MAC: u32 = 5;
// ======== Available Ring ========
//
@@ -159,7 +168,13 @@ pub const VIRTIO_F_VERSION_1: u32 = 32;
// chain.
#[repr(C)]
pub struct AvailableRingElement {
pub table_index: VolatileCell<u16>,
pub table_index: AtomicU16,
}
impl AvailableRingElement {
pub fn set_table_index(&self, index: u16) {
self.table_index.store(index, Ordering::SeqCst)
}
}
const_assert_eq!(core::mem::size_of::<AvailableRingElement>(), 2);
@@ -167,7 +182,7 @@ const_assert_eq!(core::mem::size_of::<AvailableRingElement>(), 2);
#[repr(C)]
pub struct AvailableRing {
pub flags: VolatileCell<u16>,
pub head_index: VolatileCell<u16>,
pub head_index: AtomicU16,
pub elements: IncompleteArrayField<AvailableRingElement>,
}
@@ -177,7 +192,7 @@ impl Default for AvailableRing {
fn default() -> Self {
Self {
flags: VolatileCell::new(0),
head_index: VolatileCell::new(0),
head_index: AtomicU16::new(0),
elements: IncompleteArrayField::new(),
}
}
@@ -225,9 +240,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 {
@@ -239,12 +254,29 @@ impl Buffer {
}
}
pub fn new_unsized<T>(val: &syscall::Dma<[T]>) -> Self {
Self {
buffer: val.physical(),
size: core::mem::size_of::<T>() * val.len(),
flags: DescriptorFlags::empty(),
}
}
pub fn new_sized<T>(val: &syscall::Dma<[T]>, size: usize) -> Self {
Self {
buffer: val.physical(),
size,
flags: DescriptorFlags::empty(),
}
}
pub fn flags(mut self, flags: DescriptorFlags) -> Self {
self.flags = flags;
self
}
}
/// XXX: The [`DescriptorFlags::NEXT`] flag is set automatically.
pub struct ChainBuilder {
buffers: Vec<Buffer>,
}
@@ -256,12 +288,16 @@ impl ChainBuilder {
}
}
pub fn chain(mut self, buffer: Buffer) -> Self {
pub fn chain(mut self, mut buffer: Buffer) -> Self {
buffer.flags |= DescriptorFlags::NEXT;
self.buffers.push(buffer);
self
}
pub fn build(self) -> Vec<Buffer> {
pub fn build(mut self) -> Vec<Buffer> {
let last_buffer = self.buffers.last_mut().expect("virtio-core: empty chain");
last_buffer.flags.remove(DescriptorFlags::NEXT);
self.buffers
}
}
@@ -1,14 +1,39 @@
use crate::spec::*;
use crate::utils::align;
use crate::*;
use pcid_interface::PciHeader;
use event::EventQueue;
use syscall::{Dma, PHYSMAP_WRITE};
use core::mem::size_of;
use core::sync::atomic::{fence, AtomicU16, Ordering};
use core::sync::atomic::{AtomicU16, Ordering};
use std::collections::VecDeque;
use std::fs::File;
use std::future::Future;
use std::os::fd::AsRawFd;
use std::sync::{Arc, Mutex, Weak};
use std::task::{Poll, Waker};
#[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.
///
@@ -43,23 +68,63 @@ const fn queue_part_sizes(queue_size: usize) -> (usize, usize, usize) {
)
}
pub struct QueueInner<'a> {
pub descriptor: Dma<[Descriptor]>,
pub available: Available<'a>,
pub used: Used<'a>,
/// Keeps track of unused descriptor indicies.
pub descriptor_stack: VecDeque<u16>,
notification_bell: &'a mut VolatileCell<u16>,
head_index: u16,
pub struct PendingRequest<'a> {
queue: Arc<Queue<'a>>,
first_descriptor: u32,
}
unsafe impl Sync for QueueInner<'_> {}
unsafe impl Send for QueueInner<'_> {}
impl<'a> Future for PendingRequest<'a> {
type Output = u32;
fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
// XXX: Register the waker before checking the queue to avoid the race condition
// where you lose a notification.
self.queue
.waker
.lock()
.unwrap()
.insert(self.first_descriptor, cx.waker().clone());
let used_head = self.queue.used.head_index();
let used_element = self.queue.used.get_element_at((used_head - 1) as usize);
let written = used_element.written.get();
let mut table_index = used_element.table_index.get();
if table_index == self.first_descriptor {
// The request has been completed; recycle the descriptors used.
while self.queue.descriptor[table_index as usize]
.flags()
.contains(DescriptorFlags::NEXT)
{
let next_index = self.queue.descriptor[table_index as usize].next();
self.queue.descriptor_stack.push(table_index as u16);
table_index = next_index.into();
}
// Push the last descriptor.
self.queue.descriptor_stack.push(table_index as u16);
self.queue
.waker
.lock()
.unwrap()
.remove(&self.first_descriptor);
return Poll::Ready(written);
} else {
return Poll::Pending;
}
}
}
pub struct Queue<'a> {
pub inner: Mutex<QueueInner<'a>>,
pub queue_index: u16,
pub waker: Mutex<std::collections::HashMap<u32, Waker>>,
pub used: Used<'a>,
pub descriptor: Dma<[Descriptor]>,
pub available: Available<'a>,
notification_bell: &'a mut AtomicU16,
descriptor_stack: crossbeam_queue::SegQueue<u16>,
sref: Weak<Self>,
}
@@ -69,94 +134,79 @@ impl<'a> Queue<'a> {
available: Available<'a>,
used: Used<'a>,
notification_bell: &'a mut VolatileCell<u16>,
notification_bell: &'a mut AtomicU16,
queue_index: u16,
) -> Arc<Self> {
let descriptor_stack = crossbeam_queue::SegQueue::new();
(0..descriptor.len() as u16).for_each(|i| descriptor_stack.push(i));
Arc::new_cyclic(|sref| Self {
inner: Mutex::new(QueueInner {
head_index: 0,
descriptor_stack: (0..(descriptor.len() - 1) as u16).rev().collect(),
descriptor,
available,
used,
notification_bell,
}),
notification_bell,
available,
descriptor,
used,
waker: Mutex::new(std::collections::HashMap::new()),
queue_index,
descriptor_stack,
sref: sref.clone(),
})
}
pub fn send(&self, chain: Vec<Buffer>) {
#[must_use = "The function returns a future that must be awaited to ensure the sent request is completed."]
pub fn send(&self, chain: Vec<Buffer>) -> PendingRequest<'a> {
let mut first_descriptor: Option<usize> = None;
let mut last_descriptor: Option<usize> = None;
for buffer in chain.iter() {
let descriptor = self.alloc_descriptor();
let mut inner = self.inner.lock().unwrap();
let descriptor = self.descriptor_stack.pop().unwrap() as usize;
if first_descriptor.is_none() {
first_descriptor = Some(descriptor);
}
inner.descriptor[descriptor].address = buffer.buffer as u64;
inner.descriptor[descriptor].flags = buffer.flags;
inner.descriptor[descriptor].size = buffer.size as u32;
self.descriptor[descriptor].set_addr(buffer.buffer as u64);
self.descriptor[descriptor].set_flags(buffer.flags);
self.descriptor[descriptor].set_size(buffer.size as u32);
if let Some(index) = last_descriptor {
inner.descriptor[index].next = descriptor as u16;
self.descriptor[index].set_next(Some(descriptor as u16));
}
last_descriptor = Some(descriptor);
}
let mut inner = self.inner.lock().unwrap();
let last_descriptor = last_descriptor.unwrap();
let first_descriptor = first_descriptor.unwrap();
inner.descriptor[last_descriptor].next = 0;
self.descriptor[last_descriptor].set_next(None);
fence(Ordering::SeqCst);
let index = inner.head_index as usize;
let index = self.available.head_index() as usize;
inner
.available
self.available
.get_element_at(index)
.table_index
.set(first_descriptor as u16);
.set_table_index(first_descriptor as u16);
fence(Ordering::SeqCst);
inner.available.set_head_idx(index as u16 + 1);
inner.head_index += 1;
self.available.set_head_idx(index as u16 + 1);
self.notification_bell
.store(self.queue_index, Ordering::SeqCst);
assert_eq!(inner.used.flags(), 0);
inner.notification_bell.set(0); // FIXME: This corresponds to the queue index.
}
assert_eq!(self.used.flags(), 0);
fn alloc_descriptor(&self) -> usize {
let mut inner = self.inner.lock().unwrap();
if let Some(index) = inner.descriptor_stack.pop_front() {
index as usize
} else {
log::warn!("virtiod: descriptors exhausted, waiting on garabage collector");
drop(inner);
// Wait for the garbage collector thread to release some descriptors.
//
// TODO(andypython): Instead of just yielding, we should have a proper notificiation
// mechanism. I am not aware whats the standard way redox applications
// or drivers implement basically a WaitQueue which you can use to wake
// up a thread. The descripts really should NEVER run out, but if they
// do, have a proper way to handle them.
std::thread::yield_now();
self.alloc_descriptor()
PendingRequest {
queue: self.sref.upgrade().unwrap(),
first_descriptor: first_descriptor as u32,
}
}
/// Returns the number of descriptors in the descriptor table of this queue.
pub fn descriptor_len(&self) -> usize {
self.descriptor.len()
}
}
unsafe impl Sync for Queue<'_> {}
unsafe impl Send for Queue<'_> {}
pub struct Available<'a> {
addr: usize,
size: usize,
@@ -167,7 +217,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
@@ -187,20 +237,24 @@ impl<'a> Available<'a> {
/// ## Panics
/// This function panics if the index is out of bounds.
pub fn get_element_at(&mut self, index: usize) -> &mut AvailableRingElement {
pub fn get_element_at(&self, index: usize) -> &AvailableRingElement {
// SAFETY: We have exclusive access to the elements and the number of elements
// is correct; same as the queue size.
unsafe {
self.ring
.elements
.as_mut_slice(self.queue_size)
.get_mut(index % 256)
.expect("virtio::available: index out of bounds")
.as_slice(self.queue_size)
.get(index % 256)
.expect("virtio-core::available: index out of bounds")
}
}
pub fn set_head_idx(&mut self, index: u16) {
self.ring.head_index.set(index);
pub fn head_index(&self) -> u16 {
self.ring.head_index.load(Ordering::SeqCst)
}
pub fn set_head_idx(&self, index: u16) {
self.ring.head_index.store(index, Ordering::SeqCst);
}
pub fn phys_addr(&self) -> usize {
@@ -210,7 +264,7 @@ impl<'a> Available<'a> {
impl Drop for Available<'_> {
fn drop(&mut self) {
log::warn!("virtio: dropping 'available' ring at {:#x}", self.addr);
log::warn!("virtio-core: dropping 'available' ring at {:#x}", self.addr);
unsafe {
syscall::physunmap(self.addr).unwrap();
@@ -229,7 +283,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
@@ -249,7 +303,21 @@ impl<'a> Used<'a> {
/// ## Panics
/// This function panics if the index is out of bounds.
pub fn get_element_at(&mut self, index: usize) -> &mut UsedRingElement {
pub fn get_element_at(&self, index: usize) -> &UsedRingElement {
// SAFETY: We have exclusive access to the elements and the number of elements
// is correct; same as the queue size.
unsafe {
self.ring
.elements
.as_slice(self.queue_size)
.get(index % 256)
.expect("virtio-core::used: index out of bounds")
}
}
/// ## Panics
/// This function panics if the index is out of bounds.
pub fn get_mut_element_at(&mut self, index: usize) -> &mut UsedRingElement {
// SAFETY: We have exclusive access to the elements and the number of elements
// is correct; same as the queue size.
unsafe {
@@ -257,7 +325,7 @@ impl<'a> Used<'a> {
.elements
.as_mut_slice(self.queue_size)
.get_mut(index % 256)
.expect("virtio::used: index out of bounds")
.expect("virtio-core::used: index out of bounds")
}
}
@@ -276,7 +344,7 @@ impl<'a> Used<'a> {
impl Drop for Used<'_> {
fn drop(&mut self) {
log::warn!("virtio: dropping 'used' ring at {:#x}", self.addr);
log::warn!("virtio-core: dropping 'used' ring at {:#x}", self.addr);
unsafe {
syscall::physunmap(self.addr).unwrap();
@@ -286,39 +354,24 @@ impl Drop for Used<'_> {
}
pub struct StandardTransport<'a> {
header: PciHeader,
common: Mutex<&'a mut CommonCfg>,
notify: *const u8,
notify_mul: u32,
queue_index: AtomicU16,
sref: Weak<Self>,
}
impl<'a> StandardTransport<'a> {
pub fn new(
header: PciHeader,
common: &'a mut CommonCfg,
notify: *const u8,
notify_mul: u32,
) -> Arc<Self> {
Arc::new_cyclic(|sref| Self {
header,
pub fn new(common: &'a mut CommonCfg, notify: *const u8, notify_mul: u32) -> Arc<Self> {
Arc::new(Self {
common: Mutex::new(common),
notify,
notify_mul,
queue_index: AtomicU16::new(0),
sref: sref.clone(),
})
}
pub fn sref(&self) -> Arc<Self> {
// UNWRAP: The constructor ensures that we are wrapped in our own `Arc`. So this
// unwrap is going to be unreachable.
self.sref.upgrade().unwrap()
}
pub fn check_device_feature(&self, feature: u32) -> bool {
let mut common = self.common.lock().unwrap();
@@ -362,7 +415,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, irq_handle: &File) -> Result<Arc<Queue<'a>>, Error> {
let mut common = self.common.lock().unwrap();
let queue_index = self.queue_index.fetch_add(1, Ordering::SeqCst);
@@ -371,21 +424,22 @@ impl<'a> StandardTransport<'a> {
let queue_size = common.queue_size.get() as usize;
let queue_notify_idx = common.queue_notify_off.get();
log::info!("notify_idx: {}", queue_notify_idx);
// Allocate memory for the queue structues.
let descriptor = unsafe {
Dma::<[Descriptor]>::zeroed_unsized(queue_size).map_err(Error::SyscallError)?
};
let mut avail = Available::new(queue_size)?;
let avail = Available::new(queue_size)?;
let mut used = Used::new(queue_size)?;
for i in 0..queue_size {
// XXX: Fill the `table_index` of the elements with `T::MAX` to help with
// debugging since qemu reports them as illegal values.
avail.get_element_at(i).table_index.set(u16::MAX);
used.get_element_at(i).table_index.set(u32::MAX);
avail
.get_element_at(i)
.table_index
.store(u16::MAX, Ordering::Relaxed);
used.get_mut_element_at(i).table_index.set(u32::MAX);
}
common.queue_desc.set(descriptor.physical() as u64);
@@ -401,10 +455,33 @@ impl<'a> StandardTransport<'a> {
let notification_bell = unsafe {
let offset = self.notify_mul * queue_notify_idx as u32;
&mut *(self.notify.add(offset as usize) as *mut VolatileCell<u16>)
&mut *(self.notify.add(offset as usize) as *mut AtomicU16)
};
log::info!("virtio: enabled queue #{queue_index} (size={queue_size})");
Ok(Queue::new(descriptor, avail, used, notification_bell))
log::info!("virtio-core: enabled queue #{queue_index} (size={queue_size})");
let queue = Queue::new(descriptor, avail, used, notification_bell, queue_index);
let queue_copy = queue.clone();
let irq_fd = irq_handle.as_raw_fd();
std::thread::spawn(move || {
let mut event_queue = EventQueue::<usize>::new().unwrap();
event_queue
.add(irq_fd, move |_| -> Result<Option<usize>, std::io::Error> {
for (_, task) in queue_copy.waker.lock().unwrap().iter() {
task.wake_by_ref();
}
Ok(None)
})
.unwrap();
loop {
event_queue.run().unwrap();
}
});
Ok(queue)
}
}
@@ -63,3 +63,43 @@ impl<T> IncompleteArrayField<T> {
pub const fn align(val: usize, align: usize) -> usize {
(val + align) & !align
}
pub const fn align_down(addr: usize) -> usize {
addr & !(syscall::PAGE_SIZE - 1)
}
#[cfg(target_os = "redox")]
pub fn setup_logging(level: log::LevelFilter, name: &str) {
use redox_log::{OutputBuilder, RedoxLogger};
let mut logger = RedoxLogger::new().with_output(
OutputBuilder::stderr()
.with_filter(level)
.with_ansi_escape_codes()
.flush_on_newline(true)
.build(),
);
match OutputBuilder::in_redox_logging_scheme("disk", "pcie", format!("{name}.log")) {
Ok(builder) => {
logger = logger.with_output(builder.with_filter(level).flush_on_newline(true).build())
}
Err(err) => eprintln!("virtio-core::utils: failed to create log: {}", err),
}
match OutputBuilder::in_redox_logging_scheme("disk", "pcie", format!("{name}.ansi.log")) {
Ok(builder) => {
logger = logger.with_output(
builder
.with_filter(level)
.with_ansi_escape_codes()
.flush_on_newline(true)
.build(),
)
}
Err(err) => eprintln!("virtio-core::utils: failed to create ANSI log: {}", err),
}
logger.enable().unwrap();
log::info!("virtio-core::utils: enabled logger");
}
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "virtio-gpud"
version = "0.1.0"
edition = "2021"
authors = ["Anhad Singh <andypython@protonmail.com>"]
[dependencies]
log = "0.4"
virtio-core = { path = "../virtio-core" }
pcid = { path = "../pcid" }
redox-daemon = "0.1"
+28
View File
@@ -0,0 +1,28 @@
use pcid_interface::PcidServerHandle;
use virtio_core::transport::Error;
fn deamon(deamon: redox_daemon::Daemon) -> Result<(), Error> {
let mut pcid_handle = PcidServerHandle::connect_default()?;
// Double check that we have the right device.
//
// 0x1050 - virtio-gpu
let pci_config = pcid_handle.fetch_config()?;
assert_eq!(pci_config.func.devid, 0x1050);
log::info!("virtio-gpu: initiating startup sequence :^)");
let device = virtio_core::probe_device(&mut pcid_handle)?;
loop {}
}
fn daemon_runner(redox_daemon: redox_daemon::Daemon) -> ! {
deamon(redox_daemon).unwrap();
unreachable!();
}
pub fn main() {
#[cfg(target_os = "redox")]
virtio_core::utils::setup_logging(log::LevelFilter::Trace, "virtio-gpud");
redox_daemon::Daemon::new(daemon_runner).expect("virtio-core: failed to daemonize");
}
+16
View File
@@ -0,0 +1,16 @@
[package]
name = "virtio-netd"
version = "0.1.0"
edition = "2021"
[dependencies]
log = "0.4"
static_assertions = "1.1.0"
futures = { version = "0.3.28", features = ["executor"] }
virtio-core = { path = "../virtio-core" }
pcid = { path = "../pcid" }
redox-daemon = "0.1"
redox_syscall = "0.3"
netutils = { git = "https://gitlab.redox-os.org/redox-os/netutils.git", branch = "redox-unix" }
+128
View File
@@ -0,0 +1,128 @@
mod scheme;
use std::fs::File;
use std::io::{Read, Write};
use std::os::fd::{FromRawFd, RawFd};
use pcid_interface::PcidServerHandle;
use syscall::{Packet, SchemeBlockMut};
use virtio_core::spec::VIRTIO_NET_F_MAC;
use virtio_core::transport::Error;
use scheme::NetworkScheme;
#[derive(Debug)]
#[repr(C)]
pub struct VirtHeader {
pub flags: u8,
pub gso_type: u8,
pub hdr_len: u16,
pub gso_size: u16,
pub csum_start: u16,
pub csum_offset: u16,
pub num_buffers: u16,
}
static_assertions::const_assert_eq!(core::mem::size_of::<VirtHeader>(), 12);
const MAX_BUFFER_LEN: usize = 65535;
fn deamon(deamon: redox_daemon::Daemon) -> Result<(), Error> {
let mut pcid_handle = PcidServerHandle::connect_default()?;
// Double check that we have the right device.
//
// 0x1000 - virtio-net
let pci_config = pcid_handle.fetch_config()?;
assert_eq!(pci_config.func.devid, 0x1000);
log::info!("virtio-net: initiating startup sequence :^)");
let device = virtio_core::probe_device(&mut pcid_handle)?;
let device_space = device.device_space;
// Negotiate device features:
let mac_addr = if device.transport.check_device_feature(VIRTIO_NET_F_MAC) {
let mac = (0..6)
.map(|i| unsafe { core::ptr::read_volatile(device_space.add(i)) })
.collect::<Vec<u8>>();
let mac_str = format!(
"{:>02X}:{:>02X}:{:>02X}:{:>02X}:{:>02X}:{:>02X}",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]
);
log::info!("virtio-net: device MAC is {mac_str}");
device.transport.ack_driver_feature(VIRTIO_NET_F_MAC);
mac_str
} else {
unimplemented!()
};
device.transport.finalize_features();
// Allocate the recieve and transmit queues:
//
// > Empty buffers are placed in one virtqueue for receiving
// > packets, and outgoing packets are enqueued into another
// > for transmission in that order.
//
// TODO(andypython): Should we use the same IRQ vector for both?
let rx_queue = device
.transport
.setup_queue(virtio_core::MSIX_PRIMARY_VECTOR, &device.irq_handle)?;
let tx_queue = device
.transport
.setup_queue(virtio_core::MSIX_PRIMARY_VECTOR, &device.irq_handle)?;
device.transport.run_device();
let mut name = pci_config.func.name();
name.push_str("_virtio_net");
// Create the network scheme.
//
// FIXME(andypython): It should be fine to have multiple network devices.
let socket_fd = syscall::open(
&format!(":network"),
syscall::O_RDWR | syscall::O_CREAT | syscall::O_CLOEXEC,
)
.map_err(Error::SyscallError)?;
let mut socket_fd = unsafe { File::from_raw_fd(socket_fd as RawFd) };
let mut scheme = NetworkScheme::new(rx_queue, tx_queue);
let _ = netutils::setcfg("mac", &mac_addr);
deamon.ready().expect("virtio-netd: failed to deamonize");
loop {
let mut packet = Packet::default();
socket_fd
.read(&mut packet)
.expect("virtio-netd: failed to read packet");
let result = scheme.handle(&mut packet);
// `packet.a` contains the return value.
packet.a = result.expect("virtio-netd: failed to handle packet");
socket_fd
.write(&packet)
.expect("virtio-netd: failed to write packet");
}
}
fn daemon_runner(redox_daemon: redox_daemon::Daemon) -> ! {
deamon(redox_daemon).unwrap();
unreachable!();
}
pub fn main() {
#[cfg(target_os = "redox")]
virtio_core::utils::setup_logging(log::LevelFilter::Trace, "virtio-netd");
redox_daemon::Daemon::new(daemon_runner).expect("virtio-core: failed to daemonize");
}
+163
View File
@@ -0,0 +1,163 @@
use std::collections::BTreeMap;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use syscall::Error as SysError;
use syscall::*;
use virtio_core::spec::{Buffer, ChainBuilder, DescriptorFlags};
use virtio_core::transport::Queue;
use crate::{VirtHeader, MAX_BUFFER_LEN};
pub struct NetworkScheme<'a> {
/// Reciever Queue.
rx: Arc<Queue<'a>>,
rx_buffers: Vec<Dma<[u8]>>,
/// Transmiter Queue.
tx: Arc<Queue<'a>>,
/// File descriptor handles.
handles: BTreeMap<usize, usize>,
next_id: AtomicUsize,
recv_head: u16,
}
impl<'a> NetworkScheme<'a> {
pub fn new(rx: Arc<Queue<'a>>, tx: Arc<Queue<'a>>) -> Self {
// Populate all of the `rx_queue` with buffers to maximize performence.
let mut rx_buffers = vec![];
for i in 0..(rx.descriptor_len() as usize) {
rx_buffers.push(unsafe { Dma::<[u8]>::zeroed_unsized(MAX_BUFFER_LEN) }.unwrap());
let chain = ChainBuilder::new()
.chain(Buffer::new_unsized(&rx_buffers[i]).flags(DescriptorFlags::WRITE_ONLY))
.build();
let _ = rx.send(chain);
}
Self {
rx,
rx_buffers,
tx,
handles: BTreeMap::new(),
next_id: AtomicUsize::new(0),
recv_head: 0,
}
}
/// Returns the number of bytes read. Returns `0` if the operation would block.
fn try_recv(&mut self, target: &mut [u8]) -> usize {
let header_size = core::mem::size_of::<VirtHeader>();
if self.recv_head == self.rx.used.head_index() {
// The read would block.
return 0;
}
let idx = self.rx.used.head_index() as usize;
let element = self.rx.used.get_element_at(idx - 1);
let descriptor_idx = element.table_index.get();
let payload_size = element.written.get() as usize - header_size;
// XXX: The header and packet are added as one output descriptor to the transmit queue,
// and the device is notified of the new entry (see 5.1.5 Device Initialization).
let buffer = &self.rx_buffers[descriptor_idx as usize];
// TODO: Check the header.
let _header = unsafe { &*(buffer.as_ptr() as *const VirtHeader) };
let packet = &buffer[header_size..(header_size + payload_size)];
// Copy the packet into the buffer.
target[..payload_size].copy_from_slice(&packet);
self.recv_head = self.rx.used.head_index();
payload_size
}
}
impl<'a> SchemeBlockMut for NetworkScheme<'a> {
fn open(
&mut self,
_path: &str,
flags: usize,
uid: u32,
_gid: u32,
) -> syscall::Result<Option<usize>> {
if uid != 0 {
return Err(SysError::new(EACCES));
}
let id = self.next_id.fetch_add(1, Ordering::SeqCst);
self.handles.insert(id, flags);
Ok(Some(id))
}
fn read(&mut self, id: usize, buf: &mut [u8]) -> syscall::Result<Option<usize>> {
let flags = *self.handles.get(&id).ok_or(SysError::new(EBADF))?;
let bytes = self.try_recv(buf);
if bytes != 0 {
// We read some bytes.
Ok(Some(bytes))
} else if flags & O_NONBLOCK == O_NONBLOCK {
// We are in non-blocking mode.
Err(SysError::new(EWOULDBLOCK))
} else {
// Block
unimplemented!()
}
}
fn write(&mut self, id: usize, buffer: &[u8]) -> syscall::Result<Option<usize>> {
if self.handles.get(&id).is_none() {
return Err(SysError::new(EBADF));
}
let header = unsafe { Dma::<VirtHeader>::zeroed()?.assume_init() };
let mut payload = unsafe { Dma::<[u8]>::zeroed_unsized(buffer.len())? };
payload.copy_from_slice(buffer);
let chain = ChainBuilder::new()
.chain(Buffer::new(&header))
.chain(Buffer::new_unsized(&payload))
.build();
futures::executor::block_on(self.tx.send(chain));
Ok(Some(buffer.len()))
}
fn dup(&mut self, _old_id: usize, _buf: &[u8]) -> syscall::Result<Option<usize>> {
unimplemented!()
}
fn fevent(
&mut self,
id: usize,
_flags: syscall::EventFlags,
) -> syscall::Result<Option<syscall::EventFlags>> {
if self.handles.get(&id).is_none() {
return Err(SysError::new(EBADF));
}
Ok(Some(syscall::EventFlags::empty()))
}
fn fpath(&mut self, _id: usize, _buf: &mut [u8]) -> syscall::Result<Option<usize>> {
unimplemented!()
}
fn fsync(&mut self, _id: usize) -> syscall::Result<Option<usize>> {
unimplemented!()
}
fn close(&mut self, _id: usize) -> syscall::Result<Option<usize>> {
unimplemented!()
}
}
-20
View File
@@ -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 devices 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`
-476
View File
@@ -1,476 +0,0 @@
#![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};
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 event::EventQueue;
use syscall::{Io, Packet, SchemeBlockMut, PHYSMAP_NO_CACHE, PHYSMAP_WRITE};
use virtiod::utils::VolatileCell;
mod scheme;
pub fn main() -> anyhow::Result<()> {
#[cfg(target_os = "redox")]
setup_logging();
redox_daemon::Daemon::new(daemon_runner).expect("virtio-core: failed to daemonize");
}
#[cfg(target_os = "redox")]
fn setup_logging() {
use redox_log::{OutputBuilder, RedoxLogger};
let mut logger = RedoxLogger::new().with_output(
OutputBuilder::stderr()
.with_filter(log::LevelFilter::Trace)
.with_ansi_escape_codes()
.flush_on_newline(true)
.build(),
);
match OutputBuilder::in_redox_logging_scheme("disk", "pcie", "virtiod.log") {
Ok(builder) => {
logger = logger.with_output(
builder
.with_filter(log::LevelFilter::Trace)
.flush_on_newline(true)
.build(),
)
}
Err(err) => eprintln!("virtiod: failed to create log: {}", err),
}
match OutputBuilder::in_redox_logging_scheme("disk", "pcie", "virtiod.ansi.log") {
Ok(builder) => {
logger = logger.with_output(
builder
.with_filter(log::LevelFilter::Trace)
.with_ansi_escape_codes()
.flush_on_newline(true)
.build(),
)
}
Err(err) => eprintln!("virtiod: failed to create ansi log: {}", err),
}
logger.enable().unwrap();
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>,
pub heads: VolatileCell<u8>,
pub sectors: VolatileCell<u8>,
}
#[repr(C)]
pub struct BlockDeviceConfig {
capacity: VolatileCell<u64>,
pub size_max: VolatileCell<u32>,
pub seq_max: VolatileCell<u32>,
pub geometry: BlockGeometry,
blk_size: VolatileCell<u32>,
}
impl BlockDeviceConfig {
/// Returns the capacity of the block device in bytes.
pub fn capacity(&self) -> u64 {
self.capacity.get()
}
pub fn block_size(&self) -> u32 {
self.blk_size.get()
}
}
#[repr(u32)]
pub enum BlockRequestTy {
In = 0,
Out = 1,
}
const_assert_eq!(core::mem::size_of::<BlockRequestTy>(), 4);
#[repr(C)]
pub struct BlockVirtRequest {
pub ty: BlockRequestTy,
pub reserved: u32,
pub sector: u64,
}
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()?;
// 0x1001 - virtio-blk
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;
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_copy = queue.clone();
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(),
move |_| -> Result<Option<usize>, io::Error> {
// Read from ISR to acknowledge the interrupt.
let _isr = isr.get() as usize;
let mut inner = queue_copy.inner.lock().unwrap();
let used_head = inner.used.head_index();
if progress_head == used_head {
return Ok(None);
}
for i in progress_head..used_head {
let used = inner.used.get_element_at(i as usize);
let mut desc_idx = used.table_index.get();
inner.descriptor_stack.push_back(desc_idx as u16);
loop {
let desc = &inner.descriptor[desc_idx as usize];
if !desc.flags.contains(DescriptorFlags::NEXT) {
break;
}
desc_idx = desc.next.into();
inner.descriptor_stack.push_back(desc_idx as u16);
}
}
progress_head = used_head;
drop(inner);
let mut buf = [0u8; 8];
irq_handle.read(&mut buf)?;
// Acknowledge the interrupt.
// irq_handle.write(&buf)?;
Ok(None)
},
)
.unwrap();
loop {
event_queue.run().unwrap();
}
});
// At this point the device is alive!
transport.run_device();
log::info!(
"virtio-blk: disk size: {} sectors and block size of {} bytes",
device_space.capacity.get(),
device_space.blk_size.get()
);
let mut name = pci_config.func.name();
name.push_str("_virtio_blk");
let scheme_name = format!("disk/{}", name);
let socket_fd = syscall::open(
&format!(":{}", scheme_name),
syscall::O_RDWR | syscall::O_CREAT | syscall::O_CLOEXEC,
)
.map_err(Error::SyscallError)?;
let mut socket_file = unsafe { File::from_raw_fd(socket_fd as RawFd) };
let mut scheme = scheme::DiskScheme::new(queue, device_space);
deamon.ready().expect("virtio: failed to deamonize");
loop {
let mut packet = Packet::default();
socket_file
.read(&mut packet)
.expect("ahcid: failed to read disk scheme");
let packey = scheme.handle(&mut packet);
packet.a = packey.unwrap();
socket_file
.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) -> ! {
deamon(redox_daemon).unwrap();
unreachable!();
}