1pub mod bus;
4
5use self::bus::{DeviceFunction, DeviceFunctionInfo, PciError, PciRoot, PCI_CAP_ID_VNDR};
6use super::{DeviceStatus, DeviceType, Transport};
7use crate::{
8 hal::{Hal, PhysAddr},
9 nonnull_slice_from_raw_parts,
10 volatile::{
11 volread, volwrite, ReadOnly, Volatile, VolatileReadable, VolatileWritable, WriteOnly,
12 },
13 Error,
14};
15use core::{
16 fmt::{self, Display, Formatter},
17 mem::{align_of, size_of},
18 ptr::{addr_of_mut, NonNull},
19};
20
21const VIRTIO_VENDOR_ID: u16 = 0x1af4;
23
24const PCI_DEVICE_ID_OFFSET: u16 = 0x1040;
26
27const TRANSITIONAL_NETWORK: u16 = 0x1000;
28const TRANSITIONAL_BLOCK: u16 = 0x1001;
29const TRANSITIONAL_MEMORY_BALLOONING: u16 = 0x1002;
30const TRANSITIONAL_CONSOLE: u16 = 0x1003;
31const TRANSITIONAL_SCSI_HOST: u16 = 0x1004;
32const TRANSITIONAL_ENTROPY_SOURCE: u16 = 0x1005;
33const TRANSITIONAL_9P_TRANSPORT: u16 = 0x1009;
34
35const CAP_BAR_OFFSET: u8 = 4;
37const CAP_BAR_OFFSET_OFFSET: u8 = 8;
39const CAP_LENGTH_OFFSET: u8 = 12;
41const CAP_NOTIFY_OFF_MULTIPLIER_OFFSET: u8 = 16;
43
44const VIRTIO_PCI_CAP_COMMON_CFG: u8 = 1;
46const VIRTIO_PCI_CAP_NOTIFY_CFG: u8 = 2;
48const VIRTIO_PCI_CAP_ISR_CFG: u8 = 3;
50const VIRTIO_PCI_CAP_DEVICE_CFG: u8 = 4;
52
53fn device_type(pci_device_id: u16) -> DeviceType {
54 match pci_device_id {
55 TRANSITIONAL_NETWORK => DeviceType::Network,
56 TRANSITIONAL_BLOCK => DeviceType::Block,
57 TRANSITIONAL_MEMORY_BALLOONING => DeviceType::MemoryBalloon,
58 TRANSITIONAL_CONSOLE => DeviceType::Console,
59 TRANSITIONAL_SCSI_HOST => DeviceType::ScsiHost,
60 TRANSITIONAL_ENTROPY_SOURCE => DeviceType::EntropySource,
61 TRANSITIONAL_9P_TRANSPORT => DeviceType::_9P,
62 id if id >= PCI_DEVICE_ID_OFFSET => DeviceType::from(id - PCI_DEVICE_ID_OFFSET),
63 _ => DeviceType::Invalid,
64 }
65}
66
67pub fn virtio_device_type(device_function_info: &DeviceFunctionInfo) -> Option<DeviceType> {
70 if device_function_info.vendor_id == VIRTIO_VENDOR_ID {
71 let device_type = device_type(device_function_info.device_id);
72 if device_type != DeviceType::Invalid {
73 return Some(device_type);
74 }
75 }
76 None
77}
78
79#[derive(Debug)]
83pub struct PciTransport {
84 device_type: DeviceType,
85 device_function: DeviceFunction,
87 common_cfg: NonNull<CommonCfg>,
89 notify_region: NonNull<[WriteOnly<u16>]>,
91 notify_off_multiplier: u32,
92 isr_status: NonNull<Volatile<u8>>,
94 config_space: Option<NonNull<[u32]>>,
96}
97
98impl PciTransport {
99 pub fn new<H: Hal>(
104 root: &mut PciRoot,
105 device_function: DeviceFunction,
106 ) -> Result<Self, VirtioPciError> {
107 let device_vendor = root.config_read_word(device_function, 0);
108 let device_id = (device_vendor >> 16) as u16;
109 let vendor_id = device_vendor as u16;
110 if vendor_id != VIRTIO_VENDOR_ID {
111 return Err(VirtioPciError::InvalidVendorId(vendor_id));
112 }
113 let device_type = device_type(device_id);
114
115 let mut common_cfg = None;
117 let mut notify_cfg = None;
118 let mut notify_off_multiplier = 0;
119 let mut isr_cfg = None;
120 let mut device_cfg = None;
121 for capability in root.capabilities(device_function) {
122 if capability.id != PCI_CAP_ID_VNDR {
123 continue;
124 }
125 let cap_len = capability.private_header as u8;
126 let cfg_type = (capability.private_header >> 8) as u8;
127 if cap_len < 16 {
128 continue;
129 }
130 let struct_info = VirtioCapabilityInfo {
131 bar: root.config_read_word(device_function, capability.offset + CAP_BAR_OFFSET)
132 as u8,
133 offset: root
134 .config_read_word(device_function, capability.offset + CAP_BAR_OFFSET_OFFSET),
135 length: root
136 .config_read_word(device_function, capability.offset + CAP_LENGTH_OFFSET),
137 };
138
139 match cfg_type {
140 VIRTIO_PCI_CAP_COMMON_CFG if common_cfg.is_none() => {
141 common_cfg = Some(struct_info);
142 }
143 VIRTIO_PCI_CAP_NOTIFY_CFG if cap_len >= 20 && notify_cfg.is_none() => {
144 notify_cfg = Some(struct_info);
145 notify_off_multiplier = root.config_read_word(
146 device_function,
147 capability.offset + CAP_NOTIFY_OFF_MULTIPLIER_OFFSET,
148 );
149 }
150 VIRTIO_PCI_CAP_ISR_CFG if isr_cfg.is_none() => {
151 isr_cfg = Some(struct_info);
152 }
153 VIRTIO_PCI_CAP_DEVICE_CFG if device_cfg.is_none() => {
154 device_cfg = Some(struct_info);
155 }
156 _ => {}
157 }
158 }
159
160 let common_cfg = get_bar_region::<H, _>(
161 root,
162 device_function,
163 &common_cfg.ok_or(VirtioPciError::MissingCommonConfig)?,
164 )?;
165
166 let notify_cfg = notify_cfg.ok_or(VirtioPciError::MissingNotifyConfig)?;
167 if notify_off_multiplier % 2 != 0 {
168 return Err(VirtioPciError::InvalidNotifyOffMultiplier(
169 notify_off_multiplier,
170 ));
171 }
172 let notify_region = get_bar_region_slice::<H, _>(root, device_function, ¬ify_cfg)?;
173
174 let isr_status = get_bar_region::<H, _>(
175 root,
176 device_function,
177 &isr_cfg.ok_or(VirtioPciError::MissingIsrConfig)?,
178 )?;
179
180 let config_space = if let Some(device_cfg) = device_cfg {
181 Some(get_bar_region_slice::<H, _>(
182 root,
183 device_function,
184 &device_cfg,
185 )?)
186 } else {
187 None
188 };
189
190 Ok(Self {
191 device_type,
192 device_function,
193 common_cfg,
194 notify_region,
195 notify_off_multiplier,
196 isr_status,
197 config_space,
198 })
199 }
200}
201
202impl Transport for PciTransport {
203 fn device_type(&self) -> DeviceType {
204 self.device_type
205 }
206
207 fn read_device_features(&mut self) -> u64 {
208 unsafe {
211 volwrite!(self.common_cfg, device_feature_select, 0);
212 let mut device_features_bits = volread!(self.common_cfg, device_feature) as u64;
213 volwrite!(self.common_cfg, device_feature_select, 1);
214 device_features_bits |= (volread!(self.common_cfg, device_feature) as u64) << 32;
215 device_features_bits
216 }
217 }
218
219 fn write_driver_features(&mut self, driver_features: u64) {
220 unsafe {
223 volwrite!(self.common_cfg, driver_feature_select, 0);
224 volwrite!(self.common_cfg, driver_feature, driver_features as u32);
225 volwrite!(self.common_cfg, driver_feature_select, 1);
226 volwrite!(
227 self.common_cfg,
228 driver_feature,
229 (driver_features >> 32) as u32
230 );
231 }
232 }
233
234 fn max_queue_size(&mut self, queue: u16) -> u32 {
235 unsafe {
238 volwrite!(self.common_cfg, queue_select, queue);
239 volread!(self.common_cfg, queue_size).into()
240 }
241 }
242
243 fn notify(&mut self, queue: u16) {
244 unsafe {
247 volwrite!(self.common_cfg, queue_select, queue);
248 let queue_notify_off = volread!(self.common_cfg, queue_notify_off);
250
251 let offset_bytes = usize::from(queue_notify_off) * self.notify_off_multiplier as usize;
252 let index = offset_bytes / size_of::<u16>();
253 addr_of_mut!((*self.notify_region.as_ptr())[index]).vwrite(queue);
254 }
255 }
256
257 fn get_status(&self) -> DeviceStatus {
258 let status = unsafe { volread!(self.common_cfg, device_status) };
261 DeviceStatus::from_bits_truncate(status.into())
262 }
263
264 fn set_status(&mut self, status: DeviceStatus) {
265 unsafe {
268 volwrite!(self.common_cfg, device_status, status.bits() as u8);
269 }
270 }
271
272 fn set_guest_page_size(&mut self, _guest_page_size: u32) {
273 }
275
276 fn requires_legacy_layout(&self) -> bool {
277 false
278 }
279
280 fn queue_set(
281 &mut self,
282 queue: u16,
283 size: u32,
284 descriptors: PhysAddr,
285 driver_area: PhysAddr,
286 device_area: PhysAddr,
287 ) {
288 unsafe {
291 volwrite!(self.common_cfg, queue_select, queue);
292 volwrite!(self.common_cfg, queue_size, size as u16);
293 volwrite!(self.common_cfg, queue_desc, descriptors as u64);
294 volwrite!(self.common_cfg, queue_driver, driver_area as u64);
295 volwrite!(self.common_cfg, queue_device, device_area as u64);
296 volwrite!(self.common_cfg, queue_enable, 1);
297 }
298 }
299
300 fn queue_unset(&mut self, _queue: u16) {
301 }
304
305 fn queue_used(&mut self, queue: u16) -> bool {
306 unsafe {
309 volwrite!(self.common_cfg, queue_select, queue);
310 volread!(self.common_cfg, queue_enable) == 1
311 }
312 }
313
314 fn ack_interrupt(&mut self) -> bool {
315 let isr_status = unsafe { self.isr_status.as_ptr().vread() };
319 isr_status & 0x3 != 0
321 }
322
323 fn config_space<T>(&self) -> Result<NonNull<T>, Error> {
324 if let Some(config_space) = self.config_space {
325 if size_of::<T>() > config_space.len() * size_of::<u32>() {
326 Err(Error::ConfigSpaceTooSmall)
327 } else if align_of::<T>() > 4 {
328 panic!(
330 "Driver expected config space alignment of {} bytes, but VirtIO only guarantees 4 byte alignment.",
331 align_of::<T>()
332 );
333 } else {
334 let config_space_ptr = NonNull::new(config_space.as_ptr() as *mut u32).unwrap();
336 Ok(config_space_ptr.cast())
337 }
338 } else {
339 Err(Error::ConfigSpaceMissing)
340 }
341 }
342}
343
344unsafe impl Send for PciTransport {}
346
347unsafe impl Sync for PciTransport {}
350
351impl Drop for PciTransport {
352 fn drop(&mut self) {
353 self.set_status(DeviceStatus::empty());
355 while self.get_status() != DeviceStatus::empty() {}
356 }
357}
358
359#[repr(C)]
361struct CommonCfg {
362 device_feature_select: Volatile<u32>,
363 device_feature: ReadOnly<u32>,
364 driver_feature_select: Volatile<u32>,
365 driver_feature: Volatile<u32>,
366 msix_config: Volatile<u16>,
367 num_queues: ReadOnly<u16>,
368 device_status: Volatile<u8>,
369 config_generation: ReadOnly<u8>,
370 queue_select: Volatile<u16>,
371 queue_size: Volatile<u16>,
372 queue_msix_vector: Volatile<u16>,
373 queue_enable: Volatile<u16>,
374 queue_notify_off: Volatile<u16>,
375 queue_desc: Volatile<u64>,
376 queue_driver: Volatile<u64>,
377 queue_device: Volatile<u64>,
378}
379
380#[derive(Clone, Debug, Eq, PartialEq)]
382struct VirtioCapabilityInfo {
383 bar: u8,
385 offset: u32,
387 length: u32,
389}
390
391fn get_bar_region<H: Hal, T>(
392 root: &mut PciRoot,
393 device_function: DeviceFunction,
394 struct_info: &VirtioCapabilityInfo,
395) -> Result<NonNull<T>, VirtioPciError> {
396 let bar_info = root.bar_info(device_function, struct_info.bar)?;
397 let (bar_address, bar_size) = bar_info
398 .memory_address_size()
399 .ok_or(VirtioPciError::UnexpectedIoBar)?;
400 if bar_address == 0 {
401 return Err(VirtioPciError::BarNotAllocated(struct_info.bar));
402 }
403 if struct_info.offset + struct_info.length > bar_size
404 || size_of::<T>() > struct_info.length as usize
405 {
406 return Err(VirtioPciError::BarOffsetOutOfRange);
407 }
408 let paddr = bar_address as PhysAddr + struct_info.offset as PhysAddr;
409 let vaddr = unsafe { H::mmio_phys_to_virt(paddr, struct_info.length as usize) };
412 if vaddr.as_ptr() as usize % align_of::<T>() != 0 {
413 return Err(VirtioPciError::Misaligned {
414 vaddr,
415 alignment: align_of::<T>(),
416 });
417 }
418 Ok(vaddr.cast())
419}
420
421fn get_bar_region_slice<H: Hal, T>(
422 root: &mut PciRoot,
423 device_function: DeviceFunction,
424 struct_info: &VirtioCapabilityInfo,
425) -> Result<NonNull<[T]>, VirtioPciError> {
426 let ptr = get_bar_region::<H, T>(root, device_function, struct_info)?;
427 Ok(nonnull_slice_from_raw_parts(
428 ptr,
429 struct_info.length as usize / size_of::<T>(),
430 ))
431}
432
433#[derive(Clone, Debug, Eq, PartialEq)]
435pub enum VirtioPciError {
436 InvalidVendorId(u16),
438 MissingCommonConfig,
440 MissingNotifyConfig,
442 InvalidNotifyOffMultiplier(u32),
445 MissingIsrConfig,
447 UnexpectedIoBar,
449 BarNotAllocated(u8),
451 BarOffsetOutOfRange,
453 Misaligned {
455 vaddr: NonNull<u8>,
457 alignment: usize,
459 },
460 Pci(PciError),
462}
463
464impl Display for VirtioPciError {
465 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
466 match self {
467 Self::InvalidVendorId(vendor_id) => write!(
468 f,
469 "PCI device vender ID {:#06x} was not the VirtIO vendor ID {:#06x}.",
470 vendor_id, VIRTIO_VENDOR_ID
471 ),
472 Self::MissingCommonConfig => write!(
473 f,
474 "No valid `VIRTIO_PCI_CAP_COMMON_CFG` capability was found."
475 ),
476 Self::MissingNotifyConfig => write!(
477 f,
478 "No valid `VIRTIO_PCI_CAP_NOTIFY_CFG` capability was found."
479 ),
480 Self::InvalidNotifyOffMultiplier(notify_off_multiplier) => {
481 write!(
482 f,
483 "`VIRTIO_PCI_CAP_NOTIFY_CFG` capability has a `notify_off_multiplier` that is not a multiple of 2: {}",
484 notify_off_multiplier
485 )
486 }
487 Self::MissingIsrConfig => {
488 write!(f, "No valid `VIRTIO_PCI_CAP_ISR_CFG` capability was found.")
489 }
490 Self::UnexpectedIoBar => write!(f, "Unexpected IO BAR (expected memory BAR)."),
491 Self::BarNotAllocated(bar_index) => write!(f, "Bar {} not allocated.", bar_index),
492 Self::BarOffsetOutOfRange => write!(f, "Capability offset greater than BAR length."),
493 Self::Misaligned { vaddr, alignment } => write!(
494 f,
495 "Virtual address {:#018?} was not aligned to a {} byte boundary as expected.",
496 vaddr, alignment
497 ),
498 Self::Pci(pci_error) => pci_error.fmt(f),
499 }
500 }
501}
502
503impl From<PciError> for VirtioPciError {
504 fn from(error: PciError) -> Self {
505 Self::Pci(error)
506 }
507}
508
509unsafe impl Send for VirtioPciError {}
511
512unsafe impl Sync for VirtioPciError {}
514
515#[cfg(test)]
516mod tests {
517 use super::*;
518
519 #[test]
520 fn transitional_device_ids() {
521 assert_eq!(device_type(0x1000), DeviceType::Network);
522 assert_eq!(device_type(0x1002), DeviceType::MemoryBalloon);
523 assert_eq!(device_type(0x1009), DeviceType::_9P);
524 }
525
526 #[test]
527 fn offset_device_ids() {
528 assert_eq!(device_type(0x1040), DeviceType::Invalid);
529 assert_eq!(device_type(0x1045), DeviceType::MemoryBalloon);
530 assert_eq!(device_type(0x1049), DeviceType::_9P);
531 assert_eq!(device_type(0x1058), DeviceType::Memory);
532 assert_eq!(device_type(0x1059), DeviceType::Sound);
533 assert_eq!(device_type(0x1060), DeviceType::Invalid);
534 }
535
536 #[test]
537 fn virtio_device_type_valid() {
538 assert_eq!(
539 virtio_device_type(&DeviceFunctionInfo {
540 vendor_id: VIRTIO_VENDOR_ID,
541 device_id: TRANSITIONAL_BLOCK,
542 class: 0,
543 subclass: 0,
544 prog_if: 0,
545 revision: 0,
546 header_type: bus::HeaderType::Standard,
547 }),
548 Some(DeviceType::Block)
549 );
550 }
551
552 #[test]
553 fn virtio_device_type_invalid() {
554 assert_eq!(
556 virtio_device_type(&DeviceFunctionInfo {
557 vendor_id: 0x1234,
558 device_id: TRANSITIONAL_BLOCK,
559 class: 0,
560 subclass: 0,
561 prog_if: 0,
562 revision: 0,
563 header_type: bus::HeaderType::Standard,
564 }),
565 None
566 );
567
568 assert_eq!(
570 virtio_device_type(&DeviceFunctionInfo {
571 vendor_id: VIRTIO_VENDOR_ID,
572 device_id: 0x1040,
573 class: 0,
574 subclass: 0,
575 prog_if: 0,
576 revision: 0,
577 header_type: bus::HeaderType::Standard,
578 }),
579 None
580 );
581 }
582}