#![no_std]
pub mod capability;
pub mod device_type;
mod register;
pub use register::{CommandRegister, DevselTiming, StatusRegister};
use crate::capability::CapabilityIterator;
use bit_field::BitField;
use core::fmt;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub struct PciAddress(u32);
impl PciAddress {
pub fn new(segment: u16, bus: u8, device: u8, function: u8) -> PciAddress {
let mut result = 0;
result.set_bits(0..3, function as u32);
result.set_bits(3..8, device as u32);
result.set_bits(8..16, bus as u32);
result.set_bits(16..32, segment as u32);
PciAddress(result)
}
pub fn segment(&self) -> u16 {
self.0.get_bits(16..32) as u16
}
pub fn bus(&self) -> u8 {
self.0.get_bits(8..16) as u8
}
pub fn device(&self) -> u8 {
self.0.get_bits(3..8) as u8
}
pub fn function(&self) -> u8 {
self.0.get_bits(0..3) as u8
}
}
impl fmt::Display for PciAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:04x}:{:02x}:{:02x}.{}", self.segment(), self.bus(), self.device(), self.function())
}
}
impl fmt::Debug for PciAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self)
}
}
pub type VendorId = u16;
pub type DeviceId = u16;
pub type DeviceRevision = u8;
pub type BaseClass = u8;
pub type SubClass = u8;
pub type Interface = u8;
pub type SubsystemId = u16;
pub type SubsystemVendorId = u16;
pub type InterruptLine = u8;
pub type InterruptPin = u8;
pub trait ConfigRegionAccess {
unsafe fn read(&self, address: PciAddress, offset: u16) -> u32;
unsafe fn write(&self, address: PciAddress, offset: u16, value: u32);
}
impl<T: ConfigRegionAccess + ?Sized> ConfigRegionAccess for &T {
#[inline]
unsafe fn read(&self, address: PciAddress, offset: u16) -> u32 {
(**self).read(address, offset)
}
#[inline]
unsafe fn write(&self, address: PciAddress, offset: u16, value: u32) {
(**self).write(address, offset, value)
}
}
#[non_exhaustive]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum HeaderType {
Endpoint,
PciPciBridge,
CardBusBridge,
Unknown(u8),
}
pub struct PciHeader(PciAddress);
impl PciHeader {
pub fn new(address: PciAddress) -> PciHeader {
PciHeader(address)
}
pub fn address(&self) -> PciAddress {
self.0
}
pub fn id(&self, access: impl ConfigRegionAccess) -> (VendorId, DeviceId) {
let id = unsafe { access.read(self.0, 0x00) };
(id.get_bits(0..16) as VendorId, id.get_bits(16..32) as DeviceId)
}
pub fn header_type(&self, access: impl ConfigRegionAccess) -> HeaderType {
match unsafe { access.read(self.0, 0x0c) }.get_bits(16..23) {
0x00 => HeaderType::Endpoint,
0x01 => HeaderType::PciPciBridge,
0x02 => HeaderType::CardBusBridge,
t => HeaderType::Unknown(t as u8),
}
}
pub fn has_multiple_functions(&self, access: impl ConfigRegionAccess) -> bool {
unsafe { access.read(self.0, 0x0c) }.get_bit(23)
}
pub fn revision_and_class(
&self,
access: impl ConfigRegionAccess,
) -> (DeviceRevision, BaseClass, SubClass, Interface) {
let field = unsafe { access.read(self.0, 0x08) };
(
field.get_bits(0..8) as DeviceRevision,
field.get_bits(24..32) as BaseClass,
field.get_bits(16..24) as SubClass,
field.get_bits(8..16) as Interface,
)
}
pub fn status(&self, access: impl ConfigRegionAccess) -> StatusRegister {
let data = unsafe { access.read(self.0, 0x4).get_bits(16..32) };
StatusRegister::new(data as u16)
}
pub fn command(&self, access: impl ConfigRegionAccess) -> CommandRegister {
let data = unsafe { access.read(self.0, 0x4).get_bits(0..16) };
CommandRegister::from_bits_retain(data as u16)
}
pub fn update_command<F>(&mut self, access: impl ConfigRegionAccess, f: F)
where
F: FnOnce(CommandRegister) -> CommandRegister,
{
let mut data = unsafe { access.read(self.0, 0x4) };
let new_command = f(CommandRegister::from_bits_retain(data.get_bits(0..16) as u16));
data.set_bits(0..16, new_command.bits() as u32);
unsafe {
access.write(self.0, 0x4, data);
}
}
}
pub struct EndpointHeader(PciAddress);
impl EndpointHeader {
pub fn from_header(header: PciHeader, access: impl ConfigRegionAccess) -> Option<EndpointHeader> {
match header.header_type(access) {
HeaderType::Endpoint => Some(EndpointHeader(header.0)),
_ => None,
}
}
pub fn header(&self) -> PciHeader {
PciHeader(self.0)
}
pub fn status(&self, access: impl ConfigRegionAccess) -> StatusRegister {
self.header().status(access)
}
pub fn command(&self, access: impl ConfigRegionAccess) -> CommandRegister {
self.header().command(access)
}
pub fn update_command<F>(&mut self, access: impl ConfigRegionAccess, f: F)
where
F: FnOnce(CommandRegister) -> CommandRegister,
{
self.header().update_command(access, f);
}
pub fn capability_pointer(&self, access: impl ConfigRegionAccess) -> u16 {
let status = self.status(&access);
if status.has_capability_list() {
unsafe { access.read(self.0, 0x34).get_bits(0..8) as u16 }
} else {
0
}
}
pub fn capabilities<T: ConfigRegionAccess>(&self, access: T) -> CapabilityIterator<T> {
let pointer = self.capability_pointer(&access);
CapabilityIterator::new(self.0, pointer, access)
}
pub fn subsystem(&self, access: impl ConfigRegionAccess) -> (SubsystemId, SubsystemVendorId) {
let data = unsafe { access.read(self.0, 0x2c) };
(data.get_bits(16..32) as u16, data.get_bits(0..16) as u16)
}
pub fn bar(&self, slot: u8, access: impl ConfigRegionAccess) -> Option<Bar> {
if slot >= 6 {
return None;
}
let offset = 0x10 + (slot as u16) * 4;
let bar = unsafe { access.read(self.0, offset) };
if !bar.get_bit(0) {
let prefetchable = bar.get_bit(3);
let address = bar.get_bits(4..32) << 4;
match bar.get_bits(1..3) {
0b00 => {
let size = unsafe {
access.write(self.0, offset, 0xffffffff);
let mut readback = access.read(self.0, offset);
access.write(self.0, offset, address);
if readback == 0x0 {
return None;
}
readback.set_bits(0..4, 0);
1 << readback.trailing_zeros()
};
Some(Bar::Memory32 { address, size, prefetchable })
}
0b10 => {
if slot >= 5 {
return None;
}
let address_upper = unsafe { access.read(self.0, offset + 4) };
let size = unsafe {
access.write(self.0, offset, 0xffffffff);
access.write(self.0, offset + 4, 0xffffffff);
let mut readback_low = access.read(self.0, offset);
let readback_high = access.read(self.0, offset + 4);
access.write(self.0, offset, address);
access.write(self.0, offset + 4, address_upper);
readback_low.set_bits(0..4, 0);
if readback_low != 0 {
(1 << readback_low.trailing_zeros()) as u64
} else {
1u64 << ((readback_high.trailing_zeros() + 32) as u64)
}
};
let address = {
let mut address = address as u64;
address.set_bits(32..64, address_upper as u64);
address
};
Some(Bar::Memory64 { address, size, prefetchable })
}
_ => panic!("BAR Memory type is reserved!"),
}
} else {
Some(Bar::Io { port: bar.get_bits(2..32) << 2 })
}
}
pub unsafe fn write_bar(
&mut self,
slot: u8,
access: impl ConfigRegionAccess,
value: usize,
) -> Result<(), BarWriteError> {
match self.bar(slot, &access) {
Some(Bar::Memory64 { .. }) => {
let offset = 0x10 + (slot as u16) * 4;
unsafe {
access.write(self.0, offset, value.get_bits(0..32) as u32);
access.write(self.0, offset + 4, value.get_bits(32..64) as u32);
}
Ok(())
}
Some(Bar::Memory32 { .. }) | Some(Bar::Io { .. }) => {
if value > u32::MAX as usize {
return Err(BarWriteError::InvalidValue);
}
let offset = 0x10 + (slot as u16) * 4;
unsafe {
access.write(self.0, offset, value as u32);
}
Ok(())
}
None => Err(BarWriteError::NoSuchBar),
}
}
pub fn interrupt(&self, access: impl ConfigRegionAccess) -> (InterruptPin, InterruptLine) {
let data = unsafe { access.read(self.0, 0x3c) };
(data.get_bits(8..16) as u8, data.get_bits(0..8) as u8)
}
pub fn update_interrupt<F>(&mut self, access: impl ConfigRegionAccess, f: F)
where
F: FnOnce((InterruptPin, InterruptLine)) -> (InterruptPin, InterruptLine),
{
let mut data = unsafe { access.read(self.0, 0x3c) };
let (new_pin, new_line) = f((data.get_bits(8..16) as u8, data.get_bits(0..8) as u8));
data.set_bits(8..16, new_pin.into());
data.set_bits(0..8, new_line.into());
unsafe {
access.write(self.0, 0x3c, data);
}
}
}
pub struct PciPciBridgeHeader(PciAddress);
impl PciPciBridgeHeader {
pub fn from_header(header: PciHeader, access: impl ConfigRegionAccess) -> Option<PciPciBridgeHeader> {
match header.header_type(access) {
HeaderType::PciPciBridge => Some(PciPciBridgeHeader(header.0)),
_ => None,
}
}
pub fn header(&self) -> PciHeader {
PciHeader(self.0)
}
pub fn status(&self, access: impl ConfigRegionAccess) -> StatusRegister {
self.header().status(access)
}
pub fn command(&self, access: impl ConfigRegionAccess) -> CommandRegister {
self.header().command(access)
}
pub fn update_command<F>(&mut self, access: impl ConfigRegionAccess, f: F)
where
F: FnOnce(CommandRegister) -> CommandRegister,
{
self.header().update_command(access, f);
}
pub fn primary_bus_number(&self, access: impl ConfigRegionAccess) -> u8 {
let data = unsafe { access.read(self.0, 0x18).get_bits(0..8) };
data as u8
}
pub fn secondary_bus_number(&self, access: impl ConfigRegionAccess) -> u8 {
let data = unsafe { access.read(self.0, 0x18).get_bits(8..16) };
data as u8
}
pub fn subordinate_bus_number(&self, access: impl ConfigRegionAccess) -> u8 {
let data = unsafe { access.read(self.0, 0x18).get_bits(16..24) };
data as u8
}
pub fn update_bus_number<F>(&self, access: impl ConfigRegionAccess, f: F)
where
F: FnOnce(BusNumber) -> BusNumber,
{
let mut data = unsafe { access.read(self.0, 0x18) };
let new_bus = f(BusNumber {
primary: data.get_bits(0..8) as u8,
secondary: data.get_bits(8..16) as u8,
subordinate: data.get_bits(16..24) as u8,
});
data.set_bits(16..24, new_bus.subordinate.into());
data.set_bits(8..16, new_bus.secondary.into());
data.set_bits(0..8, new_bus.primary.into());
unsafe {
access.write(self.0, 0x18, data);
}
}
}
pub struct BusNumber {
pub primary: u8,
pub secondary: u8,
pub subordinate: u8,
}
pub const MAX_BARS: usize = 6;
#[derive(Clone, Copy, Debug)]
pub enum Bar {
Memory32 { address: u32, size: u32, prefetchable: bool },
Memory64 { address: u64, size: u64, prefetchable: bool },
Io { port: u32 },
}
impl Bar {
pub fn unwrap_io(self) -> u32 {
match self {
Bar::Io { port } => port,
Bar::Memory32 { .. } | Bar::Memory64 { .. } => panic!("expected IO BAR, found memory BAR"),
}
}
pub fn unwrap_mem(self) -> (usize, usize) {
match self {
Bar::Memory32 { address, size, prefetchable: _ } => (address as usize, size as usize),
Bar::Memory64 { address, size, prefetchable: _ } => (
address.try_into().expect("conversion from 64bit BAR to usize failed"),
size.try_into().expect("conversion from 64bit BAR to usize failed"),
),
Bar::Io { .. } => panic!("expected memory BAR, found IO BAR"),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum BarWriteError {
NoSuchBar,
InvalidValue,
}