Skip to content

Commit 32562ec

Browse files
bchaliosManciukic
authored andcommitted
pci: add virtio-pci transport implementation
Add a VirtIO PCI transport implementation. When a Firecracker microVM is launched with --enable-pci, we will create all VirtIO devices using the PCI transport layer. Snapshotting of VirtIO PCI devices is not supported and we will add this functionality in later commit. Add a couple of tests that ensure that PCI configuration space is what expected. We read common fields and make sure the BAR we allocate for the VirtIO device is what expected. Signed-off-by: Babis Chalios <[email protected]>
1 parent d736960 commit 32562ec

File tree

11 files changed

+1887
-11
lines changed

11 files changed

+1887
-11
lines changed

Cargo.lock

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/vmm/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ gdb = ["arrayvec", "gdbstub", "gdbstub_arch"]
1717

1818
acpi_tables = { path = "../acpi-tables" }
1919
aes-gcm = { version = "0.10.1", default-features = false, features = ["aes"] }
20+
anyhow = "1.0.98"
2021
arrayvec = { version = "0.7.6", optional = true }
2122
aws-lc-rs = { version = "1.13.1", features = ["bindgen"] }
2223
base64 = "0.22.1"
2324
bincode = { version = "2.0.1", features = ["serde"] }
2425
bitflags = "2.9.1"
26+
byteorder = "1.5.0"
2527
crc64 = "2.0.0"
2628
derive_more = { version = "2.0.1", default-features = false, features = [
2729
"from",

src/vmm/src/builder.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@ pub fn build_microvm_for_boot(
169169

170170
let mut device_manager = DeviceManager::new(event_manager, &vcpus_exit_evt, &vm)?;
171171

172+
let vm = Arc::new(vm);
173+
172174
let entry_point = load_kernel(&boot_config.kernel_file, vm.guest_memory())?;
173175
let initrd = InitrdConfig::from_config(boot_config, vm.guest_memory())?;
174176

@@ -271,7 +273,7 @@ pub fn build_microvm_for_boot(
271273
instance_info: instance_info.clone(),
272274
shutdown_exit_code: None,
273275
kvm,
274-
vm: Arc::new(vm),
276+
vm,
275277
uffd: None,
276278
vcpus_handles: Vec::new(),
277279
vcpus_exit_evt,
@@ -554,7 +556,7 @@ fn setup_pvtime(
554556

555557
fn attach_entropy_device(
556558
device_manager: &mut DeviceManager,
557-
vm: &Vm,
559+
vm: &Arc<Vm>,
558560
cmdline: &mut LoaderKernelCmdline,
559561
entropy_device: &Arc<Mutex<Entropy>>,
560562
event_manager: &mut EventManager,
@@ -571,7 +573,7 @@ fn attach_entropy_device(
571573

572574
fn attach_block_devices<'a, I: Iterator<Item = &'a Arc<Mutex<Block>>> + Debug>(
573575
device_manager: &mut DeviceManager,
574-
vm: &Vm,
576+
vm: &Arc<Vm>,
575577
cmdline: &mut LoaderKernelCmdline,
576578
blocks: I,
577579
event_manager: &mut EventManager,
@@ -600,7 +602,7 @@ fn attach_block_devices<'a, I: Iterator<Item = &'a Arc<Mutex<Block>>> + Debug>(
600602

601603
fn attach_net_devices<'a, I: Iterator<Item = &'a Arc<Mutex<Net>>> + Debug>(
602604
device_manager: &mut DeviceManager,
603-
vm: &Vm,
605+
vm: &Arc<Vm>,
604606
cmdline: &mut LoaderKernelCmdline,
605607
net_devices: I,
606608
event_manager: &mut EventManager,
@@ -616,7 +618,7 @@ fn attach_net_devices<'a, I: Iterator<Item = &'a Arc<Mutex<Net>>> + Debug>(
616618

617619
fn attach_unixsock_vsock_device(
618620
device_manager: &mut DeviceManager,
619-
vm: &Vm,
621+
vm: &Arc<Vm>,
620622
cmdline: &mut LoaderKernelCmdline,
621623
unix_vsock: &Arc<Mutex<Vsock<VsockUnixBackend>>>,
622624
event_manager: &mut EventManager,
@@ -629,7 +631,7 @@ fn attach_unixsock_vsock_device(
629631

630632
fn attach_balloon_device(
631633
device_manager: &mut DeviceManager,
632-
vm: &Vm,
634+
vm: &Arc<Vm>,
633635
cmdline: &mut LoaderKernelCmdline,
634636
balloon: &Arc<Mutex<Balloon>>,
635637
event_manager: &mut EventManager,

src/vmm/src/device_manager/mod.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ pub enum AttachDeviceError {
7979
#[cfg(target_arch = "aarch64")]
8080
/// Error creating serial device: {0}
8181
CreateSerial(#[from] std::io::Error),
82+
/// Error attach PCI device: {0}
83+
PciTransport(#[from] PciManagerError),
8284
}
8385

8486
#[derive(Debug)]
@@ -160,8 +162,10 @@ impl DeviceManager {
160162
})
161163
}
162164

163-
/// Attaches a VirtioDevice device to the device manager and event manager.
164-
pub(crate) fn attach_virtio_device<T: 'static + VirtioDevice + MutEventSubscriber + Debug>(
165+
/// Attaches an MMIO VirtioDevice device to the device manager and event manager.
166+
pub(crate) fn attach_mmio_virtio_device<
167+
T: 'static + VirtioDevice + MutEventSubscriber + Debug,
168+
>(
165169
&mut self,
166170
vm: &Vm,
167171
id: String,
@@ -184,6 +188,25 @@ impl DeviceManager {
184188
Ok(())
185189
}
186190

191+
/// Attaches a VirtioDevice device to the device manager and event manager.
192+
pub(crate) fn attach_virtio_device<T: 'static + VirtioDevice + MutEventSubscriber + Debug>(
193+
&mut self,
194+
vm: &Arc<Vm>,
195+
id: String,
196+
device: Arc<Mutex<T>>,
197+
cmdline: &mut Cmdline,
198+
is_vhost_user: bool,
199+
) -> Result<(), AttachDeviceError> {
200+
if self.pci_devices.pci_segment.is_some() {
201+
self.pci_devices
202+
.attach_pci_virtio_device(vm, &self.resource_allocator, id, device)?;
203+
} else {
204+
self.attach_mmio_virtio_device(vm, id, device, cmdline, is_vhost_user)?;
205+
}
206+
207+
Ok(())
208+
}
209+
187210
/// Attaches a [`BootTimer`] to the VM
188211
pub(crate) fn attach_boot_timer_device(
189212
&mut self,

src/vmm/src/device_manager/pci_mngr.rs

Lines changed: 129 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,29 @@
11
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
use std::sync::Arc;
4+
use std::collections::HashMap;
5+
use std::fmt::Debug;
6+
use std::sync::{Arc, Mutex};
57

8+
use event_manager::MutEventSubscriber;
9+
use log::debug;
10+
use pci::{PciBarRegionType, PciDevice, PciDeviceError, PciRootError};
611
use serde::{Deserialize, Serialize};
712
use vm_device::BusError;
813

9-
use super::resources::ResourceAllocator;
14+
use crate::Vm;
15+
use crate::device_manager::resources::ResourceAllocator;
1016
use crate::devices::pci::PciSegment;
17+
use crate::devices::virtio::device::VirtioDevice;
18+
use crate::devices::virtio::transport::pci::device::{VirtioPciDevice, VirtioPciDeviceError};
19+
use crate::vstate::vm::InterruptError;
1120

1221
#[derive(Debug, Default)]
1322
pub struct PciDevices {
1423
/// PCIe segment of the VMM, if PCI is enabled. We currently support a single PCIe segment.
1524
pub pci_segment: Option<PciSegment>,
25+
/// All VirtIO PCI devices of the system
26+
pub virtio_devices: HashMap<(u32, String), Arc<Mutex<VirtioPciDevice>>>,
1627
}
1728

1829
#[derive(Debug, thiserror::Error, displaydoc::Display)]
@@ -21,6 +32,16 @@ pub enum PciManagerError {
2132
ResourceAllocation(#[from] vm_allocator::Error),
2233
/// Bus error: {0}
2334
Bus(#[from] BusError),
35+
/// PCI root error: {0}
36+
PciRoot(#[from] PciRootError),
37+
/// MSI error: {0}
38+
Msi(#[from] InterruptError),
39+
/// VirtIO PCI device error: {0}
40+
VirtioPciDevice(#[from] VirtioPciDeviceError),
41+
/// PCI device error: {0}
42+
PciDeviceError(#[from] PciDeviceError),
43+
/// KVM error: {0}
44+
Kvm(#[from] vmm_sys_util::errno::Error),
2445
}
2546

2647
impl PciDevices {
@@ -61,6 +82,112 @@ impl PciDevices {
6182

6283
Ok(())
6384
}
85+
86+
fn register_bars_with_bus(
87+
resource_allocator: &ResourceAllocator,
88+
virtio_device: &Arc<Mutex<VirtioPciDevice>>,
89+
) -> Result<(), PciManagerError> {
90+
for bar in &virtio_device.lock().expect("Poisoned lock").bar_regions {
91+
match bar.region_type() {
92+
PciBarRegionType::IoRegion => {
93+
debug!(
94+
"Inserting I/O BAR region: {:#x}:{:#x}",
95+
bar.addr(),
96+
bar.size()
97+
);
98+
#[cfg(target_arch = "x86_64")]
99+
resource_allocator.pio_bus.insert(
100+
virtio_device.clone(),
101+
bar.addr(),
102+
bar.size(),
103+
)?;
104+
#[cfg(target_arch = "aarch64")]
105+
log::error!("pci: We do not support I/O region allocation")
106+
}
107+
PciBarRegionType::Memory32BitRegion | PciBarRegionType::Memory64BitRegion => {
108+
debug!(
109+
"Inserting MMIO BAR region: {:#x}:{:#x}",
110+
bar.addr(),
111+
bar.size()
112+
);
113+
resource_allocator.mmio_bus.insert(
114+
virtio_device.clone(),
115+
bar.addr(),
116+
bar.size(),
117+
)?;
118+
}
119+
}
120+
}
121+
122+
Ok(())
123+
}
124+
125+
pub(crate) fn attach_pci_virtio_device<
126+
T: 'static + VirtioDevice + MutEventSubscriber + Debug,
127+
>(
128+
&mut self,
129+
vm: &Arc<Vm>,
130+
resource_allocator: &ResourceAllocator,
131+
id: String,
132+
device: Arc<Mutex<T>>,
133+
) -> Result<(), PciManagerError> {
134+
// We should only be reaching this point if PCI is enabled
135+
let pci_segment = self.pci_segment.as_ref().unwrap();
136+
let pci_device_bdf = pci_segment.next_device_bdf()?;
137+
debug!("Allocating BDF: {pci_device_bdf:?} for device");
138+
let mem = vm.guest_memory().clone();
139+
140+
// Allocate one MSI vector per queue, plus one for configuration
141+
let msix_num =
142+
u16::try_from(device.lock().expect("Poisoned lock").queues().len() + 1).unwrap();
143+
144+
let msix_vectors = Arc::new(Vm::create_msix_group(
145+
vm.clone(),
146+
resource_allocator,
147+
msix_num,
148+
)?);
149+
150+
// Create the transport
151+
let mut virtio_device =
152+
VirtioPciDevice::new(id.clone(), mem, device, msix_vectors, pci_device_bdf.into())?;
153+
154+
// Allocate bars
155+
let mut mmio32_allocator = resource_allocator
156+
.mmio32_memory
157+
.lock()
158+
.expect("Poisoned lock");
159+
let mut mmio64_allocator = resource_allocator
160+
.mmio64_memory
161+
.lock()
162+
.expect("Poisoned lock");
163+
164+
virtio_device.allocate_bars(&mut mmio32_allocator, &mut mmio64_allocator, None)?;
165+
166+
let virtio_device = Arc::new(Mutex::new(virtio_device));
167+
pci_segment
168+
.pci_bus
169+
.lock()
170+
.expect("Poisoned lock")
171+
.add_device(pci_device_bdf.device() as u32, virtio_device.clone())?;
172+
173+
Self::register_bars_with_bus(resource_allocator, &virtio_device)?;
174+
virtio_device
175+
.lock()
176+
.expect("Poisoned lock")
177+
.register_notification_ioevent(vm)?;
178+
179+
Ok(())
180+
}
181+
182+
/// Gets the specified device.
183+
pub fn get_virtio_device(
184+
&self,
185+
device_type: u32,
186+
device_id: &str,
187+
) -> Option<&Arc<Mutex<VirtioPciDevice>>> {
188+
self.virtio_devices
189+
.get(&(device_type, device_id.to_string()))
190+
}
64191
}
65192

66193
#[derive(Default, Debug, Clone, Serialize, Deserialize)]

src/vmm/src/devices/virtio/device.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ pub trait VirtioDevice: AsAny + Send {
148148

149149
/// Optionally deactivates this device and returns ownership of the guest memory map, interrupt
150150
/// event, and queue events.
151-
fn reset(&mut self) -> Option<(EventFd, Vec<EventFd>)> {
151+
fn reset(&mut self) -> Option<(Arc<dyn VirtioInterrupt>, Vec<EventFd>)> {
152152
None
153153
}
154154

src/vmm/src/devices/virtio/queue.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,19 @@ impl Queue {
669669

670670
new - used_event - Wrapping(1) < new - old
671671
}
672+
673+
/// Resets the Virtio Queue
674+
pub(crate) fn reset(&mut self) {
675+
self.ready = false;
676+
self.size = self.max_size;
677+
self.desc_table_address = GuestAddress(0);
678+
self.avail_ring_address = GuestAddress(0);
679+
self.used_ring_address = GuestAddress(0);
680+
self.next_avail = Wrapping(0);
681+
self.next_used = Wrapping(0);
682+
self.num_added = Wrapping(0);
683+
self.uses_notif_suppression = false;
684+
}
672685
}
673686

674687
#[cfg(kani)]

src/vmm/src/devices/virtio/transport/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ use vmm_sys_util::eventfd::EventFd;
88

99
/// MMIO transport for VirtIO devices
1010
pub mod mmio;
11+
/// PCI transport for VirtIO devices
12+
pub mod pci;
1113

1214
/// Represents the types of interrupts used by VirtIO devices
1315
#[derive(Debug, Clone)]

0 commit comments

Comments
 (0)