|
| 1 | +/*! |
| 2 | +`pciclient` provides high-level methods to invoke `lspci` and query for attached devices information. |
| 3 | +
|
| 4 | +pciclient provides util functions that can: |
| 5 | +- List the devices attached with some filtering option. |
| 6 | +- Detect specifically if EFA device is attached. |
| 7 | +*/ |
| 8 | +mod private; |
| 9 | + |
| 10 | +use private::{call_list_devices, check_efa_attachment, PciClient}; |
| 11 | + |
| 12 | +use bon::Builder; |
| 13 | +use derive_getters::Getters; |
| 14 | +use std::ffi::OsString; |
| 15 | + |
| 16 | +/// This [`ListDevicesParam`] is based on the "-d" flag for `lspci`, which |
| 17 | +/// allows selection/filtering of the output. Spec for "-d": |
| 18 | +/// ```sh |
| 19 | +/// -d [<vendor>]:[<device>][:<class>[:<prog-if>]] |
| 20 | +/// |
| 21 | +/// Show only devices with specified vendor, device, class ID, |
| 22 | +/// and programming interface. The ID's are given in |
| 23 | +/// hexadecimal and may be omitted or given as "*", both |
| 24 | +/// meaning "any value". The class ID can contain "x" |
| 25 | +/// characters which stand for "any digit". |
| 26 | +/// ``` |
| 27 | +#[derive(Debug, Default, PartialEq, Builder)] |
| 28 | +pub struct ListDevicesParam { |
| 29 | + #[builder(into)] |
| 30 | + vendor: Option<String>, |
| 31 | + #[builder(into)] |
| 32 | + device: Option<String>, |
| 33 | + #[builder(into)] |
| 34 | + class: Option<String>, |
| 35 | + #[builder(into)] |
| 36 | + program_interface: Option<String>, |
| 37 | +} |
| 38 | + |
| 39 | +impl ListDevicesParam { |
| 40 | + /// Convert the ListDevicesParam into proper command line arguments so that |
| 41 | + /// we can feed it to the lspci() function where it makes the actual call to the |
| 42 | + /// lspci binary. |
| 43 | + /// |
| 44 | + /// As mentioned in [`list_devices`], we will use "-n" and "-m" to decorate the output |
| 45 | + /// so that it returns machine-readable format which ensures compatibility and numeric output |
| 46 | + /// for the vendor code and device codes. |
| 47 | + fn into_command_args(self) -> Vec<OsString> { |
| 48 | + let mut args: Vec<String> = vec!["-n".into(), "-m".into()]; |
| 49 | + if self != ListDevicesParam::default() { |
| 50 | + let parts: Vec<String> = |
| 51 | + vec![self.vendor, self.device, self.class, self.program_interface] |
| 52 | + .into_iter() |
| 53 | + .map(|part| part.unwrap_or_default()) |
| 54 | + .collect(); |
| 55 | + let additional_args = parts.join(":"); |
| 56 | + args.push("-d".to_string()); |
| 57 | + args.push(additional_args); |
| 58 | + } |
| 59 | + args.into_iter().map(OsString::from).collect() |
| 60 | + } |
| 61 | +} |
| 62 | + |
| 63 | +/// The [`ListDevicesOutput`] is based on the output format that is decorated by "-n -m". |
| 64 | +#[derive(Debug, Default, PartialEq, Eq, Getters)] |
| 65 | +pub struct ListDevicesOutput { |
| 66 | + pci_slot: String, |
| 67 | + class: String, |
| 68 | + vendor: String, |
| 69 | + device: String, |
| 70 | + subsystem_vendor: Option<String>, |
| 71 | + subsystem_device: Option<String>, |
| 72 | + revision: Option<String>, |
| 73 | + program_interface: Option<String>, |
| 74 | +} |
| 75 | + |
| 76 | +/// Query the list of the devices with options to filter the output. |
| 77 | +/// # Input |
| 78 | +/// list_devices_param: [`ListDevicesParam`]. We will under the hood convert the [`ListDevicesParam`] |
| 79 | +/// to the low level arguments to `lspci`. |
| 80 | +/// According to https://man7.org/linux/man-pages/man8/lspci.8.html#MACHINE_READABLE_OUTPUT, |
| 81 | +/// > If you intend to process the output of lspci automatically, |
| 82 | +/// > please use one of the machine-readable output formats (-m, -vm, -vmm) |
| 83 | +/// > described in this section. All other formats are likely to change between versions of lspci. |
| 84 | +/// |
| 85 | +/// * We will use `-m` to get the machine-readable format which ensures compatibility. |
| 86 | +/// * We will use `-n` to get the numeric output for the vendor code and device codes. |
| 87 | +/// * We will optionally insert `-d <selection_expression>` to filter the output. |
| 88 | +/// |
| 89 | +/// # Output |
| 90 | +/// Output is [`Result<Vec<ListDevicesOutput>>`], the [`ListDevicesOutput`] is based on |
| 91 | +/// the output format that is decorated by "-n -m". |
| 92 | +/// |
| 93 | +/// # Example |
| 94 | +/// ```rust,ignore |
| 95 | +/// use pciclient::{ListDevicesParam, list_devices}; |
| 96 | +/// |
| 97 | +/// let list_devices_param = ListDevicesParam::builder().vendor("1d0f").build(); |
| 98 | +/// match list_devices(list_devices_param) { |
| 99 | +/// Ok(list_devices_output) => { |
| 100 | +/// for device_info in list_devices_output { |
| 101 | +/// println!("vendor: {}, class: {}, device: {}", device_info.vendor(), device_info.class(), device_info.device()); |
| 102 | +/// } |
| 103 | +/// }, |
| 104 | +/// Err(_) => { |
| 105 | +/// println!("Failed to list devices"); |
| 106 | +/// } |
| 107 | +/// } |
| 108 | +/// ``` |
| 109 | +pub fn list_devices(list_devices_param: ListDevicesParam) -> Result<Vec<ListDevicesOutput>> { |
| 110 | + call_list_devices(PciClient {}, list_devices_param) |
| 111 | +} |
| 112 | + |
| 113 | +/// Call `lspci` and check if there is any EFA device attached. |
| 114 | +pub fn is_efa_attached() -> Result<bool> { |
| 115 | + check_efa_attachment(PciClient {}) |
| 116 | +} |
| 117 | + |
| 118 | +mod error { |
| 119 | + use snafu::Snafu; |
| 120 | + |
| 121 | + #[derive(Debug, Snafu)] |
| 122 | + #[snafu(visibility(pub(super)))] |
| 123 | + |
| 124 | + pub enum PciClientError { |
| 125 | + #[snafu(display("Deserialization error: {}", source))] |
| 126 | + Serde { source: serde_json::Error }, |
| 127 | + |
| 128 | + #[snafu(display("Command lspci failed: {}", source))] |
| 129 | + CommandFailure { source: std::io::Error }, |
| 130 | + |
| 131 | + #[snafu(display("Exection of lspci failed: {}", reason))] |
| 132 | + ExecutionFailure { reason: String }, |
| 133 | + |
| 134 | + #[snafu(display("Failed to parse the lspci output: {}, reason: {}", output, reason))] |
| 135 | + ParseListDevicesOutputFailure { output: String, reason: String }, |
| 136 | + |
| 137 | + #[snafu(display( |
| 138 | + "Failed to parse the lspci output, missing required field: {}", |
| 139 | + required_field |
| 140 | + ))] |
| 141 | + MissingRequiredField { required_field: String }, |
| 142 | + } |
| 143 | +} |
| 144 | + |
| 145 | +pub use error::PciClientError; |
| 146 | +pub type Result<T> = std::result::Result<T, PciClientError>; |
0 commit comments