480 lines
17 KiB
Rust
480 lines
17 KiB
Rust
use bitflags::bitflags;
|
|
use byteorder::{ByteOrder, LittleEndian};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use super::bar::PciBar;
|
|
use super::class::PciClass;
|
|
use super::func::ConfigReader;
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum PciHeaderError {
|
|
NoDevice,
|
|
UnknownHeaderType(u8),
|
|
}
|
|
|
|
bitflags! {
|
|
/// Flags found in the status register of a PCI device
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct PciHeaderType: u8 {
|
|
/// A general PCI device (Type 0x01).
|
|
const GENERAL = 0b00000000;
|
|
/// A PCI-to-PCI bridge device (Type 0x01).
|
|
const PCITOPCI = 0b00000001;
|
|
/// A PCI-to-PCI bridge device (Type 0x02).
|
|
const CARDBUSBRIDGE = 0b00000010;
|
|
/// A multifunction device.
|
|
const MULTIFUNCTION = 0b01000000;
|
|
/// Mask used for fetching the header type.
|
|
const HEADER_TYPE = 0b00000011;
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
|
pub struct SharedPciHeader {
|
|
vendor_id: u16,
|
|
device_id: u16,
|
|
command: u16,
|
|
status: u16,
|
|
revision: u8,
|
|
interface: u8,
|
|
subclass: u8,
|
|
class: PciClass,
|
|
cache_line_size: u8,
|
|
latency_timer: u8,
|
|
header_type: PciHeaderType,
|
|
bist: u8,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
|
pub enum PciHeader {
|
|
General {
|
|
shared: SharedPciHeader,
|
|
bars: [PciBar; 6],
|
|
cardbus_cis_ptr: u32,
|
|
subsystem_vendor_id: u16,
|
|
subsystem_id: u16,
|
|
expansion_rom_bar: u32,
|
|
cap_pointer: u8,
|
|
interrupt_line: u8,
|
|
interrupt_pin: u8,
|
|
min_grant: u8,
|
|
max_latency: u8,
|
|
},
|
|
PciToPci {
|
|
shared: SharedPciHeader,
|
|
bars: [PciBar; 2],
|
|
primary_bus_num: u8,
|
|
secondary_bus_num: u8,
|
|
subordinate_bus_num: u8,
|
|
secondary_latency_timer: u8,
|
|
io_base: u8,
|
|
io_limit: u8,
|
|
secondary_status: u16,
|
|
mem_base: u16,
|
|
mem_limit: u16,
|
|
prefetch_base: u16,
|
|
prefetch_limit: u16,
|
|
prefetch_base_upper: u32,
|
|
prefetch_limit_upper: u32,
|
|
io_base_upper: u16,
|
|
io_limit_upper: u16,
|
|
cap_pointer: u8,
|
|
expansion_rom: u32,
|
|
interrupt_line: u8,
|
|
interrupt_pin: u8,
|
|
bridge_control: u16,
|
|
},
|
|
}
|
|
|
|
impl PciHeader {
|
|
fn get_bars(bytes: &[u8], bars: &mut [PciBar]) {
|
|
let mut i = 0;
|
|
while i < bars.len() {
|
|
let offset = i * 4;
|
|
let bar_bytes = match bytes.get(offset..offset + 4) {
|
|
Some(some) => some,
|
|
None => continue,
|
|
};
|
|
|
|
match PciBar::from(LittleEndian::read_u32(bar_bytes)) {
|
|
PciBar::Memory64(mut addr) => {
|
|
let high_bytes = match bytes.get(offset + 4..offset + 8) {
|
|
Some(some) => some,
|
|
None => continue,
|
|
};
|
|
addr |= (LittleEndian::read_u32(high_bytes) as u64) << 32;
|
|
bars[i] = PciBar::Memory64(addr);
|
|
i += 2;
|
|
}
|
|
bar => {
|
|
bars[i] = bar;
|
|
i += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Parse the bytes found in the Configuration Space of the PCI device into
|
|
/// a more usable PciHeader.
|
|
pub fn from_reader<T: ConfigReader>(reader: T) -> Result<PciHeader, PciHeaderError> {
|
|
if unsafe { reader.read_u32(0) } != 0xffffffff {
|
|
// Read the initial 16 bytes and set variables used by all header types.
|
|
let bytes = unsafe { reader.read_range(0, 16) };
|
|
let vendor_id = LittleEndian::read_u16(&bytes[0..2]);
|
|
let device_id = LittleEndian::read_u16(&bytes[2..4]);
|
|
let command = LittleEndian::read_u16(&bytes[4..6]);
|
|
let status = LittleEndian::read_u16(&bytes[6..8]);
|
|
let revision = bytes[8];
|
|
let interface = bytes[9];
|
|
let subclass = bytes[10];
|
|
let class = PciClass::from(bytes[11]);
|
|
let cache_line_size = bytes[12];
|
|
let latency_timer = bytes[13];
|
|
let header_type = PciHeaderType::from_bits_truncate(bytes[14]);
|
|
let bist = bytes[15];
|
|
let shared = SharedPciHeader {
|
|
vendor_id,
|
|
device_id,
|
|
command,
|
|
status,
|
|
revision,
|
|
interface,
|
|
subclass,
|
|
class,
|
|
cache_line_size,
|
|
latency_timer,
|
|
header_type,
|
|
bist,
|
|
};
|
|
|
|
match header_type & PciHeaderType::HEADER_TYPE {
|
|
PciHeaderType::GENERAL => {
|
|
let bytes = unsafe { reader.read_range(16, 48) };
|
|
let mut bars = [PciBar::None; 6];
|
|
Self::get_bars(&bytes, &mut bars);
|
|
let cardbus_cis_ptr = LittleEndian::read_u32(&bytes[24..28]);
|
|
let subsystem_vendor_id = LittleEndian::read_u16(&bytes[28..30]);
|
|
let subsystem_id = LittleEndian::read_u16(&bytes[30..32]);
|
|
let expansion_rom_bar = LittleEndian::read_u32(&bytes[32..36]);
|
|
let cap_pointer = bytes[36];
|
|
let interrupt_line = bytes[44];
|
|
let interrupt_pin = bytes[45];
|
|
let min_grant = bytes[46];
|
|
let max_latency = bytes[47];
|
|
Ok(PciHeader::General {
|
|
shared,
|
|
bars,
|
|
cardbus_cis_ptr,
|
|
subsystem_vendor_id,
|
|
subsystem_id,
|
|
expansion_rom_bar,
|
|
cap_pointer,
|
|
interrupt_line,
|
|
interrupt_pin,
|
|
min_grant,
|
|
max_latency,
|
|
})
|
|
}
|
|
PciHeaderType::PCITOPCI => {
|
|
let bytes = unsafe { reader.read_range(16, 48) };
|
|
let mut bars = [PciBar::None; 2];
|
|
Self::get_bars(&bytes, &mut bars);
|
|
let primary_bus_num = bytes[8];
|
|
let secondary_bus_num = bytes[9];
|
|
let subordinate_bus_num = bytes[10];
|
|
let secondary_latency_timer = bytes[11];
|
|
let io_base = bytes[12];
|
|
let io_limit = bytes[13];
|
|
let secondary_status = LittleEndian::read_u16(&bytes[14..16]);
|
|
let mem_base = LittleEndian::read_u16(&bytes[16..18]);
|
|
let mem_limit = LittleEndian::read_u16(&bytes[18..20]);
|
|
let prefetch_base = LittleEndian::read_u16(&bytes[20..22]);
|
|
let prefetch_limit = LittleEndian::read_u16(&bytes[22..24]);
|
|
let prefetch_base_upper = LittleEndian::read_u32(&bytes[24..28]);
|
|
let prefetch_limit_upper = LittleEndian::read_u32(&bytes[28..32]);
|
|
let io_base_upper = LittleEndian::read_u16(&bytes[32..34]);
|
|
let io_limit_upper = LittleEndian::read_u16(&bytes[34..36]);
|
|
let cap_pointer = bytes[36];
|
|
let expansion_rom = LittleEndian::read_u32(&bytes[40..44]);
|
|
let interrupt_line = bytes[44];
|
|
let interrupt_pin = bytes[45];
|
|
let bridge_control = LittleEndian::read_u16(&bytes[46..48]);
|
|
Ok(PciHeader::PciToPci {
|
|
shared,
|
|
bars,
|
|
primary_bus_num,
|
|
secondary_bus_num,
|
|
subordinate_bus_num,
|
|
secondary_latency_timer,
|
|
io_base,
|
|
io_limit,
|
|
secondary_status,
|
|
mem_base,
|
|
mem_limit,
|
|
prefetch_base,
|
|
prefetch_limit,
|
|
prefetch_base_upper,
|
|
prefetch_limit_upper,
|
|
io_base_upper,
|
|
io_limit_upper,
|
|
cap_pointer,
|
|
expansion_rom,
|
|
interrupt_line,
|
|
interrupt_pin,
|
|
bridge_control,
|
|
})
|
|
}
|
|
id => Err(PciHeaderError::UnknownHeaderType(id.bits())),
|
|
}
|
|
} else {
|
|
Err(PciHeaderError::NoDevice)
|
|
}
|
|
}
|
|
|
|
/// Return the Header Type.
|
|
pub fn header_type(&self) -> PciHeaderType {
|
|
match self {
|
|
&PciHeader::General {
|
|
shared: SharedPciHeader { header_type, .. },
|
|
..
|
|
}
|
|
| &PciHeader::PciToPci {
|
|
shared: SharedPciHeader { header_type, .. },
|
|
..
|
|
} => header_type,
|
|
}
|
|
}
|
|
|
|
/// Return the Vendor ID field.
|
|
pub fn vendor_id(&self) -> u16 {
|
|
match self {
|
|
&PciHeader::General {
|
|
shared: SharedPciHeader { vendor_id, .. },
|
|
..
|
|
}
|
|
| &PciHeader::PciToPci {
|
|
shared: SharedPciHeader { vendor_id, .. },
|
|
..
|
|
} => vendor_id,
|
|
}
|
|
}
|
|
|
|
/// Return the Device ID field.
|
|
pub fn device_id(&self) -> u16 {
|
|
match self {
|
|
&PciHeader::General {
|
|
shared: SharedPciHeader { device_id, .. },
|
|
..
|
|
}
|
|
| &PciHeader::PciToPci {
|
|
shared: SharedPciHeader { device_id, .. },
|
|
..
|
|
} => device_id,
|
|
}
|
|
}
|
|
|
|
/// Return the Revision field.
|
|
pub fn revision(&self) -> u8 {
|
|
match self {
|
|
&PciHeader::General {
|
|
shared: SharedPciHeader { revision, .. },
|
|
..
|
|
}
|
|
| &PciHeader::PciToPci {
|
|
shared: SharedPciHeader { revision, .. },
|
|
..
|
|
} => revision,
|
|
}
|
|
}
|
|
|
|
/// Return the Interface field.
|
|
pub fn interface(&self) -> u8 {
|
|
match self {
|
|
&PciHeader::General {
|
|
shared: SharedPciHeader { interface, .. },
|
|
..
|
|
}
|
|
| &PciHeader::PciToPci {
|
|
shared: SharedPciHeader { interface, .. },
|
|
..
|
|
} => interface,
|
|
}
|
|
}
|
|
|
|
/// Return the Subclass field.
|
|
pub fn subclass(&self) -> u8 {
|
|
match self {
|
|
&PciHeader::General {
|
|
shared: SharedPciHeader { subclass, .. },
|
|
..
|
|
}
|
|
| &PciHeader::PciToPci {
|
|
shared: SharedPciHeader { subclass, .. },
|
|
..
|
|
} => subclass,
|
|
}
|
|
}
|
|
|
|
/// Return the Class field.
|
|
pub fn class(&self) -> PciClass {
|
|
match self {
|
|
&PciHeader::General {
|
|
shared: SharedPciHeader { class, .. },
|
|
..
|
|
}
|
|
| &PciHeader::PciToPci {
|
|
shared: SharedPciHeader { class, .. },
|
|
..
|
|
} => class,
|
|
}
|
|
}
|
|
|
|
/// Return the Headers BARs.
|
|
pub fn bars(&self) -> &[PciBar] {
|
|
match self {
|
|
&PciHeader::General { ref bars, .. } => bars,
|
|
&PciHeader::PciToPci { ref bars, .. } => bars,
|
|
}
|
|
}
|
|
|
|
/// Return the BAR at the given index.
|
|
///
|
|
/// # Panics
|
|
/// This function panics if the requested BAR index is beyond the length of the header
|
|
/// types BAR array.
|
|
pub fn get_bar(&self, idx: usize) -> PciBar {
|
|
match self {
|
|
&PciHeader::General { bars, .. } => {
|
|
assert!(idx < 6, "the general PCI device only has 6 BARs");
|
|
bars[idx]
|
|
}
|
|
&PciHeader::PciToPci { bars, .. } => {
|
|
assert!(idx < 2, "the general PCI device only has 2 BARs");
|
|
bars[idx]
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Return the Interrupt Line field.
|
|
pub fn interrupt_line(&self) -> u8 {
|
|
match self {
|
|
&PciHeader::General { interrupt_line, .. }
|
|
| &PciHeader::PciToPci { interrupt_line, .. } => interrupt_line,
|
|
}
|
|
}
|
|
|
|
pub fn status(&self) -> u16 {
|
|
match self {
|
|
&PciHeader::General {
|
|
shared: SharedPciHeader { status, .. },
|
|
..
|
|
}
|
|
| &PciHeader::PciToPci {
|
|
shared: SharedPciHeader { status, .. },
|
|
..
|
|
} => status,
|
|
}
|
|
}
|
|
|
|
pub fn cap_pointer(&self) -> u8 {
|
|
match self {
|
|
&PciHeader::General { cap_pointer, .. } | &PciHeader::PciToPci { cap_pointer, .. } => {
|
|
cap_pointer
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
impl<'a> ConfigReader for &'a [u8] {
|
|
unsafe fn read_u32(&self, offset: u16) -> u32 {
|
|
let offset = offset as usize;
|
|
assert!(offset < self.len());
|
|
LittleEndian::read_u32(&self[offset..offset + 4])
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::{PciHeaderError, PciHeader, PciHeaderType};
|
|
use super::super::func::ConfigReader;
|
|
use super::super::class::PciClass;
|
|
use super::super::bar::PciBar;
|
|
|
|
const IGB_DEV_BYTES: [u8; 256] = [
|
|
0x86, 0x80, 0x33, 0x15, 0x07, 0x04, 0x10, 0x00, 0x03, 0x00, 0x00, 0x02, 0x10, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x50, 0xf7, 0x00, 0x00, 0x00, 0x00, 0x01, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x58, 0xf7,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd9, 0x15, 0x33, 0x15,
|
|
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x01, 0x00, 0x00,
|
|
0x01, 0x50, 0x23, 0xc8, 0x08, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x05, 0x70, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x11, 0xa0, 0x04, 0x80, 0x03, 0x00, 0x00, 0x00, 0x03, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
|
0x10, 0x00, 0x02, 0x00, 0xc2, 0x8c, 0x00, 0x10, 0x0f, 0x28, 0x19, 0x00, 0x11, 0x5c, 0x42, 0x00,
|
|
0x42, 0x00, 0x11, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
|
];
|
|
|
|
#[test]
|
|
fn tset_parse_igb_dev() {
|
|
let header = PciHeader::from_reader(&IGB_DEV_BYTES[..]).unwrap();
|
|
assert_eq!(header.header_type(), PciHeaderType::GENERAL);
|
|
assert_eq!(header.device_id(), 0x1533);
|
|
assert_eq!(header.vendor_id(), 0x8086);
|
|
assert_eq!(header.revision(), 3);
|
|
assert_eq!(header.interface(), 0);
|
|
assert_eq!(header.class(), PciClass::Network);
|
|
assert_eq!(header.subclass(), 0);
|
|
assert_eq!(header.bars().len(), 6);
|
|
assert_eq!(header.get_bar(0), PciBar::Memory32(0xf7500000));
|
|
assert_eq!(header.get_bar(1), PciBar::None);
|
|
assert_eq!(header.get_bar(2), PciBar::Port(0xb000));
|
|
assert_eq!(header.get_bar(3), PciBar::Memory32(0xf7580000));
|
|
assert_eq!(header.get_bar(4), PciBar::None);
|
|
assert_eq!(header.get_bar(5), PciBar::None);
|
|
assert_eq!(header.interrupt_line(), 10);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_nonexistent() {
|
|
let bytes = [
|
|
0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff
|
|
];
|
|
assert_eq!(PciHeader::from_reader(&bytes[..]), Err(PciHeaderError::NoDevice));
|
|
}
|
|
|
|
#[test]
|
|
fn test_read_range() {
|
|
let res = unsafe { (&IGB_DEV_BYTES[..]).read_range(0, 4) };
|
|
assert_eq!(res, &[0x86, 0x80, 0x33, 0x15][..]);
|
|
|
|
let res = unsafe { (&IGB_DEV_BYTES[..]).read_range(16, 32) };
|
|
let expected = [
|
|
0x00, 0x00, 0x50, 0xf7, 0x00, 0x00, 0x00, 0x00,
|
|
0x01, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x58, 0xf7,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0xd9, 0x15, 0x33, 0x15
|
|
];
|
|
assert_eq!(res, expected);
|
|
}
|
|
|
|
macro_rules! read_range_should_panic {
|
|
($name:ident, $len:expr) => {
|
|
#[test]
|
|
#[should_panic(expected = "invalid range length")]
|
|
fn $name() {
|
|
let _ = unsafe { (&IGB_DEV_BYTES[..]).read_range(0, $len) };
|
|
}
|
|
}
|
|
}
|
|
|
|
read_range_should_panic!(test_short_len, 2);
|
|
read_range_should_panic!(test_not_mod_4_len, 7);
|
|
}
|