mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +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:
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)
|
||||
}
|
||||
Reference in New Issue
Block a user