virtio_drivers/transport/
pci.rs

1//! PCI transport for VirtIO.
2
3pub 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
21/// The PCI vendor ID for VirtIO devices.
22const VIRTIO_VENDOR_ID: u16 = 0x1af4;
23
24/// The offset to add to a VirtIO device ID to get the corresponding PCI device ID.
25const 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
35/// The offset of the bar field within `virtio_pci_cap`.
36const CAP_BAR_OFFSET: u8 = 4;
37/// The offset of the offset field with `virtio_pci_cap`.
38const CAP_BAR_OFFSET_OFFSET: u8 = 8;
39/// The offset of the `length` field within `virtio_pci_cap`.
40const CAP_LENGTH_OFFSET: u8 = 12;
41/// The offset of the`notify_off_multiplier` field within `virtio_pci_notify_cap`.
42const CAP_NOTIFY_OFF_MULTIPLIER_OFFSET: u8 = 16;
43
44/// Common configuration.
45const VIRTIO_PCI_CAP_COMMON_CFG: u8 = 1;
46/// Notifications.
47const VIRTIO_PCI_CAP_NOTIFY_CFG: u8 = 2;
48/// ISR Status.
49const VIRTIO_PCI_CAP_ISR_CFG: u8 = 3;
50/// Device specific configuration.
51const 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
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.
69pub 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)]
83pub 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
98impl 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.
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
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        // 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
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
234    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
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
257    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
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
272    fn set_guest_page_size(&mut self, _guest_page_size: u32) {
273        // No-op, the PCI transport doesn't care.
274    }
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        // 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
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
305    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
314    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
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.
345unsafe 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.
349unsafe impl Sync for PciTransport {}
350
351impl Drop for PciTransport {
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)]
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/// Information about a VirtIO structure within some BAR, as provided by a `virtio_pci_cap`.
381#[derive(Clone, Debug, Eq, PartialEq)]
382struct 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
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    // 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
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/// An error encountered initialising a VirtIO PCI transport.
434#[derive(Clone, Debug, Eq, PartialEq)]
435pub 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
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
509// SAFETY: The `vaddr` field of `VirtioPciError::Misaligned` is only used for debug output.
510unsafe impl Send for VirtioPciError {}
511
512// SAFETY: The `vaddr` field of `VirtioPciError::Misaligned` is only used for debug output.
513unsafe 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        // Non-VirtIO vendor ID.
555        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        // Invalid device ID.
569        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}