395 lines
12 KiB
Rust
395 lines
12 KiB
Rust
use alloc::sync::Arc;
|
|
use legacy::PciLegacyConfigurationSpace;
|
|
|
|
use super::{PciAddress, PciBaseAddress, PciCapability, PciCapabilityId, PciEcam};
|
|
use crate::{device::PciInterruptPin, PciCommandRegister, PciStatusRegister};
|
|
|
|
pub(super) mod ecam;
|
|
pub(super) mod legacy;
|
|
|
|
macro_rules! pci_config_field_getter {
|
|
($self:ident, u32, $offset:expr) => {
|
|
$self.read_u32($offset)
|
|
};
|
|
|
|
($self:ident, u16, $offset:expr) => {
|
|
$self.read_u16($offset)
|
|
};
|
|
|
|
($self:ident, u8, $offset:expr) => {
|
|
$self.read_u8($offset)
|
|
};
|
|
}
|
|
|
|
macro_rules! pci_config_field_setter {
|
|
($self:ident, u32, $offset:expr, $value:expr) => {
|
|
$self.write_u32($offset, $value)
|
|
};
|
|
|
|
($self:ident, u16, $offset:expr, $value:expr) => {{
|
|
$self.write_u16($offset, $value)
|
|
}};
|
|
|
|
($self:ident, u8, $offset:expr, $value:expr) => {
|
|
$self.write_u8($offset, $value)
|
|
};
|
|
}
|
|
|
|
macro_rules! pci_config_field {
|
|
(
|
|
$offset:expr => $ty:ident,
|
|
$(#[$getter_meta:meta])* $getter:ident
|
|
$(, $(#[$setter_meta:meta])* $setter:ident)?
|
|
) => {
|
|
$(#[$getter_meta])*
|
|
fn $getter(&self) -> $ty {
|
|
pci_config_field_getter!(self, $ty, $offset)
|
|
}
|
|
|
|
$(
|
|
$(#[$setter_meta])*
|
|
fn $setter(&self, value: $ty) {
|
|
pci_config_field_setter!(self, $ty, $offset, value)
|
|
}
|
|
)?
|
|
};
|
|
}
|
|
|
|
/// Describes a configuration space access method for a PCI device
|
|
#[derive(Debug, Clone)]
|
|
pub enum PciConfigSpace {
|
|
/// Legacy configuration space.
|
|
///
|
|
/// See [PciLegacyConfigurationSpace].
|
|
Legacy(PciLegacyConfigurationSpace),
|
|
|
|
/// Enhanced Configuration Access Mechanism (PCIe).
|
|
///
|
|
/// See [PciEcam].
|
|
Ecam(PciEcam),
|
|
}
|
|
|
|
pub struct CapabilityIterator<'s, S: PciConfigurationSpace + ?Sized> {
|
|
space: &'s S,
|
|
current: Option<usize>,
|
|
}
|
|
|
|
impl<S: PciConfigurationSpace + ?Sized> Iterator for CapabilityIterator<'_, S> {
|
|
type Item = (Option<PciCapabilityId>, usize, usize);
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
let offset = self.current? & !0x3;
|
|
|
|
let id = PciCapabilityId::try_from(self.space.read_u8(offset)).ok();
|
|
let len = self.space.read_u8(offset + 2);
|
|
let next_pointer = self.space.read_u8(offset + 1);
|
|
|
|
self.current = if next_pointer != 0 {
|
|
Some(next_pointer as usize)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
Some((id, offset, len as usize))
|
|
}
|
|
}
|
|
|
|
/// Interface for accessing the configuration space of a device
|
|
pub trait PciConfigurationSpace {
|
|
/// Reads a 32-bit value from the device configuration space.
|
|
///
|
|
/// # Note
|
|
///
|
|
/// The `offset` must be u32-aligned.
|
|
fn read_u32(&self, offset: usize) -> u32;
|
|
|
|
/// Writes a 32-bit value to the device configuration space.
|
|
///
|
|
/// # Note
|
|
///
|
|
/// The `offset` must be u32-aligned.
|
|
fn write_u32(&self, offset: usize, value: u32);
|
|
|
|
/// Reads a 16-bit value from the device configuration space.
|
|
///
|
|
/// # Note
|
|
///
|
|
/// The `offset` must be u16-aligned.
|
|
fn read_u16(&self, offset: usize) -> u16 {
|
|
assert_eq!(offset & 1, 0);
|
|
let value = self.read_u32(offset & !3);
|
|
(value >> ((offset & 3) * 8)) as u16
|
|
}
|
|
|
|
/// Reads a byte from the device configuration space
|
|
fn read_u8(&self, offset: usize) -> u8 {
|
|
let value = self.read_u32(offset & !3);
|
|
(value >> ((offset & 3) * 8)) as u8
|
|
}
|
|
|
|
/// Writes a 16-bit value to the device configuration space.
|
|
///
|
|
/// # Note
|
|
///
|
|
/// The `offset` must be u16-aligned.
|
|
fn write_u16(&self, offset: usize, value: u16) {
|
|
let shift = ((offset >> 1) & 1) << 4;
|
|
assert_eq!(offset & 1, 0);
|
|
let mut tmp = self.read_u32(offset & !3);
|
|
tmp &= !(0xFFFF << shift);
|
|
tmp |= (value as u32) << shift;
|
|
self.write_u32(offset & !3, tmp);
|
|
}
|
|
|
|
/// Writes a byte to the device configuration space
|
|
fn write_u8(&self, _offset: usize, _value: u16) {
|
|
todo!()
|
|
}
|
|
|
|
/// Returns `true` if the device is present on the bus (i.e. configuration space is not filled
|
|
/// with only 1's)
|
|
fn is_valid(&self) -> bool {
|
|
self.vendor_id() != 0xFFFF && self.device_id() != 0xFFFF
|
|
}
|
|
|
|
pci_config_field!(
|
|
0x00 => u16,
|
|
#[doc = "Returns the Vendor ID"] vendor_id
|
|
);
|
|
pci_config_field!(0x02 => u16,
|
|
#[doc = "Returns the Device ID"] device_id
|
|
);
|
|
pci_config_field!(
|
|
0x04 => u16,
|
|
#[doc = "Returns the value of the command register"] command,
|
|
#[doc = "Writes to the command word register"] set_command
|
|
);
|
|
pci_config_field!(
|
|
0x06 => u16,
|
|
#[doc = "Returns the value of the status register"] status
|
|
);
|
|
|
|
pci_config_field!(
|
|
0x08 => u8,
|
|
#[doc = "Returns the device Revision ID"]
|
|
rev_id
|
|
);
|
|
pci_config_field!(
|
|
0x09 => u8,
|
|
#[doc = "Returns the device Prog IF field"]
|
|
prog_if
|
|
);
|
|
pci_config_field!(
|
|
0x0A => u8,
|
|
#[doc = "Returns the device Subclass field"]
|
|
subclass
|
|
);
|
|
pci_config_field!(
|
|
0x0B => u8,
|
|
#[doc = "Returns the device Class Code field"]
|
|
class_code
|
|
);
|
|
|
|
// ...
|
|
pci_config_field!(
|
|
0x0E => u8,
|
|
#[doc = "Returns the header type of the device"]
|
|
header_type
|
|
);
|
|
pci_config_field!(
|
|
0x19 => u8,
|
|
#[doc = r#"
|
|
Returns the secondary bus number associated with this device
|
|
|
|
# Note
|
|
|
|
The function is only valid for devices with `header_type() == 1`
|
|
"#]
|
|
secondary_bus
|
|
);
|
|
pci_config_field!(
|
|
0x34 => u8,
|
|
#[doc =
|
|
r"Returns the offset within the configuration space where the Capabilities List
|
|
is located. Only valid if the corresponding Status Register bit is set"
|
|
]
|
|
capability_pointer
|
|
);
|
|
|
|
fn interrupt_pin(&self) -> Option<PciInterruptPin> {
|
|
PciInterruptPin::try_from(self.read_u8(0x3D) as u32).ok()
|
|
}
|
|
|
|
fn interrupt_line(&self) -> Option<u8> {
|
|
let value = self.read_u8(0x3C);
|
|
if value < 16 {
|
|
Some(value)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// # Safety
|
|
///
|
|
/// This function is only meant to be called before the device has seen any use by the OS,
|
|
/// it has not been tested outside of this use case.
|
|
unsafe fn bar_size(&self, index: usize) -> usize {
|
|
let cmd = self.command();
|
|
|
|
// Disable I/O and memory
|
|
self.set_command(
|
|
cmd & !(PciCommandRegister::ENABLE_IO | PciCommandRegister::ENABLE_MEMORY).bits(),
|
|
);
|
|
|
|
let orig_value = self.bar(index).unwrap();
|
|
// TODO preserve prefetch bit
|
|
let mask_value = match orig_value {
|
|
PciBaseAddress::Io(_) => PciBaseAddress::Io(0xFFFC),
|
|
PciBaseAddress::Memory32(_) => PciBaseAddress::Memory32(0xFFFFFFF0),
|
|
PciBaseAddress::Memory64(_) => PciBaseAddress::Memory64(0xFFFFFFFFFFFFFFF0),
|
|
};
|
|
self.set_bar(index, mask_value);
|
|
let new_value = self.bar(index).unwrap();
|
|
|
|
let size = match new_value {
|
|
PciBaseAddress::Io(address) if address != 0 => ((!address) + 1) as usize,
|
|
PciBaseAddress::Memory32(address) if address != 0 => ((!address) + 1) as usize,
|
|
PciBaseAddress::Memory64(address) if address != 0 => ((!address) + 1) as usize,
|
|
_ => 0,
|
|
};
|
|
|
|
self.set_bar(index, orig_value);
|
|
self.set_command(cmd);
|
|
|
|
size
|
|
}
|
|
|
|
/// Updates the value of the Base Address Register with given index.
|
|
///
|
|
/// # Note
|
|
///
|
|
/// The function is only valid for devices with `header_type() == 0`
|
|
///
|
|
/// The `index` corresponds to the actual configuration space BAR index.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// Precondition: the device must have memory access disabled through its command register
|
|
/// prior to setting a BAR.
|
|
unsafe fn set_bar(&self, index: usize, value: PciBaseAddress) {
|
|
assert!(index < 6);
|
|
|
|
match value {
|
|
PciBaseAddress::Io(value) => {
|
|
self.write_u32(0x10 + index * 4, ((value as u32) & !0x3) | 1)
|
|
}
|
|
PciBaseAddress::Memory32(address) => self.write_u32(0x10 + index * 4, address & !0xF),
|
|
PciBaseAddress::Memory64(address) => {
|
|
self.write_u32(0x10 + index * 4, ((address as u32) & !0xF) | (2 << 1));
|
|
self.write_u32(0x10 + (index + 1) * 4, (address >> 32) as u32);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns the value of the Base Address Register with given index.
|
|
///
|
|
/// # Note
|
|
///
|
|
/// The function is only valid for devices with `header_type() == 0`
|
|
///
|
|
/// The `index` corresponds to the actual configuration space BAR index, i.e. if a 64-bit
|
|
/// address occupies [BAR0, BAR1] and BAR 1 is requested, the function will return [None].
|
|
fn bar(&self, index: usize) -> Option<PciBaseAddress> {
|
|
assert!(index < 6);
|
|
|
|
if index % 2 == 0 {
|
|
let w0 = self.read_u32(0x10 + index * 4);
|
|
|
|
match w0 & 1 {
|
|
0 => match (w0 >> 1) & 3 {
|
|
0 => {
|
|
// 32-bit memory BAR
|
|
Some(PciBaseAddress::Memory32(w0 & !0xF))
|
|
}
|
|
2 => {
|
|
// 64-bit memory BAR
|
|
let w1 = self.read_u32(0x10 + (index + 1) * 4);
|
|
Some(PciBaseAddress::Memory64(
|
|
((w1 as u64) << 32) | ((w0 as u64) & !0xF),
|
|
))
|
|
}
|
|
_ => unimplemented!(),
|
|
},
|
|
1 => Some(PciBaseAddress::Io((w0 as u16) & !0x3)),
|
|
_ => unreachable!(),
|
|
}
|
|
} else {
|
|
let prev_w0 = self.read_u32(0x10 + (index - 1) * 4);
|
|
if prev_w0 & 0x7 == 0x4 {
|
|
// Previous BAR is 64-bit memory and this one is its continuation
|
|
return None;
|
|
}
|
|
|
|
let w0 = self.read_u32(0x10 + index * 4);
|
|
|
|
match w0 & 1 {
|
|
0 => match (w0 >> 1) & 3 {
|
|
0 => {
|
|
// 32-bit memory BAR
|
|
Some(PciBaseAddress::Memory32(w0 & !0xF))
|
|
}
|
|
// TODO can 64-bit BARs not be on a 64-bit boundary?
|
|
2 => todo!(),
|
|
_ => unimplemented!(),
|
|
},
|
|
1 => todo!(),
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns an iterator over the PCI capabilities
|
|
fn capability_iter(&self) -> CapabilityIterator<Self> {
|
|
let status = PciStatusRegister::from_bits_retain(self.status());
|
|
|
|
let current = if status.contains(PciStatusRegister::CAPABILITIES_LIST) {
|
|
let ptr = self.capability_pointer() as usize;
|
|
|
|
if ptr != 0 {
|
|
Some(self.capability_pointer() as usize)
|
|
} else {
|
|
None
|
|
}
|
|
} else {
|
|
// Return an empty iterator
|
|
None
|
|
};
|
|
|
|
CapabilityIterator {
|
|
space: self,
|
|
current,
|
|
}
|
|
}
|
|
|
|
/// Locates a capability within this configuration space
|
|
fn capability<C: PciCapability>(&self) -> Option<C::CapabilityData<'_, Self>> {
|
|
self.capability_iter().find_map(|(id, offset, len)| {
|
|
if id.map_or(false, |id| id == C::ID) && C::check(self, offset, len) {
|
|
Some(C::data(self, offset, len))
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<T: PciConfigurationSpace> PciConfigurationSpace for Arc<T> {
|
|
fn read_u32(&self, offset: usize) -> u32 {
|
|
T::read_u32(self.as_ref(), offset)
|
|
}
|
|
|
|
fn write_u32(&self, offset: usize, value: u32) {
|
|
T::write_u32(self.as_ref(), offset, value);
|
|
}
|
|
}
|