Skip to main content

virtio_driver/pci/
device.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use core::array;
6use fidl_next_fuchsia_hardware_pci as fidl_pci;
7use fuchsia_async::{OnInterrupt, Task};
8use futures::StreamExt;
9use mmio::region::MmioRegion;
10use mmio::vmo::VmoMemory;
11use std::num::NonZero;
12
13use super::capabilities::VirtioPciCapabilities;
14use super::common_configuration::{VirtioPciCommonConfiguration, *};
15use super::pci_notifications::VirtioPciNotifications;
16use super::pci_queue::VirtioPciQueue;
17use crate::{DeviceStatus, VirtioBufferRef, VirtioFeatureBits, VirtioMemoryRange};
18
19/// Manages a fully initialized virtio device using the PCI transport.
20///
21/// [`VirtioPciDeviceBuilder`] implements virtio initialization and creates
22/// instances.
23pub struct VirtioPciDevice {
24    /// FIDL client for PCI operations.
25    ///
26    /// May be necessary for future extensions, such as suspending.
27    #[expect(dead_code)]
28    pci: fidl_next::Client<fidl_pci::Device>,
29
30    /// BTI that can pin VMOs in device-addressable memory.
31    bti: zx::Bti,
32
33    /// The feature bits negotiated with the virtio device.
34    feature_bits: VirtioFeatureBits,
35
36    #[expect(dead_code)]
37    configuration: VirtioPciCommonConfiguration<MmioRegion<VmoMemory>>,
38
39    notifications: VirtioPciNotifications,
40
41    /// The configured virtqueues for the device.
42    queues: Box<[VirtioPciQueue]>,
43
44    /// Signaled whenever the device issues a queue notification interrupt.
45    interrupt_receiver: futures::channel::mpsc::UnboundedReceiver<()>,
46
47    /// Forwards hardware interrupts to [`Self::interrupt_receiver`].
48    ///
49    /// The driver must keep the task alive to receive interrupts.
50    #[expect(dead_code)]
51    interrupt_task: Task<()>,
52}
53
54impl VirtioPciDevice {
55    /// Returns a BTI that can pin VMOs in device-addressable memory.
56    pub fn bti(&self) -> &zx::Bti {
57        &self.bti
58    }
59
60    /// Returns the feature bits negotiated with the virtio device.
61    pub fn feature_bits(&self) -> VirtioFeatureBits {
62        self.feature_bits
63    }
64
65    /// Submits a buffer to the queue, and waits for the buffer to be returned.
66    pub async fn submit_and_wait_for_buffer(
67        &mut self,
68        queue_index: u16,
69        buffer: &[VirtioMemoryRange],
70    ) -> Result<u32, zx::Status> {
71        if queue_index as usize >= self.queues.len() {
72            return Err(zx::Status::INVALID_ARGS);
73        }
74        let queue = &mut self.queues[queue_index as usize];
75
76        let buffer_ref = VirtioBufferRef::new(buffer);
77
78        // SAFETY: The method keeps the ranges alive until it returns. The
79        // method only returns after the buffers are returned.
80        let submitted_buffer_id = unsafe { queue.submit_buffer(buffer_ref)? };
81
82        self.notifications.trigger(queue.notification_data());
83
84        let written_bytes = loop {
85            if let Some(returned_buffer_info) = queue.take_returned_buffer() {
86                assert!(
87                    returned_buffer_info.submitted_buffer_id == submitted_buffer_id,
88                    "Multiple concurrent buffer submissions not yet supported",
89                );
90                break returned_buffer_info.written_bytes;
91            }
92
93            // Intentionally ignoring the value. The channel conveys that the
94            // device may have returned a buffer, which the loop checks above.
95            let _ = self.interrupt_receiver.next().await;
96        };
97
98        Ok(written_bytes)
99    }
100
101    /// Spawns a task that forwards `interrupt` firings to `interrupt_sender`.
102    ///
103    /// The task acknowledges each interrupt, so the device can issue the next
104    /// one.
105    fn start_interrupt_task(
106        interrupt: zx::Interrupt,
107        interrupt_sender: futures::channel::mpsc::UnboundedSender<()>,
108    ) -> Task<()> {
109        // [`OnInterrupt`] is not Unpin.
110        let mut interrupt_stream = Box::pin(OnInterrupt::new(interrupt));
111
112        Task::local(async move {
113            while let Some(Ok(_interrupt_timestamp)) = interrupt_stream.as_mut().next().await {
114                // Intentionally ignoring the failure. A failure means that the
115                // device was dropped, and this task will stop shortly.
116                let _ = interrupt_sender.unbounded_send(());
117
118                if let Err(status) =
119                    std::pin::Pin::get_ref(interrupt_stream.as_ref()).as_ref().ack()
120                {
121                    log::error!("Failed to acknowledge a queue interrupt: {:?}", status);
122                }
123            }
124        })
125    }
126}
127
128/// The MSI-X vectors used by a virtio device on the PCI transport.
129// @cite(virtio): sec="4.1.5.1.2" title="MSI-X Vector Configuration"
130struct VirtioPciMsixVectors {
131    /// Receives the interrupts issued on each MSI-X vector.
132    pub interrupts: [zx::Interrupt; Self::COUNT],
133}
134
135impl VirtioPciMsixVectors {
136    /// Number of distinct MSI-X vectors used for all device notifications.
137    pub const COUNT: usize = 2;
138
139    /// The vector that the device uses to report configuration changes.
140    pub const CONFIGURATION_CHANGE_VECTOR: u16 = 0;
141
142    /// The vector that the device uses to report returned queue buffers.
143    pub const QUEUE_NOTIFICATION_VECTOR: u16 = 1;
144
145    /// Vector value that disables a device's interrupts.
146    ///
147    /// The device reports that it could not configure a vector by setting the
148    /// vector's register to this value.
149    // @cite(virtio): sec="4.1.5.1.2" title="MSI-X Vector Configuration"
150    // @alias(virtio): theirs="VIRTIO_MSI_NO_VECTOR"
151    pub const INVALID: u16 = 0xffff;
152}
153
154/// Builder pattern instantiation for [`VirtioPciDevice`].
155///
156/// The builder implements virtio initialization. This is a multi-step process
157/// because it mixes logic specific to each virtio device type with logic
158/// applicable to all devices that use the PCI transport.
159pub struct VirtioPciDeviceBuilder {
160    pci: fidl_next::Client<fidl_pci::Device>,
161    bti: zx::Bti,
162    msix_vectors: VirtioPciMsixVectors,
163
164    configuration: VirtioPciCommonConfiguration<MmioRegion<VmoMemory>>,
165    notifications: VirtioPciNotifications,
166
167    /// Emptied by [`take_device_configuration()`].
168    device_configuration: Option<MmioRegion<VmoMemory>>,
169
170    /// Populated by [`read_feature_bits()`].
171    offered_features: Option<VirtioFeatureBits>,
172
173    /// Populated by [`write_accepted_features()`].
174    accepted_features: Option<VirtioFeatureBits>,
175
176    /// Populated by [`initialize_virtqueues()`].
177    queues: Vec<VirtioPciQueue>,
178}
179
180impl VirtioPciDeviceBuilder {
181    /// Returns a builder ready for feature bits negotiation.
182    ///
183    /// See [`offered_features()`] for the next initialization step.
184    pub async fn new(
185        pci: fidl_next::ClientEnd<fidl_pci::Device>,
186    ) -> Result<VirtioPciDeviceBuilder, zx::Status> {
187        // Our implementation follows steps 1-3 and the part of step 4 that
188        // covers reading offered feature bits.
189        // @cite(virtio): sec="3.1" title="Device Initialization"
190
191        let pci = pci.spawn();
192        let bti = Self::get_pci_bti(&pci).await?;
193        let msix_vectors = Self::get_pci_interrupts(&pci).await?;
194        let capabilities = VirtioPciCapabilities::new(&pci).await?;
195
196        let mut builder = Self {
197            pci: pci.clone(),
198            bti,
199            msix_vectors,
200
201            configuration: capabilities.common_configuration,
202            notifications: capabilities.notifications,
203            device_configuration: capabilities.device_configuration,
204
205            offered_features: None,
206            accepted_features: None,
207            queues: Vec::new(),
208        };
209
210        builder.reset_virtio();
211        builder.read_offered_features();
212        Ok(builder)
213    }
214
215    /// Returns the features offered by the device.
216    ///
217    /// Use the result to compute device type-specific accepted features, and
218    /// then call [`accept_features()`].
219    pub fn offered_features(&self) -> VirtioFeatureBits {
220        // `unwrap()` will not panic because [`new()`] calls
221        // [`read_offered_features()`].
222        self.offered_features.unwrap()
223    }
224
225    /// Leaves the virtio device ready for device type-specific initialization.
226    ///
227    /// `accepted_features` must be a subset of the bits returned by
228    /// [`offered_features()`].
229    ///
230    /// After this method succeeds, perform device-specific initialization and
231    /// then call [`build()`].
232    ///
233    /// The directions above entail that the method completes feature
234    /// negotiation, performs transport-specific initialization, and
235    /// automatically configures all discovered virtqueues.
236    ///
237    /// On failure, sets [`DeviceStatus::driver_terminated`] to true, signaling
238    /// that the driver will abandon this device.
239    pub async fn accept_features(
240        &mut self,
241        mut accepted_features: VirtioFeatureBits,
242    ) -> Result<(), zx::Status> {
243        // Our implementation follows steps 5-6, the part of step 4 that covers
244        // writing accepted feature bits, and the part of step 7 that does
245        // per-bus setup and virtqueue discovery and configuration.
246        // @cite(virtio): sec="3.1" title="Device Initialization"
247
248        // `unwrap()` will not panic because [`new()`] calls
249        // [`read_offered_features()`].
250        let offered_features = self.offered_features.unwrap();
251
252        if !offered_features.uses_virtio1_standard() {
253            log::warn!("Refusing to operate device without virtio 1.0+ standard support");
254            self.set_driver_terminated();
255            return Err(zx::Status::NOT_SUPPORTED);
256        }
257        accepted_features.set_uses_virtio1_standard(true);
258
259        self.write_accepted_features(accepted_features)?;
260        self.initialize_transport()?;
261        self.initialize_virtqueues()?;
262
263        Ok(())
264    }
265
266    /// Returns a BTI that can pin VMOs in device-addressable memory.
267    ///
268    /// Intended to be used during device type-specific initialization.
269    pub fn bti(&self) -> &zx::Bti {
270        &self.bti
271    }
272
273    /// Returns the memory area that holds device-specific configuration.
274    ///
275    /// The device must present at least one
276    /// [`PciCapabilityType::DEVICE_CONFIGURATION`] capability for any device
277    /// type that has a device type-specific configuration.
278    ///
279    /// Returns [`None`] if the device does not expose any device-specific
280    /// configuration, or if the method is called more than once.
281    // @cite(virtio): sec="2.5" title="Device Configuration Space"
282    // @cite(virtio): sec="4.1.4.6" title="Device-specific configuration"
283    pub fn take_device_configuration(&mut self) -> Option<MmioRegion<VmoMemory>> {
284        self.device_configuration.take()
285    }
286
287    /// Consumes the builder and produces an initialized device.
288    ///
289    /// Must be called after device-specific initialization is completed, which
290    /// must follow a successful call to [`accept_features()`].
291    ///
292    /// On failure, sets [`DeviceStatus::driver_terminated`] to true, signaling
293    /// that the driver will abandon this device.
294    pub fn build(mut self) -> Result<VirtioPciDevice, zx::Status> {
295        // Step 8 in the specification.
296        // @cite(virtio): sec="3.1" title="Device Initialization"
297        self.finish_virtio_initialization()?;
298
299        let (interrupt_sender, interrupt_receiver) = futures::channel::mpsc::unbounded();
300
301        // The configuration change vector is not used yet.
302        // TODO(https://fxbug.dev/504722357): Report configuration changes to
303        // the device type-specific driver code.
304        let [_configuration_change_interrupt, queue_notification_interrupt] =
305            self.msix_vectors.interrupts;
306        let interrupt_task =
307            VirtioPciDevice::start_interrupt_task(queue_notification_interrupt, interrupt_sender);
308
309        Ok(VirtioPciDevice {
310            pci: self.pci,
311            bti: self.bti,
312            feature_bits: self.accepted_features.expect("accept_features() not called"),
313
314            configuration: self.configuration,
315            notifications: self.notifications,
316
317            queues: self.queues.into_boxed_slice(),
318
319            interrupt_receiver,
320            interrupt_task,
321        })
322    }
323
324    /// Retrieves a BTI from the PCI device.
325    ///
326    /// The returned BTI is suitable for pinning pages in physical memory
327    /// addressable by the PCI device.
328    async fn get_pci_bti(pci: &fidl_next::Client<fidl_pci::Device>) -> Result<zx::Bti, zx::Status> {
329        /// [`fuchsia.hardware.pci/Device.GetBti()`] argument referencing a BTI
330        /// that produces physical addresses in the PCI device's addressable
331        /// space.
332        const PCI_DEVICE_ADDRESSABLE_BTI_ID: u32 = 0;
333
334        let get_bti_response = pci
335            .get_bti(PCI_DEVICE_ADDRESSABLE_BTI_ID)
336            .await
337            .map_err(|_| zx::Status::INTERNAL)?
338            .map_err(|_| zx::Status::INTERNAL)?;
339        let bti = get_bti_response.bti;
340        debug_assert!(!bti.is_invalid(), "GetBti() returned invalid BTI");
341        Ok(bti)
342    }
343
344    /// Configures the PCI device to issue interrupts using MSI-X vectors.
345    ///
346    /// Returns Zircon receivers for the configured MSI-X vectors.
347    ///
348    /// All error conditions are logged.
349    // @cite(virtio): sec="4.1.5.1.2" title="MSI-X Vector Configuration"
350    async fn get_pci_interrupts(
351        pci: &fidl_next::Client<fidl_pci::Device>,
352    ) -> Result<VirtioPciMsixVectors, zx::Status> {
353        // MSI-X interrupts are signaled via memory writes (MMIO registers in
354        // interrupt controllers). The PCI device must be allowed to be a bus
355        // transaction initiator, so it can issue writes.
356        pci.set_bus_mastering(true)
357            .await
358            .map_err(|error| {
359                log::warn!("Failed to enable PCI bus mastering: {:?}", error);
360                zx::Status::INTERNAL
361            })?
362            .map_err(|status| {
363                log::warn!("PCI bus rejected enabling bus mastering: {:?}", status);
364                zx::Status::INTERNAL
365            })?;
366
367        pci.set_interrupt_mode(fidl_pci::InterruptMode::MsiX, VirtioPciMsixVectors::COUNT as u32)
368            .await
369            .map_err(|error| {
370                log::warn!("Failed to switch the PCI device to MSI-X interrupts: {:?}", error);
371                zx::Status::INTERNAL
372            })?
373            .map_err(|status| {
374                log::warn!("PCI bus rejected MSI-X interrupts: {:?}", status);
375                zx::Status::INTERNAL
376            })?;
377
378        let mut interrupts: [zx::Interrupt; VirtioPciMsixVectors::COUNT] =
379            array::from_fn(|_vector_index| zx::Interrupt::invalid());
380        for (vector_index, interrupt) in interrupts.iter_mut().enumerate() {
381            let map_interrupt_response = pci
382                .map_interrupt(vector_index as u32)
383                .await
384                .map_err(|error| {
385                    log::warn!("Failed to map MSI-X vector {}: {:?}", vector_index, error);
386                    zx::Status::INTERNAL
387                })?
388                .map_err(|status| {
389                    log::warn!(
390                        "PCI bus rejected mapping MSI-X vector {}: {:?}",
391                        vector_index,
392                        status
393                    );
394                    zx::Status::INTERNAL
395                })?;
396            *interrupt = map_interrupt_response.interrupt;
397        }
398
399        Ok(VirtioPciMsixVectors { interrupts })
400    }
401
402    /// Drives a virtio device through reset and early initialization.
403    ///
404    /// Currently, must be called at most once. This precondition can be easily
405    /// relaxed if a driver wants to support resetting the virtio device on
406    /// failures.
407    ///
408    /// Returns when the virtio device is ready to perform feature negotiation.
409    fn reset_virtio(&mut self) {
410        // In the future, we may want to support resetting a virtio device after it encounters
411        // an error. If we go down that path, we should reset [`offered_features`] and
412        // [`accepted_features`] here.
413        debug_assert!(
414            self.offered_features.is_none(),
415            "Reset not currently supported after feature negotiation started"
416        );
417        debug_assert!(
418            self.accepted_features.is_none(),
419            "Reset not currently supported after feature negotiation"
420        );
421
422        // Step 1 in the specification.
423        // @cite(virtio): sec="3.1" title="Device Initialization"
424        let mut device_status_register = self.configuration.device_status_mut();
425        device_status_register.write(DeviceStatusReg(DeviceStatus::RESET.0));
426
427        while device_status_register.read().value() != DeviceStatus::RESET.0 {}
428
429        // Step 2 in the specification.
430        // @cite(virtio): sec="3.1" title="Device Initialization"
431        let mut device_status = DeviceStatus::RESET;
432        device_status.set_virtio_device_detected(true);
433        device_status_register.write(DeviceStatusReg(device_status.0));
434
435        // Step 3 in the specification.
436        // @cite(virtio): sec="3.1" title="Device Initialization"
437        device_status.set_driver_found(true);
438        device_status_register.write(DeviceStatusReg(device_status.0));
439    }
440
441    /// Reads feature bits offered by the virtio device into a field.
442    fn read_offered_features(&mut self) {
443        debug_assert!(self.offered_features.is_none(), "Offered features already read");
444
445        // The read part of step 4 in the specification.
446        // @cite(virtio): sec="3.1" title="Device Initialization"
447        let mut raw_feature_bits: u128 = 0;
448        for word_index in 0..4 {
449            self.configuration
450                .device_features_word_index_mut()
451                .write(DeviceFeaturesWordIndex(word_index));
452            let raw_feature_word: u32 = self.configuration.device_features_word().read().value();
453            raw_feature_bits |= (raw_feature_word as u128) << (word_index * 32);
454        }
455
456        self.offered_features = Some(VirtioFeatureBits(raw_feature_bits));
457    }
458
459    /// Completes virtio initialization that is not specific to a device type.
460    ///
461    /// `feature_bits` are the feature bits accepted by the driver.
462    /// `feature_bits` must be a subset of the feature bits offered by the
463    /// device, which can be obtained by calling [`read_device_features()`].
464    ///
465    /// Must be called at most once during feature negotiation.
466    ///
467    /// Returns when the virtio device has completed feature negotiation.
468    ///
469    /// On failure, sets [`DeviceStatus::driver_terminated`] to true, signaling
470    /// that the driver will abandon this device.
471    fn write_accepted_features(
472        &mut self,
473        feature_bits: VirtioFeatureBits,
474    ) -> Result<(), zx::Status> {
475        debug_assert!(
476            !self.offered_features.is_none(),
477            "Accepted features are not based on offered features"
478        );
479        debug_assert!(self.accepted_features.is_none(), "Accepted features already written");
480        debug_assert!(
481            feature_bits.0 & self.offered_features.unwrap().0 == feature_bits.0,
482            "Accepted features are not a subset of the offered features",
483        );
484
485        let mut device_status = DeviceStatus(self.configuration.device_status().read().value());
486
487        let mut expected_device_status = DeviceStatus::default();
488        expected_device_status.set_virtio_device_detected(true);
489        expected_device_status.set_driver_found(true);
490
491        if expected_device_status != device_status {
492            log::warn!(
493                "Unexpected virtio device status during feature negotiation: {:?}",
494                device_status
495            );
496
497            device_status.set_driver_terminated(true);
498            self.configuration.device_status_mut().write(DeviceStatusReg(device_status.0));
499            return Err(zx::Status::IO);
500        }
501
502        // The write part of step 4 in the specification.
503        // @cite(virtio): sec="3.1" title="Device Initialization"
504        let mut raw_feature_bits: u128 = feature_bits.0;
505        for word_index in 0..4 {
506            let raw_feature_word: u32 = raw_feature_bits as u32;
507            raw_feature_bits >>= 32;
508
509            // Skip over words that wouldn't set any bits.
510            //
511            // This avoids using higher word index values on devices that may
512            // not be able to handle them.
513            if raw_feature_word == 0 {
514                continue;
515            }
516
517            self.configuration
518                .driver_features_word_index_mut()
519                .write(DriverFeaturesWordIndex(word_index));
520            self.configuration
521                .driver_features_word_mut()
522                .write(DriverFeaturesWord(raw_feature_word));
523        }
524
525        // Step 5 in the specification.
526        // @cite(virtio): sec="3.1" title="Device Initialization"
527        device_status.set_feature_negotiation_complete(true);
528        self.configuration.device_status_mut().write(DeviceStatusReg(device_status.0));
529
530        // Step 6 in the specification.
531        // @cite(virtio): sec="3.1" title="Device Initialization"
532        device_status = DeviceStatus(self.configuration.device_status().read().value());
533        if !device_status.feature_negotiation_complete() {
534            log::warn!(
535                "virtio device does not support offered features {:#x} {:?}",
536                feature_bits.0,
537                feature_bits
538            );
539
540            device_status.set_driver_terminated(true);
541            self.configuration.device_status_mut().write(DeviceStatusReg(device_status.0));
542            return Err(zx::Status::IO);
543        }
544
545        self.accepted_features = Some(feature_bits);
546        Ok(())
547    }
548
549    /// Performs transport-specific initialization.
550    ///
551    /// The device must have completed feature negotiation.
552    ///
553    /// On failure, sets [`DeviceStatus::driver_terminated`] to true, signaling
554    /// that the driver will abandon this device.
555    fn initialize_transport(&mut self) -> Result<(), zx::Status> {
556        // The bus-specific part of step 7 in the specification.
557        // @cite(virtio): sec="3.1" title="Device Initialization"
558        // @cite(virtio): sec="4.1.5.1.2" title="MSI-X Vector Configuration"
559        self.configuration.configuration_change_msix_vector_mut().write(
560            ConfigurationChangeMsixVector(VirtioPciMsixVectors::CONFIGURATION_CHANGE_VECTOR),
561        );
562
563        // Devices report that they ran out of MSI-X resources by leaving the
564        // vector register set to the invalid vector value.
565        // @cite(virtio): sec="4.1.5.1.2" title="MSI-X Vector Configuration"
566        if self.configuration.configuration_change_msix_vector().read().0
567            == VirtioPciMsixVectors::INVALID
568        {
569            log::warn!("virtio device rejected the configuration change MSI-X vector");
570            self.set_driver_terminated();
571            return Err(zx::Status::IO);
572        }
573        Ok(())
574    }
575
576    /// Discovers and configures all the device's virtqueues.
577    ///
578    /// The device must have completed PCI transport initialization.
579    ///
580    /// On failure, sets [`DeviceStatus::driver_terminated`] to true, signaling
581    /// that the driver will abandon this device.
582    fn initialize_virtqueues(&mut self) -> Result<(), zx::Status> {
583        debug_assert!(self.queues.is_empty(), "The device's virtqueues are already set up");
584
585        let queue_count = self.configuration.queue_count().read().value();
586
587        for queue_index in 0..queue_count {
588            // @cite(virtio): sec="4.1.4.3" title="Common configuration structure layout"
589            self.configuration
590                .configured_queue_index_mut()
591                .write(ConfiguredQueueIndex(queue_index));
592
593            let queue_capacity_raw = self.configuration.configured_queue_capacity().read().value();
594            let Some(queue_capacity) = NonZero::<u16>::new(queue_capacity_raw) else {
595                log::warn!("virtqueue {} is disabled (capacity set to 0)", queue_index);
596                self.set_driver_terminated();
597                return Err(zx::Status::IO_DATA_LOSS);
598            };
599
600            let notification_offset =
601                self.configuration.configured_queue_notification_offset().read();
602
603            // The ID needs to be read from the configuration area if
604            // [`VirtioFeatureBits::uses_custom_virtqueue_ids`] was negotiated.
605            let queue_id = queue_index;
606
607            let notification_data =
608                self.notifications.data_for_queue(queue_id, notification_offset);
609
610            let (pci_queue, queue_memory_layout) =
611                VirtioPciQueue::new(&self.bti, queue_capacity, notification_data)?;
612
613            // 64-bit fields must be treated and accessed as two 32-bit fields,
614            // with the low 32-bit part followed by the high 32-bit part.
615            // @cite(virtio): sec="4.1.3.1" title="Driver Requirements: PCI Device Layout"
616            // @cite(virtio): sec="4.1.4.3" title="Common configuration structure layout"
617            let desc_addr = queue_memory_layout.descriptor_table_physical_address;
618            self.configuration
619                .configured_queue_descriptor_table_address_low_mut()
620                .write(ConfiguredQueueDescriptorTableAddressLow(desc_addr as u32));
621            self.configuration
622                .configured_queue_descriptor_table_address_high_mut()
623                .write(ConfiguredQueueDescriptorTableAddressHigh((desc_addr >> 32) as u32));
624
625            let driver_addr = queue_memory_layout.submitted_ring_physical_address;
626            self.configuration
627                .configured_queue_driver_area_address_low_mut()
628                .write(ConfiguredQueueDriverAreaAddressLow(driver_addr as u32));
629            self.configuration
630                .configured_queue_driver_area_address_high_mut()
631                .write(ConfiguredQueueDriverAreaAddressHigh((driver_addr >> 32) as u32));
632
633            let device_addr = queue_memory_layout.returned_ring_physical_address;
634            self.configuration
635                .configured_queue_device_area_address_low_mut()
636                .write(ConfiguredQueueDeviceAreaAddressLow(device_addr as u32));
637            self.configuration
638                .configured_queue_device_area_address_high_mut()
639                .write(ConfiguredQueueDeviceAreaAddressHigh((device_addr >> 32) as u32));
640
641            // @cite(virtio): sec="4.1.5.1.2" title="MSI-X Vector Configuration"
642            self.configuration
643                .configured_queue_msix_vector_mut()
644                .write(ConfiguredQueueMsixVector(VirtioPciMsixVectors::QUEUE_NOTIFICATION_VECTOR));
645            if self.configuration.configured_queue_msix_vector().read().0
646                == VirtioPciMsixVectors::INVALID
647            {
648                log::warn!("virtio device rejected the MSI-X vector for queue {}", queue_index);
649                self.set_driver_terminated();
650                return Err(zx::Status::IO);
651            }
652
653            self.configuration.configured_queue_enabled_mut().write(ConfiguredQueueEnabled(1));
654            self.queues.push(pci_queue);
655        }
656
657        Ok(())
658    }
659
660    /// Completes virtio device initialization.
661    ///
662    /// Must be called after device-specific initialization is complete.
663    ///
664    /// On failure, sets [`DeviceStatus::driver_terminated`] to true, signaling
665    /// that the driver will abandon this device.
666    fn finish_virtio_initialization(&mut self) -> Result<(), zx::Status> {
667        let mut device_status = DeviceStatus(self.configuration.device_status().read().value());
668
669        let mut expected_device_status = DeviceStatus::default();
670        expected_device_status.set_virtio_device_detected(true);
671        expected_device_status.set_driver_found(true);
672        expected_device_status.set_feature_negotiation_complete(true);
673
674        if expected_device_status != device_status {
675            log::warn!(
676                "Unexpected virtio device status after device-specific initialization: {:?}",
677                device_status
678            );
679
680            device_status.set_driver_terminated(true);
681            self.configuration.device_status_mut().write(DeviceStatusReg(device_status.0));
682            return Err(zx::Status::IO);
683        }
684
685        // Step 8 in the specification.
686        // @cite(virtio): sec="3.1" title="Device Initialization"
687        device_status.set_driver_initialized(true);
688        self.configuration.device_status_mut().write(DeviceStatusReg(device_status.0));
689
690        Ok(())
691    }
692
693    /// Signals that the driver will abandon this device.
694    ///
695    /// Sets [`DeviceStatus::driver_terminated`] to true (1).
696    // @cite(virtio): sec="2.1" title="Device Status Field"
697    // @cite(virtio): sec="3.1.1" title="Driver Requirements: Device Initialization"
698    fn set_driver_terminated(&mut self) {
699        let mut device_status = DeviceStatus(self.configuration.device_status().read().value());
700        device_status.set_driver_terminated(true);
701        self.configuration.device_status_mut().write(DeviceStatusReg(device_status.0));
702    }
703}
704
705// TODO(https://fxbug.dev/522425080): Add unit tests for MMIO access sequences.