From 9e48530e62fd13dc89c170f77b93b58542c69b20 Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Fri, 31 Jan 2025 18:03:08 +0200 Subject: [PATCH] pci: add lspci-like utility --- kernel/driver/bus/pci/src/device.rs | 13 +- kernel/driver/bus/pci/src/lib.rs | 80 ++++++--- kernel/driver/bus/pci/src/nodes.rs | 135 +++++++++++++++ kernel/libk/src/fs/sysfs/mod.rs | 4 + kernel/libk/src/fs/sysfs/object.rs | 19 +++ userspace/Cargo.lock | 73 +++++++- userspace/sysutils/Cargo.toml | 6 + userspace/sysutils/src/lspci.rs | 248 ++++++++++++++++++++++++++++ xtask/src/build/userspace.rs | 1 + 9 files changed, 550 insertions(+), 29 deletions(-) create mode 100644 kernel/driver/bus/pci/src/nodes.rs create mode 100644 userspace/sysutils/src/lspci.rs diff --git a/kernel/driver/bus/pci/src/device.rs b/kernel/driver/bus/pci/src/device.rs index 7531fdfe..c28c863a 100644 --- a/kernel/driver/bus/pci/src/device.rs +++ b/kernel/driver/bus/pci/src/device.rs @@ -19,6 +19,16 @@ use crate::{ pub struct PciDeviceInfo { /// Address of the device pub address: PciAddress, + /// Class field of the configuration space + pub class: u8, + /// Subclass field of the configuration space + pub subclass: u8, + /// Prog IF field of the configuration space + pub prog_if: u8, + /// Vendor ID field of the configuration space + pub vendor_id: u16, + /// Device ID field of the configuration space + pub device_id: u16, /// Configuration space access method pub config_space: PciConfigSpace, /// Describes the PCI segment this device is a part of @@ -83,7 +93,8 @@ pub struct PciDriver { /// Used to store PCI bus devices which were enumerated by the kernel pub struct PciBusDevice { pub(crate) info: PciDeviceInfo, - pub(crate) driver: Option>, + pub(crate) device: Option>, + pub(crate) driver_name: Option<&'static str>, } impl PciDeviceInfo { diff --git a/kernel/driver/bus/pci/src/lib.rs b/kernel/driver/bus/pci/src/lib.rs index aef2d944..043beb02 100644 --- a/kernel/driver/bus/pci/src/lib.rs +++ b/kernel/driver/bus/pci/src/lib.rs @@ -8,10 +8,11 @@ use core::fmt; #[cfg(target_arch = "x86_64")] use acpi::mcfg::McfgEntry; -use alloc::{collections::BTreeMap, sync::Arc, vec::Vec}; +use alloc::{collections::BTreeMap, format, sync::Arc, vec::Vec}; use bitflags::bitflags; use device::{PciBusDevice, PciDeviceInfo, PciDriver, PciInterrupt, PciInterruptRoute, PciMatch}; use device_api::device::Device; +use libk::fs::sysfs::{self, object::KObject}; use libk_mm::address::PhysicalAddress; use libk_util::{sync::IrqSafeSpinlock, OneTimeInit}; use space::legacy; @@ -19,6 +20,7 @@ use yggdrasil_abi::error::Error; pub mod capability; pub mod device; +mod nodes; mod space; pub use space::{ @@ -208,7 +210,7 @@ pub struct PciSegmentInfo { pub struct PciBusSegment { allocator: Option, info: Arc, - devices: Vec, + devices: Vec>>>, } #[derive(Debug)] @@ -240,6 +242,14 @@ impl PciBaseAddress { _ => None, } } + + pub fn is_zero(&self) -> bool { + match *self { + Self::Memory32(base) => base == 0, + Self::Memory64(base) => base == 0, + Self::Io(base) => base == 0, + } + } } impl PciBusSegment { @@ -348,13 +358,39 @@ impl PciBusSegment { } } + let vendor_id = config.vendor_id(); + let device_id = config.device_id(); + let class = config.class_code(); + let subclass = config.subclass(); + let prog_if = config.prog_if(); + let info = PciDeviceInfo { address, + vendor_id, + device_id, + class, + subclass, + prog_if, segment: self.info.clone(), config_space: config, interrupt_config: Arc::new(OneTimeInit::new()), }; - self.devices.push(PciBusDevice { info, driver: None }); + + let object = nodes::make_sysfs_object(PciBusDevice { + info, + driver_name: None, + device: None, + }); + let pci_object = PCI_SYSFS_NODE.or_init_with(|| { + let bus_object = sysfs::bus().unwrap(); + let pci_object = KObject::new(()); + bus_object.add_object("pci", pci_object.clone()).ok(); + pci_object + }); + + let name = format!("{address}"); + pci_object.add_object(name, object.clone()).ok(); + self.devices.push(object); Ok(()) } @@ -407,7 +443,8 @@ impl PciBusManager { for segment in this.segments.iter_mut() { for device in segment.devices.iter_mut() { - if !f(device)? { + let mut device = device.lock(); + if !f(&mut *device)? { return Ok(()); } } @@ -555,39 +592,29 @@ impl PciConfigurationSpace for PciConfigSpace { } fn setup_bus_device(device: &mut PciBusDevice) -> Result<(), Error> { - if device.driver.is_some() { + if device.device.is_some() { return Ok(()); } - let config = &device.info.config_space; - log::debug!( "{}: {:04x}:{:04x}", device.info.address, - config.vendor_id(), - config.device_id() + device.info.vendor_id, + device.info.device_id ); - let class = config.class_code(); - let subclass = config.subclass(); - let prog_if = config.prog_if(); - let drivers = PCI_DRIVERS.lock(); for driver in drivers.iter() { - if driver - .check - .check_device(&device.info, class, subclass, prog_if) - { + if driver.check.check_device(&device.info) { // TODO add the device to the bus log::debug!(" -> {:?}", driver.name); let instance = (driver.probe)(&device.info)?; unsafe { instance.clone().init() }?; - device.driver.replace(instance); + device.device.replace(instance); + device.driver_name.replace(driver.name); break; - } else { - log::debug!(" -> No driver"); } } @@ -595,17 +622,19 @@ fn setup_bus_device(device: &mut PciBusDevice) -> Result<(), Error> { } impl PciMatch { - pub fn check_device(&self, info: &PciDeviceInfo, class: u8, subclass: u8, prog_if: u8) -> bool { + pub fn check_device(&self, info: &PciDeviceInfo) -> bool { match self { Self::Generic(f) => f(info), &Self::Vendor(vendor_, device_) => { - info.config_space.vendor_id() == vendor_ && info.config_space.device_id() == device_ + info.vendor_id == vendor_ && info.device_id == device_ } &Self::Class(class_, Some(subclass_), Some(prog_if_)) => { - class_ == class && subclass_ == subclass && prog_if_ == prog_if + class_ == info.class && subclass_ == info.subclass && prog_if_ == info.prog_if } - &Self::Class(class_, Some(subclass_), _) => class_ == class && subclass_ == subclass, - &Self::Class(class_, _, _) => class_ == class, + &Self::Class(class_, Some(subclass_), _) => { + class_ == info.class && subclass_ == info.subclass + } + &Self::Class(class_, _, _) => class_ == info.class, } } } @@ -651,3 +680,4 @@ pub fn register_generic_driver( static PCI_DRIVERS: IrqSafeSpinlock> = IrqSafeSpinlock::new(Vec::new()); static PCI_MANAGER: IrqSafeSpinlock = IrqSafeSpinlock::new(PciBusManager::new()); +static PCI_SYSFS_NODE: OneTimeInit>> = OneTimeInit::new(); diff --git a/kernel/driver/bus/pci/src/nodes.rs b/kernel/driver/bus/pci/src/nodes.rs new file mode 100644 index 00000000..5cc33090 --- /dev/null +++ b/kernel/driver/bus/pci/src/nodes.rs @@ -0,0 +1,135 @@ +use alloc::{format, string::String, sync::Arc}; +use libk::{ + error::Error, + fs::sysfs::{ + attribute::{StringAttribute, StringAttributeOps}, + object::KObject, + }, +}; +use libk_util::sync::IrqSafeSpinlock; + +use crate::{device::PciBusDevice, PciBaseAddress, PciCapabilityId, PciConfigurationSpace}; + +pub(crate) fn make_sysfs_object( + device: PciBusDevice, +) -> Arc>> { + struct Resources; + struct Capabilities; + struct Driver; + struct Class; + struct Id; + + impl StringAttributeOps for Driver { + type Data = IrqSafeSpinlock; + const NAME: &'static str = "driver"; + + fn read(state: &Self::Data) -> Result { + let state = state.lock(); + if let Some(driver) = state.driver_name { + Ok(driver.into()) + } else { + Ok("".into()) + } + } + } + + impl StringAttributeOps for Id { + type Data = IrqSafeSpinlock; + const NAME: &'static str = "id"; + + fn read(state: &Self::Data) -> Result { + let state = state.lock(); + Ok(format!( + "{:04x}:{:04x}", + state.info.vendor_id, state.info.device_id + )) + } + } + + impl StringAttributeOps for Class { + type Data = IrqSafeSpinlock; + const NAME: &'static str = "class"; + + fn read(state: &Self::Data) -> Result { + let state = state.lock(); + Ok(format!( + "{:02x}:{:02x}:{:02x}", + state.info.class, state.info.subclass, state.info.prog_if + )) + } + } + + impl StringAttributeOps for Resources { + type Data = IrqSafeSpinlock; + const NAME: &'static str = "resources"; + const NEWLINE: bool = false; + + fn read(state: &Self::Data) -> Result { + use core::fmt::Write; + + let state = state.lock(); + let mut output = String::new(); + for i in 0..6 { + if let Some(bar) = state.info.config_space.bar(i) { + if bar.is_zero() { + continue; + } + + match bar { + PciBaseAddress::Io(base) => { + writeln!(output, "{i}:pio:{base:#06x}").ok(); + } + PciBaseAddress::Memory32(base) => { + writeln!(output, "{i}:m32:{base:#010x}").ok(); + } + PciBaseAddress::Memory64(base) => { + writeln!(output, "{i}:m64:{base:#018x}").ok(); + } + } + } + } + if output.is_empty() { + output.push('\n'); + } + Ok(output) + } + } + + impl StringAttributeOps for Capabilities { + type Data = IrqSafeSpinlock; + const NAME: &'static str = "capabilities"; + const NEWLINE: bool = false; + + fn read(state: &Self::Data) -> Result { + use core::fmt::Write; + let state = state.lock(); + let mut output = String::new(); + for (capability, offset, _) in state.info.config_space.capability_iter() { + write!(output, "{offset:04x}:").ok(); + match capability { + PciCapabilityId::Msi => write!(output, "msi").ok(), + PciCapabilityId::MsiX => write!(output, "msix").ok(), + PciCapabilityId::VendorSpecific => write!(output, "vendor-specific").ok(), + PciCapabilityId::Unknown => write!(output, "unknown").ok(), + }; + writeln!(output).ok(); + } + if output.is_empty() { + output.push('\n'); + } + Ok(output) + } + } + + let object = KObject::new(IrqSafeSpinlock::new(device)); + + object + .add_attribute(StringAttribute::from(Capabilities)) + .ok(); + object.add_attribute(StringAttribute::from(Resources)).ok(); + object.add_attribute(StringAttribute::from(Driver)).ok(); + object.add_attribute(StringAttribute::from(Class)).ok(); + object.add_attribute(StringAttribute::from(Id)).ok(); + + object +} diff --git a/kernel/libk/src/fs/sysfs/mod.rs b/kernel/libk/src/fs/sysfs/mod.rs index 97e440ee..2820d28c 100644 --- a/kernel/libk/src/fs/sysfs/mod.rs +++ b/kernel/libk/src/fs/sysfs/mod.rs @@ -25,6 +25,10 @@ pub fn device() -> Option<&'static Arc>> { object::DEVICE_OBJECT.try_get() } +pub fn bus() -> Option<&'static Arc>> { + object::BUS_OBJECT.try_get() +} + pub fn init() { ROOT.init(object::setup_fixed_objects()); } diff --git a/kernel/libk/src/fs/sysfs/object.rs b/kernel/libk/src/fs/sysfs/object.rs index 7e52d80b..0c66f69f 100644 --- a/kernel/libk/src/fs/sysfs/object.rs +++ b/kernel/libk/src/fs/sysfs/object.rs @@ -1,3 +1,5 @@ +use core::ops::{Deref, DerefMut}; + use alloc::sync::Arc; use libk_util::OneTimeInit; use yggdrasil_abi::{error::Error, io::FileMode}; @@ -42,6 +44,20 @@ impl KObject { } } +impl Deref for KObject { + type Target = D; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl DerefMut for KObject { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } +} + unsafe impl Send for KObject {} unsafe impl Sync for KObject {} @@ -54,6 +70,8 @@ pub static KERNEL_OBJECT: OneTimeInit>> = OneTimeInit::new(); pub static DEVICE_OBJECT: OneTimeInit>> = OneTimeInit::new(); // `/debug` pub static DEBUG_OBJECT: OneTimeInit>> = OneTimeInit::new(); +// `/bus` +pub static BUS_OBJECT: OneTimeInit>> = OneTimeInit::new(); fn setup_fixed_object(root: &Arc>, obj: &OneTimeInit>>, name: &str) { let obj = obj.init(KObject::new(())); @@ -66,6 +84,7 @@ pub fn setup_fixed_objects() -> Arc { setup_fixed_object(root, &KERNEL_OBJECT, "kernel"); setup_fixed_object(root, &DEVICE_OBJECT, "device"); setup_fixed_object(root, &DEBUG_OBJECT, "debug"); + setup_fixed_object(root, &BUS_OBJECT, "bus"); root.node.clone() } diff --git a/userspace/Cargo.lock b/userspace/Cargo.lock index 7f83f912..c9c0c554 100644 --- a/userspace/Cargo.lock +++ b/userspace/Cargo.lock @@ -331,7 +331,7 @@ version = "0.1.0" dependencies = [ "clap", "ed25519-dalek", - "rand 0.8.5", + "rand 0.8.5 (git+https://git.alnyan.me/yggdrasil/rand.git?branch=alnyan%2Fyggdrasil-rng_core-0.6.4)", "rsa", "thiserror", ] @@ -862,7 +862,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.5", + "rand 0.8.5 (git+https://git.alnyan.me/yggdrasil/rand.git?branch=alnyan%2Fyggdrasil-rng_core-0.6.4)", "smallvec", "zeroize", ] @@ -941,6 +941,19 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "pci-ids" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d88ae3281b415d856e9c2ddbcdd5961e71c1a3e90138512c04d720241853a6af" +dependencies = [ + "nom", + "phf", + "phf_codegen", + "proc-macro2", + "quote", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -956,6 +969,44 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.15" @@ -1045,6 +1096,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand" version = "0.8.5" @@ -1225,7 +1285,7 @@ dependencies = [ "env_logger", "libterm", "log", - "rand 0.8.5", + "rand 0.8.5 (git+https://git.alnyan.me/yggdrasil/rand.git?branch=alnyan%2Fyggdrasil-rng_core-0.6.4)", "sha2", "thiserror", "x25519-dalek", @@ -1393,6 +1453,12 @@ dependencies = [ "rand_core 0.6.4 (git+https://git.alnyan.me/yggdrasil/rand.git?branch=alnyan%2Fyggdrasil-rng_core-0.6.4)", ] +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" version = "0.4.9" @@ -1485,6 +1551,7 @@ dependencies = [ "humansize", "init", "libterm", + "pci-ids", "rand 0.9.0-alpha.1", "serde", "serde_json", diff --git a/userspace/sysutils/Cargo.toml b/userspace/sysutils/Cargo.toml index 232b9bc9..dc61d768 100644 --- a/userspace/sysutils/Cargo.toml +++ b/userspace/sysutils/Cargo.toml @@ -21,6 +21,7 @@ chrono.workspace = true # TODO own impl humansize = { version = "2.1.3", features = ["impl_style"] } +pci-ids = { version = "0.2.5" } init = { path = "../init" } @@ -117,9 +118,14 @@ path = "src/sync.rs" name = "sleep" path = "src/sleep.rs" +[[bin]] +name = "lspci" +path = "src/lspci.rs" + [[bin]] name = "tst" path = "src/tst.rs" + [lints] workspace = true diff --git a/userspace/sysutils/src/lspci.rs b/userspace/sysutils/src/lspci.rs new file mode 100644 index 00000000..7eadee59 --- /dev/null +++ b/userspace/sysutils/src/lspci.rs @@ -0,0 +1,248 @@ +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 { + 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 { + 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 { + 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 { + 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, 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 { + let str = fs::read_to_string(device.path.join("id"))?; + DeviceId::from_str(str.trim()) +} + +fn read_device_regions(device: &Device) -> Result, 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 + } + } +} diff --git a/xtask/src/build/userspace.rs b/xtask/src/build/userspace.rs index 5c96a98d..253792ef 100644 --- a/xtask/src/build/userspace.rs +++ b/xtask/src/build/userspace.rs @@ -45,6 +45,7 @@ const PROGRAMS: &[(&str, &str)] = &[ ("date", "bin/date"), ("sync", "bin/sync"), ("sleep", "bin/sleep"), + ("lspci", "bin/lspci"), ("tst", "bin/tst"), // netutils ("netconf", "sbin/netconf"),