Add stub PCI device implementation.

Introduce a very simple stub PCI device that can be added to the bus
at a specified address with given PCI config parameters (vendor,
device, etc.). This is useful for cases where we just require a device
to be present during PCI enumeration.

The case that motivates this is a vfio device passthrough
configuration that passes only selected functions of a given device at
the original addresses, but function 0 is not passed through. Absence
of function 0 would be interpreted in enumeration as the entire device
being absent (in accordance with the specification). Putting a stub
device at function 0 fixes this.

BUG=b:167947780
TEST=New unit test, boot minimal Linux image and verify enumerated PCI device.

Change-Id: Iaedffe1c4ac2ad8f6ff6e00dfeebbbfeb5490551
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3245497
Auto-Submit: Mattias Nissler <mnissler@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
Commit-Queue: Mattias Nissler <mnissler@chromium.org>
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
This commit is contained in:
Mattias Nissler 2021-10-21 12:05:29 +00:00 committed by Commit Bot
parent fce74e6fc2
commit de2c640f17
6 changed files with 218 additions and 5 deletions

View file

@ -49,8 +49,8 @@ pub use self::irqchip::*;
#[cfg(feature = "audio")]
pub use self::pci::{Ac97Backend, Ac97Dev, Ac97Parameters};
pub use self::pci::{
PciAddress, PciBridge, PciConfigIo, PciConfigMmio, PciDevice, PciDeviceError, PciInterruptPin,
PciRoot, PcieRootPort, VfioPciDevice,
PciAddress, PciBridge, PciClassCode, PciConfigIo, PciConfigMmio, PciDevice, PciDeviceError,
PciInterruptPin, PciRoot, PcieRootPort, StubPciDevice, StubPciParameters, VfioPciDevice,
};
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub use self::pit::{Pit, PitError};

View file

@ -17,6 +17,7 @@ mod pci_configuration;
mod pci_device;
mod pci_root;
mod pcie;
mod stub;
mod vfio_pci;
#[cfg(feature = "audio")]
@ -31,6 +32,7 @@ pub use self::pci_device::Error as PciDeviceError;
pub use self::pci_device::PciDevice;
pub use self::pci_root::{PciAddress, PciConfigIo, PciConfigMmio, PciRoot};
pub use self::pcie::{PciBridge, PcieRootPort};
pub use self::stub::{StubPciDevice, StubPciParameters};
pub use self::vfio_pci::VfioPciDevice;
/// PCI has four interrupt pins A->D.

127
devices/src/pci/stub.rs Normal file
View file

@ -0,0 +1,127 @@
// Copyright 2021 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Implements a stub PCI device. This can be used to put a device on the PCI bus that will
//! show up in PCI device enumeration with the configured parameters. The device will otherwise be
//! non-functional, in particular it doesn't have any BARs, IRQs etc. and neither will it handle
//! config register interactions.
//!
//! The motivation for stub PCI devices is the case of multifunction PCI devices getting passed
//! through via VFIO to the guest. Per PCI device enumeration, functions other than 0 will only be
//! scanned if function 0 is present. A stub PCI device is useful in that situation to present
//! something to the guest on function 0.
use base::RawDescriptor;
use resources::{Alloc, SystemAllocator};
use crate::pci::pci_configuration::{
PciBarConfiguration, PciClassCode, PciConfiguration, PciHeaderType, PciProgrammingInterface,
PciSubclass,
};
use crate::pci::pci_device::{PciDevice, Result};
use crate::pci::{PciAddress, PciDeviceError};
pub struct StubPciParameters {
pub address: PciAddress,
pub vendor_id: u16,
pub device_id: u16,
pub class: PciClassCode,
pub subclass: u8,
pub programming_interface: u8,
pub multifunction: bool,
pub subsystem_device_id: u16,
pub subsystem_vendor_id: u16,
pub revision_id: u8,
}
pub struct StubPciDevice {
requested_address: PciAddress,
assigned_address: Option<PciAddress>,
config_regs: PciConfiguration,
}
struct NumericPciSubClass(u8);
impl PciSubclass for NumericPciSubClass {
fn get_register_value(&self) -> u8 {
self.0
}
}
struct NumericPciProgrammingInterface(u8);
impl PciProgrammingInterface for NumericPciProgrammingInterface {
fn get_register_value(&self) -> u8 {
self.0
}
}
impl StubPciDevice {
pub fn new(config: &StubPciParameters) -> StubPciDevice {
let config_regs = PciConfiguration::new(
config.vendor_id,
config.device_id,
config.class,
&NumericPciSubClass(config.subclass),
Some(&NumericPciProgrammingInterface(
config.programming_interface,
)),
PciHeaderType::Device,
config.multifunction,
config.subsystem_device_id,
config.subsystem_vendor_id,
config.revision_id,
);
Self {
requested_address: config.address,
assigned_address: None,
config_regs,
}
}
}
impl PciDevice for StubPciDevice {
fn debug_label(&self) -> String {
"Stub".to_owned()
}
fn allocate_address(&mut self, resources: &mut SystemAllocator) -> Result<PciAddress> {
if self.assigned_address.is_none() {
if resources.reserve_pci(
Alloc::PciBar {
bus: self.requested_address.bus,
dev: self.requested_address.dev,
func: self.requested_address.func,
bar: 0,
},
self.debug_label(),
) {
self.assigned_address = Some(self.requested_address);
}
}
self.assigned_address
.ok_or(PciDeviceError::PciAllocationFailed)
}
fn keep_rds(&self) -> Vec<RawDescriptor> {
Vec::new()
}
fn get_bar_configuration(&self, bar_num: usize) -> Option<PciBarConfiguration> {
self.config_regs.get_bar_configuration(bar_num)
}
fn read_config_register(&self, reg_idx: usize) -> u32 {
self.config_regs.read_reg(reg_idx)
}
fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]) {
(&mut self.config_regs).write_reg(reg_idx, offset, data)
}
fn read_bar(&mut self, _addr: u64, _data: &mut [u8]) {}
fn write_bar(&mut self, _addr: u64, _data: &[u8]) {}
}

View file

@ -30,6 +30,7 @@ use devices::virtio::gpu::GpuParameters;
#[cfg(feature = "audio")]
use devices::Ac97Parameters;
use devices::ProtectionType;
use devices::StubPciParameters;
use libc::{getegid, geteuid};
use vm_control::BatteryType;
@ -384,6 +385,7 @@ pub struct Config {
pub dmi_path: Option<PathBuf>,
pub no_legacy: bool,
pub host_cpu_topology: bool,
pub stub_pci_devices: Vec<StubPciParameters>,
}
impl Default for Config {
@ -479,6 +481,7 @@ impl Default for Config {
dmi_path: None,
no_legacy: false,
host_cpu_topology: false,
stub_pci_devices: Vec::new(),
}
}
}

View file

@ -51,8 +51,8 @@ use devices::Ac97Dev;
use devices::ProtectionType;
use devices::{
self, BusDeviceObj, HostHotPlugKey, IrqChip, IrqEventIndex, KvmKernelIrqChip, PciAddress,
PciDevice, VcpuRunState, VfioContainer, VfioDevice, VfioPciDevice, VfioPlatformDevice,
VirtioPciDevice,
PciDevice, StubPciDevice, VcpuRunState, VfioContainer, VfioDevice, VfioPciDevice,
VfioPlatformDevice, VirtioPciDevice,
};
#[cfg(feature = "usb")]
use devices::{HostBackendDeviceProvider, XhciController};
@ -1767,6 +1767,11 @@ fn create_devices(
}
}
for params in &cfg.stub_pci_devices {
// Stub devices don't need jailing since they don't do anything.
devices.push((Box::new(StubPciDevice::new(params)), None));
}
Ok(devices)
}

View file

@ -7,6 +7,7 @@
pub mod panic_hook;
use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::default::Default;
use std::fs::{File, OpenOptions};
use std::io::{BufRead, BufReader};
@ -39,9 +40,9 @@ use devices::virtio::{
},
vhost::user::device::run_gpu_device,
};
use devices::ProtectionType;
#[cfg(feature = "audio")]
use devices::{Ac97Backend, Ac97Parameters};
use devices::{PciAddress, PciClassCode, ProtectionType, StubPciParameters};
use disk::{self, QcowFile};
#[cfg(feature = "composite-disk")]
use disk::{
@ -876,6 +877,52 @@ fn parse_direct_io_options(s: Option<&str>) -> argument::Result<DirectIoOption>
})
}
fn parse_stub_pci_parameters(s: Option<&str>) -> argument::Result<StubPciParameters> {
let s = s.ok_or(argument::Error::ExpectedValue(String::from(
"stub-pci-device configuration expected",
)))?;
let mut options = argument::parse_key_value_options("stub-pci-device", s, ',');
let addr = options
.next()
.ok_or(argument::Error::ExpectedValue(String::from(
"stub-pci-device: expected device address",
)))?
.key();
let mut params = StubPciParameters {
address: PciAddress::from_string(addr),
vendor_id: 0,
device_id: 0,
class: PciClassCode::Other,
subclass: 0,
programming_interface: 0,
multifunction: false,
subsystem_device_id: 0,
subsystem_vendor_id: 0,
revision_id: 0,
};
for opt in options {
match opt.key() {
"vendor" => params.vendor_id = opt.parse_numeric::<u16>()?,
"device" => params.device_id = opt.parse_numeric::<u16>()?,
"class" => {
let class = opt.parse_numeric::<u32>()?;
params.class = PciClassCode::try_from((class >> 16) as u8)
.map_err(|_| opt.invalid_value_err(String::from("Unknown class code")))?;
params.subclass = (class >> 8) as u8;
params.programming_interface = class as u8;
}
"multifunction" => params.multifunction = opt.parse_or::<bool>(true)?,
"subsystem_vendor" => params.subsystem_vendor_id = opt.parse_numeric::<u16>()?,
"subsystem_device" => params.subsystem_device_id = opt.parse_numeric::<u16>()?,
"revision" => params.revision_id = opt.parse_numeric::<u8>()?,
_ => return Err(opt.invalid_key_err()),
}
}
Ok(params)
}
fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::Result<()> {
match name {
"" => {
@ -1983,6 +2030,9 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
"host-cpu-topology" => {
cfg.host_cpu_topology = true;
}
"stub-pci-device" => {
cfg.stub_pci_devices.push(parse_stub_pci_parameters(value)?);
}
"help" => return Err(argument::Error::PrintHelp),
_ => unreachable!(),
}
@ -2286,6 +2336,15 @@ iommu=on|off - indicates whether to enable virtio IOMMU for this device"),
Argument::flag("no-legacy", "Don't use legacy KBD/RTC devices emulation"),
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
Argument::flag("host-cpu-topology", "Use mirror cpu topology of Host for Guest VM"),
Argument::value("stub-pci-device", "DOMAIN:BUS:DEVICE.FUNCTION[,vendor=NUM][,device=NUM][,class=NUM][,multifunction][,subsystem_vendor=NUM][,subsystem_device=NUM][,revision=NUM]", "Comma-separated key=value pairs for setting up a stub PCI device that just enumerates. The first option in the list must specify a PCI address to claim.
Optional further parameters
vendor=NUM - PCI vendor ID
device=NUM - PCI device ID
class=NUM - PCI class (including class code, subclass, and programming interface)
multifunction - whether to set the multifunction flag
subsystem_vendor=NUM - PCI subsystem vendor ID
subsystem_device=NUM - PCI subsystem device ID
revision=NUM - revision"),
Argument::short_flag('h', "help", "Print help message.")];
let mut cfg = Config::default();
@ -3540,4 +3599,21 @@ mod tests {
fn parse_battery_invaild_type_value() {
parse_battery_options(Some("type=xxx")).expect_err("parse should have failed");
}
#[test]
fn parse_stub_pci() {
let params = parse_stub_pci_parameters(Some("0000:01:02.3,vendor=0xfffe,device=0xfffd,class=0xffc1c2,multifunction=true,subsystem_vendor=0xfffc,subsystem_device=0xfffb,revision=0xa")).unwrap();
assert_eq!(params.address.bus, 1);
assert_eq!(params.address.dev, 2);
assert_eq!(params.address.func, 3);
assert_eq!(params.vendor_id, 0xfffe);
assert_eq!(params.device_id, 0xfffd);
assert_eq!(params.class as u8, PciClassCode::Other as u8);
assert_eq!(params.subclass, 0xc1);
assert_eq!(params.programming_interface, 0xc2);
assert!(params.multifunction);
assert_eq!(params.subsystem_vendor_id, 0xfffc);
assert_eq!(params.subsystem_device_id, 0xfffb);
assert_eq!(params.revision_id, 0xa);
}
}