Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
296 changes: 294 additions & 2 deletions src/drivers/fs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,298 @@
//! [File System Device]: https://docs.oasis-open.org/virtio/virtio/v1.2/cs01/virtio-v1.2-cs01.html#x1-45800011

#[cfg(feature = "pci")]
pub mod virtio_fs;
mod pci;

use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use core::mem::MaybeUninit;
use core::str;

use fuse_abi::linux::fuse_out_header;
use num_enum::TryFromPrimitive;
use pci_types::InterruptLine;
use smallvec::SmallVec;
use virtio::FeatureBits;
use virtio::fs::ConfigVolatileFieldAccess;
use volatile::VolatileRef;
use volatile::access::ReadOnly;

use crate::config::VIRTIO_MAX_QUEUE_SIZE;
use crate::drivers::Driver;
use crate::drivers::virtio::error::VirtioFsError;
#[cfg(not(feature = "pci"))]
use crate::drivers::virtio::transport::mmio::{ComCfg, IsrStatus, NotifCfg};
#[cfg(feature = "pci")]
pub mod virtio_pci;
use crate::drivers::virtio::transport::pci::{ComCfg, IsrStatus, NotifCfg};
use crate::drivers::virtio::virtqueue::error::VirtqError;
use crate::drivers::virtio::virtqueue::split::SplitVq;
use crate::drivers::virtio::virtqueue::{
AvailBufferToken, BufferElem, BufferType, VirtQueue, Virtq,
};
use crate::errno::Errno;
use crate::fs::fuse::{self, FuseError, FuseInterface, Rsp, RspHeader};
use crate::mm::device_alloc::DeviceAlloc;

/// A wrapper struct for the raw configuration structure.
/// Handling the right access to fields, as some are read-only
/// for the driver.
pub(crate) struct FsDevCfg {
pub raw: VolatileRef<'static, virtio::fs::Config, ReadOnly>,
pub dev_id: u16,
pub features: virtio::fs::F,
}

/// Virtio file system driver struct.
///
/// Struct allows to control devices virtqueues as also
/// the device itself.
#[allow(dead_code)]
pub(crate) struct VirtioFsDriver {
pub(super) dev_cfg: FsDevCfg,
pub(super) com_cfg: ComCfg,
pub(super) isr_stat: IsrStatus,
pub(super) notif_cfg: NotifCfg,
pub(super) vqueues: Vec<VirtQueue>,
pub(super) irq: InterruptLine,
}

// Backend-independent interface for Virtio network driver
impl VirtioFsDriver {
#[cfg(feature = "pci")]
pub fn get_dev_id(&self) -> u16 {
self.dev_cfg.dev_id
}

#[cfg(feature = "pci")]
pub fn set_failed(&mut self) {
self.com_cfg.set_failed();
}

/// Negotiates a subset of features, understood and wanted by both the OS
/// and the device.
fn negotiate_features(&mut self, driver_features: virtio::fs::F) -> Result<(), VirtioFsError> {
let device_features = virtio::fs::F::from(self.com_cfg.dev_features());

if device_features.requirements_satisfied() {
debug!(
"Feature set wanted by filesystem driver are in conformance with specification."
);
} else {
return Err(VirtioFsError::FeatureRequirementsNotMet(device_features));
}

if device_features.contains(driver_features) {
// If device supports subset of features write feature set to common config
self.com_cfg.set_drv_features(driver_features.into());
Ok(())
} else {
Err(VirtioFsError::IncompatibleFeatureSets(
driver_features,
device_features,
))
}
}

/// Initializes the device in adherence to specification. Returns Some(VirtioFsError)
/// upon failure and None in case everything worked as expected.
///
/// See Virtio specification v1.1. - 3.1.1.
/// and v1.1. - 5.11.5
pub(crate) fn init_dev(&mut self) -> Result<(), VirtioFsError> {
// Reset
self.com_cfg.reset_dev();

// Indicate device, that OS noticed it
self.com_cfg.ack_dev();

// Indicate device, that driver is able to handle it
self.com_cfg.set_drv();

let features = virtio::fs::F::VERSION_1;
self.negotiate_features(features)?;

// Indicates the device, that the current feature set is final for the driver
// and will not be changed.
self.com_cfg.features_ok();

// Checks if the device has accepted final set. This finishes feature negotiation.
if self.com_cfg.check_features() {
info!(
"Features have been negotiated between virtio filesystem device {:x} and driver.",
self.dev_cfg.dev_id
);
// Set feature set in device config fur future use.
self.dev_cfg.features = features;
} else {
return Err(VirtioFsError::FailFeatureNeg(self.dev_cfg.dev_id));
}

// 1 highprio queue, and n normal request queues
let vqnum = self
.dev_cfg
.raw
.as_ptr()
.num_request_queues()
.read()
.to_ne() + 1;
if vqnum == 0 {
error!("0 request queues requested from device. Aborting!");
return Err(VirtioFsError::Unknown);
}

// create the queues and tell device about them
for i in 0..vqnum as u16 {
let vq = VirtQueue::Split(
SplitVq::new(
&mut self.com_cfg,
&self.notif_cfg,
VIRTIO_MAX_QUEUE_SIZE,
i,
self.dev_cfg.features.into(),
)
.unwrap(),
);
self.vqueues.push(vq);
}

// At this point the device is "live"
self.com_cfg.drv_ok();

Ok(())
}
}

impl FuseInterface for VirtioFsDriver {
fn send_command<O: fuse::ops::Op + 'static>(
&mut self,
cmd: fuse::Cmd<O>,
rsp_payload_len: u32,
) -> Result<fuse::Rsp<O>, FuseError>
where
<O as fuse::ops::Op>::InStruct: Send,
<O as fuse::ops::Op>::OutStruct: Send,
{
let fuse::Cmd {
headers: cmd_headers,
payload: cmd_payload_opt,
} = cmd;
let send = if let Some(cmd_payload) = cmd_payload_opt {
SmallVec::from_buf([
BufferElem::Sized(cmd_headers),
BufferElem::Vector(cmd_payload),
])
} else {
let mut vec = SmallVec::new();
vec.push(BufferElem::Sized(cmd_headers));
vec
};

// If the operation fails, it is possible for its header to be uninitialized.
// For this reason, we use a instantiation of the RspHeader structure where
// the op_header field is MaybeUninit
let rsp_headers =
Box::<RspHeader<O, MaybeUninit<O::OutStruct>>, _>::new_uninit_in(DeviceAlloc);
let recv = if rsp_payload_len == 0 {
let mut vec = SmallVec::new();
vec.push(BufferElem::Sized(rsp_headers));
vec
} else {
SmallVec::from_buf([
BufferElem::Sized(rsp_headers),
BufferElem::Vector(Vec::with_capacity_in(rsp_payload_len as usize, DeviceAlloc)),
])
};

let buffer_tkn = AvailBufferToken::new(send, recv).unwrap();
let mut transfer_result =
self.vqueues[1].dispatch_blocking(buffer_tkn, BufferType::Direct)?;

let (dyn_headers, written_header_len) =
transfer_result.used_recv_buff.pop_front_raw().unwrap();
let headers = dyn_headers
.downcast::<MaybeUninit<RspHeader<O, MaybeUninit<O::OutStruct>>>>()
.unwrap();
if written_header_len < size_of::<fuse_out_header>() {
return Err(VirtqError::IncompleteWrite.into());
}

// SAFETY: we confirmed that the out_header was written. The op_header does not need to be initialized at this stage,
// as it is behind a nested MaybeUninit.
let headers = unsafe { headers.assume_init() };

if headers.out_header.error != 0
|| (written_header_len - size_of::<fuse_out_header>()) != size_of::<O::OutStruct>()
{
// "However, if the reply is an error reply (i.e., error is set), then no further payload data should be sent,
// independent of the request." (fuse man page)

return Err(FuseError::IOError(
Errno::try_from_primitive(-headers.out_header.error).unwrap_or(Errno::Io),
));
}

// SAFETY: the conditional above ensures that the second field was filled in, so we can transmute it from MaybeUninit to normal.
let headers = unsafe {
core::mem::transmute::<
Box<RspHeader<O, MaybeUninit<O::OutStruct>>, _>,
Box<RspHeader<O>, _>,
>(headers)
};
let payload = transfer_result.used_recv_buff.pop_front_vec();
Ok(Rsp { headers, payload })
}

fn get_mount_point(&self) -> String {
let tag = self.dev_cfg.raw.as_ptr().tag().read();
let tag = str::from_utf8(&tag).unwrap();
let tag = tag.split('\0').next().unwrap();
tag.to_owned()
}
}

impl Driver for VirtioFsDriver {
fn get_interrupt_number(&self) -> InterruptLine {
self.irq
}

fn get_name(&self) -> &'static str {
"virtio"
}
}

/// Error module of virtios filesystem driver.
pub mod error {
use thiserror::Error;

/// Network filesystem error enum.
#[derive(Error, Debug, Copy, Clone)]
pub enum VirtioFsError {
#[cfg(feature = "pci")]
#[error(
"Virtio filesystem driver failed, for device {0:x}, due to a missing or malformed device config!"
)]
NoDevCfg(u16),

#[error(
"Virtio filesystem driver failed, for device {0:x}, device did not acknowledge negotiated feature set!"
)]
FailFeatureNeg(u16),

/// The first field contains the feature bits wanted by the driver.
/// but which are incompatible with the device feature set, second field.
#[error("Feature set: {0:?} , is incompatible with the device features: {1:?}")]
IncompatibleFeatureSets(virtio::fs::F, virtio::fs::F),

/// Set of features does not adhere to the requirements of features
/// indicated by the specification
#[error(
"Virtio filesystem driver tried to set feature bit without setting dependency feature. Feat set: {0:?}"
)]
FeatureRequirementsNotMet(virtio::fs::F),

#[error("Virtio filesystem failed, driver failed due unknown reason!")]
Unknown,
}
}
2 changes: 1 addition & 1 deletion src/drivers/fs/virtio_pci.rs → src/drivers/fs/pci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use alloc::vec::Vec;
use volatile::VolatileRef;

use crate::arch::pci::PciConfigRegion;
use crate::drivers::fs::virtio_fs::{FsDevCfg, VirtioFsDriver};
use crate::drivers::fs::{FsDevCfg, VirtioFsDriver};
use crate::drivers::pci::PciDevice;
use crate::drivers::virtio::error::{self, VirtioError};
use crate::drivers::virtio::transport::pci;
Expand Down
Loading