diff --git a/crates/scmi/README.md b/crates/scmi/README.md index b7e3dcd..1e347d7 100644 --- a/crates/scmi/README.md +++ b/crates/scmi/README.md @@ -30,6 +30,7 @@ the Examples section below. Can be used multiple times for multiple exposed devices. If no device is specified then no device will be provided to the guest OS but VirtIO SCMI will be still available there. + Use `help` as the device ID to list help on all the available devices. You can set `RUST_LOG` environment variable to `debug` to get maximum messages on the standard error output. diff --git a/crates/scmi/src/devices.rs b/crates/scmi/src/devices.rs index be86507..50b8d14 100644 --- a/crates/scmi/src/devices.rs +++ b/crates/scmi/src/devices.rs @@ -1,9 +1,10 @@ // SPDX-FileCopyrightText: Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 -use std::collections::HashMap; - use log::debug; +use std::collections::HashMap; +use std::fmt::Write; +use std::process::exit; use crate::{ scmi::{ @@ -15,6 +16,10 @@ use crate::{ DeviceProperties, }; +enum ExitCodes { + Help = 1, +} + type DeviceSpecification = fn() -> Box; type NameDeviceMapping = HashMap; @@ -24,6 +29,40 @@ pub fn available_devices() -> NameDeviceMapping { devices } +fn devices_help() -> String { + let mut help = String::new(); + writeln!(help, "Available devices:").unwrap(); + for (name, constructor) in available_devices().iter() { + let device = constructor(); + let short_help = device.short_help(); + let long_help = device.long_help(); + let parameters_help = device.parameters_help(); + writeln!(help, "\n- {name}: {short_help}").unwrap(); + for line in long_help.lines() { + writeln!(help, " {line}").unwrap(); + } + if !parameters_help.is_empty() { + writeln!(help, " Parameters:").unwrap(); + for parameter in parameters_help { + writeln!(help, " - {parameter}").unwrap(); + } + } + } + writeln!(help, "\nDevice specification example:").unwrap(); + writeln!( + help, + "--device iio,path=/sys/bus/iio/devices/iio:device0,channel=in_accel" + ) + .unwrap(); + help +} + +pub(crate) fn print_devices_help() { + let help = devices_help(); + println!("{}", help); + exit(ExitCodes::Help as i32); +} + // Common sensor infrastructure pub struct Sensor { @@ -44,6 +83,18 @@ trait SensorT: Send { fn sensor(&self) -> &Sensor; fn sensor_mut(&mut self) -> &mut Sensor; + fn short_help(&self) -> String { + "sensor".to_owned() + } + + fn long_help(&self) -> String { + "".to_owned() + } + + fn parameters_help(&self) -> Vec { + vec!["name: an optional name of the sensor, max. 15 characters".to_owned()] + } + fn protocol(&self) -> ProtocolId { SENSOR_PROTOCOL_ID } @@ -182,6 +233,18 @@ impl ScmiDevice for SensorDevice { self.0.configure(properties) } + fn short_help(&self) -> String { + self.0.short_help() + } + + fn long_help(&self) -> String { + self.0.long_help() + } + + fn parameters_help(&self) -> Vec { + self.0.parameters_help() + } + fn protocol(&self) -> ProtocolId { self.0.protocol() } @@ -207,6 +270,14 @@ impl SensorT for FakeSensor { &mut self.sensor } + fn short_help(&self) -> String { + "fake accelerometer".to_owned() + } + + fn long_help(&self) -> String { + "A simple 3-axes sensor providing fake pre-defined values.".to_owned() + } + fn number_of_axes(&self) -> u32 { 3 } @@ -245,3 +316,25 @@ impl FakeSensor { Box::new(sensor_device) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_help() { + let help = devices_help(); + assert!( + help.contains("Available devices:\n"), + "global label missing" + ); + assert!(help.contains("fake:"), "sensor name missing"); + assert!( + help.contains("fake accelerometer"), + "short description missing" + ); + assert!(help.contains("3-axes sensor"), "long description missing"); + assert!(help.contains("Parameters:\n"), "parameter label missing"); + assert!(help.contains("- name:"), "parameter `name' missing"); + } +} diff --git a/crates/scmi/src/main.rs b/crates/scmi/src/main.rs index c9e176e..bc91842 100644 --- a/crates/scmi/src/main.rs +++ b/crates/scmi/src/main.rs @@ -29,7 +29,11 @@ struct ScmiArgs { #[clap(short, long, help = "vhost-user socket to use")] socket_path: String, // Specification of SCMI devices to create. - #[clap(short, long)] + #[clap( + short, + long, + help = "Devices to expose (use `help' device for more info)" + )] #[arg(num_args(1..))] device: Vec, } diff --git a/crates/scmi/src/scmi.rs b/crates/scmi/src/scmi.rs index d4b982b..87534dc 100644 --- a/crates/scmi/src/scmi.rs +++ b/crates/scmi/src/scmi.rs @@ -480,6 +480,9 @@ pub enum ScmiDeviceError { } pub trait ScmiDevice: Send { + fn short_help(&self) -> String; + fn long_help(&self) -> String; + fn parameters_help(&self) -> Vec; fn configure(&mut self, properties: &DeviceProperties) -> Result<(), String>; fn protocol(&self) -> ProtocolId; fn handle( diff --git a/crates/scmi/src/vhu_scmi.rs b/crates/scmi/src/vhu_scmi.rs index 2049383..47fc1e3 100644 --- a/crates/scmi/src/vhu_scmi.rs +++ b/crates/scmi/src/vhu_scmi.rs @@ -21,6 +21,7 @@ use vmm_sys_util::epoll::EventSet; use vmm_sys_util::eventfd::{EventFd, EFD_NONBLOCK}; use crate::devices::available_devices; +use crate::devices::print_devices_help; use crate::scmi::ScmiDevice; use crate::scmi::{MessageHeader, ScmiHandler, ScmiRequest}; use crate::VuScmiConfig; @@ -99,6 +100,9 @@ impl VuScmiBackend { let mut handler = ScmiHandler::new(); let device_mapping = available_devices(); for (name, properties) in config.devices.iter() { + if name == "help" { + print_devices_help(); + } match device_mapping.get(name) { Some(constructor) => { let mut device: Box = constructor();