virtio_pci_x86/
pci.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2025.
4
5// Suppress clippy warning generated within a tock-registers macro
6#![allow(clippy::modulo_one)]
7
8use core::ptr;
9
10use kernel::utilities::cells::OptionalCell;
11use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable};
12use kernel::utilities::registers::{register_bitfields, register_structs, ReadOnly, ReadWrite};
13
14use pci_x86::cap::{Cap, VendorCap};
15use pci_x86::{Command, Device};
16
17use virtio::devices::{VirtIODeviceDriver, VirtIODeviceType};
18use virtio::queues::Virtqueue;
19use virtio::transports::{VirtIOInitializationError, VirtIOTransport};
20
21/// PCI vendor ID for all Virtio devices
22pub const VENDOR_ID: u16 = 0x1AF4;
23
24/// Base value for PCI device IDs for Virtio devices
25///
26/// Actual device ID of a Virtio device (as reported via PCI configuration space) will be this base
27/// value plus the device type ID as defined in section 5 of the Virtio spec.
28///
29/// For example, the PCI device ID of a Virtio network device (device type 1) would be
30/// `DEVICE_ID_BASE + 1 = 0x1041`.
31pub const DEVICE_ID_BASE: u16 = 0x1040;
32
33/// Enumeration of Virtio configuration structure types
34enum CfgType {
35    Common,
36    Notify,
37    Isr,
38    Device,
39    Pci,
40    SharedMemory,
41    Vendor,
42    Reserved,
43}
44
45/// A vendor-specific PCI capability which provides the location of a
46/// Virtio configuration structure.
47struct VirtioCap<'a>(VendorCap<'a>);
48
49impl VirtioCap<'_> {
50    /// Returns the type of configuration structure described by this
51    /// capability.
52    fn cfg_type(&self) -> CfgType {
53        match self.0.read8(3) {
54            1 => CfgType::Common,
55            2 => CfgType::Notify,
56            3 => CfgType::Isr,
57            4 => CfgType::Device,
58            5 => CfgType::Pci,
59            8 => CfgType::SharedMemory,
60            9 => CfgType::Vendor,
61            _ => CfgType::Reserved,
62        }
63    }
64
65    /// Returns index of the BAR containing the configuration structure.
66    fn bar(&self) -> u8 {
67        self.0.read8(4)
68    }
69
70    /// Returns the offset within the BAR region where the configuration
71    /// structure starts.
72    fn offset(&self) -> u32 {
73        self.0.read32(8)
74    }
75}
76
77register_bitfields![
78    u8,
79
80    /// Bits indicating the initialization status of a virtio device
81    ///
82    /// See virtio spec, section 2.1
83    DeviceStatus [
84        /// Guest OS has recognized this as a valid virtio device
85        ACKNOWLEDGE OFFSET(0) NUMBITS(1) [],
86
87        /// Guest OS has a driver available for this device
88        DRIVER OFFSET(1) NUMBITS(1) [],
89
90        /// Guest driver is loaded and ready to use
91        DRIVER_OK OFFSET(2) NUMBITS(1) [],
92
93        /// Guest OS has completed feature negotiation
94        FEATURES_OK OFFSET(3) NUMBITS(1) [],
95
96        /// Device has experienced an unrecoverable error
97        DEVICE_NEEDS_RESET OFFSET(6) NUMBITS(1) [],
98
99        /// Used by guest to indicate an unrecoverable error
100        FAILED OFFSET(7) NUMBITS(1) [],
101    ],
102
103    /// Virtio ISR status register
104    ///
105    /// See virtio spec, section 4.1.4.5
106    IsrStatus [
107        /// Indicates a pending virtqueue interrupt
108        QUEUE OFFSET(0) NUMBITS(1) [],
109
110        /// Indicates a device configuration change
111        DEVICE_CFG OFFSET(1) NUMBITS(1) [],
112    ]
113];
114
115register_structs! {
116    /// Virtio common configuration registers, as defined in section 4.1.4.3
117    CommonCfg {
118        //
119        // About the whole device
120        //
121
122        /// Selects feature bits to be offered by the device
123        (0x00 => device_feature_select: ReadWrite<u32>),
124
125        /// Reports feature bits offered by the device
126        (0x04 => device_feature: ReadOnly<u32>),
127
128        /// Selects feature bits accepted by the driver
129        (0x08 => driver_feature_select: ReadWrite<u32>),
130
131        /// Reports feature bits accepted by the driver
132        (0x0C => driver_feature: ReadWrite<u32>),
133
134        /// Selects the MSI-X vector used by the device to report
135        /// configuration changes.
136        (0x10 => config_msix_vector: ReadWrite<u16>),
137
138        /// Maximum number of virtqueues supported by the device (not
139        /// including administration queues).
140        (0x12 => num_queues: ReadOnly<u16>),
141
142        /// Reports device status, as defined in section 2.1
143        (0x14 => device_status: ReadWrite<u8, DeviceStatus::Register>),
144
145        /// Updated by device each time configuration noticeably changes.
146        (0x15 => config_generation: ReadOnly<u8>),
147
148        //
149        // About a specific virtqueue
150        //
151
152        /// Selects which virtqueue the following fields refer to
153        (0x16 => queue_select: ReadWrite<u16>),
154
155        /// Specifies the size of the selected virtqueue.
156        ///
157        /// On reset, specifies the maximum queue size supported by the
158        /// device. A value of 0 means the queue is not available.
159        (0x18 => queue_size: ReadWrite<u16>),
160
161        /// Selects the MSI-X vector used by the device for virtqueue
162        /// notifications.
163        (0x1A => queue_msix_vector: ReadWrite<u16>),
164
165        /// May be used by the driver to inhibit executiton of requests
166        /// from the selected virtqueue.
167        (0x1C => queue_enable: ReadWrite<u16>),
168
169        /// Offset of notification address for the selected virtqueue
170        ///
171        /// See section 4.1.4.4 for details on computing the absolute
172        /// notification address.
173        (0x1E => queue_notify_off: ReadOnly<u16>),
174
175        /// Physical address of descriptor area for the selected virtqueue
176        (0x20 => queue_desc: ReadWrite<u64>),
177
178        /// Physical address of driver area for the selected virtqueue
179        (0x28 => queue_driver: ReadWrite<u64>),
180
181        /// Physical address of device area for the selected virtqueue
182        (0x30 => queue_device: ReadWrite<u64>),
183
184        /// Value to be used by the driver when sending an available buffer
185        /// notification to the device.
186        ///
187        /// Only relevant if the NOTIF_CONFIG_DATA feature bit has been
188        /// negotiated.
189        (0x38 => queue_notif_config_data: ReadOnly<u16>),
190
191        /// Can be used to reset the selected virtqueue
192        (0x3A => queue_reset: ReadWrite<u16>),
193
194        //
195        // About the administration virtqueue
196        //
197
198        /// Index of the first administration virtqueue
199        (0x3C => admin_queue_index: ReadOnly<u16>),
200
201        /// Number of administration virtqueues supported by the device
202        (0x3E => admin_queue_num: ReadOnly<u16>),
203
204        (0x40 => @END),
205    },
206
207    /// Virtio ISR status register, as defined by section 4.1.4.5
208    IsrStatusCfg {
209        (0x00 => isr_status: ReadOnly<u8, IsrStatus::Register>),
210
211        (0x01 => @END),
212    }
213}
214
215// Transport feature bits used here
216const F_VERSION_1: u64 = 1u64 << 32;
217
218pub struct VirtIOPCIDevice {
219    dev: Device,
220    dev_type: VirtIODeviceType,
221    common_cfg: &'static CommonCfg,
222    isr_cfg: &'static IsrStatusCfg,
223    notify_base: usize,
224    notify_off_multiplier: usize,
225    queues: OptionalCell<&'static [&'static dyn Virtqueue]>,
226}
227
228impl VirtIOPCIDevice {
229    /// Construct from a PCI device by parsing virtio-pci capability list.
230    pub fn from_pci_device(dev: Device, dev_type: VirtIODeviceType) -> Option<Self> {
231        let mut common_cfg_ptr = ptr::null_mut::<CommonCfg>();
232        let mut isr_cfg_ptr = ptr::null_mut::<IsrStatusCfg>();
233        let mut notify_base = 0;
234        let mut notify_off_multiplier = 0;
235
236        // Iterate over Virtio capabilities
237        for cap in dev.capabilities() {
238            let Cap::Vendor(cap) = cap else { continue };
239            let cap = VirtioCap(cap);
240
241            // Read capability fields and compute address of the
242            // configuration structure
243            let bar = cap.bar();
244            let base = dev.bar_addr(bar)?;
245            let offset = cap.offset() as usize;
246            let addr = base + offset;
247
248            // Store the pointer, but only the first time we encounter
249            // it (as per the Virtio spec, section 4.1.4.1)
250            match cap.cfg_type() {
251                CfgType::Common => {
252                    if common_cfg_ptr.is_null() {
253                        common_cfg_ptr = addr as *mut CommonCfg;
254                    }
255                }
256
257                CfgType::Isr => {
258                    if isr_cfg_ptr.is_null() {
259                        isr_cfg_ptr = addr as *mut IsrStatusCfg;
260                    }
261                }
262
263                CfgType::Notify => {
264                    notify_base = addr;
265                    // virtio_pci_notify_cap.notify_off_multiplier at offset 16
266                    notify_off_multiplier = cap.0.read32(16) as usize;
267                }
268
269                _ => {}
270            }
271        }
272
273        // Return None if we were not able to find pointers for all the
274        // necessary configuration structures. Otherwise, construct and return
275        // the device object.
276        //
277        // Safety: We assume hardware is providing us with valid pointers.
278        let common_cfg = unsafe { common_cfg_ptr.as_ref() }?;
279        let isr_cfg = unsafe { isr_cfg_ptr.as_ref() }?;
280        if notify_base == 0 || notify_off_multiplier == 0 {
281            return None;
282        }
283
284        Some(Self {
285            dev,
286            dev_type,
287            common_cfg,
288            isr_cfg,
289            notify_base,
290            notify_off_multiplier,
291            queues: OptionalCell::empty(),
292        })
293    }
294
295    /// Helper method to negotiate device features during initialization.
296    fn negotiate_features(
297        &self,
298        driver: &dyn VirtIODeviceDriver,
299    ) -> Result<(), VirtIOInitializationError> {
300        // Read the first 64 feature bits (spec doesn't define anything beyond that)
301        let mut feats_offered = 0u64;
302        self.common_cfg.device_feature_select.set(0);
303        feats_offered |= self.common_cfg.device_feature.get() as u64;
304        self.common_cfg.device_feature_select.set(1);
305        feats_offered |= (self.common_cfg.device_feature.get() as u64) << 32;
306
307        let mut feats_requested = 0u64;
308
309        // Only transport feature we currently support or require is F_VERSION_1
310        if (feats_offered & F_VERSION_1) != 0 {
311            feats_requested |= F_VERSION_1;
312        } else {
313            return Err(VirtIOInitializationError::InvalidVirtIOVersion);
314        }
315
316        // Negotiate device-specific features
317        let drv_feats_offered = feats_offered & 0xFFF;
318        let drv_feats_requested = driver.negotiate_features(drv_feats_offered).ok_or(
319            VirtIOInitializationError::FeatureNegotiationFailed {
320                offered: feats_offered,
321                accepted: None,
322            },
323        )?;
324        feats_requested |= drv_feats_requested & 0xFFF;
325
326        // Write requested features back to device
327        self.common_cfg.driver_feature_select.set(0);
328        self.common_cfg
329            .driver_feature
330            .set((feats_requested & 0xFFFF_FFFF) as u32);
331        self.common_cfg.driver_feature_select.set(1);
332        self.common_cfg
333            .driver_feature
334            .set(((feats_requested >> 32) & 0xFFFF_FFFF) as u32);
335
336        // Lock in requested features and verify that the device accepted them
337        self.common_cfg
338            .device_status
339            .modify(DeviceStatus::FEATURES_OK::SET);
340        let feats_accepted = self
341            .common_cfg
342            .device_status
343            .is_set(DeviceStatus::FEATURES_OK);
344        if !feats_accepted {
345            return Err(VirtIOInitializationError::FeatureNegotiationFailed {
346                offered: feats_offered,
347                accepted: Some(feats_requested),
348            });
349        }
350
351        Ok(())
352    }
353
354    /// Helper method to prepare virtqueues during initialization.
355    fn init_queues(
356        &self,
357        queues: &'static [&'static dyn Virtqueue],
358    ) -> Result<(), VirtIOInitializationError> {
359        for (index, queue) in queues.iter().enumerate() {
360            self.common_cfg.queue_select.set(index as u16);
361
362            // Must be disabled before configuration
363            let enabled = self.common_cfg.queue_enable.get();
364            if enabled != 0 {
365                return Err(VirtIOInitializationError::DeviceError);
366            }
367
368            // Read max queue size; 0 means unavailable
369            let max_size = self.common_cfg.queue_size.get() as usize;
370            if max_size == 0 {
371                return Err(VirtIOInitializationError::VirtqueueNotAvailable(index));
372            }
373
374            let size = queue.negotiate_queue_size(max_size);
375            queue.initialize(index as u32, size);
376
377            // Program selected size
378            self.common_cfg.queue_size.set(size as u16);
379
380            // Set queue addresses
381            let addrs = queue.physical_addresses();
382            self.common_cfg.queue_desc.set(addrs.descriptor_area);
383            self.common_cfg.queue_driver.set(addrs.driver_area);
384            self.common_cfg.queue_device.set(addrs.device_area);
385
386            // Enable queue
387            self.common_cfg.queue_enable.set(1);
388        }
389
390        self.queues.set(queues);
391
392        Ok(())
393    }
394
395    /// Handle interrupt by checking ISR status and dispatching to queues.
396    pub fn handle_interrupt(&self) {
397        // Reading clears
398        let isr = self.isr_cfg.isr_status.extract();
399
400        if isr.is_set(IsrStatus::QUEUE) {
401            self.queues.map(|queues| {
402                for q in queues.iter() {
403                    q.used_interrupt();
404                }
405            });
406        }
407
408        if isr.is_set(IsrStatus::DEVICE_CFG) {
409            // Config change is currently unhandled
410        }
411    }
412}
413
414impl VirtIOTransport for VirtIOPCIDevice {
415    fn initialize(
416        &self,
417        driver: &dyn VirtIODeviceDriver,
418        queues: &'static [&'static dyn Virtqueue],
419    ) -> Result<VirtIODeviceType, VirtIOInitializationError> {
420        // Ensure the given driver matches the device type reported by the PCI device
421        if self.dev_type != driver.device_type() {
422            return Err(VirtIOInitializationError::IncompatibleDriverDeviceType(
423                self.dev_type,
424            ));
425        }
426
427        // Ensure bus properties are configured on the PCI device
428        let mut cmd = self.dev.command();
429        cmd.modify(Command::MEM_SPACE::SET);
430        cmd.modify(Command::BUS_MASTER::SET);
431        self.dev.set_command(cmd);
432
433        //
434        // The following code implements the Virtio initialization sequence as described in section
435        // 3.1.1 of the spec, taking into account the specifics of the PCI transport as described in
436        // section 4.1.5.1.
437        //
438
439        // 1. Reset device
440        self.common_cfg.device_status.set(0);
441
442        // 2. Set ACKNOWLEDGE bit, indicating we have recognized the device
443        self.common_cfg
444            .device_status
445            .modify(DeviceStatus::ACKNOWLEDGE::SET);
446
447        // 3. Set DRIVER bit, indicating we know how to drive the device
448        self.common_cfg
449            .device_status
450            .modify(DeviceStatus::DRIVER::SET);
451
452        let res = (|| {
453            // 4-6. Feature negotiation
454            self.negotiate_features(driver)?;
455
456            // 7. Queue setup
457            self.init_queues(queues)?;
458
459            // Pre init hook
460            driver
461                .pre_device_initialization()
462                .map_err(VirtIOInitializationError::DriverPreInitializationError)?;
463
464            // 8. DRIVER_OK
465            self.common_cfg
466                .device_status
467                .modify(DeviceStatus::DRIVER_OK::SET);
468
469            // Live
470            driver.device_initialized().map_err(|err| {
471                VirtIOInitializationError::DriverInitializationError(self.dev_type, err)
472            })?;
473
474            Ok(self.dev_type)
475        })();
476
477        if res.is_err() {
478            self.common_cfg
479                .device_status
480                .modify(DeviceStatus::FAILED::SET);
481        }
482
483        res
484    }
485
486    fn queue_notify(&self, queue_id: u32) {
487        // When using PCI transport, available notifications are delivered by writing a value into
488        // the memory-mapped "notification area" of the device.
489        //
490        // Section 4.1.4.4 describes how to compute the address to write for a specific virtqueue.
491        //
492        // Section 4.1.5.2 describes the value that should be written. Note that the current PCI
493        // transport implementation in this module does not negotiate either the F_NOTIFICATION_DATA
494        // or F_NOTIF_CONFIG_DATA feature bits, so the value written is simply the 16-bit queue
495        // index.
496
497        self.common_cfg.queue_select.set(queue_id as u16);
498
499        // If queue index isn't valid, return early before attempting to write notification
500        if self.common_cfg.queue_size.get() == 0 {
501            return;
502        }
503
504        // Compute the byte address to write
505        let off = self.common_cfg.queue_notify_off.get() as usize;
506        let notify_addr = self.notify_base + (off * self.notify_off_multiplier);
507
508        // Write notification value to the computed address
509        //
510        // Safety: Address is computed according to virtio spec. We have verified the specified
511        // queue index is valid, and we assume all other values reported by the hardware are valid.
512        let notify_ptr = notify_addr as *mut u16;
513        unsafe {
514            notify_ptr.write_volatile(queue_id as u16);
515        }
516    }
517}