pub mod bar;
#[cfg(test)]
mod tests;
pub use bar::{Bar, BarKind, Bars};
use alloc::collections::BTreeMap;
use alloc::format;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt;
use crate::core::error::{BusError, Error, Result};
use crate::core::space::{AccessConstraints, MemAttrs, MemOps, MemResult};
use crate::core::sync::{LockRank, Mutex};
use crate::core::value::{Endian, Width};
pub const CONFIG_SPACE_LEN: u16 = 0x100;
pub const MAX_DEVICE: u8 = 31;
pub const MAX_FUNCTION: u8 = 7;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Bdf {
pub bus: u8,
pub device: u8,
pub function: u8,
}
impl Bdf {
pub fn new(bus: u8, device: u8, function: u8) -> Result<Bdf> {
if device > MAX_DEVICE || function > MAX_FUNCTION {
return Err(Error::Config {
at: format!("{bus:02x}:{device:02x}.{function}"),
message: format!(
"a PCI bus carries device numbers 0-{MAX_DEVICE} and function \
numbers 0-{MAX_FUNCTION}"
),
});
}
Ok(Bdf {
bus,
device,
function,
})
}
}
impl fmt::Display for Bdf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:02x}:{:02x}.{}", self.bus, self.device, self.function)
}
}
pub trait PciFunction: fmt::Debug + Send + Sync {
fn config_read(&self, offset: u16, dst: &mut [u8], attrs: MemAttrs);
fn config_write(&self, offset: u16, src: &[u8], attrs: MemAttrs);
}
pub struct PciBus {
functions: Mutex<BTreeMap<Bdf, Arc<dyn PciFunction>>>,
}
impl fmt::Debug for PciBus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("PciBus");
match self.functions.try_lock() {
Some(map) => s.field("functions", &map.len()),
None => s.field("functions", &"<in use>"),
};
s.finish()
}
}
impl Default for PciBus {
fn default() -> PciBus {
PciBus::new()
}
}
impl PciBus {
#[must_use]
pub fn new() -> PciBus {
PciBus {
functions: Mutex::with_rank(LockRank::DEVICE, BTreeMap::new()),
}
}
pub fn attach(&self, at: Bdf, function: Arc<dyn PciFunction>) -> Result<()> {
let mut map = self.functions.lock();
if map.contains_key(&at) {
return Err(Error::Config {
at: format!("{at}"),
message: String::from("two PCI functions cannot share one address"),
});
}
map.insert(at, function);
Ok(())
}
pub fn detach(&self, at: Bdf) -> bool {
self.functions.lock().remove(&at).is_some()
}
#[must_use]
pub fn function(&self, at: Bdf) -> Option<Arc<dyn PciFunction>> {
self.functions.lock().get(&at).cloned()
}
#[must_use]
pub fn addresses(&self) -> Vec<Bdf> {
self.functions.lock().keys().copied().collect()
}
pub fn config_read(&self, at: Bdf, offset: u16, dst: &mut [u8], attrs: MemAttrs) {
match self.function(at) {
Some(f) => f.config_read(offset, dst, attrs),
None => dst.fill(0xff),
}
}
pub fn config_write(&self, at: Bdf, offset: u16, src: &[u8], attrs: MemAttrs) {
if let Some(f) = self.function(at) {
f.config_write(offset, src, attrs);
}
}
}
pub const CONFIG_PORT_WINDOW_LEN: u64 = 8;
const CONFIG_ENABLE: u32 = 0x8000_0000;
const CONFADD_MASK: u32 = CONFIG_ENABLE | 0x00ff_fffc;
#[derive(Debug)]
pub struct ConfigPorts {
bus: Arc<PciBus>,
address: Mutex<u32>,
passthrough: Mutex<Option<Arc<dyn MemOps>>>,
}
impl ConfigPorts {
#[must_use]
pub fn new(bus: Arc<PciBus>) -> ConfigPorts {
ConfigPorts {
bus,
address: Mutex::with_rank(LockRank::LEAF, 0),
passthrough: Mutex::with_rank(LockRank::LEAF, None),
}
}
#[must_use]
pub fn bus(&self) -> &Arc<PciBus> {
&self.bus
}
pub fn set_passthrough(&self, ops: Arc<dyn MemOps>) {
*self.passthrough.lock() = Some(ops);
}
#[must_use]
pub fn passthrough(&self) -> Option<Arc<dyn MemOps>> {
self.passthrough.lock().clone()
}
#[must_use]
pub fn address(&self) -> u32 {
*self.address.lock()
}
pub fn set_address(&self, value: u32) {
*self.address.lock() = value & CONFADD_MASK;
}
pub fn reset(&self) {
*self.address.lock() = 0;
}
fn target(&self, offset: u64) -> Option<(Bdf, u16)> {
let addr = *self.address.lock();
if addr & CONFIG_ENABLE == 0 {
return None;
}
let bdf = Bdf {
bus: ((addr >> 16) & 0xff) as u8,
device: ((addr >> 11) & 0x1f) as u8,
function: ((addr >> 8) & 0x07) as u8,
};
let register = ((addr & 0xfc) as u16) | (offset & 0x3) as u16;
Some((bdf, register))
}
}
impl MemOps for ConfigPorts {
fn read(&self, offset: u64, dst: &mut [u8], attrs: MemAttrs) -> MemResult {
let len = dst.len() as u64;
if offset.saturating_add(len) > CONFIG_PORT_WINDOW_LEN {
return Err(BusError::BadAccess);
}
if offset < 4 {
if offset == 0 && len == 4 {
dst.copy_from_slice(&self.address().to_le_bytes());
return Ok(());
}
if offset + len > 4 {
return Err(BusError::BadAccess);
}
return match self.passthrough() {
Some(ops) => ops.read(offset, dst, attrs),
None => {
dst.fill(0xff);
Ok(())
}
};
}
if offset - 4 + len > 4 {
return Err(BusError::BadAccess);
}
match self.target(offset - 4) {
None => dst.fill(0xff),
Some((bdf, register)) => self.bus.config_read(bdf, register, dst, attrs),
}
Ok(())
}
fn write(&self, offset: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
let len = src.len() as u64;
if offset.saturating_add(len) > CONFIG_PORT_WINDOW_LEN {
return Err(BusError::BadAccess);
}
if attrs.debug {
return Err(BusError::BadAccess);
}
if offset < 4 {
if offset == 0 && len == 4 {
let value = u32::from_le_bytes([src[0], src[1], src[2], src[3]]);
*self.address.lock() = value & CONFADD_MASK;
return Ok(());
}
if offset + len > 4 {
return Err(BusError::BadAccess);
}
return match self.passthrough() {
Some(ops) => ops.write(offset, src, attrs),
None => Ok(()),
};
}
if offset - 4 + len > 4 {
return Err(BusError::BadAccess);
}
if let Some((bdf, register)) = self.target(offset - 4) {
self.bus.config_write(bdf, register, src, attrs);
}
Ok(())
}
fn constraints(&self) -> AccessConstraints {
AccessConstraints::IO
.with_widths(Width::U8, Width::U32)
.with_endian(Endian::Little)
}
}
#[derive(Debug, Clone)]
pub struct ConfigSpace {
bytes: [u8; CONFIG_SPACE_LEN as usize],
writable: [bool; CONFIG_SPACE_LEN as usize],
}
impl Default for ConfigSpace {
fn default() -> ConfigSpace {
ConfigSpace::new()
}
}
impl ConfigSpace {
#[must_use]
pub fn new() -> ConfigSpace {
ConfigSpace {
bytes: [0; CONFIG_SPACE_LEN as usize],
writable: [false; CONFIG_SPACE_LEN as usize],
}
}
pub fn hardwire(&mut self, offset: u16, value: u32, len: u16) {
let bytes = value.to_le_bytes();
for i in 0..len.min(4) {
self.set_byte(offset.saturating_add(i), bytes[i as usize]);
}
}
pub fn allow(&mut self, offset: u16, len: u16) {
for i in 0..len {
let at = offset.saturating_add(i) as usize;
if let Some(slot) = self.writable.get_mut(at) {
*slot = true;
}
}
}
#[must_use]
pub fn byte(&self, offset: u16) -> u8 {
self.bytes.get(offset as usize).copied().unwrap_or(0)
}
pub fn set_byte(&mut self, offset: u16, value: u8) {
if let Some(slot) = self.bytes.get_mut(offset as usize) {
*slot = value;
}
}
#[must_use]
pub fn is_writable(&self, offset: u16) -> bool {
self.writable.get(offset as usize).copied().unwrap_or(false)
}
pub fn read(&self, offset: u16, dst: &mut [u8]) {
for (i, slot) in dst.iter_mut().enumerate() {
*slot = self.byte(offset.saturating_add(i as u16));
}
}
pub fn write(&mut self, offset: u16, src: &[u8]) -> bool {
let mut changed = false;
for (i, byte) in src.iter().enumerate() {
let at = offset.saturating_add(i as u16) as usize;
if at < self.bytes.len() && self.writable[at] && self.bytes[at] != *byte {
self.bytes[at] = *byte;
changed = true;
}
}
changed
}
#[must_use]
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
pub fn restore(&mut self, bytes: &[u8]) {
for (at, byte) in bytes.iter().enumerate().take(self.bytes.len()) {
if self.writable[at] {
self.bytes[at] = *byte;
}
}
}
}
pub mod config {
pub const VENDOR_ID: u16 = 0x00;
pub const DEVICE_ID: u16 = 0x02;
pub const COMMAND: u16 = 0x04;
pub const STATUS: u16 = 0x06;
pub const REVISION_ID: u16 = 0x08;
pub const CLASS_CODE: u16 = 0x09;
pub const CACHE_LINE_SIZE: u16 = 0x0c;
pub const LATENCY_TIMER: u16 = 0x0d;
pub const HEADER_TYPE: u16 = 0x0e;
pub const BIST: u16 = 0x0f;
pub const BAR0: u16 = 0x10;
pub const EXPANSION_ROM: u16 = 0x30;
pub const INTERRUPT_LINE: u16 = 0x3c;
pub const INTERRUPT_PIN: u16 = 0x3d;
pub const COMMAND_IO: u16 = 0x0001;
pub const COMMAND_MEMORY: u16 = 0x0002;
pub const COMMAND_MASTER: u16 = 0x0004;
pub const CLASS_BRIDGE: u8 = 0x06;
pub const SUBCLASS_HOST_BRIDGE: u8 = 0x00;
pub const CLASS_DISPLAY: u8 = 0x03;
pub const SUBCLASS_VGA: u8 = 0x00;
pub const VENDOR_INTEL: u16 = 0x8086;
}
pub mod buses {
use super::PciBus;
use alloc::sync::Arc;
use crate::core::error::Result;
use crate::core::hosts::{HostKind, HostObjects};
use crate::core::props::Props;
pub const KIND: HostKind = HostKind::new("pci-bus");
pub fn open(hosts: &HostObjects, name: &str) -> Result<Arc<PciBus>> {
hosts.open(KIND, name, PciBus::new)
}
pub fn attach(props: &Props, name: &str) -> Result<Arc<PciBus>> {
props.host(KIND, name, PciBus::new)
}
pub fn get(hosts: &HostObjects, name: &str) -> Result<Option<Arc<PciBus>>> {
hosts.get(KIND, name)
}
pub fn close(hosts: &HostObjects, name: &str) -> bool {
hosts.close(KIND, name)
}
}