pci: add lspci-like utility

This commit is contained in:
Mark Poliakov 2025-01-31 18:03:08 +02:00
parent abdf53368b
commit 9e48530e62
9 changed files with 550 additions and 29 deletions

View File

@ -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<Arc<dyn Device>>,
pub(crate) device: Option<Arc<dyn Device>>,
pub(crate) driver_name: Option<&'static str>,
}
impl PciDeviceInfo {

View File

@ -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<BusAddressAllocator>,
info: Arc<PciSegmentInfo>,
devices: Vec<PciBusDevice>,
devices: Vec<Arc<KObject<IrqSafeSpinlock<PciBusDevice>>>>,
}
#[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<Vec<PciDriver>> = IrqSafeSpinlock::new(Vec::new());
static PCI_MANAGER: IrqSafeSpinlock<PciBusManager> = IrqSafeSpinlock::new(PciBusManager::new());
static PCI_SYSFS_NODE: OneTimeInit<Arc<KObject<()>>> = OneTimeInit::new();

View File

@ -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<KObject<IrqSafeSpinlock<PciBusDevice>>> {
struct Resources;
struct Capabilities;
struct Driver;
struct Class;
struct Id;
impl StringAttributeOps for Driver {
type Data = IrqSafeSpinlock<PciBusDevice>;
const NAME: &'static str = "driver";
fn read(state: &Self::Data) -> Result<String, Error> {
let state = state.lock();
if let Some(driver) = state.driver_name {
Ok(driver.into())
} else {
Ok("".into())
}
}
}
impl StringAttributeOps for Id {
type Data = IrqSafeSpinlock<PciBusDevice>;
const NAME: &'static str = "id";
fn read(state: &Self::Data) -> Result<String, Error> {
let state = state.lock();
Ok(format!(
"{:04x}:{:04x}",
state.info.vendor_id, state.info.device_id
))
}
}
impl StringAttributeOps for Class {
type Data = IrqSafeSpinlock<PciBusDevice>;
const NAME: &'static str = "class";
fn read(state: &Self::Data) -> Result<String, Error> {
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<PciBusDevice>;
const NAME: &'static str = "resources";
const NEWLINE: bool = false;
fn read(state: &Self::Data) -> Result<String, Error> {
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<PciBusDevice>;
const NAME: &'static str = "capabilities";
const NEWLINE: bool = false;
fn read(state: &Self::Data) -> Result<String, Error> {
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
}

View File

@ -25,6 +25,10 @@ pub fn device() -> Option<&'static Arc<KObject<()>>> {
object::DEVICE_OBJECT.try_get()
}
pub fn bus() -> Option<&'static Arc<KObject<()>>> {
object::BUS_OBJECT.try_get()
}
pub fn init() {
ROOT.init(object::setup_fixed_objects());
}

View File

@ -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<D> KObject<D> {
}
}
impl<D> Deref for KObject<D> {
type Target = D;
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl<D> DerefMut for KObject<D> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.data
}
}
unsafe impl<D: Send> Send for KObject<D> {}
unsafe impl<D: Send> Sync for KObject<D> {}
@ -54,6 +70,8 @@ pub static KERNEL_OBJECT: OneTimeInit<Arc<KObject<()>>> = OneTimeInit::new();
pub static DEVICE_OBJECT: OneTimeInit<Arc<KObject<()>>> = OneTimeInit::new();
// `/debug`
pub static DEBUG_OBJECT: OneTimeInit<Arc<KObject<()>>> = OneTimeInit::new();
// `/bus`
pub static BUS_OBJECT: OneTimeInit<Arc<KObject<()>>> = OneTimeInit::new();
fn setup_fixed_object(root: &Arc<KObject<()>>, obj: &OneTimeInit<Arc<KObject<()>>>, name: &str) {
let obj = obj.init(KObject::new(()));
@ -66,6 +84,7 @@ pub fn setup_fixed_objects() -> Arc<Node> {
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()
}

73
userspace/Cargo.lock generated
View File

@ -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",

View File

@ -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

View File

@ -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<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
}
}
}

View File

@ -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"),