| // 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::os::unix::io::RawFd; |
| use std::sync::Arc; |
| |
| use byteorder::{ByteOrder, LittleEndian}; |
| use sync::Mutex; |
| |
| use crate::pci::pci_configuration::{ |
| PciBridgeSubclass, PciClassCode, PciConfiguration, PciHeaderType, |
| }; |
| use crate::pci::pci_device::PciDevice; |
| use crate::BusDevice; |
| |
| // A PciDevice that holds the root hub's configuration. |
| struct PciRootConfiguration { |
| config: PciConfiguration, |
| } |
| |
| impl PciDevice for PciRootConfiguration { |
| fn debug_label(&self) -> String { |
| "pci root device".to_owned() |
| } |
| fn keep_fds(&self) -> Vec<RawFd> { |
| Vec::new() |
| } |
| 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, |
| /// Devices attached to this bridge. |
| devices: Vec<Arc<Mutex<dyn BusDevice>>>, |
| } |
| |
| impl PciRoot { |
| /// Create an empty PCI root bus. |
| pub fn new() -> Self { |
| PciRoot { |
| root_configuration: PciRootConfiguration { |
| config: PciConfiguration::new( |
| 0, |
| 0, |
| 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>>) { |
| self.devices.push(device); |
| } |
| |
| 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.root_configuration.config_register_read(register) |
| } |
| dev_num => self |
| .devices |
| .get(dev_num - 1) |
| .map_or(0xffff_ffff, |d| d.lock().config_register_read(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.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().config_register_write(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), |
| (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 PciConfigIo { |
| fn debug_label(&self) -> String { |
| format!("pci config io-port 0x{:03x}", self.config_address) |
| } |
| |
| 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 debug_label(&self) -> String { |
| format!("pci config mmio") |
| } |
| |
| 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 > u32::max_value() as u64 { |
| 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 > u32::max_value() as u64 { |
| 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) |
| } |