| // 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::fmt::{self, Display}; |
| |
| use crate::pci::PciInterruptPin; |
| |
| // 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 { |
| 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(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(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().skip(2) { |
| self.write_byte_internal(cap_offset + i, *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 |
| } |
| } |
| |
| impl Default for PciBarConfiguration { |
| fn default() -> Self { |
| PciBarConfiguration { |
| reg_idx: 0, |
| addr: 0, |
| size: 0, |
| region_type: PciBarRegionType::Memory32BitRegion, |
| 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 data_model::DataInit; |
| |
| use super::*; |
| |
| #[repr(packed)] |
| #[derive(Clone, Copy)] |
| #[allow(dead_code)] |
| struct TestCap { |
| _vndr: u8, |
| _next: u8, |
| len: u8, |
| foo: u8, |
| } |
| |
| // It is safe to implement DataInit; all members are simple numbers and any value is valid. |
| unsafe impl DataInit 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); |
| } |
| } |