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}