diff --git a/devices/src/lib.rs b/devices/src/lib.rs index 3726a880ab..eaf6b4e9be 100644 --- a/devices/src/lib.rs +++ b/devices/src/lib.rs @@ -30,7 +30,8 @@ pub use self::bus::{Bus, BusDevice, BusRange}; pub use self::cmos::Cmos; pub use self::pl030::Pl030; 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::Error as ProxyError; pub use self::serial::Serial; diff --git a/devices/src/pci/mod.rs b/devices/src/pci/mod.rs index afcad2c4cf..f5e5d1f06f 100644 --- a/devices/src/pci/mod.rs +++ b/devices/src/pci/mod.rs @@ -6,8 +6,11 @@ mod pci_configuration; mod pci_device; +mod pci_root; 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. #[derive(Copy, Clone)] diff --git a/devices/src/pci/pci_root.rs b/devices/src/pci/pci_root.rs new file mode 100644 index 0000000000..3f9852dd2c --- /dev/null +++ b/devices/src/pci/pci_root.rs @@ -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 = std::result::Result; + +/// 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, Minijail)>, +} + +impl PciDeviceList { + pub fn new() -> Self { + PciDeviceList { + devices: Vec::new(), + } + } + + pub fn add_device(&mut self, device: Box, 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>>, +} + +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(&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, + ) +}