hermit/drivers/virtio/transport/
pci.rs

1//! A module containing all virtio specific pci functionality
2//!
3//! The module contains ...
4#![allow(dead_code)]
5
6use alloc::vec::Vec;
7use core::ptr::NonNull;
8use core::{mem, ptr};
9
10use memory_addresses::PhysAddr;
11use pci_types::capability::PciCapability;
12use virtio::pci::{
13	CapCfgType, CapData, CommonCfg, CommonCfgVolatileFieldAccess, CommonCfgVolatileWideFieldAccess,
14	IsrStatus as IsrStatusRaw, NotificationData,
15};
16use virtio::{DeviceStatus, le16, le32};
17use volatile::access::ReadOnly;
18use volatile::{VolatilePtr, VolatileRef};
19
20use crate::arch::memory_barrier;
21use crate::arch::pci::PciConfigRegion;
22use crate::drivers::error::DriverError;
23#[cfg(feature = "fuse")]
24use crate::drivers::fs::virtio_fs::VirtioFsDriver;
25#[cfg(all(
26	not(all(target_arch = "x86_64", feature = "rtl8139")),
27	any(feature = "tcp", feature = "udp")
28))]
29use crate::drivers::net::virtio::VirtioNetDriver;
30use crate::drivers::pci::PciDevice;
31use crate::drivers::pci::error::PciError;
32use crate::drivers::virtio::error::VirtioError;
33use crate::drivers::virtio::transport::pci::PciBar as VirtioPciBar;
34#[cfg(feature = "vsock")]
35use crate::drivers::vsock::VirtioVsockDriver;
36
37/// Maps a given device specific pci configuration structure and
38/// returns a static reference to it.
39pub fn map_dev_cfg<T>(cap: &PciCap) -> Option<&'static mut T> {
40	if cap.cap.cfg_type != CapCfgType::Device {
41		error!("Capability of device config has wrong id. Mapping not possible...");
42		return None;
43	};
44
45	if cap.bar_len() < cap.len() + cap.offset() {
46		error!(
47			"Device config of device {:x}, does not fit into memory specified by bar!",
48			cap.dev_id(),
49		);
50		return None;
51	}
52
53	// Drivers MAY do this check. See Virtio specification v1.1. - 4.1.4.1
54	if cap.len() < u64::try_from(mem::size_of::<T>()).unwrap() {
55		error!(
56			"Device specific config from device {:x}, does not represent actual structure specified by the standard!",
57			cap.dev_id()
58		);
59		return None;
60	}
61
62	let virt_addr_raw = cap.bar_addr() + cap.offset();
63
64	// Create mutable reference to the PCI structure in PCI memory
65	let dev_cfg: &'static mut T =
66		unsafe { &mut *(ptr::with_exposed_provenance_mut(virt_addr_raw.try_into().unwrap())) };
67
68	Some(dev_cfg)
69}
70
71/// Virtio's PCI capabilities structure.
72/// See Virtio specification v.1.1 - 4.1.4
73///
74/// Indicating: Where the capability field is mapped in memory and
75/// Which id (sometimes also indicates priority for multiple
76/// capabilities of same type) it holds.
77///
78/// This structure does NOT represent the structure in the standard,
79/// as it is not directly mapped into address space from PCI device
80/// configuration space.
81/// Therefore the struct only contains necessary information to map
82/// corresponding config type into address space.
83#[derive(Clone)]
84pub struct PciCap {
85	bar: PciBar,
86	dev_id: u16,
87	cap: CapData,
88}
89
90impl PciCap {
91	pub fn offset(&self) -> u64 {
92		self.cap.offset.to_ne()
93	}
94
95	pub fn len(&self) -> u64 {
96		self.cap.length.to_ne()
97	}
98
99	pub fn bar_len(&self) -> u64 {
100		self.bar.length
101	}
102
103	pub fn bar_addr(&self) -> u64 {
104		self.bar.mem_addr
105	}
106
107	pub fn dev_id(&self) -> u16 {
108		self.dev_id
109	}
110
111	/// Returns a reference to the actual structure inside the PCI devices memory space.
112	fn map_common_cfg(&self) -> Option<VolatileRef<'static, CommonCfg>> {
113		if self.bar.length < self.len() + self.offset() {
114			error!(
115				"Common config of the capability with id {} of device {:x} does not fit into memory specified by bar {:x}!",
116				self.cap.id, self.dev_id, self.bar.index
117			);
118			return None;
119		}
120
121		// Drivers MAY do this check. See Virtio specification v1.1. - 4.1.4.1
122		if self.len() < u64::try_from(mem::size_of::<CommonCfg>()).unwrap() {
123			error!(
124				"Common config of with id {}, does not represent actual structure specified by the standard!",
125				self.cap.id
126			);
127			return None;
128		}
129
130		let virt_addr_raw = self.bar.mem_addr + self.offset();
131		let ptr = NonNull::new(ptr::with_exposed_provenance_mut::<CommonCfg>(
132			virt_addr_raw.try_into().unwrap(),
133		))
134		.unwrap();
135
136		// Create mutable reference to the PCI structure in PCI memory
137		let com_cfg_raw = unsafe { VolatileRef::new(ptr) };
138
139		Some(com_cfg_raw)
140	}
141
142	fn map_isr_status(&self) -> Option<VolatileRef<'static, IsrStatusRaw>> {
143		if self.bar.length < self.len() + self.offset() {
144			error!(
145				"ISR status config with id {} of device {:x}, does not fit into memory specified by bar {:x}!",
146				self.cap.id, self.dev_id, self.bar.index
147			);
148			return None;
149		}
150
151		let virt_addr_raw = self.bar.mem_addr + self.offset();
152		let ptr = NonNull::new(ptr::with_exposed_provenance_mut::<IsrStatusRaw>(
153			virt_addr_raw.try_into().unwrap(),
154		))
155		.unwrap();
156
157		// Create mutable reference to the PCI structure in the devices memory area
158		let isr_stat_raw = unsafe { VolatileRef::new(ptr) };
159
160		Some(isr_stat_raw)
161	}
162}
163
164/// Universal Caplist Collections holds all universal capability structures for
165/// a given Virtio PCI device.
166///
167/// As Virtio's PCI devices are allowed to present multiple capability
168/// structures of the same config type, the structure
169/// provides a driver with all capabilities, sorted in descending priority,
170/// allowing the driver to choose.
171/// The structure contains a special dev_cfg_list field, a vector holding
172/// [PciCap] objects, to allow the driver to map its
173/// device specific configurations independently.
174pub struct UniCapsColl {
175	pub(crate) com_cfg: ComCfg,
176	pub(crate) notif_cfg: NotifCfg,
177	pub(crate) isr_cfg: IsrStatus,
178	pub(crate) sh_mem_cfg_list: Vec<ShMemCfg>,
179	pub(crate) dev_cfg_list: Vec<PciCap>,
180}
181/// Wraps a [`CommonCfg`] in order to preserve
182/// the original structure.
183///
184/// Provides a safe API for Raw structure and allows interaction with the device via
185/// the structure.
186pub struct ComCfg {
187	/// References the raw structure in PCI memory space. Is static as
188	/// long as the device is present, which is mandatory in order to let this code work.
189	com_cfg: VolatileRef<'static, CommonCfg>,
190	/// Preferences of the device for this config. From 1 (highest) to 2^7-1 (lowest)
191	rank: u8,
192}
193
194// Private interface of ComCfg
195impl ComCfg {
196	fn new(raw: VolatileRef<'static, CommonCfg>, rank: u8) -> Self {
197		ComCfg { com_cfg: raw, rank }
198	}
199}
200
201pub struct VqCfgHandler<'a> {
202	vq_index: u16,
203	raw: VolatileRef<'a, CommonCfg>,
204}
205
206impl VqCfgHandler<'_> {
207	// TODO: Create type for queue selected invariant to get rid of `self.select_queue()` everywhere.
208	fn select_queue(&mut self) {
209		self.raw
210			.as_mut_ptr()
211			.queue_select()
212			.write(self.vq_index.into());
213	}
214
215	/// Sets the size of a given virtqueue. In case the provided size exceeds the maximum allowed
216	/// size, the size is set to this maximum instead. Else size is set to the provided value.
217	///
218	/// Returns the set size in form of a `u16`.
219	pub fn set_vq_size(&mut self, size: u16) -> u16 {
220		self.select_queue();
221		let queue_size = self.raw.as_mut_ptr().queue_size();
222
223		if queue_size.read().to_ne() >= size {
224			queue_size.write(size.into());
225		}
226
227		queue_size.read().to_ne()
228	}
229
230	pub fn set_ring_addr(&mut self, addr: PhysAddr) {
231		self.select_queue();
232		self.raw
233			.as_mut_ptr()
234			.queue_desc()
235			.write(addr.as_u64().into());
236	}
237
238	pub fn set_drv_ctrl_addr(&mut self, addr: PhysAddr) {
239		self.select_queue();
240		self.raw
241			.as_mut_ptr()
242			.queue_driver()
243			.write(addr.as_u64().into());
244	}
245
246	pub fn set_dev_ctrl_addr(&mut self, addr: PhysAddr) {
247		self.select_queue();
248		self.raw
249			.as_mut_ptr()
250			.queue_device()
251			.write(addr.as_u64().into());
252	}
253
254	pub fn notif_off(&mut self) -> u16 {
255		self.select_queue();
256		self.raw.as_mut_ptr().queue_notify_off().read().to_ne()
257	}
258
259	pub fn enable_queue(&mut self) {
260		self.select_queue();
261		self.raw.as_mut_ptr().queue_enable().write(1.into());
262	}
263}
264
265// Public Interface of ComCfg
266impl ComCfg {
267	/// Select a queue via an index. If queue does NOT exist returns `None`, else
268	/// returns `Some(VqCfgHandler)`.
269	///
270	/// INFO: The queue size is automatically bounded by constant `src::config:VIRTIO_MAX_QUEUE_SIZE`.
271	pub fn select_vq(&mut self, index: u16) -> Option<VqCfgHandler<'_>> {
272		self.com_cfg.as_mut_ptr().queue_select().write(index.into());
273
274		if self.com_cfg.as_mut_ptr().queue_size().read().to_ne() == 0 {
275			None
276		} else {
277			Some(VqCfgHandler {
278				vq_index: index,
279				raw: self.com_cfg.borrow_mut(),
280			})
281		}
282	}
283
284	pub fn device_config_space(&self) -> VolatilePtr<'_, CommonCfg, ReadOnly> {
285		self.com_cfg.as_ptr()
286	}
287
288	/// Returns the device status field.
289	pub fn dev_status(&self) -> u8 {
290		self.com_cfg.as_ptr().device_status().read().bits()
291	}
292
293	/// Resets the device status field to zero.
294	pub fn reset_dev(&mut self) {
295		memory_barrier();
296		self.com_cfg
297			.as_mut_ptr()
298			.device_status()
299			.write(DeviceStatus::empty());
300	}
301
302	/// Sets the device status field to FAILED.
303	/// A driver MUST NOT initialize and use the device any further after this.
304	/// A driver MAY use the device again after a proper reset of the device.
305	pub fn set_failed(&mut self) {
306		memory_barrier();
307		self.com_cfg
308			.as_mut_ptr()
309			.device_status()
310			.write(DeviceStatus::FAILED);
311	}
312
313	/// Sets the ACKNOWLEDGE bit in the device status field. This indicates, the
314	/// OS has notived the device
315	pub fn ack_dev(&mut self) {
316		memory_barrier();
317		self.com_cfg
318			.as_mut_ptr()
319			.device_status()
320			.update(|s| s | DeviceStatus::ACKNOWLEDGE);
321	}
322
323	/// Sets the DRIVER bit in the device status field. This indicates, the OS
324	/// know how to run this device.
325	pub fn set_drv(&mut self) {
326		memory_barrier();
327		self.com_cfg
328			.as_mut_ptr()
329			.device_status()
330			.update(|s| s | DeviceStatus::DRIVER);
331	}
332
333	/// Sets the FEATURES_OK bit in the device status field.
334	///
335	/// Drivers MUST NOT accept new features after this step.
336	pub fn features_ok(&mut self) {
337		memory_barrier();
338		self.com_cfg
339			.as_mut_ptr()
340			.device_status()
341			.update(|s| s | DeviceStatus::FEATURES_OK);
342	}
343
344	/// In order to correctly check feature negotiaten, this function
345	/// MUST be called after [self.features_ok()](ComCfg::features_ok()) in order to check
346	/// if features have been accepted by the device after negotiation.
347	///
348	/// Re-reads device status to ensure the FEATURES_OK bit is still set:
349	/// otherwise, the device does not support our subset of features and the device is unusable.
350	pub fn check_features(&self) -> bool {
351		memory_barrier();
352		self.com_cfg
353			.as_ptr()
354			.device_status()
355			.read()
356			.contains(DeviceStatus::FEATURES_OK)
357	}
358
359	/// Sets the DRIVER_OK bit in the device status field.
360	///
361	/// After this call, the device is "live"!
362	pub fn drv_ok(&mut self) {
363		memory_barrier();
364		self.com_cfg
365			.as_mut_ptr()
366			.device_status()
367			.update(|s| s | DeviceStatus::DRIVER_OK);
368	}
369
370	/// Returns the features offered by the device.
371	pub fn dev_features(&mut self) -> virtio::F {
372		let com_cfg = self.com_cfg.as_mut_ptr();
373		let device_feature_select = com_cfg.device_feature_select();
374		let device_feature = com_cfg.device_feature();
375
376		// Indicate device to show high 32 bits in device_feature field.
377		// See Virtio specification v1.1. - 4.1.4.3
378		memory_barrier();
379		device_feature_select.write(1.into());
380		memory_barrier();
381
382		// read high 32 bits of device features
383		let mut device_features = u64::from(device_feature.read().to_ne()) << 32;
384
385		// Indicate device to show low 32 bits in device_feature field.
386		// See Virtio specification v1.1. - 4.1.4.3
387		device_feature_select.write(0.into());
388		memory_barrier();
389
390		// read low 32 bits of device features
391		device_features |= u64::from(device_feature.read().to_ne());
392
393		virtio::F::from_bits_retain(u128::from(device_features).into())
394	}
395
396	/// Write selected features into driver_select field.
397	pub fn set_drv_features(&mut self, features: virtio::F) {
398		let features = features.bits().to_ne() as u64;
399		let com_cfg = self.com_cfg.as_mut_ptr();
400		let driver_feature_select = com_cfg.driver_feature_select();
401		let driver_feature = com_cfg.driver_feature();
402
403		let high: u32 = (features >> 32) as u32;
404		let low: u32 = features as u32;
405
406		// Indicate to device that driver_features field shows low 32 bits.
407		// See Virtio specification v1.1. - 4.1.4.3
408		memory_barrier();
409		driver_feature_select.write(0.into());
410		memory_barrier();
411
412		// write low 32 bits of device features
413		driver_feature.write(low.into());
414
415		// Indicate to device that driver_features field shows high 32 bits.
416		// See Virtio specification v1.1. - 4.1.4.3
417		driver_feature_select.write(1.into());
418		memory_barrier();
419
420		// write high 32 bits of device features
421		driver_feature.write(high.into());
422	}
423}
424
425/// Notification Structure to handle virtqueue notification settings.
426/// See Virtio specification v1.1 - 4.1.4.4
427pub struct NotifCfg {
428	/// Start addr, from where the notification addresses for the virtqueues are computed
429	base_addr: u64,
430	notify_off_multiplier: u32,
431	/// Preferences of the device for this config. From 1 (highest) to 2^7-1 (lowest)
432	rank: u8,
433	/// defines the maximum size of the notification space, starting from base_addr.
434	length: u64,
435}
436
437impl NotifCfg {
438	fn new(cap: &PciCap) -> Option<Self> {
439		if cap.bar.length < cap.len() + cap.offset() {
440			error!(
441				"Notification config with id {} of device {:x}, does not fit into memory specified by bar {:x}!",
442				cap.cap.id, cap.dev_id, cap.bar.index
443			);
444			return None;
445		}
446
447		let notify_off_multiplier = cap.cap.notify_off_multiplier?.to_ne();
448
449		// define base memory address from which the actual Queue Notify address can be derived via
450		// base_addr + queue_notify_off * notify_off_multiplier.
451		//
452		// Where queue_notify_off is taken from the respective common configuration struct.
453		// See Virtio specification v1.1. - 4.1.4.4
454		//
455		// Base address here already includes offset!
456		let base_addr = cap.bar.mem_addr + cap.offset();
457
458		Some(NotifCfg {
459			base_addr,
460			notify_off_multiplier,
461			rank: cap.cap.id,
462			length: cap.len(),
463		})
464	}
465
466	pub fn notification_location(&self, vq_cfg_handler: &mut VqCfgHandler<'_>) -> *mut le32 {
467		let addend = u32::from(vq_cfg_handler.notif_off()) * self.notify_off_multiplier;
468		let addr = self.base_addr + u64::from(addend);
469		ptr::with_exposed_provenance_mut(addr.try_into().unwrap())
470	}
471}
472
473/// Control structure, allowing to notify a device via PCI bus.
474/// Typically hold by a virtqueue.
475pub struct NotifCtrl {
476	/// Indicates if VIRTIO_F_NOTIFICATION_DATA has been negotiated
477	f_notif_data: bool,
478	/// Where to write notification
479	notif_addr: *mut le32,
480}
481
482// FIXME: make `notif_addr` implement `Send` instead
483unsafe impl Send for NotifCtrl {}
484
485impl NotifCtrl {
486	/// Returns a new controller. By default MSI-X capabilities and VIRTIO_F_NOTIFICATION_DATA
487	/// are disabled.
488	pub fn new(notif_addr: *mut le32) -> Self {
489		NotifCtrl {
490			f_notif_data: false,
491			notif_addr,
492		}
493	}
494
495	/// Enables VIRTIO_F_NOTIFICATION_DATA. This changes which data is provided to the device. ONLY a good idea if Feature has been negotiated.
496	pub fn enable_notif_data(&mut self) {
497		self.f_notif_data = true;
498	}
499
500	pub fn notify_dev(&self, data: NotificationData) {
501		// See Virtio specification v.1.1. - 4.1.5.2
502		// Depending in the feature negotiation, we write either only the
503		// virtqueue index or the index and the next position inside the queue.
504
505		if self.f_notif_data {
506			unsafe {
507				self.notif_addr.write_volatile(data.into_bits());
508			}
509		} else {
510			unsafe {
511				self.notif_addr
512					.cast::<le16>()
513					.write_volatile(data.vqn().into());
514			}
515		}
516	}
517}
518
519/// Wraps a [IsrStatusRaw] in order to preserve
520/// the original structure and allow interaction with the device via
521/// the structure.
522///
523/// Provides a safe API for Raw structure and allows interaction with the device via
524/// the structure.
525pub struct IsrStatus {
526	/// References the raw structure in PCI memory space. Is static as
527	/// long as the device is present, which is mandatory in order to let this code work.
528	isr_stat: VolatileRef<'static, IsrStatusRaw>,
529	/// Preferences of the device for this config. From 1 (highest) to 2^7-1 (lowest)
530	rank: u8,
531}
532
533impl IsrStatus {
534	fn new(raw: VolatileRef<'static, IsrStatusRaw>, rank: u8) -> Self {
535		IsrStatus {
536			isr_stat: raw,
537			rank,
538		}
539	}
540
541	pub fn is_queue_interrupt(&self) -> IsrStatusRaw {
542		self.isr_stat.as_ptr().read()
543	}
544
545	pub fn acknowledge(&mut self) {
546		// nothing to do
547	}
548}
549
550/// Shared memory configuration structure of Virtio PCI devices.
551/// See Virtio specification v1.1. - 4.1.4.7
552///
553/// Each shared memory region is defined via a single shared
554/// memory structure. Each region is identified by an id indicated
555/// via the capability.id field of PciCapRaw.
556///
557/// The shared memory region is defined via a PciCap64 structure.
558/// See Virtio specification v.1.1 - 4.1.4 for structure.
559///
560// Only used for capabilities that require offsets or lengths
561// larger than 4GB.
562// #[repr(C)]
563// struct PciCap64 {
564//    pci_cap: PciCap,
565//    offset_hi: u32,
566//    length_hi: u32
567pub struct ShMemCfg {
568	mem_addr: u64,
569	length: u64,
570	sh_mem: ShMem,
571	/// Shared memory regions are identified via an ID
572	/// See Virtio specification v1.1. - 4.1.4.7
573	id: u8,
574}
575
576impl ShMemCfg {
577	fn new(cap: &PciCap) -> Option<Self> {
578		if cap.bar.length < cap.len() + cap.offset() {
579			error!(
580				"Shared memory config of with id {} of device {:x}, does not fit into memory specified by bar {:x}!",
581				cap.cap.id, cap.dev_id, cap.bar.index
582			);
583			return None;
584		}
585
586		let offset = cap.cap.offset.to_ne();
587		let length = cap.cap.length.to_ne();
588
589		let virt_addr_raw = cap.bar.mem_addr + offset;
590		let raw_ptr = ptr::with_exposed_provenance_mut::<u8>(virt_addr_raw.try_into().unwrap());
591
592		// Zero initialize shared memory area
593		unsafe {
594			for i in 0..usize::try_from(length).unwrap() {
595				*(raw_ptr.add(i)) = 0;
596			}
597		};
598
599		// Currently in place in order to ensure a safe cast below
600		// "len: cap.bar.length as usize"
601		// In order to remove this assert a safe conversion from
602		// kernel PciBar struct into usize must be made
603		assert!(mem::size_of::<usize>() == 8);
604
605		Some(ShMemCfg {
606			mem_addr: virt_addr_raw,
607			length: cap.len(),
608			sh_mem: ShMem {
609				ptr: raw_ptr,
610				len: cap.bar.length as usize,
611			},
612			id: cap.cap.id,
613		})
614	}
615}
616
617/// Defines a shared memory locate at location ptr with a length of len.
618/// The shared memories Drop implementation does not dealloc the memory
619/// behind the pointer but sets it to zero, to prevent leakage of data.
620struct ShMem {
621	ptr: *mut u8,
622	len: usize,
623}
624
625impl core::ops::Deref for ShMem {
626	type Target = [u8];
627
628	fn deref(&self) -> &[u8] {
629		unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
630	}
631}
632
633impl core::ops::DerefMut for ShMem {
634	fn deref_mut(&mut self) -> &mut [u8] {
635		unsafe { core::slice::from_raw_parts_mut(self.ptr, self.len) }
636	}
637}
638
639// Upon drop the shared memory region is "deleted" with zeros.
640impl Drop for ShMem {
641	fn drop(&mut self) {
642		for i in 0..self.len {
643			unsafe {
644				*(self.ptr.add(i)) = 0;
645			}
646		}
647	}
648}
649
650/// PciBar stores the virtual memory address and associated length of memory space
651/// a PCI device's physical memory indicated by the device's BAR has been mapped to.
652//
653// Currently all fields are public as the struct is instantiated in the drivers::virtio::env module
654#[derive(Copy, Clone, Debug)]
655pub struct PciBar {
656	index: u8,
657	mem_addr: u64,
658	length: u64,
659}
660
661impl PciBar {
662	pub fn new(index: u8, mem_addr: u64, length: u64) -> Self {
663		PciBar {
664			index,
665			mem_addr,
666			length,
667		}
668	}
669}
670
671/// Reads all PCI capabilities, starting at the capabilities list pointer from the
672/// PCI device.
673///
674/// Returns ONLY Virtio specific capabilities, which allow to locate the actual capability
675/// structures inside the memory areas, indicated by the BaseAddressRegisters (BAR's).
676fn read_caps(device: &PciDevice<PciConfigRegion>) -> Result<Vec<PciCap>, PciError> {
677	let device_id = device.device_id();
678
679	let capabilities = device
680		.capabilities()
681		.unwrap()
682		.filter_map(|capability| match capability {
683			PciCapability::Vendor(capability) => Some(capability),
684			_ => None,
685		})
686		.map(|addr| CapData::read(addr, device.access()).unwrap())
687		.filter(|cap| cap.cfg_type != CapCfgType::Pci)
688		.map(|cap| {
689			let slot = cap.bar;
690			let (addr, size) = device.memory_map_bar(slot, true).unwrap();
691			PciCap {
692				bar: VirtioPciBar::new(slot, addr.as_u64(), size.try_into().unwrap()),
693				dev_id: device_id,
694				cap,
695			}
696		})
697		.collect::<Vec<_>>();
698
699	if capabilities.is_empty() {
700		error!("No virtio capability found for device {device_id:x}");
701		Err(PciError::NoVirtioCaps(device_id))
702	} else {
703		Ok(capabilities)
704	}
705}
706
707pub(crate) fn map_caps(device: &PciDevice<PciConfigRegion>) -> Result<UniCapsColl, VirtioError> {
708	let device_id = device.device_id();
709
710	// In case caplist pointer is not used, abort as it is essential
711	if !device.status().has_capability_list() {
712		error!("Found virtio device without capability list. Aborting!");
713		return Err(VirtioError::FromPci(PciError::NoCapPtr(device_id)));
714	}
715
716	// Get list of PciCaps pointing to capabilities
717	let cap_list = match read_caps(device) {
718		Ok(list) => list,
719		Err(pci_error) => return Err(VirtioError::FromPci(pci_error)),
720	};
721
722	let mut com_cfg = None;
723	let mut notif_cfg = None;
724	let mut isr_cfg = None;
725	let mut sh_mem_cfg_list = Vec::new();
726	let mut dev_cfg_list = Vec::new();
727	// Map Caps in virtual memory
728	for pci_cap in cap_list {
729		match pci_cap.cap.cfg_type {
730			CapCfgType::Common => {
731				if com_cfg.is_none() {
732					match pci_cap.map_common_cfg() {
733						Some(cap) => com_cfg = Some(ComCfg::new(cap, pci_cap.cap.id)),
734						None => error!(
735							"Common config capability with id {}, of device {:x}, could not be mapped!",
736							pci_cap.cap.id, device_id
737						),
738					}
739				}
740			}
741			CapCfgType::Notify => {
742				if notif_cfg.is_none() {
743					match NotifCfg::new(&pci_cap) {
744						Some(notif) => notif_cfg = Some(notif),
745						None => error!(
746							"Notification config capability with id {}, of device {device_id:x} could not be used!",
747							pci_cap.cap.id
748						),
749					}
750				}
751			}
752			CapCfgType::Isr => {
753				if isr_cfg.is_none() {
754					match pci_cap.map_isr_status() {
755						Some(isr_stat) => isr_cfg = Some(IsrStatus::new(isr_stat, pci_cap.cap.id)),
756						None => error!(
757							"ISR status config capability with id {}, of device {device_id:x} could not be used!",
758							pci_cap.cap.id
759						),
760					}
761				}
762			}
763			CapCfgType::SharedMemory => match ShMemCfg::new(&pci_cap) {
764				Some(sh_mem) => sh_mem_cfg_list.push(sh_mem),
765				None => error!(
766					"Shared Memory config capability with id {}, of device {device_id:x} could not be used!",
767					pci_cap.cap.id,
768				),
769			},
770			CapCfgType::Device => dev_cfg_list.push(pci_cap),
771
772			// PCI's configuration space is allowed to hold other structures, which are not virtio specific and are therefore ignored
773			// in the following
774			_ => continue,
775		}
776	}
777
778	Ok(UniCapsColl {
779		com_cfg: com_cfg.ok_or(VirtioError::NoComCfg(device_id))?,
780		notif_cfg: notif_cfg.ok_or(VirtioError::NoNotifCfg(device_id))?,
781		isr_cfg: isr_cfg.ok_or(VirtioError::NoIsrCfg(device_id))?,
782		sh_mem_cfg_list,
783		dev_cfg_list,
784	})
785}
786
787/// Checks existing drivers for support of given device. Upon match, provides
788/// driver with a [`PciDevice<PciConfigRegion>`] reference, allowing access to the capabilities
789/// list of the given device through [map_caps].
790pub(crate) fn init_device(
791	device: &PciDevice<PciConfigRegion>,
792) -> Result<VirtioDriver, DriverError> {
793	let device_id = device.device_id();
794
795	if device_id < 0x1040 {
796		warn!(
797			"Legacy/transitional Virtio device, with id: {device_id:#x} is NOT supported, skipping!"
798		);
799
800		// Return Driver error inidacting device is not supported
801		return Err(DriverError::InitVirtioDevFail(
802			VirtioError::DevNotSupported(device_id),
803		));
804	}
805
806	let id = virtio::Id::from(u8::try_from(device_id - 0x1040).unwrap());
807
808	match id {
809		#[cfg(all(
810			not(all(target_arch = "x86_64", feature = "rtl8139")),
811			any(feature = "tcp", feature = "udp")
812		))]
813		virtio::Id::Net => match VirtioNetDriver::init(device) {
814			Ok(virt_net_drv) => {
815				info!("Virtio network driver initialized.");
816
817				let irq = device.get_irq().unwrap();
818				crate::arch::interrupts::add_irq_name(irq, "virtio");
819				info!("Virtio interrupt handler at line {irq}");
820
821				Ok(VirtioDriver::Network(virt_net_drv))
822			}
823			Err(virtio_error) => {
824				error!(
825					"Virtio networkd driver could not be initialized with device: {device_id:x}"
826				);
827				Err(DriverError::InitVirtioDevFail(virtio_error))
828			}
829		},
830		#[cfg(feature = "vsock")]
831		virtio::Id::Vsock => match VirtioVsockDriver::init(device) {
832			Ok(virt_sock_drv) => {
833				info!("Virtio sock driver initialized.");
834
835				let irq = device.get_irq().unwrap();
836				crate::arch::interrupts::add_irq_name(irq, "virtio");
837				info!("Virtio interrupt handler at line {irq}");
838
839				Ok(VirtioDriver::Vsock(virt_sock_drv))
840			}
841			Err(virtio_error) => {
842				error!("Virtio sock driver could not be initialized with device: {device_id:x}");
843				Err(DriverError::InitVirtioDevFail(virtio_error))
844			}
845		},
846		#[cfg(feature = "fuse")]
847		virtio::Id::Fs => {
848			// TODO: check subclass
849			// TODO: proper error handling on driver creation fail
850			match VirtioFsDriver::init(device) {
851				Ok(virt_fs_drv) => {
852					info!("Virtio filesystem driver initialized.");
853					Ok(VirtioDriver::FileSystem(virt_fs_drv))
854				}
855				Err(virtio_error) => {
856					error!(
857						"Virtio filesystem driver could not be initialized with device: {device_id:x}"
858					);
859					Err(DriverError::InitVirtioDevFail(virtio_error))
860				}
861			}
862		}
863		id => {
864			warn!("Virtio device {id:?} is not supported, skipping!");
865
866			// Return Driver error inidacting device is not supported
867			Err(DriverError::InitVirtioDevFail(
868				VirtioError::DevNotSupported(device_id),
869			))
870		}
871	}
872}
873
874pub(crate) enum VirtioDriver {
875	#[cfg(all(
876		not(all(target_arch = "x86_64", feature = "rtl8139")),
877		any(feature = "tcp", feature = "udp")
878	))]
879	Network(VirtioNetDriver),
880	#[cfg(feature = "vsock")]
881	Vsock(VirtioVsockDriver),
882	#[cfg(feature = "fuse")]
883	FileSystem(VirtioFsDriver),
884}