mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2025-02-10 20:19:07 +00:00
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:
parent
fce74e6fc2
commit
de2c640f17
6 changed files with 218 additions and 5 deletions
|
@ -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};
|
||||
|
|
|
@ -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
127
devices/src/pci/stub.rs
Normal 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]) {}
|
||||
}
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
78
src/main.rs
78
src/main.rs
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue