249 lines
6.3 KiB
Rust

use std::{fmt, fs, io, path::PathBuf, process::ExitCode, str::FromStr};
use clap::Parser;
use pci_ids::FromId;
#[derive(Debug, Parser)]
struct Args {
#[clap(short, help = "Display numeric IDs only")]
numeric: bool,
#[clap(short, help = "Print all the information about the devices")]
verbose: bool,
}
struct PciAddress {
bus: u8,
device: u8,
function: u8,
}
struct DeviceId {
vendor: u16,
device: u16,
}
struct Device {
path: PathBuf,
address: PciAddress,
}
enum Resource {
Io(u16),
Memory32(u32),
Memory64(u64),
}
struct PciRegion {
index: usize,
resource: Resource
}
#[derive(Debug, thiserror::Error)]
enum Error {
#[error("{0}")]
Io(#[from] io::Error),
#[error("Invalid PCI ID")]
InvalidId,
}
impl FromStr for PciAddress {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut elements = s.split(':');
let bus = elements
.next()
.and_then(|p| u8::from_str_radix(p, 16).ok())
.ok_or(Error::InvalidId)?;
let device = elements
.next()
.and_then(|p| u8::from_str_radix(p, 16).ok())
.ok_or(Error::InvalidId)?;
let function = elements
.next()
.and_then(|p| u8::from_str_radix(p, 16).ok())
.ok_or(Error::InvalidId)?;
if elements.next().is_some() {
return Err(Error::InvalidId);
}
Ok(Self {
bus,
device,
function,
})
}
}
impl FromStr for DeviceId {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (vendor, device) = s.split_once(':').ok_or(Error::InvalidId)?;
let vendor = u16::from_str_radix(vendor, 16).map_err(|_| Error::InvalidId)?;
let device = u16::from_str_radix(device, 16).map_err(|_| Error::InvalidId)?;
Ok(Self { vendor, device })
}
}
impl FromStr for Resource {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (ty, addr) = s.split_once(':').ok_or(Error::InvalidId)?;
let addr = addr.strip_prefix("0x").ok_or(Error::InvalidId)?;
match ty {
"pio" => {
let addr = u16::from_str_radix(addr, 16).map_err(|_| Error::InvalidId)?;
Ok(Self::Io(addr))
},
"m32" => {
let addr = u32::from_str_radix(addr, 16).map_err(|_| Error::InvalidId)?;
Ok(Self::Memory32(addr))
},
"m64" => {
let addr = u64::from_str_radix(addr, 16).map_err(|_| Error::InvalidId)?;
Ok(Self::Memory64(addr))
},
_ => Err(Error::InvalidId)
}
}
}
impl FromStr for PciRegion {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (bar, resource) = s.split_once(':').ok_or(Error::InvalidId)?;
let index = usize::from_str(bar).map_err(|_| Error::InvalidId)?;
let resource = Resource::from_str(resource)?;
Ok(Self {
index, resource
})
}
}
impl fmt::Display for PciAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{:02x}:{:02x}.{:x}",
self.bus, self.device, self.function
)
}
}
impl fmt::Display for DeviceId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:04x}:{:04x}", self.vendor, self.device)
}
}
impl fmt::Display for Resource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::Io(addr) => write!(f, "I/O at {addr:#x}"),
Self::Memory32(addr) => write!(f, "Memory (32-bit) at {addr:#x}"),
Self::Memory64(addr) => write!(f, "Memory (64-bit) at {addr:#x}")
}
}
}
fn list_devices() -> Result<Vec<Device>, Error> {
let mut entries = Vec::new();
for entry in fs::read_dir("/sys/bus/pci")? {
let Ok(entry) = entry else { continue };
let file_name = entry.file_name();
let Some(name) = file_name.to_str() else {
continue;
};
if let Ok(address) = PciAddress::from_str(name) {
entries.push(Device {
path: entry.path(),
address,
});
}
}
Ok(entries)
}
fn read_device_id(device: &Device) -> Result<DeviceId, Error> {
let str = fs::read_to_string(device.path.join("id"))?;
DeviceId::from_str(str.trim())
}
fn read_device_regions(device: &Device) -> Result<Vec<PciRegion>, Error> {
let str = fs::read_to_string(device.path.join("resources"))?;
let mut regions = Vec::new();
for line in str.split('\n') {
let line = line.trim();
let Ok(region) = PciRegion::from_str(line) else { continue };
regions.push(region);
}
Ok(regions)
}
fn vendor_id(args: &Args, id: u16) -> String {
if args.numeric {
format!("{id:04x}")
} else {
pci_ids::Vendor::from_id(id).map(|v| v.name().into()).unwrap_or_else(|| format!("{id:04x}"))
}
}
fn device_id(args: &Args, vid: u16, pid: u16) -> String {
if args.numeric {
format!("{pid:04x}")
} else {
pci_ids::Device::from_vid_pid(vid, pid).map(|v| v.name().into()).unwrap_or_else(|| format!("{pid:04x}"))
}
}
fn print_device(args: &Args, device: &Device) -> Result<(), Error> {
let id = read_device_id(device)?;
print!("{}: ", device.address);
let vid = vendor_id(args, id.vendor);
let pid = device_id(args, id.vendor, id.device);
let device_name = if args.numeric {
format!("{vid}:{pid}")
}else {
format!("[{vid}] {pid}")
};
println!("{device_name}");
if args.verbose {
for region in read_device_regions(device)? {
println!(" BAR {}: {}", region.index, region.resource);
}
}
Ok(())
}
fn run(args: &Args) -> Result<(), Error> {
let devices = list_devices()?;
for device in devices {
if let Err(error) = print_device(args, &device) {
eprintln!("{}: {error}", device.address);
}
}
Ok(())
}
fn main() -> ExitCode {
let args = Args::parse();
match run(&args) {
Ok(()) => ExitCode::SUCCESS,
Err(error) => {
eprintln!("{error}");
ExitCode::FAILURE
}
}
}