1 //! PCI transport for VirtIO.
2 
3 pub mod bus;
4 
5 use self::bus::{DeviceFunction, DeviceFunctionInfo, PciError, PciRoot, PCI_CAP_ID_VNDR};
6 use super::{DeviceStatus, DeviceType, Transport};
7 use 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 };
15 use core::{
16     fmt::{self, Display, Formatter},
17     mem::{align_of, size_of},
18     ptr::{addr_of_mut, NonNull},
19 };
20 
21 /// The PCI vendor ID for VirtIO devices.
22 const VIRTIO_VENDOR_ID: u16 = 0x1af4;
23 
24 /// The offset to add to a VirtIO device ID to get the corresponding PCI device ID.
25 const PCI_DEVICE_ID_OFFSET: u16 = 0x1040;
26 
27 const TRANSITIONAL_NETWORK: u16 = 0x1000;
28 const TRANSITIONAL_BLOCK: u16 = 0x1001;
29 const TRANSITIONAL_MEMORY_BALLOONING: u16 = 0x1002;
30 const TRANSITIONAL_CONSOLE: u16 = 0x1003;
31 const TRANSITIONAL_SCSI_HOST: u16 = 0x1004;
32 const TRANSITIONAL_ENTROPY_SOURCE: u16 = 0x1005;
33 const TRANSITIONAL_9P_TRANSPORT: u16 = 0x1009;
34 
35 /// The offset of the bar field within `virtio_pci_cap`.
36 const CAP_BAR_OFFSET: u8 = 4;
37 /// The offset of the offset field with `virtio_pci_cap`.
38 const CAP_BAR_OFFSET_OFFSET: u8 = 8;
39 /// The offset of the `length` field within `virtio_pci_cap`.
40 const CAP_LENGTH_OFFSET: u8 = 12;
41 /// The offset of the`notify_off_multiplier` field within `virtio_pci_notify_cap`.
42 const CAP_NOTIFY_OFF_MULTIPLIER_OFFSET: u8 = 16;
43 
44 /// Common configuration.
45 const VIRTIO_PCI_CAP_COMMON_CFG: u8 = 1;
46 /// Notifications.
47 const VIRTIO_PCI_CAP_NOTIFY_CFG: u8 = 2;
48 /// ISR Status.
49 const VIRTIO_PCI_CAP_ISR_CFG: u8 = 3;
50 /// Device specific configuration.
51 const VIRTIO_PCI_CAP_DEVICE_CFG: u8 = 4;
52 
device_type(pci_device_id: u16) -> DeviceType53 fn 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 
67 /// Returns the type of VirtIO device to which the given PCI vendor and device ID corresponds, or
68 /// `None` if it is not a recognised VirtIO device.
virtio_device_type(device_function_info: &DeviceFunctionInfo) -> Option<DeviceType>69 pub 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 /// PCI transport for VirtIO.
80 ///
81 /// Ref: 4.1 Virtio Over PCI Bus
82 #[derive(Debug)]
83 pub struct PciTransport {
84     device_type: DeviceType,
85     /// The bus, device and function identifier for the VirtIO device.
86     device_function: DeviceFunction,
87     /// The common configuration structure within some BAR.
88     common_cfg: NonNull<CommonCfg>,
89     /// The start of the queue notification region within some BAR.
90     notify_region: NonNull<[WriteOnly<u16>]>,
91     notify_off_multiplier: u32,
92     /// The ISR status register within some BAR.
93     isr_status: NonNull<Volatile<u8>>,
94     /// The VirtIO device-specific configuration within some BAR.
95     config_space: Option<NonNull<[u32]>>,
96 }
97 
98 impl PciTransport {
99     /// Construct a new PCI VirtIO device driver for the given device function on the given PCI
100     /// root controller.
101     ///
102     /// The PCI device must already have had its BARs allocated.
new<H: Hal>( root: &mut PciRoot, device_function: DeviceFunction, ) -> Result<Self, VirtioPciError>103     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         // Find the PCI capabilities we need.
116         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, &notify_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 
202 impl Transport for PciTransport {
device_type(&self) -> DeviceType203     fn device_type(&self) -> DeviceType {
204         self.device_type
205     }
206 
read_device_features(&mut self) -> u64207     fn read_device_features(&mut self) -> u64 {
208         // Safe because the common config pointer is valid and we checked in get_bar_region that it
209         // was aligned.
210         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 
write_driver_features(&mut self, driver_features: u64)219     fn write_driver_features(&mut self, driver_features: u64) {
220         // Safe because the common config pointer is valid and we checked in get_bar_region that it
221         // was aligned.
222         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 
max_queue_size(&mut self, queue: u16) -> u32234     fn max_queue_size(&mut self, queue: u16) -> u32 {
235         // Safe because the common config pointer is valid and we checked in get_bar_region that it
236         // was aligned.
237         unsafe {
238             volwrite!(self.common_cfg, queue_select, queue);
239             volread!(self.common_cfg, queue_size).into()
240         }
241     }
242 
notify(&mut self, queue: u16)243     fn notify(&mut self, queue: u16) {
244         // Safe because the common config and notify region pointers are valid and we checked in
245         // get_bar_region that they were aligned.
246         unsafe {
247             volwrite!(self.common_cfg, queue_select, queue);
248             // TODO: Consider caching this somewhere (per queue).
249             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 
get_status(&self) -> DeviceStatus257     fn get_status(&self) -> DeviceStatus {
258         // Safe because the common config pointer is valid and we checked in get_bar_region that it
259         // was aligned.
260         let status = unsafe { volread!(self.common_cfg, device_status) };
261         DeviceStatus::from_bits_truncate(status.into())
262     }
263 
set_status(&mut self, status: DeviceStatus)264     fn set_status(&mut self, status: DeviceStatus) {
265         // Safe because the common config pointer is valid and we checked in get_bar_region that it
266         // was aligned.
267         unsafe {
268             volwrite!(self.common_cfg, device_status, status.bits() as u8);
269         }
270     }
271 
set_guest_page_size(&mut self, _guest_page_size: u32)272     fn set_guest_page_size(&mut self, _guest_page_size: u32) {
273         // No-op, the PCI transport doesn't care.
274     }
275 
requires_legacy_layout(&self) -> bool276     fn requires_legacy_layout(&self) -> bool {
277         false
278     }
279 
queue_set( &mut self, queue: u16, size: u32, descriptors: PhysAddr, driver_area: PhysAddr, device_area: PhysAddr, )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         // Safe because the common config pointer is valid and we checked in get_bar_region that it
289         // was aligned.
290         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 
queue_unset(&mut self, _queue: u16)300     fn queue_unset(&mut self, _queue: u16) {
301         // The VirtIO spec doesn't allow queues to be unset once they have been set up for the PCI
302         // transport, so this is a no-op.
303     }
304 
queue_used(&mut self, queue: u16) -> bool305     fn queue_used(&mut self, queue: u16) -> bool {
306         // Safe because the common config pointer is valid and we checked in get_bar_region that it
307         // was aligned.
308         unsafe {
309             volwrite!(self.common_cfg, queue_select, queue);
310             volread!(self.common_cfg, queue_enable) == 1
311         }
312     }
313 
ack_interrupt(&mut self) -> bool314     fn ack_interrupt(&mut self) -> bool {
315         // Safe because the common config pointer is valid and we checked in get_bar_region that it
316         // was aligned.
317         // Reading the ISR status resets it to 0 and causes the device to de-assert the interrupt.
318         let isr_status = unsafe { self.isr_status.as_ptr().vread() };
319         // TODO: Distinguish between queue interrupt and device configuration interrupt.
320         isr_status & 0x3 != 0
321     }
322 
config_space<T>(&self) -> Result<NonNull<T>, Error>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 as this should only happen if the driver is written incorrectly.
329                 panic!(
330                     "Driver expected config space alignment of {} bytes, but VirtIO only guarantees 4 byte alignment.",
331                     align_of::<T>()
332                 );
333             } else {
334                 // TODO: Use NonNull::as_non_null_ptr once it is stable.
335                 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 
344 // SAFETY: MMIO can be done from any thread or CPU core.
345 unsafe impl Send for PciTransport {}
346 
347 // SAFETY: `&PciTransport` only allows MMIO reads or getting the config space, both of which are
348 // fine to happen concurrently on different CPU cores.
349 unsafe impl Sync for PciTransport {}
350 
351 impl Drop for PciTransport {
drop(&mut self)352     fn drop(&mut self) {
353         // Reset the device when the transport is dropped.
354         self.set_status(DeviceStatus::empty());
355         while self.get_status() != DeviceStatus::empty() {}
356     }
357 }
358 
359 /// `virtio_pci_common_cfg`, see 4.1.4.3 "Common configuration structure layout".
360 #[repr(C)]
361 struct 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 /// Information about a VirtIO structure within some BAR, as provided by a `virtio_pci_cap`.
381 #[derive(Clone, Debug, Eq, PartialEq)]
382 struct VirtioCapabilityInfo {
383     /// The bar in which the structure can be found.
384     bar: u8,
385     /// The offset within the bar.
386     offset: u32,
387     /// The length in bytes of the structure within the bar.
388     length: u32,
389 }
390 
get_bar_region<H: Hal, T>( root: &mut PciRoot, device_function: DeviceFunction, struct_info: &VirtioCapabilityInfo, ) -> Result<NonNull<T>, VirtioPciError>391 fn 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     // Safe because the paddr and size describe a valid MMIO region, at least according to the PCI
410     // bus.
411     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 
get_bar_region_slice<H: Hal, T>( root: &mut PciRoot, device_function: DeviceFunction, struct_info: &VirtioCapabilityInfo, ) -> Result<NonNull<[T]>, VirtioPciError>421 fn 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 /// An error encountered initialising a VirtIO PCI transport.
434 #[derive(Clone, Debug, Eq, PartialEq)]
435 pub enum VirtioPciError {
436     /// PCI device vender ID was not the VirtIO vendor ID.
437     InvalidVendorId(u16),
438     /// No valid `VIRTIO_PCI_CAP_COMMON_CFG` capability was found.
439     MissingCommonConfig,
440     /// No valid `VIRTIO_PCI_CAP_NOTIFY_CFG` capability was found.
441     MissingNotifyConfig,
442     /// `VIRTIO_PCI_CAP_NOTIFY_CFG` capability has a `notify_off_multiplier` that is not a multiple
443     /// of 2.
444     InvalidNotifyOffMultiplier(u32),
445     /// No valid `VIRTIO_PCI_CAP_ISR_CFG` capability was found.
446     MissingIsrConfig,
447     /// An IO BAR was provided rather than a memory BAR.
448     UnexpectedIoBar,
449     /// A BAR which we need was not allocated an address.
450     BarNotAllocated(u8),
451     /// The offset for some capability was greater than the length of the BAR.
452     BarOffsetOutOfRange,
453     /// The virtual address was not aligned as expected.
454     Misaligned {
455         /// The virtual address in question.
456         vaddr: NonNull<u8>,
457         /// The expected alignment in bytes.
458         alignment: usize,
459     },
460     /// A generic PCI error,
461     Pci(PciError),
462 }
463 
464 impl Display for VirtioPciError {
fmt(&self, f: &mut Formatter) -> fmt::Result465     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 
503 impl From<PciError> for VirtioPciError {
from(error: PciError) -> Self504     fn from(error: PciError) -> Self {
505         Self::Pci(error)
506     }
507 }
508 
509 // SAFETY: The `vaddr` field of `VirtioPciError::Misaligned` is only used for debug output.
510 unsafe impl Send for VirtioPciError {}
511 
512 // SAFETY: The `vaddr` field of `VirtioPciError::Misaligned` is only used for debug output.
513 unsafe impl Sync for VirtioPciError {}
514 
515 #[cfg(test)]
516 mod tests {
517     use super::*;
518 
519     #[test]
transitional_device_ids()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]
offset_device_ids()527     fn offset_device_ids() {
528         assert_eq!(device_type(0x1045), DeviceType::MemoryBalloon);
529         assert_eq!(device_type(0x1049), DeviceType::_9P);
530         assert_eq!(device_type(0x1058), DeviceType::Memory);
531         assert_eq!(device_type(0x1040), DeviceType::Invalid);
532         assert_eq!(device_type(0x1059), DeviceType::Invalid);
533     }
534 
535     #[test]
virtio_device_type_valid()536     fn virtio_device_type_valid() {
537         assert_eq!(
538             virtio_device_type(&DeviceFunctionInfo {
539                 vendor_id: VIRTIO_VENDOR_ID,
540                 device_id: TRANSITIONAL_BLOCK,
541                 class: 0,
542                 subclass: 0,
543                 prog_if: 0,
544                 revision: 0,
545                 header_type: bus::HeaderType::Standard,
546             }),
547             Some(DeviceType::Block)
548         );
549     }
550 
551     #[test]
virtio_device_type_invalid()552     fn virtio_device_type_invalid() {
553         // Non-VirtIO vendor ID.
554         assert_eq!(
555             virtio_device_type(&DeviceFunctionInfo {
556                 vendor_id: 0x1234,
557                 device_id: TRANSITIONAL_BLOCK,
558                 class: 0,
559                 subclass: 0,
560                 prog_if: 0,
561                 revision: 0,
562                 header_type: bus::HeaderType::Standard,
563             }),
564             None
565         );
566 
567         // Invalid device ID.
568         assert_eq!(
569             virtio_device_type(&DeviceFunctionInfo {
570                 vendor_id: VIRTIO_VENDOR_ID,
571                 device_id: 0x1040,
572                 class: 0,
573                 subclass: 0,
574                 prog_if: 0,
575                 revision: 0,
576                 header_type: bus::HeaderType::Standard,
577             }),
578             None
579         );
580     }
581 }
582