mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2024-12-22 13:45:20 +00:00
pci: Add minimal PCI host emulation crate
This crate is based on the crosvm devices/src/pci implementation from 107edb3e We introduced a few changes: - This one is a standalone crate. The device crate does not carry any PCI specific bits. - Simplified PCI root configuration. We only carry a pointer to a PciConfiguration, not a wrapper around it. - Simplified BAR allocation API. All BARs from the PciDevice instance must be generated at once through the PciDevice.allocate_bars() method. - The PCI BARs are added to the MMIO bus from the PciRoot add_device() method. Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
This commit is contained in:
parent
fa3951df22
commit
e8308dd13b
16
pci/Cargo.toml
Normal file
16
pci/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "pci"
|
||||
version = "0.1.0"
|
||||
authors = ["Samuel Ortiz <sameo@linux.intel.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
byteorder = "*"
|
||||
devices = { path = "../devices" }
|
||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls" }
|
||||
kvm-bindings = "0.1"
|
||||
libc = ">=0.2.39"
|
||||
log = "*"
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
vmm-sys-util = { git = "https://github.com/sameo/vmm-sys-util" }
|
639
pci/src/configuration.rs
Executable file
639
pci/src/configuration.rs
Executable file
@ -0,0 +1,639 @@
|
||||
// Copyright 2018 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.
|
||||
|
||||
use crate::PciInterruptPin;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
// The number of 32bit registers in the config space, 256 bytes.
|
||||
const NUM_CONFIGURATION_REGISTERS: usize = 64;
|
||||
|
||||
const STATUS_REG: usize = 1;
|
||||
const STATUS_REG_CAPABILITIES_USED_MASK: u32 = 0x0010_0000;
|
||||
const BAR0_REG: usize = 4;
|
||||
const BAR_IO_ADDR_MASK: u32 = 0xffff_fffc;
|
||||
const BAR_MEM_ADDR_MASK: u32 = 0xffff_fff0;
|
||||
const NUM_BAR_REGS: usize = 6;
|
||||
const CAPABILITY_LIST_HEAD_OFFSET: usize = 0x34;
|
||||
const FIRST_CAPABILITY_OFFSET: usize = 0x40;
|
||||
const CAPABILITY_MAX_OFFSET: usize = 192;
|
||||
|
||||
const INTERRUPT_LINE_PIN_REG: usize = 15;
|
||||
|
||||
/// Represents the types of PCI headers allowed in the configuration registers.
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum PciHeaderType {
|
||||
Device,
|
||||
Bridge,
|
||||
}
|
||||
|
||||
/// Classes of PCI nodes.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum PciClassCode {
|
||||
TooOld,
|
||||
MassStorage,
|
||||
NetworkController,
|
||||
DisplayController,
|
||||
MultimediaController,
|
||||
MemoryController,
|
||||
BridgeDevice,
|
||||
SimpleCommunicationController,
|
||||
BaseSystemPeripheral,
|
||||
InputDevice,
|
||||
DockingStation,
|
||||
Processor,
|
||||
SerialBusController,
|
||||
WirelessController,
|
||||
IntelligentIoController,
|
||||
EncryptionController,
|
||||
DataAcquisitionSignalProcessing,
|
||||
Other = 0xff,
|
||||
}
|
||||
|
||||
impl PciClassCode {
|
||||
pub fn get_register_value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
/// A PCI sublcass. Each class in `PciClassCode` can specify a unique set of subclasses. This trait
|
||||
/// is implemented by each subclass. It allows use of a trait object to generate configurations.
|
||||
pub trait PciSubclass {
|
||||
/// Convert this subclass to the value used in the PCI specification.
|
||||
fn get_register_value(&self) -> u8;
|
||||
}
|
||||
|
||||
/// Subclasses of the MultimediaController class.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum PciMultimediaSubclass {
|
||||
VideoController = 0x00,
|
||||
AudioController = 0x01,
|
||||
TelephonyDevice = 0x02,
|
||||
AudioDevice = 0x03,
|
||||
Other = 0x80,
|
||||
}
|
||||
|
||||
impl PciSubclass for PciMultimediaSubclass {
|
||||
fn get_register_value(&self) -> u8 {
|
||||
*self as u8
|
||||
}
|
||||
}
|
||||
|
||||
/// Subclasses of the BridgeDevice
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum PciBridgeSubclass {
|
||||
HostBridge = 0x00,
|
||||
IsaBridge = 0x01,
|
||||
EisaBridge = 0x02,
|
||||
McaBridge = 0x03,
|
||||
PciToPciBridge = 0x04,
|
||||
PcmciaBridge = 0x05,
|
||||
NuBusBridge = 0x06,
|
||||
CardBusBridge = 0x07,
|
||||
RACEwayBridge = 0x08,
|
||||
PciToPciSemiTransparentBridge = 0x09,
|
||||
InfiniBrandToPciHostBridge = 0x0a,
|
||||
OtherBridgeDevice = 0x80,
|
||||
}
|
||||
|
||||
impl PciSubclass for PciBridgeSubclass {
|
||||
fn get_register_value(&self) -> u8 {
|
||||
*self as u8
|
||||
}
|
||||
}
|
||||
|
||||
/// Subclass of the SerialBus
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum PciSerialBusSubClass {
|
||||
Firewire = 0x00,
|
||||
ACCESSbus = 0x01,
|
||||
SSA = 0x02,
|
||||
USB = 0x03,
|
||||
}
|
||||
|
||||
impl PciSubclass for PciSerialBusSubClass {
|
||||
fn get_register_value(&self) -> u8 {
|
||||
*self as u8
|
||||
}
|
||||
}
|
||||
|
||||
/// A PCI class programming interface. Each combination of `PciClassCode` and
|
||||
/// `PciSubclass` can specify a set of register-level programming interfaces.
|
||||
/// This trait is implemented by each programming interface.
|
||||
/// It allows use of a trait object to generate configurations.
|
||||
pub trait PciProgrammingInterface {
|
||||
/// Convert this programming interface to the value used in the PCI specification.
|
||||
fn get_register_value(&self) -> u8;
|
||||
}
|
||||
|
||||
/// Types of PCI capabilities.
|
||||
pub enum PciCapabilityID {
|
||||
ListID = 0,
|
||||
PowerManagement = 0x01,
|
||||
AcceleratedGraphicsPort = 0x02,
|
||||
VitalProductData = 0x03,
|
||||
SlotIdentification = 0x04,
|
||||
MessageSignalledInterrupts = 0x05,
|
||||
CompactPCIHotSwap = 0x06,
|
||||
PCIX = 0x07,
|
||||
HyperTransport = 0x08,
|
||||
VendorSpecific = 0x09,
|
||||
Debugport = 0x0A,
|
||||
CompactPCICentralResourceControl = 0x0B,
|
||||
PCIStandardHotPlugController = 0x0C,
|
||||
BridgeSubsystemVendorDeviceID = 0x0D,
|
||||
AGPTargetPCIPCIbridge = 0x0E,
|
||||
SecureDevice = 0x0F,
|
||||
PCIExpress = 0x10,
|
||||
MSIX = 0x11,
|
||||
SATADataIndexConf = 0x12,
|
||||
PCIAdvancedFeatures = 0x13,
|
||||
PCIEnhancedAllocation = 0x14,
|
||||
}
|
||||
|
||||
/// A PCI capability list. Devices can optionally specify capabilities in their configuration space.
|
||||
pub trait PciCapability {
|
||||
fn bytes(&self) -> &[u8];
|
||||
fn id(&self) -> PciCapabilityID;
|
||||
}
|
||||
|
||||
/// Contains the configuration space of a PCI node.
|
||||
/// See the [specification](https://en.wikipedia.org/wiki/PCI_configuration_space).
|
||||
/// The configuration space is accessed with DWORD reads and writes from the guest.
|
||||
pub struct PciConfiguration {
|
||||
registers: [u32; NUM_CONFIGURATION_REGISTERS],
|
||||
writable_bits: [u32; NUM_CONFIGURATION_REGISTERS], // writable bits for each register.
|
||||
bar_used: [bool; NUM_BAR_REGS],
|
||||
// Contains the byte offset and size of the last capability.
|
||||
last_capability: Option<(usize, usize)>,
|
||||
}
|
||||
|
||||
/// See pci_regs.h in kernel
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum PciBarRegionType {
|
||||
Memory32BitRegion = 0,
|
||||
IORegion = 0x01,
|
||||
Memory64BitRegion = 0x04,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum PciBarPrefetchable {
|
||||
NotPrefetchable = 0,
|
||||
Prefetchable = 0x08,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct PciBarConfiguration {
|
||||
addr: u64,
|
||||
size: u64,
|
||||
reg_idx: usize,
|
||||
region_type: PciBarRegionType,
|
||||
prefetchable: PciBarPrefetchable,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
BarAddressInvalid(u64, u64),
|
||||
BarInUse(usize),
|
||||
BarInUse64(usize),
|
||||
BarInvalid(usize),
|
||||
BarInvalid64(usize),
|
||||
BarSizeInvalid(u64),
|
||||
CapabilityEmpty,
|
||||
CapabilityLengthInvalid(usize),
|
||||
CapabilitySpaceFull(usize),
|
||||
}
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::Error::*;
|
||||
match self {
|
||||
BarAddressInvalid(a, s) => write!(f, "address {} size {} too big", a, s),
|
||||
BarInUse(b) => write!(f, "bar {} already used", b),
|
||||
BarInUse64(b) => write!(f, "64bit bar {} already used(requires two regs)", b),
|
||||
BarInvalid(b) => write!(f, "bar {} invalid, max {}", b, NUM_BAR_REGS - 1),
|
||||
BarInvalid64(b) => write!(
|
||||
f,
|
||||
"64bitbar {} invalid, requires two regs, max {}",
|
||||
b,
|
||||
NUM_BAR_REGS - 1
|
||||
),
|
||||
BarSizeInvalid(s) => write!(f, "bar address {} not a power of two", s),
|
||||
CapabilityEmpty => write!(f, "empty capabilities are invalid"),
|
||||
CapabilityLengthInvalid(l) => write!(f, "Invalid capability length {}", l),
|
||||
CapabilitySpaceFull(s) => write!(f, "capability of size {} doesn't fit", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PciConfiguration {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
vendor_id: u16,
|
||||
device_id: u16,
|
||||
class_code: PciClassCode,
|
||||
subclass: &dyn PciSubclass,
|
||||
programming_interface: Option<&dyn PciProgrammingInterface>,
|
||||
header_type: PciHeaderType,
|
||||
subsystem_vendor_id: u16,
|
||||
subsystem_id: u16,
|
||||
) -> Self {
|
||||
let mut registers = [0u32; NUM_CONFIGURATION_REGISTERS];
|
||||
let mut writable_bits = [0u32; NUM_CONFIGURATION_REGISTERS];
|
||||
registers[0] = u32::from(device_id) << 16 | u32::from(vendor_id);
|
||||
// TODO(dverkamp): Status should be write-1-to-clear
|
||||
writable_bits[1] = 0x0000_ffff; // Status (r/o), command (r/w)
|
||||
let pi = if let Some(pi) = programming_interface {
|
||||
pi.get_register_value()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
registers[2] = u32::from(class_code.get_register_value()) << 24
|
||||
| u32::from(subclass.get_register_value()) << 16
|
||||
| u32::from(pi) << 8;
|
||||
writable_bits[3] = 0x0000_00ff; // Cacheline size (r/w)
|
||||
match header_type {
|
||||
PciHeaderType::Device => {
|
||||
registers[3] = 0x0000_0000; // Header type 0 (device)
|
||||
writable_bits[15] = 0x0000_00ff; // Interrupt line (r/w)
|
||||
}
|
||||
PciHeaderType::Bridge => {
|
||||
registers[3] = 0x0001_0000; // Header type 1 (bridge)
|
||||
writable_bits[9] = 0xfff0_fff0; // Memory base and limit
|
||||
writable_bits[15] = 0xffff_00ff; // Bridge control (r/w), interrupt line (r/w)
|
||||
}
|
||||
};
|
||||
registers[11] = u32::from(subsystem_id) << 16 | u32::from(subsystem_vendor_id);
|
||||
|
||||
PciConfiguration {
|
||||
registers,
|
||||
writable_bits,
|
||||
bar_used: [false; NUM_BAR_REGS],
|
||||
last_capability: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads a 32bit register from `reg_idx` in the register map.
|
||||
pub fn read_reg(&self, reg_idx: usize) -> u32 {
|
||||
*(self.registers.get(reg_idx).unwrap_or(&0xffff_ffff))
|
||||
}
|
||||
|
||||
/// Writes a 32bit register to `reg_idx` in the register map.
|
||||
pub fn write_reg(&mut self, reg_idx: usize, value: u32) {
|
||||
if let Some(r) = self.registers.get_mut(reg_idx) {
|
||||
*r = value & self.writable_bits[reg_idx];
|
||||
} else {
|
||||
warn!("bad PCI register write {}", reg_idx);
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a 16bit word to `offset`. `offset` must be 16bit aligned.
|
||||
pub fn write_word(&mut self, offset: usize, value: u16) {
|
||||
let shift = match offset % 4 {
|
||||
0 => 0,
|
||||
2 => 16,
|
||||
_ => {
|
||||
warn!("bad PCI config write offset {}", offset);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let reg_idx = offset / 4;
|
||||
|
||||
if let Some(r) = self.registers.get_mut(reg_idx) {
|
||||
let writable_mask = self.writable_bits[reg_idx];
|
||||
let mask = (0xffffu32 << shift) & writable_mask;
|
||||
let shifted_value = (u32::from(value) << shift) & writable_mask;
|
||||
*r = *r & !mask | shifted_value;
|
||||
} else {
|
||||
warn!("bad PCI config write offset {}", offset);
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a byte to `offset`.
|
||||
pub fn write_byte(&mut self, offset: usize, value: u8) {
|
||||
self.write_byte_internal(offset, value, true);
|
||||
}
|
||||
|
||||
/// Writes a byte to `offset`, optionally enforcing read-only bits.
|
||||
fn write_byte_internal(&mut self, offset: usize, value: u8, apply_writable_mask: bool) {
|
||||
let shift = (offset % 4) * 8;
|
||||
let reg_idx = offset / 4;
|
||||
|
||||
if let Some(r) = self.registers.get_mut(reg_idx) {
|
||||
let writable_mask = if apply_writable_mask {
|
||||
self.writable_bits[reg_idx]
|
||||
} else {
|
||||
0xffff_ffff
|
||||
};
|
||||
let mask = (0xffu32 << shift) & writable_mask;
|
||||
let shifted_value = (u32::from(value) << shift) & writable_mask;
|
||||
*r = *r & !mask | shifted_value;
|
||||
} else {
|
||||
warn!("bad PCI config write offset {}", offset);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a region specified by `config`. Configures the specified BAR(s) to
|
||||
/// report this region and size to the guest kernel. Enforces a few constraints
|
||||
/// (i.e, region size must be power of two, register not already used). Returns 'None' on
|
||||
/// failure all, `Some(BarIndex)` on success.
|
||||
pub fn add_pci_bar(&mut self, config: &PciBarConfiguration) -> Result<usize> {
|
||||
if self.bar_used[config.reg_idx] {
|
||||
return Err(Error::BarInUse(config.reg_idx));
|
||||
}
|
||||
|
||||
if config.size.count_ones() != 1 {
|
||||
return Err(Error::BarSizeInvalid(config.size));
|
||||
}
|
||||
|
||||
if config.reg_idx >= NUM_BAR_REGS {
|
||||
return Err(Error::BarInvalid(config.reg_idx));
|
||||
}
|
||||
|
||||
let bar_idx = BAR0_REG + config.reg_idx;
|
||||
let end_addr = config
|
||||
.addr
|
||||
.checked_add(config.size)
|
||||
.ok_or_else(|| Error::BarAddressInvalid(config.addr, config.size))?;
|
||||
match config.region_type {
|
||||
PciBarRegionType::Memory32BitRegion | PciBarRegionType::IORegion => {
|
||||
if end_addr > u64::from(u32::max_value()) {
|
||||
return Err(Error::BarAddressInvalid(config.addr, config.size));
|
||||
}
|
||||
}
|
||||
PciBarRegionType::Memory64BitRegion => {
|
||||
if config.reg_idx + 1 >= NUM_BAR_REGS {
|
||||
return Err(Error::BarInvalid64(config.reg_idx));
|
||||
}
|
||||
|
||||
if end_addr > u64::max_value() {
|
||||
return Err(Error::BarAddressInvalid(config.addr, config.size));
|
||||
}
|
||||
|
||||
if self.bar_used[config.reg_idx + 1] {
|
||||
return Err(Error::BarInUse64(config.reg_idx));
|
||||
}
|
||||
|
||||
self.registers[bar_idx + 1] = (config.addr >> 32) as u32;
|
||||
self.writable_bits[bar_idx + 1] = !((config.size >> 32).wrapping_sub(1)) as u32;
|
||||
self.bar_used[config.reg_idx + 1] = true;
|
||||
}
|
||||
}
|
||||
|
||||
let (mask, lower_bits) = match config.region_type {
|
||||
PciBarRegionType::Memory32BitRegion | PciBarRegionType::Memory64BitRegion => (
|
||||
BAR_MEM_ADDR_MASK,
|
||||
config.prefetchable as u32 | config.region_type as u32,
|
||||
),
|
||||
PciBarRegionType::IORegion => (BAR_IO_ADDR_MASK, config.region_type as u32),
|
||||
};
|
||||
|
||||
self.registers[bar_idx] = ((config.addr as u32) & mask) | lower_bits;
|
||||
self.writable_bits[bar_idx] = !(config.size - 1) as u32;
|
||||
self.bar_used[config.reg_idx] = true;
|
||||
Ok(config.reg_idx)
|
||||
}
|
||||
|
||||
/// Returns the address of the given BAR region.
|
||||
pub fn get_bar_addr(&self, bar_num: usize) -> u32 {
|
||||
let bar_idx = BAR0_REG + bar_num;
|
||||
|
||||
self.registers[bar_idx] & BAR_MEM_ADDR_MASK
|
||||
}
|
||||
|
||||
/// Configures the IRQ line and pin used by this device.
|
||||
pub fn set_irq(&mut self, line: u8, pin: PciInterruptPin) {
|
||||
// `pin` is 1-based in the pci config space.
|
||||
let pin_idx = (pin as u32) + 1;
|
||||
self.registers[INTERRUPT_LINE_PIN_REG] = (self.registers[INTERRUPT_LINE_PIN_REG]
|
||||
& 0xffff_0000)
|
||||
| (pin_idx << 8)
|
||||
| u32::from(line);
|
||||
}
|
||||
|
||||
/// Adds the capability `cap_data` to the list of capabilities.
|
||||
/// `cap_data` should include the two-byte PCI capability header (type, next),
|
||||
/// but not populate it. Correct values will be generated automatically based
|
||||
/// on `cap_data.id()`.
|
||||
pub fn add_capability(&mut self, cap_data: &dyn PciCapability) -> Result<usize> {
|
||||
let total_len = cap_data.bytes().len();
|
||||
// Check that the length is valid.
|
||||
if cap_data.bytes().is_empty() {
|
||||
return Err(Error::CapabilityEmpty);
|
||||
}
|
||||
let (cap_offset, tail_offset) = match self.last_capability {
|
||||
Some((offset, len)) => (Self::next_dword(offset, len), offset + 1),
|
||||
None => (FIRST_CAPABILITY_OFFSET, CAPABILITY_LIST_HEAD_OFFSET),
|
||||
};
|
||||
let end_offset = cap_offset
|
||||
.checked_add(total_len)
|
||||
.ok_or_else(|| Error::CapabilitySpaceFull(total_len))?;
|
||||
if end_offset > CAPABILITY_MAX_OFFSET {
|
||||
return Err(Error::CapabilitySpaceFull(total_len));
|
||||
}
|
||||
self.registers[STATUS_REG] |= STATUS_REG_CAPABILITIES_USED_MASK;
|
||||
self.write_byte_internal(tail_offset, cap_offset as u8, false);
|
||||
self.write_byte_internal(cap_offset, cap_data.id() as u8, false);
|
||||
self.write_byte_internal(cap_offset + 1, 0, false); // Next pointer.
|
||||
for (i, byte) in cap_data.bytes().iter().enumerate() {
|
||||
self.write_byte_internal(cap_offset + i + 2, *byte, false);
|
||||
}
|
||||
self.last_capability = Some((cap_offset, total_len));
|
||||
Ok(cap_offset)
|
||||
}
|
||||
|
||||
// Find the next aligned offset after the one given.
|
||||
fn next_dword(offset: usize, len: usize) -> usize {
|
||||
let next = offset + len;
|
||||
(next + 3) & !3
|
||||
}
|
||||
|
||||
pub fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]) {
|
||||
if offset as usize + data.len() > 4 {
|
||||
return;
|
||||
}
|
||||
|
||||
match data.len() {
|
||||
1 => self.write_byte(reg_idx * 4 + offset as usize, data[0]),
|
||||
2 => self.write_word(
|
||||
reg_idx * 4 + offset as usize,
|
||||
u16::from(data[0]) | u16::from(data[1]) << 8,
|
||||
),
|
||||
4 => self.write_reg(reg_idx, LittleEndian::read_u32(data)),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_config_register(&self, reg_idx: usize) -> u32 {
|
||||
self.read_reg(reg_idx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PciBarConfiguration {
|
||||
fn default() -> Self {
|
||||
PciBarConfiguration {
|
||||
reg_idx: 0,
|
||||
addr: 0,
|
||||
size: 0,
|
||||
region_type: PciBarRegionType::Memory64BitRegion,
|
||||
prefetchable: PciBarPrefetchable::NotPrefetchable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PciBarConfiguration {
|
||||
pub fn new(
|
||||
reg_idx: usize,
|
||||
size: u64,
|
||||
region_type: PciBarRegionType,
|
||||
prefetchable: PciBarPrefetchable,
|
||||
) -> Self {
|
||||
PciBarConfiguration {
|
||||
reg_idx,
|
||||
addr: 0,
|
||||
size,
|
||||
region_type,
|
||||
prefetchable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_register_index(mut self, reg_idx: usize) -> Self {
|
||||
self.reg_idx = reg_idx;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_address(mut self, addr: u64) -> Self {
|
||||
self.addr = addr;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_size(mut self, size: u64) -> Self {
|
||||
self.size = size;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use vm_memory::ByteValued;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
#[allow(dead_code)]
|
||||
struct TestCap {
|
||||
_vndr: u8,
|
||||
_next: u8,
|
||||
len: u8,
|
||||
foo: u8,
|
||||
}
|
||||
|
||||
// It is safe to implement BytesValued; all members are simple numbers and any value is valid.
|
||||
unsafe impl ByteValued for TestCap {}
|
||||
|
||||
impl PciCapability for TestCap {
|
||||
fn bytes(&self) -> &[u8] {
|
||||
self.as_slice()
|
||||
}
|
||||
|
||||
fn id(&self) -> PciCapabilityID {
|
||||
PciCapabilityID::VendorSpecific
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_capability() {
|
||||
let mut cfg = PciConfiguration::new(
|
||||
0x1234,
|
||||
0x5678,
|
||||
PciClassCode::MultimediaController,
|
||||
&PciMultimediaSubclass::AudioController,
|
||||
None,
|
||||
PciHeaderType::Device,
|
||||
0xABCD,
|
||||
0x2468,
|
||||
);
|
||||
|
||||
// Add two capabilities with different contents.
|
||||
let cap1 = TestCap {
|
||||
_vndr: 0,
|
||||
_next: 0,
|
||||
len: 4,
|
||||
foo: 0xAA,
|
||||
};
|
||||
let cap1_offset = cfg.add_capability(&cap1).unwrap();
|
||||
assert_eq!(cap1_offset % 4, 0);
|
||||
|
||||
let cap2 = TestCap {
|
||||
_vndr: 0,
|
||||
_next: 0,
|
||||
len: 0x04,
|
||||
foo: 0x55,
|
||||
};
|
||||
let cap2_offset = cfg.add_capability(&cap2).unwrap();
|
||||
assert_eq!(cap2_offset % 4, 0);
|
||||
|
||||
// The capability list head should be pointing to cap1.
|
||||
let cap_ptr = cfg.read_reg(CAPABILITY_LIST_HEAD_OFFSET / 4) & 0xFF;
|
||||
assert_eq!(cap1_offset, cap_ptr as usize);
|
||||
|
||||
// Verify the contents of the capabilities.
|
||||
let cap1_data = cfg.read_reg(cap1_offset / 4);
|
||||
assert_eq!(cap1_data & 0xFF, 0x09); // capability ID
|
||||
assert_eq!((cap1_data >> 8) & 0xFF, cap2_offset as u32); // next capability pointer
|
||||
assert_eq!((cap1_data >> 16) & 0xFF, 0x04); // cap1.len
|
||||
assert_eq!((cap1_data >> 24) & 0xFF, 0xAA); // cap1.foo
|
||||
|
||||
let cap2_data = cfg.read_reg(cap2_offset / 4);
|
||||
assert_eq!(cap2_data & 0xFF, 0x09); // capability ID
|
||||
assert_eq!((cap2_data >> 8) & 0xFF, 0x00); // next capability pointer
|
||||
assert_eq!((cap2_data >> 16) & 0xFF, 0x04); // cap2.len
|
||||
assert_eq!((cap2_data >> 24) & 0xFF, 0x55); // cap2.foo
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum TestPI {
|
||||
Test = 0x5a,
|
||||
}
|
||||
|
||||
impl PciProgrammingInterface for TestPI {
|
||||
fn get_register_value(&self) -> u8 {
|
||||
*self as u8
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn class_code() {
|
||||
let cfg = PciConfiguration::new(
|
||||
0x1234,
|
||||
0x5678,
|
||||
PciClassCode::MultimediaController,
|
||||
&PciMultimediaSubclass::AudioController,
|
||||
Some(&TestPI::Test),
|
||||
PciHeaderType::Device,
|
||||
0xABCD,
|
||||
0x2468,
|
||||
);
|
||||
|
||||
let class_reg = cfg.read_reg(2);
|
||||
let class_code = (class_reg >> 24) & 0xFF;
|
||||
let subclass = (class_reg >> 16) & 0xFF;
|
||||
let prog_if = (class_reg >> 8) & 0xFF;
|
||||
assert_eq!(class_code, 0x04);
|
||||
assert_eq!(subclass, 0x01);
|
||||
assert_eq!(prog_if, 0x5a);
|
||||
}
|
||||
}
|
80
pci/src/device.rs
Executable file
80
pci/src/device.rs
Executable file
@ -0,0 +1,80 @@
|
||||
// Copyright 2018 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.
|
||||
|
||||
use crate::configuration::{self, PciConfiguration};
|
||||
use crate::PciInterruptPin;
|
||||
use devices::BusDevice;
|
||||
use kvm_ioctls::*;
|
||||
use std;
|
||||
use std::fmt::{self, Display};
|
||||
use vm_allocator::SystemAllocator;
|
||||
use vm_memory::{GuestAddress, GuestUsize};
|
||||
use vmm_sys_util::EventFd;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Setup of the device capabilities failed.
|
||||
CapabilitiesSetup(configuration::Error),
|
||||
/// Allocating space for an IO BAR failed.
|
||||
IoAllocationFailed(u64),
|
||||
/// Registering an IO BAR failed.
|
||||
IoRegistrationFailed(u64, configuration::Error),
|
||||
}
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::Error::*;
|
||||
|
||||
match self {
|
||||
CapabilitiesSetup(e) => write!(f, "failed to add capability {}", e),
|
||||
IoAllocationFailed(size) => {
|
||||
write!(f, "failed to allocate space for an IO BAR, size={}", size)
|
||||
}
|
||||
IoRegistrationFailed(addr, e) => {
|
||||
write!(f, "failed to register an IO BAR, addr={} err={}", addr, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PciDevice: BusDevice {
|
||||
/// Assign a legacy PCI IRQ to this device.
|
||||
/// The device may write to `irq_evt` to trigger an interrupt.
|
||||
fn assign_irq(&mut self, _irq_evt: EventFd, _irq_num: u32, _irq_pin: PciInterruptPin) {}
|
||||
|
||||
/// Allocates the needed PCI BARs space using the `allocate` function which takes a size and
|
||||
/// returns an address. Returns a Vec of (GuestAddress, GuestUsize) tuples.
|
||||
fn allocate_bars(
|
||||
&mut self,
|
||||
_allocator: &mut SystemAllocator,
|
||||
) -> Result<Vec<(GuestAddress, GuestUsize)>> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
/// Register any capabilties specified by the device.
|
||||
fn register_device_capabilities(&mut self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets a list of ioeventfds that should be registered with the running VM. The list is
|
||||
/// returned as a Vec of (eventfd, addr, datamatch) tuples.
|
||||
fn ioeventfds(&self) -> Vec<(&EventFd, u64, NoDatamatch)> {
|
||||
Vec::new()
|
||||
}
|
||||
/// Gets the configuration registers of the Pci Device.
|
||||
fn config_registers(&self) -> &PciConfiguration; // TODO - remove these
|
||||
/// Gets the configuration registers of the Pci Device for modification.
|
||||
fn config_registers_mut(&mut self) -> &mut PciConfiguration;
|
||||
/// Reads from a BAR region mapped in to the device.
|
||||
/// * `addr` - The guest address inside the BAR.
|
||||
/// * `data` - Filled with the data from `addr`.
|
||||
fn read_bar(&mut self, addr: u64, data: &mut [u8]);
|
||||
/// Writes to a BAR region mapped in to the device.
|
||||
/// * `addr` - The guest address inside the BAR.
|
||||
/// * `data` - The data to write.
|
||||
fn write_bar(&mut self, addr: u64, data: &[u8]);
|
||||
/// Invoked when the device is sandboxed.
|
||||
fn on_device_sandboxed(&mut self) {}
|
||||
}
|
39
pci/src/lib.rs
Normal file
39
pci/src/lib.rs
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright 2018 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 pci devices and busses.
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate devices;
|
||||
extern crate kvm_ioctls;
|
||||
extern crate vm_memory;
|
||||
extern crate vmm_sys_util;
|
||||
|
||||
mod configuration;
|
||||
mod device;
|
||||
mod root;
|
||||
|
||||
pub use self::configuration::{
|
||||
PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciCapability, PciCapabilityID,
|
||||
PciClassCode, PciConfiguration, PciHeaderType, PciProgrammingInterface, PciSerialBusSubClass,
|
||||
PciSubclass,
|
||||
};
|
||||
pub use self::device::Error as PciDeviceError;
|
||||
pub use self::device::PciDevice;
|
||||
pub use self::root::{PciConfigIo, PciConfigMmio, PciRoot, PciRootError};
|
||||
|
||||
/// PCI has four interrupt pins A->D.
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum PciInterruptPin {
|
||||
IntA,
|
||||
IntB,
|
||||
IntC,
|
||||
IntD,
|
||||
}
|
||||
|
||||
impl PciInterruptPin {
|
||||
pub fn to_mask(self) -> u32 {
|
||||
self as u32
|
||||
}
|
||||
}
|
298
pci/src/root.rs
Executable file
298
pci/src/root.rs
Executable file
@ -0,0 +1,298 @@
|
||||
// Copyright 2018 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.
|
||||
|
||||
use crate::configuration::{PciBridgeSubclass, PciClassCode, PciConfiguration, PciHeaderType};
|
||||
use crate::device::Error as PciDeviceError;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use devices::BusDevice;
|
||||
use std;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use vm_memory::{Address, GuestAddress, GuestUsize};
|
||||
|
||||
const VENDOR_ID_INTEL: u16 = 0x8086;
|
||||
const DEVICE_ID_INTEL_VIRT_PCIE_HOST: u16 = 0x0d57;
|
||||
|
||||
/// Errors for device manager.
|
||||
#[derive(Debug)]
|
||||
pub enum PciRootError {
|
||||
/// Could not allocate device address space for the device.
|
||||
AllocateDeviceAddrs(PciDeviceError),
|
||||
/// Could not allocate an IRQ number.
|
||||
AllocateIrq,
|
||||
/// Could not add a device to the mmio bus.
|
||||
MmioInsert(devices::BusError),
|
||||
}
|
||||
pub type Result<T> = std::result::Result<T, PciRootError>;
|
||||
|
||||
/// Emulates the PCI Root bridge.
|
||||
pub struct PciRoot {
|
||||
/// Bus configuration for the root device.
|
||||
configuration: PciConfiguration,
|
||||
/// Devices attached to this bridge.
|
||||
devices: Vec<Arc<Mutex<dyn BusDevice>>>,
|
||||
}
|
||||
|
||||
impl PciRoot {
|
||||
/// Create an empty PCI root bus.
|
||||
pub fn new(configuration: Option<PciConfiguration>) -> Self {
|
||||
if let Some(config) = configuration {
|
||||
PciRoot {
|
||||
configuration: config,
|
||||
devices: Vec::new(),
|
||||
}
|
||||
} else {
|
||||
PciRoot {
|
||||
configuration: PciConfiguration::new(
|
||||
VENDOR_ID_INTEL,
|
||||
DEVICE_ID_INTEL_VIRT_PCIE_HOST,
|
||||
PciClassCode::BridgeDevice,
|
||||
&PciBridgeSubclass::HostBridge,
|
||||
None,
|
||||
PciHeaderType::Bridge,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
devices: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a `device` to this root PCI bus.
|
||||
pub fn add_device(
|
||||
&mut self,
|
||||
device: Arc<Mutex<dyn BusDevice>>,
|
||||
bus: &mut devices::Bus,
|
||||
bars: Vec<(GuestAddress, GuestUsize)>,
|
||||
) -> Result<()> {
|
||||
for (address, size) in bars {
|
||||
bus.insert(device.clone(), address.raw_value(), size)
|
||||
.map_err(PciRootError::MmioInsert)?;
|
||||
}
|
||||
|
||||
self.devices.push(device);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn config_space_read(
|
||||
&self,
|
||||
bus: usize,
|
||||
device: usize,
|
||||
_function: usize,
|
||||
register: usize,
|
||||
) -> u32 {
|
||||
// Only support one bus.
|
||||
if bus != 0 {
|
||||
return 0xffff_ffff;
|
||||
}
|
||||
|
||||
match device {
|
||||
0 => {
|
||||
// If bus and device are both zero, then read from the root config.
|
||||
self.configuration.read_config_register(register)
|
||||
}
|
||||
dev_num => self.devices.get(dev_num - 1).map_or(0xffff_ffff, |d| {
|
||||
d.lock().unwrap().read_config_register(register)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn config_space_write(
|
||||
&mut self,
|
||||
bus: usize,
|
||||
device: usize,
|
||||
_function: usize,
|
||||
register: usize,
|
||||
offset: u64,
|
||||
data: &[u8],
|
||||
) {
|
||||
if offset as usize + data.len() > 4 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only support one bus.
|
||||
if bus != 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
match device {
|
||||
0 => {
|
||||
// If bus and device are both zero, then read from the root config.
|
||||
self.configuration
|
||||
.write_config_register(register, offset, data);
|
||||
}
|
||||
dev_num => {
|
||||
if let Some(d) = self.devices.get(dev_num - 1) {
|
||||
d.lock()
|
||||
.unwrap()
|
||||
.write_config_register(register, offset, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Emulates PCI configuration access mechanism #1 (I/O ports 0xcf8 and 0xcfc).
|
||||
pub struct PciConfigIo {
|
||||
/// PCI root bridge.
|
||||
pci_root: PciRoot,
|
||||
/// Current address to read/write from (0xcf8 register, litte endian).
|
||||
config_address: u32,
|
||||
}
|
||||
|
||||
impl PciConfigIo {
|
||||
pub fn new(pci_root: PciRoot) -> Self {
|
||||
PciConfigIo {
|
||||
pci_root,
|
||||
config_address: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn config_space_read(&self) -> u32 {
|
||||
let enabled = (self.config_address & 0x8000_0000) != 0;
|
||||
if !enabled {
|
||||
return 0xffff_ffff;
|
||||
}
|
||||
|
||||
let (bus, device, function, register) =
|
||||
parse_config_address(self.config_address & !0x8000_0000);
|
||||
self.pci_root
|
||||
.config_space_read(bus, device, function, register)
|
||||
}
|
||||
|
||||
fn config_space_write(&mut self, offset: u64, data: &[u8]) {
|
||||
let enabled = (self.config_address & 0x8000_0000) != 0;
|
||||
if !enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
let (bus, device, function, register) =
|
||||
parse_config_address(self.config_address & !0x8000_0000);
|
||||
self.pci_root
|
||||
.config_space_write(bus, device, function, register, offset, data)
|
||||
}
|
||||
|
||||
fn set_config_address(&mut self, offset: u64, data: &[u8]) {
|
||||
if offset as usize + data.len() > 4 {
|
||||
return;
|
||||
}
|
||||
let (mask, value): (u32, u32) = match data.len() {
|
||||
1 => (
|
||||
0x0000_00ff << (offset * 8),
|
||||
u32::from(data[0]) << (offset * 8),
|
||||
),
|
||||
2 => (
|
||||
0x0000_ffff << (offset * 16),
|
||||
(u32::from(data[1]) << 8 | u32::from(data[0])) << (offset * 16),
|
||||
),
|
||||
4 => (0xffff_ffff, LittleEndian::read_u32(data)),
|
||||
_ => return,
|
||||
};
|
||||
self.config_address = (self.config_address & !mask) | value;
|
||||
}
|
||||
}
|
||||
|
||||
impl BusDevice for PciConfigIo {
|
||||
fn read(&mut self, offset: u64, data: &mut [u8]) {
|
||||
// `offset` is relative to 0xcf8
|
||||
let value = match offset {
|
||||
0...3 => self.config_address,
|
||||
4...7 => self.config_space_read(),
|
||||
_ => 0xffff_ffff,
|
||||
};
|
||||
|
||||
// Only allow reads to the register boundary.
|
||||
let start = offset as usize % 4;
|
||||
let end = start + data.len();
|
||||
if end <= 4 {
|
||||
for i in start..end {
|
||||
data[i - start] = (value >> (i * 8)) as u8;
|
||||
}
|
||||
} else {
|
||||
for d in data {
|
||||
*d = 0xff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, offset: u64, data: &[u8]) {
|
||||
// `offset` is relative to 0xcf8
|
||||
match offset {
|
||||
o @ 0...3 => self.set_config_address(o, data),
|
||||
o @ 4...7 => self.config_space_write(o - 4, data),
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Emulates PCI memory-mapped configuration access mechanism.
|
||||
pub struct PciConfigMmio {
|
||||
/// PCI root bridge.
|
||||
pci_root: PciRoot,
|
||||
}
|
||||
|
||||
impl PciConfigMmio {
|
||||
pub fn new(pci_root: PciRoot) -> Self {
|
||||
PciConfigMmio { pci_root }
|
||||
}
|
||||
|
||||
fn config_space_read(&self, config_address: u32) -> u32 {
|
||||
let (bus, device, function, register) = parse_config_address(config_address);
|
||||
self.pci_root
|
||||
.config_space_read(bus, device, function, register)
|
||||
}
|
||||
|
||||
fn config_space_write(&mut self, config_address: u32, offset: u64, data: &[u8]) {
|
||||
let (bus, device, function, register) = parse_config_address(config_address);
|
||||
self.pci_root
|
||||
.config_space_write(bus, device, function, register, offset, data)
|
||||
}
|
||||
}
|
||||
|
||||
impl BusDevice for PciConfigMmio {
|
||||
fn read(&mut self, offset: u64, data: &mut [u8]) {
|
||||
// Only allow reads to the register boundary.
|
||||
let start = offset as usize % 4;
|
||||
let end = start + data.len();
|
||||
if end > 4 || offset > u64::from(u32::max_value()) {
|
||||
for d in data {
|
||||
*d = 0xff;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let value = self.config_space_read(offset as u32);
|
||||
for i in start..end {
|
||||
data[i - start] = (value >> (i * 8)) as u8;
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, offset: u64, data: &[u8]) {
|
||||
if offset > u64::from(u32::max_value()) {
|
||||
return;
|
||||
}
|
||||
self.config_space_write(offset as u32, offset % 4, data)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the CONFIG_ADDRESS register to a (bus, device, function, register) tuple.
|
||||
fn parse_config_address(config_address: u32) -> (usize, usize, usize, usize) {
|
||||
const BUS_NUMBER_OFFSET: usize = 16;
|
||||
const BUS_NUMBER_MASK: u32 = 0x00ff;
|
||||
const DEVICE_NUMBER_OFFSET: usize = 11;
|
||||
const DEVICE_NUMBER_MASK: u32 = 0x1f;
|
||||
const FUNCTION_NUMBER_OFFSET: usize = 8;
|
||||
const FUNCTION_NUMBER_MASK: u32 = 0x07;
|
||||
const REGISTER_NUMBER_OFFSET: usize = 2;
|
||||
const REGISTER_NUMBER_MASK: u32 = 0x3f;
|
||||
|
||||
let bus_number = ((config_address >> BUS_NUMBER_OFFSET) & BUS_NUMBER_MASK) as usize;
|
||||
let device_number = ((config_address >> DEVICE_NUMBER_OFFSET) & DEVICE_NUMBER_MASK) as usize;
|
||||
let function_number =
|
||||
((config_address >> FUNCTION_NUMBER_OFFSET) & FUNCTION_NUMBER_MASK) as usize;
|
||||
let register_number =
|
||||
((config_address >> REGISTER_NUMBER_OFFSET) & REGISTER_NUMBER_MASK) as usize;
|
||||
|
||||
(bus_number, device_number, function_number, register_number)
|
||||
}
|
Loading…
Reference in New Issue
Block a user