hermit/drivers/fs/
virtio_fs.rs

1use alloc::boxed::Box;
2use alloc::string::{String, ToString};
3use alloc::vec::Vec;
4use core::mem::MaybeUninit;
5use core::str;
6
7use fuse_abi::linux::fuse_out_header;
8use num_enum::TryFromPrimitive;
9use pci_types::InterruptLine;
10use smallvec::SmallVec;
11use virtio::FeatureBits;
12use virtio::fs::ConfigVolatileFieldAccess;
13use volatile::VolatileRef;
14use volatile::access::ReadOnly;
15
16use crate::config::VIRTIO_MAX_QUEUE_SIZE;
17use crate::drivers::Driver;
18use crate::drivers::virtio::error::VirtioFsError;
19#[cfg(not(feature = "pci"))]
20use crate::drivers::virtio::transport::mmio::{ComCfg, IsrStatus, NotifCfg};
21#[cfg(feature = "pci")]
22use crate::drivers::virtio::transport::pci::{ComCfg, IsrStatus, NotifCfg};
23use crate::drivers::virtio::virtqueue::error::VirtqError;
24use crate::drivers::virtio::virtqueue::split::SplitVq;
25use crate::drivers::virtio::virtqueue::{
26	AvailBufferToken, BufferElem, BufferType, VirtQueue, Virtq, VqIndex, VqSize,
27};
28use crate::fs::fuse::{self, FuseError, FuseInterface, Rsp, RspHeader};
29use crate::io;
30use crate::mm::device_alloc::DeviceAlloc;
31
32/// A wrapper struct for the raw configuration structure.
33/// Handling the right access to fields, as some are read-only
34/// for the driver.
35pub(crate) struct FsDevCfg {
36	pub raw: VolatileRef<'static, virtio::fs::Config, ReadOnly>,
37	pub dev_id: u16,
38	pub features: virtio::fs::F,
39}
40
41/// Virtio file system driver struct.
42///
43/// Struct allows to control devices virtqueues as also
44/// the device itself.
45#[allow(dead_code)]
46pub(crate) struct VirtioFsDriver {
47	pub(super) dev_cfg: FsDevCfg,
48	pub(super) com_cfg: ComCfg,
49	pub(super) isr_stat: IsrStatus,
50	pub(super) notif_cfg: NotifCfg,
51	pub(super) vqueues: Vec<VirtQueue>,
52	pub(super) irq: InterruptLine,
53}
54
55// Backend-independent interface for Virtio network driver
56impl VirtioFsDriver {
57	#[cfg(feature = "pci")]
58	pub fn get_dev_id(&self) -> u16 {
59		self.dev_cfg.dev_id
60	}
61
62	#[cfg(feature = "pci")]
63	pub fn set_failed(&mut self) {
64		self.com_cfg.set_failed();
65	}
66
67	/// Negotiates a subset of features, understood and wanted by both the OS
68	/// and the device.
69	fn negotiate_features(&mut self, driver_features: virtio::fs::F) -> Result<(), VirtioFsError> {
70		let device_features = virtio::fs::F::from(self.com_cfg.dev_features());
71
72		if device_features.requirements_satisfied() {
73			debug!(
74				"Feature set wanted by filesystem driver are in conformance with specification."
75			);
76		} else {
77			return Err(VirtioFsError::FeatureRequirementsNotMet(device_features));
78		}
79
80		if device_features.contains(driver_features) {
81			// If device supports subset of features write feature set to common config
82			self.com_cfg.set_drv_features(driver_features.into());
83			Ok(())
84		} else {
85			Err(VirtioFsError::IncompatibleFeatureSets(
86				driver_features,
87				device_features,
88			))
89		}
90	}
91
92	/// Initializes the device in adherence to specification. Returns Some(VirtioFsError)
93	/// upon failure and None in case everything worked as expected.
94	///
95	/// See Virtio specification v1.1. - 3.1.1.
96	///                      and v1.1. - 5.11.5
97	pub(crate) fn init_dev(&mut self) -> Result<(), VirtioFsError> {
98		// Reset
99		self.com_cfg.reset_dev();
100
101		// Indicate device, that OS noticed it
102		self.com_cfg.ack_dev();
103
104		// Indicate device, that driver is able to handle it
105		self.com_cfg.set_drv();
106
107		let features = virtio::fs::F::VERSION_1;
108		self.negotiate_features(features)?;
109
110		// Indicates the device, that the current feature set is final for the driver
111		// and will not be changed.
112		self.com_cfg.features_ok();
113
114		// Checks if the device has accepted final set. This finishes feature negotiation.
115		if self.com_cfg.check_features() {
116			info!(
117				"Features have been negotiated between virtio filesystem device {:x} and driver.",
118				self.dev_cfg.dev_id
119			);
120			// Set feature set in device config fur future use.
121			self.dev_cfg.features = features;
122		} else {
123			return Err(VirtioFsError::FailFeatureNeg(self.dev_cfg.dev_id));
124		}
125
126		// 1 highprio queue, and n normal request queues
127		let vqnum = self
128			.dev_cfg
129			.raw
130			.as_ptr()
131			.num_request_queues()
132			.read()
133			.to_ne() + 1;
134		if vqnum == 0 {
135			error!("0 request queues requested from device. Aborting!");
136			return Err(VirtioFsError::Unknown);
137		}
138
139		// create the queues and tell device about them
140		for i in 0..vqnum as u16 {
141			let vq = VirtQueue::Split(
142				SplitVq::new(
143					&mut self.com_cfg,
144					&self.notif_cfg,
145					VqSize::from(VIRTIO_MAX_QUEUE_SIZE),
146					VqIndex::from(i),
147					self.dev_cfg.features.into(),
148				)
149				.unwrap(),
150			);
151			self.vqueues.push(vq);
152		}
153
154		// At this point the device is "live"
155		self.com_cfg.drv_ok();
156
157		Ok(())
158	}
159}
160
161impl FuseInterface for VirtioFsDriver {
162	fn send_command<O: fuse::ops::Op + 'static>(
163		&mut self,
164		cmd: fuse::Cmd<O>,
165		rsp_payload_len: u32,
166	) -> Result<fuse::Rsp<O>, FuseError>
167	where
168		<O as fuse::ops::Op>::InStruct: Send,
169		<O as fuse::ops::Op>::OutStruct: Send,
170	{
171		let fuse::Cmd {
172			headers: cmd_headers,
173			payload: cmd_payload_opt,
174		} = cmd;
175		let send = if let Some(cmd_payload) = cmd_payload_opt {
176			SmallVec::from_buf([
177				BufferElem::Sized(cmd_headers),
178				BufferElem::Vector(cmd_payload),
179			])
180		} else {
181			let mut vec = SmallVec::new();
182			vec.push(BufferElem::Sized(cmd_headers));
183			vec
184		};
185
186		// If the operation fails, it is possible for its header to be uninitialized.
187		// For this reason, we use a instantiation of the RspHeader structure where
188		// the op_header field is MaybeUninit
189		let rsp_headers =
190			Box::<RspHeader<O, MaybeUninit<O::OutStruct>>, _>::new_uninit_in(DeviceAlloc);
191		let recv = if rsp_payload_len == 0 {
192			let mut vec = SmallVec::new();
193			vec.push(BufferElem::Sized(rsp_headers));
194			vec
195		} else {
196			SmallVec::from_buf([
197				BufferElem::Sized(rsp_headers),
198				BufferElem::Vector(Vec::with_capacity_in(rsp_payload_len as usize, DeviceAlloc)),
199			])
200		};
201
202		let buffer_tkn = AvailBufferToken::new(send, recv).unwrap();
203		let mut transfer_result =
204			self.vqueues[1].dispatch_blocking(buffer_tkn, BufferType::Direct)?;
205
206		let (dyn_headers, written_header_len) =
207			transfer_result.used_recv_buff.pop_front_raw().unwrap();
208		let headers = dyn_headers
209			.downcast::<MaybeUninit<RspHeader<O, MaybeUninit<O::OutStruct>>>>()
210			.unwrap();
211		if written_header_len < size_of::<fuse_out_header>() {
212			return Err(VirtqError::IncompleteWrite.into());
213		}
214
215		// SAFETY: we confirmed that the out_header was written. The op_header does not need to be initialized at this stage,
216		// as it is behind a nested MaybeUninit.
217		let headers = unsafe { headers.assume_init() };
218
219		if headers.out_header.error != 0
220			|| (written_header_len - size_of::<fuse_out_header>()) != size_of::<O::OutStruct>()
221		{
222			// "However, if the reply is an error reply (i.e., error is set), then no further payload data should be sent,
223			// independent of the request." (fuse man page)
224
225			return Err(FuseError::IOError(
226				io::Error::try_from_primitive(-headers.out_header.error).unwrap_or(io::Error::EIO),
227			));
228		}
229
230		// SAFETY: the conditional above ensures that the second field was filled in, so we can transmute it from MaybeUninit to normal.
231		let headers = unsafe {
232			core::mem::transmute::<
233				Box<RspHeader<O, MaybeUninit<O::OutStruct>>, _>,
234				Box<RspHeader<O>, _>,
235			>(headers)
236		};
237		let payload = transfer_result.used_recv_buff.pop_front_vec();
238		Ok(Rsp { headers, payload })
239	}
240
241	fn get_mount_point(&self) -> String {
242		let tag = self.dev_cfg.raw.as_ptr().tag().read();
243		let tag = str::from_utf8(&tag).unwrap();
244		let tag = tag.split('\0').next().unwrap();
245		tag.to_string()
246	}
247}
248
249impl Driver for VirtioFsDriver {
250	fn get_interrupt_number(&self) -> InterruptLine {
251		self.irq
252	}
253
254	fn get_name(&self) -> &'static str {
255		"virtio"
256	}
257}
258
259/// Error module of virtios filesystem driver.
260pub mod error {
261	/// Network filesystem error enum.
262	#[derive(Debug, Copy, Clone)]
263	pub enum VirtioFsError {
264		#[cfg(feature = "pci")]
265		NoDevCfg(u16),
266		FailFeatureNeg(u16),
267		/// The first field contains the feature bits wanted by the driver.
268		/// but which are incompatible with the device feature set, second field.
269		IncompatibleFeatureSets(virtio::fs::F, virtio::fs::F),
270		/// Set of features does not adhere to the requirements of features
271		/// indicated by the specification
272		FeatureRequirementsNotMet(virtio::fs::F),
273		Unknown,
274	}
275}