249 lines
6.3 KiB
Rust
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
|
|
}
|
|
}
|
|
}
|