hermit/drivers/virtio/virtqueue/
split.rs

1//! This module contains Virtio's split virtqueue.
2//! See Virito specification v1.1. - 2.6
3
4use alloc::boxed::Box;
5use alloc::vec::Vec;
6use core::cell::UnsafeCell;
7use core::mem::{self, MaybeUninit};
8
9#[cfg(not(feature = "pci"))]
10use virtio::mmio::NotificationData;
11#[cfg(feature = "pci")]
12use virtio::pci::NotificationData;
13use virtio::{le16, virtq};
14
15#[cfg(not(feature = "pci"))]
16use super::super::transport::mmio::{ComCfg, NotifCfg, NotifCtrl};
17#[cfg(feature = "pci")]
18use super::super::transport::pci::{ComCfg, NotifCfg, NotifCtrl};
19use super::error::VirtqError;
20use super::{
21	AvailBufferToken, BufferType, MemPool, TransferToken, UsedBufferToken, Virtq, VirtqPrivate,
22	VqIndex, VqSize,
23};
24use crate::arch::memory_barrier;
25use crate::mm::device_alloc::DeviceAlloc;
26
27struct DescrRing {
28	read_idx: u16,
29	token_ring: Box<[Option<Box<TransferToken<virtq::Desc>>>]>,
30	mem_pool: MemPool,
31
32	descr_table_cell: Box<UnsafeCell<[MaybeUninit<virtq::Desc>]>, DeviceAlloc>,
33	avail_ring_cell: Box<UnsafeCell<virtq::Avail>, DeviceAlloc>,
34	used_ring_cell: Box<UnsafeCell<virtq::Used>, DeviceAlloc>,
35}
36
37impl DescrRing {
38	fn descr_table_mut(&mut self) -> &mut [MaybeUninit<virtq::Desc>] {
39		unsafe { &mut *self.descr_table_cell.get() }
40	}
41	fn avail_ring(&self) -> &virtq::Avail {
42		unsafe { &*self.avail_ring_cell.get() }
43	}
44	fn avail_ring_mut(&mut self) -> &mut virtq::Avail {
45		unsafe { &mut *self.avail_ring_cell.get() }
46	}
47	fn used_ring(&self) -> &virtq::Used {
48		unsafe { &*self.used_ring_cell.get() }
49	}
50
51	fn push(&mut self, tkn: TransferToken<virtq::Desc>) -> Result<u16, VirtqError> {
52		let mut index;
53		if let Some(ctrl_desc) = tkn.ctrl_desc.as_ref() {
54			let descriptor = SplitVq::indirect_desc(ctrl_desc.as_ref());
55
56			index = self.mem_pool.pool.pop().ok_or(VirtqError::NoDescrAvail)?.0;
57			self.descr_table_mut()[usize::from(index)] = MaybeUninit::new(descriptor);
58		} else {
59			let mut rev_all_desc_iter = SplitVq::descriptor_iter(&tkn.buff_tkn)?.rev();
60
61			// We need to handle the last descriptor (the first for the reversed iterator) specially to not set the next flag.
62			{
63				// If the [AvailBufferToken] is empty, we panic
64				let descriptor = rev_all_desc_iter.next().unwrap();
65
66				index = self.mem_pool.pool.pop().ok_or(VirtqError::NoDescrAvail)?.0;
67				self.descr_table_mut()[usize::from(index)] = MaybeUninit::new(descriptor);
68			}
69			for mut descriptor in rev_all_desc_iter {
70				// We have not updated `index` yet, so it is at this point the index of the previous descriptor that had been written.
71				descriptor.next = le16::from(index);
72
73				index = self.mem_pool.pool.pop().ok_or(VirtqError::NoDescrAvail)?.0;
74				self.descr_table_mut()[usize::from(index)] = MaybeUninit::new(descriptor);
75			}
76			// At this point, `index` is the index of the last element of the reversed iterator,
77			// thus the head of the descriptor chain.
78		}
79
80		self.token_ring[usize::from(index)] = Some(Box::new(tkn));
81
82		let len = self.token_ring.len();
83		let idx = self.avail_ring_mut().idx.to_ne();
84		self.avail_ring_mut().ring_mut(true)[idx as usize % len] = index.into();
85
86		memory_barrier();
87		let next_idx = idx.wrapping_add(1);
88		self.avail_ring_mut().idx = next_idx.into();
89
90		Ok(next_idx)
91	}
92
93	fn try_recv(&mut self) -> Result<UsedBufferToken, VirtqError> {
94		if self.read_idx == self.used_ring().idx.to_ne() {
95			return Err(VirtqError::NoNewUsed);
96		}
97		let cur_ring_index = self.read_idx as usize % self.token_ring.len();
98		let used_elem = self.used_ring().ring()[cur_ring_index];
99
100		let tkn = self.token_ring[used_elem.id.to_ne() as usize]
101			.take()
102			.expect(
103				"The buff_id is incorrect or the reference to the TransferToken was misplaced.",
104			);
105
106		// We return the indices of the now freed ring slots back to `mem_pool.`
107		let mut id_ret_idx = u16::try_from(used_elem.id.to_ne()).unwrap();
108		loop {
109			self.mem_pool.ret_id(super::MemDescrId(id_ret_idx));
110			let cur_chain_elem =
111				unsafe { self.descr_table_mut()[usize::from(id_ret_idx)].assume_init() };
112			if cur_chain_elem.flags.contains(virtq::DescF::NEXT) {
113				id_ret_idx = cur_chain_elem.next.to_ne();
114			} else {
115				break;
116			}
117		}
118
119		memory_barrier();
120		self.read_idx = self.read_idx.wrapping_add(1);
121		Ok(UsedBufferToken::from_avail_buffer_token(
122			tkn.buff_tkn,
123			used_elem.len.to_ne(),
124		))
125	}
126
127	fn drv_enable_notif(&mut self) {
128		self.avail_ring_mut()
129			.flags
130			.remove(virtq::AvailF::NO_INTERRUPT);
131	}
132
133	fn drv_disable_notif(&mut self) {
134		self.avail_ring_mut()
135			.flags
136			.insert(virtq::AvailF::NO_INTERRUPT);
137	}
138
139	fn dev_is_notif(&self) -> bool {
140		!self.used_ring().flags.contains(virtq::UsedF::NO_NOTIFY)
141	}
142}
143
144/// Virtio's split virtqueue structure
145pub struct SplitVq {
146	ring: DescrRing,
147	size: VqSize,
148	index: VqIndex,
149
150	notif_ctrl: NotifCtrl,
151}
152
153impl Virtq for SplitVq {
154	fn enable_notifs(&mut self) {
155		self.ring.drv_enable_notif();
156	}
157
158	fn disable_notifs(&mut self) {
159		self.ring.drv_disable_notif();
160	}
161
162	fn try_recv(&mut self) -> Result<UsedBufferToken, VirtqError> {
163		self.ring.try_recv()
164	}
165
166	fn dispatch_batch(
167		&mut self,
168		_tkns: Vec<(AvailBufferToken, BufferType)>,
169		_notif: bool,
170	) -> Result<(), VirtqError> {
171		unimplemented!();
172	}
173
174	fn dispatch_batch_await(
175		&mut self,
176		_tkns: Vec<(AvailBufferToken, BufferType)>,
177		_notif: bool,
178	) -> Result<(), VirtqError> {
179		unimplemented!()
180	}
181
182	fn dispatch(
183		&mut self,
184		buffer_tkn: AvailBufferToken,
185		notif: bool,
186		buffer_type: BufferType,
187	) -> Result<(), VirtqError> {
188		let transfer_tkn = Self::transfer_token_from_buffer_token(buffer_tkn, buffer_type);
189		let next_idx = self.ring.push(transfer_tkn)?;
190
191		if notif {
192			// TODO: Check whether the splitvirtquue has notifications for specific descriptors
193			// I believe it does not.
194			unimplemented!();
195		}
196
197		if self.ring.dev_is_notif() {
198			let notification_data = NotificationData::new()
199				.with_vqn(self.index.0)
200				.with_next_idx(next_idx);
201			self.notif_ctrl.notify_dev(notification_data);
202		}
203		Ok(())
204	}
205
206	fn index(&self) -> VqIndex {
207		self.index
208	}
209
210	fn size(&self) -> VqSize {
211		self.size
212	}
213
214	fn has_used_buffers(&self) -> bool {
215		self.ring.read_idx != self.ring.used_ring().idx.to_ne()
216	}
217}
218
219impl VirtqPrivate for SplitVq {
220	type Descriptor = virtq::Desc;
221	fn create_indirect_ctrl(
222		buffer_tkn: &AvailBufferToken,
223	) -> Result<Box<[Self::Descriptor]>, VirtqError> {
224		Ok(Self::descriptor_iter(buffer_tkn)?
225			.zip(1..)
226			.map(|(descriptor, next_id)| Self::Descriptor {
227				next: next_id.into(),
228				..descriptor
229			})
230			.collect::<Vec<_>>()
231			.into_boxed_slice())
232	}
233}
234
235impl SplitVq {
236	pub(crate) fn new(
237		com_cfg: &mut ComCfg,
238		notif_cfg: &NotifCfg,
239		size: VqSize,
240		index: VqIndex,
241		features: virtio::F,
242	) -> Result<Self, VirtqError> {
243		// Get a handler to the queues configuration area.
244		let Some(mut vq_handler) = com_cfg.select_vq(index.into()) else {
245			return Err(VirtqError::QueueNotExisting(index.into()));
246		};
247
248		let size = vq_handler.set_vq_size(size.0);
249
250		let mut descr_table_cell = unsafe {
251			core::mem::transmute::<
252				Box<[MaybeUninit<virtq::Desc>], DeviceAlloc>,
253				Box<UnsafeCell<[MaybeUninit<virtq::Desc>]>, DeviceAlloc>,
254			>(Box::new_uninit_slice_in(size.into(), DeviceAlloc))
255		};
256
257		let mut avail_ring_cell = {
258			let avail = virtq::Avail::try_new_in(size, true, DeviceAlloc)
259				.map_err(|_| VirtqError::AllocationError)?;
260
261			unsafe {
262				mem::transmute::<
263					Box<virtq::Avail, DeviceAlloc>,
264					Box<UnsafeCell<virtq::Avail>, DeviceAlloc>,
265				>(avail)
266			}
267		};
268
269		let mut used_ring_cell = {
270			let used = virtq::Used::try_new_in(size, true, DeviceAlloc)
271				.map_err(|_| VirtqError::AllocationError)?;
272
273			unsafe {
274				mem::transmute::<
275					Box<virtq::Used, DeviceAlloc>,
276					Box<UnsafeCell<virtq::Used>, DeviceAlloc>,
277				>(used)
278			}
279		};
280
281		// Provide memory areas of the queues data structures to the device
282		vq_handler.set_ring_addr(DeviceAlloc.phys_addr_from(descr_table_cell.as_mut()));
283		// As usize is safe here, as the *mut EventSuppr raw pointer is a thin pointer of size usize
284		vq_handler.set_drv_ctrl_addr(DeviceAlloc.phys_addr_from(avail_ring_cell.as_mut()));
285		vq_handler.set_dev_ctrl_addr(DeviceAlloc.phys_addr_from(used_ring_cell.as_mut()));
286
287		let descr_ring = DescrRing {
288			read_idx: 0,
289			token_ring: core::iter::repeat_with(|| None)
290				.take(size.into())
291				.collect::<Vec<_>>()
292				.into_boxed_slice(),
293			mem_pool: MemPool::new(size),
294
295			descr_table_cell,
296			avail_ring_cell,
297			used_ring_cell,
298		};
299
300		let mut notif_ctrl = NotifCtrl::new(notif_cfg.notification_location(&mut vq_handler));
301
302		if features.contains(virtio::F::NOTIFICATION_DATA) {
303			notif_ctrl.enable_notif_data();
304		}
305
306		vq_handler.enable_queue();
307
308		info!("Created SplitVq: idx={}, size={}", index.0, size);
309
310		Ok(SplitVq {
311			ring: descr_ring,
312			notif_ctrl,
313			size: VqSize(size),
314			index,
315		})
316	}
317}