virtio/transport/
pci.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! PCI transport for virtio devices
5
6use self::capabilities::*;
7use crate::QUEUE_MAX_SIZE;
8use crate::QueueResources;
9use crate::Resources;
10use crate::VirtioDevice;
11use crate::VirtioDoorbells;
12use crate::queue::QueueParams;
13use crate::spec::pci::*;
14use crate::spec::*;
15use chipset_device::ChipsetDevice;
16use chipset_device::io::IoResult;
17use chipset_device::mmio::MmioIntercept;
18use chipset_device::mmio::RegisterMmioIntercept;
19use chipset_device::pci::PciConfigSpace;
20use device_emulators::ReadWriteRequestType;
21use device_emulators::read_as_u32_chunks;
22use device_emulators::write_as_u32_chunks;
23use guestmem::DoorbellRegistration;
24use guestmem::MappedMemoryRegion;
25use guestmem::MemoryMapper;
26use inspect::InspectMut;
27use parking_lot::Mutex;
28use pci_core::PciInterruptPin;
29use pci_core::capabilities::PciCapability;
30use pci_core::capabilities::ReadOnlyCapability;
31use pci_core::capabilities::msix::MsixEmulator;
32use pci_core::cfg_space_emu::BarMemoryKind;
33use pci_core::cfg_space_emu::ConfigSpaceType0Emulator;
34use pci_core::cfg_space_emu::DeviceBars;
35use pci_core::cfg_space_emu::IntxInterrupt;
36use pci_core::msi::RegisterMsi;
37use pci_core::spec::hwid::ClassCode;
38use pci_core::spec::hwid::HardwareIds;
39use pci_core::spec::hwid::ProgrammingInterface;
40use pci_core::spec::hwid::Subclass;
41use std::io;
42use std::sync::Arc;
43use vmcore::device_state::ChangeDeviceState;
44use vmcore::interrupt::Interrupt;
45use vmcore::line_interrupt::LineInterrupt;
46use vmcore::save_restore::NoSavedState;
47use vmcore::save_restore::RestoreError;
48use vmcore::save_restore::SaveError;
49use vmcore::save_restore::SaveRestore;
50
51/// What kind of PCI interrupts [`VirtioPciDevice`] should use.
52pub enum PciInterruptModel<'a> {
53    Msix(&'a mut dyn RegisterMsi),
54    IntX(PciInterruptPin, LineInterrupt),
55}
56
57enum InterruptKind {
58    Msix(MsixEmulator),
59    IntX(Arc<IntxInterrupt>),
60}
61
62/// Run a virtio device over PCI
63#[derive(InspectMut)]
64pub struct VirtioPciDevice {
65    #[inspect(skip)]
66    device: Box<dyn VirtioDevice>,
67    #[inspect(skip)]
68    device_feature: [u32; 2],
69    #[inspect(hex)]
70    device_feature_select: u32,
71    #[inspect(skip)]
72    driver_feature: [u32; 2],
73    #[inspect(hex)]
74    driver_feature_select: u32,
75    msix_config_vector: u16,
76    queue_select: u32,
77    #[inspect(skip)]
78    events: Vec<pal_event::Event>,
79    #[inspect(skip)]
80    queues: Vec<QueueParams>,
81    #[inspect(skip)]
82    msix_vectors: Vec<u16>,
83    #[inspect(skip)]
84    interrupt_status: Arc<Mutex<u32>>,
85    #[inspect(hex)]
86    device_status: u32,
87    config_generation: u32,
88    config_space: ConfigSpaceType0Emulator,
89
90    #[inspect(skip)]
91    interrupt_kind: InterruptKind,
92    #[inspect(skip)]
93    doorbells: VirtioDoorbells,
94    #[inspect(skip)]
95    shared_memory_region: Option<Arc<dyn MappedMemoryRegion>>,
96    #[inspect(hex)]
97    shared_memory_size: u64,
98}
99
100impl VirtioPciDevice {
101    pub fn new(
102        device: Box<dyn VirtioDevice>,
103        mut interrupt_model: PciInterruptModel<'_>,
104        doorbell_registration: Option<Arc<dyn DoorbellRegistration>>,
105        mmio_registration: &mut dyn RegisterMmioIntercept,
106        shared_mem_mapper: Option<&dyn MemoryMapper>,
107    ) -> io::Result<Self> {
108        let traits = device.traits();
109        let queues = (0..traits.max_queues)
110            .map(|_| QueueParams {
111                size: QUEUE_MAX_SIZE,
112                ..Default::default()
113            })
114            .collect();
115        let events = (0..traits.max_queues)
116            .map(|_| pal_event::Event::new())
117            .collect();
118        let msix_vectors = vec![0; traits.max_queues.into()];
119
120        let hardware_ids = HardwareIds {
121            vendor_id: VIRTIO_VENDOR_ID,
122            device_id: VIRTIO_PCI_DEVICE_ID_BASE + traits.device_id,
123            revision_id: 1,
124            prog_if: ProgrammingInterface::NONE,
125            base_class: ClassCode::BASE_SYSTEM_PERIPHERAL,
126            sub_class: Subclass::BASE_SYSTEM_PERIPHERAL_OTHER,
127            type0_sub_vendor_id: VIRTIO_VENDOR_ID,
128            type0_sub_system_id: 0x40,
129        };
130
131        let mut caps: Vec<Box<dyn PciCapability>> = vec![
132            Box::new(ReadOnlyCapability::new(
133                "virtio-common",
134                VirtioCapability::new(VIRTIO_PCI_CAP_COMMON_CFG, 0, 0, 0, 56),
135            )),
136            Box::new(ReadOnlyCapability::new(
137                "virtio-notify",
138                VirtioNotifyCapability::new(0, 0, 56, 4),
139            )),
140            Box::new(ReadOnlyCapability::new(
141                "virtio-pci-isr",
142                VirtioCapability::new(VIRTIO_PCI_CAP_ISR_CFG, 0, 0, 60, 4),
143            )),
144            Box::new(ReadOnlyCapability::new(
145                "virtio-pci-device",
146                VirtioCapability::new(
147                    VIRTIO_PCI_CAP_DEVICE_CFG,
148                    0,
149                    0,
150                    64,
151                    traits.device_register_length,
152                ),
153            )),
154        ];
155
156        let mut bars = DeviceBars::new().bar0(
157            0x40 + traits.device_register_length as u64,
158            BarMemoryKind::Intercept(
159                mmio_registration
160                    .new_io_region("config", 0x40 + traits.device_register_length as u64),
161            ),
162        );
163
164        let msix: Option<MsixEmulator> = if let PciInterruptModel::Msix(register_msi) =
165            &mut interrupt_model
166        {
167            let (msix, msix_capability) = MsixEmulator::new(2, 64, *register_msi);
168            // setting msix as the first cap so that we don't have to update unit tests
169            // i.e: there's no reason why this can't be a .push() instead of .insert()
170            caps.insert(0, Box::new(msix_capability));
171            bars = bars.bar2(
172                msix.bar_len(),
173                BarMemoryKind::Intercept(mmio_registration.new_io_region("msix", msix.bar_len())),
174            );
175            Some(msix)
176        } else {
177            None
178        };
179
180        let shared_memory_size = traits.shared_memory.size;
181        let mut shared_memory_region = None;
182        if shared_memory_size > 0 {
183            let (control, region) = shared_mem_mapper
184                .expect("must provide mapper for shmem")
185                .new_region(
186                    shared_memory_size.try_into().expect("region too big"),
187                    "virtio-pci-shmem".into(),
188                )?;
189
190            caps.push(Box::new(ReadOnlyCapability::new(
191                "virtio-pci-shm",
192                VirtioCapability64::new(
193                    VIRTIO_PCI_CAP_SHARED_MEMORY_CFG,
194                    4, // BAR 4
195                    traits.shared_memory.id,
196                    0,
197                    shared_memory_size,
198                ),
199            )));
200
201            bars = bars.bar4(shared_memory_size, BarMemoryKind::SharedMem(control));
202            shared_memory_region = Some(region);
203        }
204
205        let mut config_space = ConfigSpaceType0Emulator::new(hardware_ids, caps, bars);
206        let interrupt_kind = match interrupt_model {
207            PciInterruptModel::Msix(_) => InterruptKind::Msix(msix.unwrap()),
208            PciInterruptModel::IntX(pin, line) => {
209                InterruptKind::IntX(config_space.set_interrupt_pin(pin, line))
210            }
211        };
212
213        Ok(VirtioPciDevice {
214            device,
215            device_feature: [
216                (traits.device_features & 0xffffffff) as u32
217                    | VIRTIO_F_RING_EVENT_IDX
218                    | VIRTIO_F_RING_INDIRECT_DESC,
219                (traits.device_features >> 32) as u32 | VIRTIO_F_VERSION_1,
220            ],
221            device_feature_select: 0,
222            driver_feature: [0; 2],
223            driver_feature_select: 0,
224            msix_config_vector: 0,
225            queue_select: 0,
226            events,
227            queues,
228            msix_vectors,
229            interrupt_status: Arc::new(Mutex::new(0)),
230            device_status: 0,
231            config_generation: 0,
232            interrupt_kind,
233            config_space,
234            doorbells: VirtioDoorbells::new(doorbell_registration),
235            shared_memory_region,
236            shared_memory_size,
237        })
238    }
239
240    fn update_config_generation(&mut self) {
241        self.config_generation = self.config_generation.wrapping_add(1);
242        if self.device_status & VIRTIO_DRIVER_OK != 0 {
243            *self.interrupt_status.lock() |= 2;
244            match &self.interrupt_kind {
245                InterruptKind::Msix(msix) => {
246                    if let Some(interrupt) = msix.interrupt(self.msix_config_vector) {
247                        interrupt.deliver();
248                    }
249                }
250                InterruptKind::IntX(line) => line.set_level(true),
251            }
252        }
253    }
254
255    fn read_u32(&mut self, offset: u16) -> u32 {
256        assert!(offset & 3 == 0);
257        let queue_select = self.queue_select as usize;
258        match offset {
259            // Device feature bank index
260            0 => self.device_feature_select,
261            // Device feature bank
262            4 => {
263                let feature_select = self.device_feature_select as usize;
264                if feature_select < self.device_feature.len() {
265                    self.device_feature[feature_select]
266                } else {
267                    0
268                }
269            }
270            // Driver feature bank index
271            8 => self.driver_feature_select,
272            // Driver feature bank
273            12 => {
274                let feature_select = self.driver_feature_select as usize;
275                if feature_select < self.driver_feature.len() {
276                    self.driver_feature[feature_select]
277                } else {
278                    0
279                }
280            }
281            16 => (self.queues.len() as u32) << 16 | self.msix_config_vector as u32,
282            20 => self.queue_select << 24 | self.config_generation << 8 | self.device_status,
283            24 => {
284                let size = if queue_select < self.queues.len() {
285                    self.queues[queue_select].size
286                } else {
287                    0
288                };
289                let msix_vector = self.msix_vectors.get(queue_select).copied().unwrap_or(0);
290                (msix_vector as u32) << 16 | size as u32
291            }
292            // Current queue enabled
293            28 => {
294                let enable = if queue_select < self.queues.len() {
295                    if self.queues[queue_select].enable {
296                        1
297                    } else {
298                        0
299                    }
300                } else {
301                    0
302                };
303                #[expect(clippy::if_same_then_else)] // fix when TODO is resolved
304                let notify_offset = if queue_select < self.queues.len() {
305                    0 // TODO: when should this be non-zero? ever?
306                } else {
307                    0
308                };
309                (notify_offset as u32) << 16 | enable as u32
310            }
311            // Queue descriptor table address (low part)
312            32 => {
313                if queue_select < self.queues.len() {
314                    self.queues[queue_select].desc_addr as u32
315                } else {
316                    0
317                }
318            }
319            // Queue descriptor table address (high part)
320            36 => {
321                if queue_select < self.queues.len() {
322                    (self.queues[queue_select].desc_addr >> 32) as u32
323                } else {
324                    0
325                }
326            }
327            // Queue descriptor available ring address (low part)
328            40 => {
329                if queue_select < self.queues.len() {
330                    self.queues[queue_select].avail_addr as u32
331                } else {
332                    0
333                }
334            }
335            // Queue descriptor available ring address (high part)
336            44 => {
337                if queue_select < self.queues.len() {
338                    (self.queues[queue_select].avail_addr >> 32) as u32
339                } else {
340                    0
341                }
342            }
343            // Queue descriptor used ring address (low part)
344            48 => {
345                if queue_select < self.queues.len() {
346                    self.queues[queue_select].used_addr as u32
347                } else {
348                    0
349                }
350            }
351            // Queue descriptor used ring address (high part)
352            52 => {
353                if queue_select < self.queues.len() {
354                    (self.queues[queue_select].used_addr >> 32) as u32
355                } else {
356                    0
357                }
358            }
359            56 => 0, // queue notification register
360            // ISR
361            60 => {
362                let mut interrupt_status = self.interrupt_status.lock();
363                let status = *interrupt_status;
364                *interrupt_status = 0;
365                if let InterruptKind::IntX(line) = &self.interrupt_kind {
366                    line.set_level(false)
367                }
368                status
369            }
370            offset if offset >= 64 => self.device.read_registers_u32(offset - 64),
371            _ => {
372                tracing::warn!(offset, "unknown bar read");
373                0xffffffff
374            }
375        }
376    }
377
378    fn write_u32(&mut self, address: u64, offset: u16, val: u32) {
379        assert!(offset & 3 == 0);
380        let queues_locked = self.device_status & VIRTIO_DRIVER_OK != 0;
381        let features_locked = queues_locked || self.device_status & VIRTIO_FEATURES_OK != 0;
382        let queue_select = self.queue_select as usize;
383        match offset {
384            // Device feature bank index
385            0 => self.device_feature_select = val,
386            // Driver feature bank index
387            8 => self.driver_feature_select = val,
388            // Driver feature bank
389            12 => {
390                let bank = self.driver_feature_select as usize;
391                if !features_locked && bank < self.driver_feature.len() {
392                    self.driver_feature[bank] = val & self.device_feature[bank];
393                }
394            }
395            16 => self.msix_config_vector = val as u16,
396            // Device status
397            20 => {
398                self.queue_select = val >> 16;
399                let val = val & 0xff;
400                if val == 0 {
401                    let started = (self.device_status & VIRTIO_DRIVER_OK) != 0;
402                    self.device_status = 0;
403                    self.config_generation = 0;
404                    if started {
405                        self.doorbells.clear();
406                        self.device.disable();
407                    }
408                    *self.interrupt_status.lock() = 0;
409                }
410
411                self.device_status |= val & (VIRTIO_ACKNOWLEDGE | VIRTIO_DRIVER | VIRTIO_FAILED);
412
413                if self.device_status & VIRTIO_FEATURES_OK == 0 && val & VIRTIO_FEATURES_OK != 0 {
414                    self.device_status |= VIRTIO_FEATURES_OK;
415                    self.update_config_generation();
416                }
417
418                if self.device_status & VIRTIO_DRIVER_OK == 0 && val & VIRTIO_DRIVER_OK != 0 {
419                    let features =
420                        ((self.driver_feature[1] as u64) << 32) | self.driver_feature[0] as u64;
421
422                    let notification_address = (address & !0xfff) + 56;
423                    for i in 0..self.events.len() {
424                        self.doorbells.add(
425                            notification_address,
426                            Some(i as u64),
427                            Some(2),
428                            &self.events[i],
429                        );
430                    }
431                    let queues = self
432                        .queues
433                        .iter()
434                        .zip(self.msix_vectors.iter().copied())
435                        .zip(self.events.iter().cloned())
436                        .map(|((queue, vector), event)| {
437                            let notify = match &self.interrupt_kind {
438                                InterruptKind::Msix(msix) => {
439                                    if let Some(interrupt) = msix.interrupt(vector) {
440                                        interrupt
441                                    } else {
442                                        tracing::warn!(vector, "invalid MSIx vector specified");
443                                        Interrupt::null()
444                                    }
445                                }
446                                InterruptKind::IntX(line) => {
447                                    let interrupt_status = self.interrupt_status.clone();
448                                    let line = line.clone();
449                                    Interrupt::from_fn(move || {
450                                        *interrupt_status.lock() |= 1;
451                                        line.set_level(true);
452                                    })
453                                }
454                            };
455
456                            QueueResources {
457                                params: *queue,
458                                notify,
459                                event,
460                            }
461                        })
462                        .collect();
463
464                    self.device.enable(Resources {
465                        features,
466                        queues,
467                        shared_memory_region: self.shared_memory_region.clone(),
468                        shared_memory_size: self.shared_memory_size,
469                    });
470
471                    self.device_status |= VIRTIO_DRIVER_OK;
472                    self.update_config_generation();
473                }
474            }
475            // Queue current size
476            24 => {
477                let msix_vector = (val >> 16) as u16;
478                if !queues_locked && queue_select < self.queues.len() {
479                    let val = val as u16;
480                    let queue = &mut self.queues[queue_select];
481                    if val > QUEUE_MAX_SIZE {
482                        queue.size = QUEUE_MAX_SIZE;
483                    } else {
484                        queue.size = val;
485                    }
486                    self.msix_vectors[queue_select] = msix_vector;
487                }
488            }
489            // Current queue enabled
490            28 => {
491                let val = val & 0xffff;
492                if !queues_locked && queue_select < self.queues.len() {
493                    let queue = &mut self.queues[queue_select];
494                    queue.enable = val != 0;
495                }
496            }
497            // Queue descriptor table address (low part)
498            32 => {
499                if !queues_locked && queue_select < self.queues.len() {
500                    let queue = &mut self.queues[queue_select];
501                    queue.desc_addr = queue.desc_addr & 0xffffffff00000000 | val as u64;
502                }
503            }
504            // Queue descriptor table address (high part)
505            36 => {
506                if !queues_locked && queue_select < self.queues.len() {
507                    let queue = &mut self.queues[queue_select];
508                    queue.desc_addr = (val as u64) << 32 | queue.desc_addr & 0xffffffff;
509                }
510            }
511            // Queue descriptor available ring address (low part)
512            40 => {
513                if !queues_locked && queue_select < self.queues.len() {
514                    let queue = &mut self.queues[queue_select];
515                    queue.avail_addr = queue.avail_addr & 0xffffffff00000000 | val as u64;
516                }
517            }
518            // Queue descriptor available ring address (high part)
519            44 => {
520                if !queues_locked && queue_select < self.queues.len() {
521                    let queue = &mut self.queues[queue_select];
522                    queue.avail_addr = (val as u64) << 32 | queue.avail_addr & 0xffffffff;
523                }
524            }
525            // Queue descriptor used ring address (low part)
526            48 => {
527                if !queues_locked && (queue_select) < self.queues.len() {
528                    let queue = &mut self.queues[queue_select];
529                    queue.used_addr = queue.used_addr & 0xffffffff00000000 | val as u64;
530                }
531            }
532            // Queue descriptor used ring address (high part)
533            52 => {
534                if !queues_locked && queue_select < self.queues.len() {
535                    let queue = &mut self.queues[queue_select];
536                    queue.used_addr = (val as u64) << 32 | queue.used_addr & 0xffffffff;
537                }
538            }
539            // Queue notification register
540            56 => {
541                if (val as usize) < self.events.len() {
542                    self.events[val as usize].signal();
543                }
544            }
545            offset if offset >= 64 => self.device.write_registers_u32(offset - 64, val),
546            _ => {
547                tracing::warn!(offset, "unknown bar write at offset");
548            }
549        }
550    }
551}
552
553impl Drop for VirtioPciDevice {
554    fn drop(&mut self) {
555        // TODO conditionalize
556        self.device.disable();
557    }
558}
559
560impl VirtioPciDevice {
561    fn read_bar_u32(&mut self, bar: u8, offset: u16) -> u32 {
562        match bar {
563            0 => self.read_u32(offset),
564            2 => {
565                if let InterruptKind::Msix(msix) = &self.interrupt_kind {
566                    msix.read_u32(offset)
567                } else {
568                    !0
569                }
570            }
571            _ => !0,
572        }
573    }
574
575    fn write_bar_u32(&mut self, address: u64, bar: u8, offset: u16, value: u32) {
576        match bar {
577            0 => self.write_u32(address, offset, value),
578            2 => {
579                if let InterruptKind::Msix(msix) = &mut self.interrupt_kind {
580                    msix.write_u32(offset, value)
581                }
582            }
583            _ => tracing::warn!(bar, offset, "Unknown write"),
584        }
585    }
586}
587
588impl ChangeDeviceState for VirtioPciDevice {
589    fn start(&mut self) {}
590
591    async fn stop(&mut self) {}
592
593    async fn reset(&mut self) {
594        // TODO
595    }
596}
597
598impl ChipsetDevice for VirtioPciDevice {
599    fn supports_mmio(&mut self) -> Option<&mut dyn MmioIntercept> {
600        Some(self)
601    }
602
603    fn supports_pci(&mut self) -> Option<&mut dyn PciConfigSpace> {
604        Some(self)
605    }
606}
607
608impl SaveRestore for VirtioPciDevice {
609    type SavedState = NoSavedState; // TODO
610
611    fn save(&mut self) -> Result<Self::SavedState, SaveError> {
612        Ok(NoSavedState)
613    }
614
615    fn restore(&mut self, NoSavedState: Self::SavedState) -> Result<(), RestoreError> {
616        Ok(())
617    }
618}
619
620impl MmioIntercept for VirtioPciDevice {
621    fn mmio_read(&mut self, address: u64, data: &mut [u8]) -> IoResult {
622        if let Some((bar, offset)) = self.config_space.find_bar(address) {
623            read_as_u32_chunks(offset, data, |offset| self.read_bar_u32(bar, offset))
624        }
625        IoResult::Ok
626    }
627
628    fn mmio_write(&mut self, address: u64, data: &[u8]) -> IoResult {
629        if let Some((bar, offset)) = self.config_space.find_bar(address) {
630            write_as_u32_chunks(offset, data, |offset, request_type| match request_type {
631                ReadWriteRequestType::Write(value) => {
632                    self.write_bar_u32(address, bar, offset, value);
633                    None
634                }
635                ReadWriteRequestType::Read => Some(self.read_bar_u32(bar, offset)),
636            })
637        }
638        IoResult::Ok
639    }
640}
641
642impl PciConfigSpace for VirtioPciDevice {
643    fn pci_cfg_read(&mut self, offset: u16, value: &mut u32) -> IoResult {
644        self.config_space.read_u32(offset, value)
645    }
646
647    fn pci_cfg_write(&mut self, offset: u16, value: u32) -> IoResult {
648        self.config_space.write_u32(offset, value)
649    }
650}
651
652pub(crate) mod capabilities {
653    use crate::spec::pci::VIRTIO_PCI_CAP_NOTIFY_CFG;
654    use pci_core::spec::caps::CapabilityId;
655
656    use zerocopy::Immutable;
657    use zerocopy::IntoBytes;
658    use zerocopy::KnownLayout;
659
660    #[repr(C)]
661    #[derive(Debug, IntoBytes, Immutable, KnownLayout)]
662    pub struct VirtioCapabilityCommon {
663        cap_id: u8,
664        cap_next: u8,
665        len: u8,
666        typ: u8,
667        bar: u8,
668        unique_id: u8,
669        padding: [u8; 2],
670        offset: u32,
671        length: u32,
672    }
673
674    impl VirtioCapabilityCommon {
675        pub fn new(len: u8, typ: u8, bar: u8, unique_id: u8, addr_off: u32, addr_len: u32) -> Self {
676            Self {
677                cap_id: CapabilityId::VENDOR_SPECIFIC.0,
678                cap_next: 0,
679                len,
680                typ,
681                bar,
682                unique_id,
683                padding: [0; 2],
684                offset: addr_off,
685                length: addr_len,
686            }
687        }
688    }
689
690    #[repr(C)]
691    #[derive(Debug, IntoBytes, Immutable, KnownLayout)]
692    pub struct VirtioCapability {
693        common: VirtioCapabilityCommon,
694    }
695
696    impl VirtioCapability {
697        pub fn new(typ: u8, bar: u8, unique_id: u8, addr_off: u32, addr_len: u32) -> Self {
698            Self {
699                common: VirtioCapabilityCommon::new(
700                    size_of::<Self>() as u8,
701                    typ,
702                    bar,
703                    unique_id,
704                    addr_off,
705                    addr_len,
706                ),
707            }
708        }
709    }
710
711    #[repr(C)]
712    #[derive(Debug, IntoBytes, Immutable, KnownLayout)]
713    pub struct VirtioCapability64 {
714        common: VirtioCapabilityCommon,
715        offset_hi: u32,
716        length_hi: u32,
717    }
718
719    impl VirtioCapability64 {
720        pub fn new(typ: u8, bar: u8, unique_id: u8, addr_off: u64, addr_len: u64) -> Self {
721            Self {
722                common: VirtioCapabilityCommon::new(
723                    size_of::<Self>() as u8,
724                    typ,
725                    bar,
726                    unique_id,
727                    addr_off as u32,
728                    addr_len as u32,
729                ),
730                offset_hi: (addr_off >> 32) as u32,
731                length_hi: (addr_len >> 32) as u32,
732            }
733        }
734    }
735
736    #[repr(C)]
737    #[derive(Debug, IntoBytes, Immutable, KnownLayout)]
738    pub struct VirtioNotifyCapability {
739        common: VirtioCapabilityCommon,
740        offset_multiplier: u32,
741    }
742
743    impl VirtioNotifyCapability {
744        pub fn new(offset_multiplier: u32, bar: u8, addr_off: u32, addr_len: u32) -> Self {
745            Self {
746                common: VirtioCapabilityCommon::new(
747                    size_of::<Self>() as u8,
748                    VIRTIO_PCI_CAP_NOTIFY_CFG,
749                    bar,
750                    0,
751                    addr_off,
752                    addr_len,
753                ),
754                offset_multiplier,
755            }
756        }
757    }
758
759    #[cfg(test)]
760    mod tests {
761        use super::*;
762        use pci_core::capabilities::PciCapability;
763        use pci_core::capabilities::ReadOnlyCapability;
764
765        #[test]
766        fn common_check() {
767            let common =
768                ReadOnlyCapability::new("common", VirtioCapability::new(0x13, 2, 0, 0x100, 0x200));
769            assert_eq!(common.read_u32(0), 0x13100009);
770            assert_eq!(common.read_u32(4), 2);
771            assert_eq!(common.read_u32(8), 0x100);
772            assert_eq!(common.read_u32(12), 0x200);
773        }
774
775        #[test]
776        fn notify_check() {
777            let notify = ReadOnlyCapability::new(
778                "notify",
779                VirtioNotifyCapability::new(0x123, 2, 0x100, 0x200),
780            );
781            assert_eq!(notify.read_u32(0), 0x2140009);
782            assert_eq!(notify.read_u32(4), 2);
783            assert_eq!(notify.read_u32(8), 0x100);
784            assert_eq!(notify.read_u32(12), 0x200);
785        }
786    }
787}