mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2025-01-31 06:30:36 +00:00
devices: pci - Add a PCI root bridge
`PciRoot` represents the root PCI bridge for the system and manages PCI devices attached to it. The root bridge has its own set of configuration registers. Change-Id: I2b15630cf5a0fc5938e66986a65782c6939fcf55 Signed-off-by: Dylan Reid <dgreid@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/1072577 Reviewed-by: Sonny Rao <sonnyrao@chromium.org>
This commit is contained in:
parent
836466aead
commit
86fdb1dc50
3 changed files with 270 additions and 1 deletions
|
@ -30,7 +30,8 @@ pub use self::bus::{Bus, BusDevice, BusRange};
|
||||||
pub use self::cmos::Cmos;
|
pub use self::cmos::Cmos;
|
||||||
pub use self::pl030::Pl030;
|
pub use self::pl030::Pl030;
|
||||||
pub use self::i8042::I8042Device;
|
pub use self::i8042::I8042Device;
|
||||||
pub use self::pci::{PciDevice, PciInterruptPin};
|
pub use self::pci::{PciDevice, PciDeviceList, PciInterruptPin, PciRoot};
|
||||||
|
pub use self::pci::PciRootError as PciRootError;
|
||||||
pub use self::proxy::ProxyDevice;
|
pub use self::proxy::ProxyDevice;
|
||||||
pub use self::proxy::Error as ProxyError;
|
pub use self::proxy::Error as ProxyError;
|
||||||
pub use self::serial::Serial;
|
pub use self::serial::Serial;
|
||||||
|
|
|
@ -6,8 +6,11 @@
|
||||||
|
|
||||||
mod pci_configuration;
|
mod pci_configuration;
|
||||||
mod pci_device;
|
mod pci_device;
|
||||||
|
mod pci_root;
|
||||||
|
|
||||||
pub use self::pci_device::PciDevice;
|
pub use self::pci_device::PciDevice;
|
||||||
|
pub use self::pci_root::Error as PciRootError;
|
||||||
|
pub use self::pci_root::{PciDeviceList, PciRoot};
|
||||||
|
|
||||||
/// PCI has four interrupt pins A->D.
|
/// PCI has four interrupt pins A->D.
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
|
|
265
devices/src/pci/pci_root.rs
Normal file
265
devices/src/pci/pci_root.rs
Normal file
|
@ -0,0 +1,265 @@
|
||||||
|
// 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 std;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
|
|
||||||
|
use io_jail::Minijail;
|
||||||
|
use sys_util::{self, EventFd};
|
||||||
|
use resources::SystemAllocator;
|
||||||
|
|
||||||
|
use Bus;
|
||||||
|
use BusDevice;
|
||||||
|
use bus::Error as BusError;
|
||||||
|
use proxy::Error as ProxyError;
|
||||||
|
use ProxyDevice;
|
||||||
|
|
||||||
|
use pci::pci_configuration::{PciBridgeSubclass, PciClassCode, PciConfiguration,
|
||||||
|
PciHeaderType};
|
||||||
|
use pci::pci_device::{self, PciDevice};
|
||||||
|
use pci::PciInterruptPin;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
CreateEventFd(sys_util::Error),
|
||||||
|
MmioRegistration(BusError),
|
||||||
|
ProxyCreation(ProxyError),
|
||||||
|
DeviceIoSpaceAllocation(pci_device::Error),
|
||||||
|
}
|
||||||
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
/// Contains the devices that will be on a PCI bus. Used to configure a PCI bus before adding it to
|
||||||
|
/// a VM. Use `generate_hub` to produce a PciRoot for use in a Vm.
|
||||||
|
pub struct PciDeviceList {
|
||||||
|
devices: Vec<(Box<PciDevice + 'static>, Minijail)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PciDeviceList {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
PciDeviceList {
|
||||||
|
devices: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_device(&mut self, device: Box<PciDevice + 'static>, jail: Minijail) {
|
||||||
|
self.devices.push((device, jail));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_root(self, mmio_bus: &mut Bus, resources: &mut SystemAllocator)
|
||||||
|
-> Result<(PciRoot, Vec<(u32, PciInterruptPin)>)> {
|
||||||
|
let mut root = PciRoot::new();
|
||||||
|
let mut pci_irqs = Vec::new();
|
||||||
|
for (dev_idx, (mut device, jail)) in self.devices.into_iter().enumerate() {
|
||||||
|
let irqfd = EventFd::new().map_err(Error::CreateEventFd)?;
|
||||||
|
let irq_num = resources.allocate_irq().unwrap() as u32;
|
||||||
|
let pci_irq_pin = match dev_idx % 4 {
|
||||||
|
0 => PciInterruptPin::IntA,
|
||||||
|
1 => PciInterruptPin::IntB,
|
||||||
|
2 => PciInterruptPin::IntC,
|
||||||
|
3 => PciInterruptPin::IntD,
|
||||||
|
_ => panic!(""), // Obviously not possible, but the compiler is not smart enough.
|
||||||
|
};
|
||||||
|
device.assign_irq(irqfd, irq_num, pci_irq_pin);
|
||||||
|
pci_irqs.push((irq_num, pci_irq_pin));
|
||||||
|
root.add_device(device, &jail, mmio_bus, resources)?;
|
||||||
|
}
|
||||||
|
Ok((root, pci_irqs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A PciDevice that holds the root hub's configuration.
|
||||||
|
struct PciRootConfiguration {
|
||||||
|
config: PciConfiguration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PciDevice for PciRootConfiguration {
|
||||||
|
fn config_registers(&self) -> &PciConfiguration {
|
||||||
|
&self.config
|
||||||
|
}
|
||||||
|
|
||||||
|
fn config_registers_mut(&mut self) -> &mut PciConfiguration {
|
||||||
|
&mut self.config
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_bar(&mut self, _addr: u64, _data: &mut [u8]) {}
|
||||||
|
|
||||||
|
fn write_bar(&mut self, _addr: u64, _data: &[u8]) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emulates the PCI Root bridge.
|
||||||
|
pub struct PciRoot {
|
||||||
|
/// Bus configuration for the root device.
|
||||||
|
root_configuration: PciRootConfiguration,
|
||||||
|
/// Current address to read/write from (0xcf8 register, litte endian).
|
||||||
|
config_address: u32,
|
||||||
|
/// Devices attached to this bridge.
|
||||||
|
devices: Vec<Arc<Mutex<ProxyDevice>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PciRoot {
|
||||||
|
/// Create an empty PCI root bus.
|
||||||
|
fn new() -> Self {
|
||||||
|
PciRoot {
|
||||||
|
root_configuration: PciRootConfiguration {
|
||||||
|
config: PciConfiguration::new(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
PciClassCode::BridgeDevice,
|
||||||
|
&PciBridgeSubclass::HostBridge,
|
||||||
|
PciHeaderType::Bridge,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
config_address: 0,
|
||||||
|
devices: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a `device` to this root PCI bus.
|
||||||
|
pub fn add_device<D: PciDevice>(&mut self, mut device: D, jail: &Minijail,
|
||||||
|
mmio_bus: &mut Bus, // TODO - move to resources or something.
|
||||||
|
resources: &mut SystemAllocator) -> Result<()> {
|
||||||
|
let ranges = device
|
||||||
|
.allocate_io_bars(resources)
|
||||||
|
.map_err(Error::DeviceIoSpaceAllocation)?;
|
||||||
|
let proxy = ProxyDevice::new(device, &jail, Vec::new())
|
||||||
|
.map_err(Error::ProxyCreation)?;
|
||||||
|
let arced_dev = Arc::new(Mutex::new(proxy));
|
||||||
|
for range in &ranges {
|
||||||
|
mmio_bus.insert(arced_dev.clone(), range.0, range.1, true)
|
||||||
|
.map_err(Error::MmioRegistration)?;
|
||||||
|
}
|
||||||
|
self.devices.push(arced_dev);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn config_space_read(&self) -> u32 {
|
||||||
|
let (enabled, bus, device, _, register) = parse_config_address(self.config_address);
|
||||||
|
|
||||||
|
// Only support one bus.
|
||||||
|
if !enabled || bus != 0 {
|
||||||
|
return 0xffff_ffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
match device {
|
||||||
|
0 => {
|
||||||
|
// If bus and device are both zero, then read from the root config.
|
||||||
|
self.root_configuration.config_register_read(register)
|
||||||
|
}
|
||||||
|
dev_num => self
|
||||||
|
.devices
|
||||||
|
.get(dev_num - 1)
|
||||||
|
.map_or(0xffff_ffff, |d| {
|
||||||
|
d.lock().unwrap().config_register_read(register)
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn config_space_write(&mut self, offset: u64, data: &[u8]) {
|
||||||
|
if offset as usize + data.len() > 4 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (enabled, bus, device, _, register) = parse_config_address(self.config_address);
|
||||||
|
|
||||||
|
// Only support one bus.
|
||||||
|
if !enabled || bus != 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match device {
|
||||||
|
0 => {
|
||||||
|
// If bus and device are both zero, then read from the root config.
|
||||||
|
self.root_configuration.config_register_write(register, offset, data);
|
||||||
|
}
|
||||||
|
dev_num => {
|
||||||
|
// dev_num is 1-indexed here.
|
||||||
|
if let Some(d) = self.devices.get(dev_num - 1) {
|
||||||
|
d.lock().unwrap().config_register_write(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),
|
||||||
|
(data[0] as u32) << (offset * 8),
|
||||||
|
),
|
||||||
|
2 => (
|
||||||
|
0x0000_ffff << (offset * 16),
|
||||||
|
((data[1] as u32) << 8 | data[0] as u32) << (offset * 16),
|
||||||
|
),
|
||||||
|
4 => (0xffff_ffff, LittleEndian::read_u32(data)),
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
self.config_address = (self.config_address & !mask) | value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BusDevice for PciRoot {
|
||||||
|
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),
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the CONFIG_ADDRESS register to a (enabled, bus, device, function, register) tuple.
|
||||||
|
fn parse_config_address(config_address: u32) -> (bool, 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 enabled = (config_address & 0x8000_0000) != 0;
|
||||||
|
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;
|
||||||
|
|
||||||
|
(
|
||||||
|
enabled,
|
||||||
|
bus_number,
|
||||||
|
device_number,
|
||||||
|
function_number,
|
||||||
|
register_number,
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in a new issue