mirror of
https://github.com/rust-vmm/vhost-device.git
synced 2026-01-01 12:41:58 +00:00
Making the device configuration polymorphic requires the device struct to exist before the device parameters are checked and assigned to the struct fields. Which means wrapping the struct fields by Option unnecessarily or introducing other data confusion. Let's extract the device configuration from traits to plain functions in order to keep the device struct's unencumbered. Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
445 lines
14 KiB
Rust
445 lines
14 KiB
Rust
// SPDX-FileCopyrightText: Red Hat, Inc.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
use std::collections::{HashMap, HashSet};
|
|
use std::fmt::Write;
|
|
use std::process::exit;
|
|
|
|
use itertools::Itertools;
|
|
use log::debug;
|
|
use thiserror::Error as ThisError;
|
|
|
|
use crate::scmi::{
|
|
DeviceResult, MessageId, MessageValue, MessageValues, ProtocolId, ScmiDevice, ScmiDeviceError,
|
|
MAX_SIMPLE_STRING_LENGTH, SENSOR_AXIS_DESCRIPTION_GET, SENSOR_CONFIG_GET, SENSOR_CONFIG_SET,
|
|
SENSOR_CONTINUOUS_UPDATE_NOTIFY, SENSOR_DESCRIPTION_GET, SENSOR_PROTOCOL_ID,
|
|
SENSOR_READING_GET, SENSOR_UNIT_METERS_PER_SECOND_SQUARED,
|
|
};
|
|
|
|
enum ExitCodes {
|
|
Help = 1,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq, ThisError)]
|
|
pub enum DeviceError {
|
|
#[error("Invalid device parameter: {0}")]
|
|
InvalidProperty(String),
|
|
#[error("Missing device parameters: {}", .0.join(", "))]
|
|
MissingDeviceProperties(Vec<String>),
|
|
#[error("Unexpected device parameters: {}", .0.join(", "))]
|
|
UnexpectedDeviceProperties(Vec<String>),
|
|
}
|
|
|
|
// [(NAME, [(PROPERTY, VALUE), ...]), ...]
|
|
pub type DeviceDescription = Vec<(String, DeviceProperties)>;
|
|
type PropertyPairs = Vec<(String, String)>;
|
|
|
|
#[derive(Debug, Eq, PartialEq, Hash)]
|
|
pub struct DeviceProperties(PropertyPairs);
|
|
|
|
impl DeviceProperties {
|
|
pub(crate) fn new(properties: PropertyPairs) -> Self {
|
|
Self(properties)
|
|
}
|
|
|
|
fn get(&self, name: &str) -> Option<&str> {
|
|
self.0
|
|
.iter()
|
|
.find(|(n, _)| n == name)
|
|
.map(|(_, v)| v.as_str())
|
|
}
|
|
|
|
fn names(&self) -> HashSet<&str> {
|
|
self.0.iter().map(|(n, _)| -> &str { n.as_str() }).collect()
|
|
}
|
|
|
|
fn extra<'a>(&'a self, allowed: &[&'a str]) -> HashSet<&str> {
|
|
let allowed_set: HashSet<&str> = HashSet::from_iter(allowed.iter().copied());
|
|
self.names().difference(&allowed_set).copied().collect()
|
|
}
|
|
|
|
fn missing<'a>(&'a self, required: &[&'a str]) -> HashSet<&str> {
|
|
let required_set: HashSet<&str> = HashSet::from_iter(required.iter().copied());
|
|
required_set.difference(&self.names()).copied().collect()
|
|
}
|
|
|
|
fn check(&self, required: &[&str], optional: &[&str]) -> Result<(), DeviceError> {
|
|
let missing = self.missing(required);
|
|
if !missing.is_empty() {
|
|
return Err(DeviceError::MissingDeviceProperties(
|
|
missing
|
|
.iter()
|
|
.sorted()
|
|
.map(|s| (*s).to_owned())
|
|
.collect::<Vec<String>>(),
|
|
));
|
|
}
|
|
let mut all_allowed = Vec::from(required);
|
|
all_allowed.extend(optional.iter());
|
|
let extra = self.extra(&all_allowed);
|
|
if !extra.is_empty() {
|
|
return Err(DeviceError::UnexpectedDeviceProperties(
|
|
extra
|
|
.iter()
|
|
.sorted()
|
|
.map(|s| (*s).to_owned())
|
|
.collect::<Vec<String>>(),
|
|
));
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
type MaybeDevice = Result<Box<dyn ScmiDevice>, DeviceError>;
|
|
type DeviceConstructor = fn(&DeviceProperties) -> MaybeDevice;
|
|
|
|
pub struct DeviceSpecification {
|
|
pub(crate) constructor: DeviceConstructor,
|
|
short_help: String,
|
|
long_help: String,
|
|
parameters_help: Vec<String>,
|
|
}
|
|
|
|
impl DeviceSpecification {
|
|
fn new(
|
|
constructor: DeviceConstructor,
|
|
short_help: &str,
|
|
long_help: &str,
|
|
parameters_help: &[&str],
|
|
) -> Self {
|
|
Self {
|
|
constructor,
|
|
short_help: short_help.to_owned(),
|
|
long_help: long_help.to_owned(),
|
|
parameters_help: parameters_help
|
|
.iter()
|
|
.map(|s| String::from(*s))
|
|
.collect::<Vec<String>>(),
|
|
}
|
|
}
|
|
}
|
|
|
|
type NameDeviceMapping = HashMap<&'static str, DeviceSpecification>;
|
|
|
|
pub fn available_devices() -> NameDeviceMapping {
|
|
let mut devices: NameDeviceMapping = HashMap::new();
|
|
devices.insert(
|
|
"fake",
|
|
DeviceSpecification::new(
|
|
FakeSensor::new,
|
|
"fake accelerometer",
|
|
"A simple 3-axes sensor providing fake pre-defined values.",
|
|
&["name: an optional name of the sensor, max. 15 characters"],
|
|
),
|
|
);
|
|
devices
|
|
}
|
|
|
|
fn devices_help() -> String {
|
|
let mut help = String::new();
|
|
writeln!(help, "Available devices:").unwrap();
|
|
for (name, specification) in available_devices().iter() {
|
|
let short_help = &specification.short_help;
|
|
let long_help = &specification.long_help;
|
|
let parameters_help = &specification.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 fn print_devices_help() {
|
|
let help = devices_help();
|
|
println!("{}", help);
|
|
exit(ExitCodes::Help as i32);
|
|
}
|
|
|
|
// Common sensor infrastructure
|
|
|
|
pub struct Sensor {
|
|
name: String,
|
|
enabled: bool,
|
|
}
|
|
|
|
impl Sensor {
|
|
fn new(properties: &DeviceProperties, default_name: &str) -> Self {
|
|
let name = properties.get("name").unwrap_or(default_name);
|
|
Self {
|
|
name: name.to_owned(),
|
|
enabled: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
trait SensorT: Send {
|
|
fn sensor(&self) -> &Sensor;
|
|
fn sensor_mut(&mut self) -> &mut Sensor;
|
|
|
|
fn protocol(&self) -> ProtocolId {
|
|
SENSOR_PROTOCOL_ID
|
|
}
|
|
|
|
fn invalid_property(&self, name: &str) -> Result<(), DeviceError> {
|
|
Result::Err(DeviceError::InvalidProperty(name.to_owned()))
|
|
}
|
|
|
|
fn process_property(&mut self, name: &str, _value: &str) -> Result<(), DeviceError> {
|
|
self.invalid_property(name)
|
|
}
|
|
|
|
fn number_of_axes(&self) -> u32 {
|
|
1
|
|
}
|
|
|
|
fn description_get(&self) -> DeviceResult {
|
|
// Continuous update required by Linux SCMI IIO driver
|
|
let low = 1 << 30;
|
|
let high = self.number_of_axes() << 16 | 1 << 8;
|
|
let name = self.sensor().name.clone();
|
|
let values: MessageValues = vec![
|
|
// attributes low
|
|
MessageValue::Unsigned(low),
|
|
// attributes high
|
|
MessageValue::Unsigned(high),
|
|
// name, up to 16 bytes with final NULL (non-extended version)
|
|
MessageValue::String(name, MAX_SIMPLE_STRING_LENGTH),
|
|
];
|
|
Ok(values)
|
|
}
|
|
|
|
fn axis_unit(&self) -> u32;
|
|
|
|
fn axis_name_prefix(&self) -> String {
|
|
"axis".to_owned()
|
|
}
|
|
|
|
fn axis_name_suffix(&self, axis: u32) -> char {
|
|
match axis {
|
|
0 => 'X',
|
|
1 => 'Y',
|
|
2 => 'Z',
|
|
_ => 'N', // shouldn't be reached currently
|
|
}
|
|
}
|
|
|
|
fn axis_description(&self, axis: u32) -> Vec<MessageValue> {
|
|
let mut values = vec![];
|
|
values.push(MessageValue::Unsigned(axis)); // axis id
|
|
values.push(MessageValue::Unsigned(0)); // attributes low
|
|
values.push(MessageValue::Unsigned(self.axis_unit())); // attributes high
|
|
|
|
// Name in the recommended format, 16 bytes:
|
|
let prefix = self.axis_name_prefix();
|
|
let suffix = self.axis_name_suffix(axis);
|
|
values.push(MessageValue::String(
|
|
format!("{prefix}_{suffix}"),
|
|
MAX_SIMPLE_STRING_LENGTH,
|
|
));
|
|
values
|
|
}
|
|
|
|
fn config_get(&self) -> DeviceResult {
|
|
let config = u32::from(self.sensor().enabled);
|
|
Ok(vec![MessageValue::Unsigned(config)])
|
|
}
|
|
|
|
fn config_set(&mut self, config: u32) -> DeviceResult {
|
|
if config & 0xFFFFFFFE != 0 {
|
|
return Result::Err(ScmiDeviceError::UnsupportedRequest);
|
|
}
|
|
self.sensor_mut().enabled = config != 0;
|
|
debug!("Sensor enabled: {}", self.sensor().enabled);
|
|
Ok(vec![])
|
|
}
|
|
|
|
fn reading_get(&mut self) -> DeviceResult;
|
|
|
|
fn handle(&mut self, message_id: MessageId, parameters: &[MessageValue]) -> DeviceResult {
|
|
match message_id {
|
|
SENSOR_DESCRIPTION_GET => self.description_get(),
|
|
SENSOR_AXIS_DESCRIPTION_GET => {
|
|
let n_sensor_axes = self.number_of_axes();
|
|
let axis_desc_index = parameters[0].get_unsigned();
|
|
if axis_desc_index >= n_sensor_axes {
|
|
return Result::Err(ScmiDeviceError::InvalidParameters);
|
|
}
|
|
let mut values = vec![MessageValue::Unsigned(n_sensor_axes - axis_desc_index)];
|
|
for i in axis_desc_index..n_sensor_axes {
|
|
let mut description = self.axis_description(i);
|
|
values.append(&mut description);
|
|
}
|
|
Ok(values)
|
|
}
|
|
SENSOR_CONFIG_GET => self.config_get(),
|
|
SENSOR_CONFIG_SET => {
|
|
let config = parameters[0].get_unsigned();
|
|
self.config_set(config)
|
|
}
|
|
SENSOR_CONTINUOUS_UPDATE_NOTIFY => {
|
|
// Linux VIRTIO SCMI insists on this.
|
|
// We can accept it and ignore it, the sensor will be still working.
|
|
Ok(vec![])
|
|
}
|
|
SENSOR_READING_GET => {
|
|
if !self.sensor().enabled {
|
|
return Result::Err(ScmiDeviceError::NotEnabled);
|
|
}
|
|
self.reading_get()
|
|
}
|
|
_ => Result::Err(ScmiDeviceError::UnsupportedRequest),
|
|
}
|
|
}
|
|
}
|
|
|
|
// It's possible to impl ScmiDevice for SensorT but it is not very useful
|
|
// because it doesn't allow to pass SensorT as ScmiDevice directly.
|
|
// Hence this wrapper.
|
|
struct SensorDevice(Box<dyn SensorT>);
|
|
|
|
impl ScmiDevice for SensorDevice {
|
|
fn protocol(&self) -> ProtocolId {
|
|
self.0.protocol()
|
|
}
|
|
|
|
fn handle(&mut self, message_id: MessageId, parameters: &[MessageValue]) -> DeviceResult {
|
|
self.0.handle(message_id, parameters)
|
|
}
|
|
}
|
|
|
|
// Particular sensor implementations
|
|
|
|
pub struct FakeSensor {
|
|
sensor: Sensor,
|
|
value: u8,
|
|
}
|
|
|
|
impl SensorT for FakeSensor {
|
|
// TODO: Define a macro for this boilerplate?
|
|
fn sensor(&self) -> &Sensor {
|
|
&self.sensor
|
|
}
|
|
fn sensor_mut(&mut self) -> &mut Sensor {
|
|
&mut self.sensor
|
|
}
|
|
|
|
fn number_of_axes(&self) -> u32 {
|
|
3
|
|
}
|
|
|
|
fn axis_unit(&self) -> u32 {
|
|
// The sensor type is "Meters per second squared", since this is the
|
|
// only, together with "Radians per second", what Google Linux IIO
|
|
// supports (accelerometers and gyroscopes only).
|
|
SENSOR_UNIT_METERS_PER_SECOND_SQUARED
|
|
}
|
|
|
|
fn axis_name_prefix(&self) -> String {
|
|
"acc".to_owned()
|
|
}
|
|
|
|
fn reading_get(&mut self) -> DeviceResult {
|
|
let value = self.value;
|
|
self.value = self.value.overflowing_add(1).0;
|
|
let mut result = vec![];
|
|
for i in 0..3 {
|
|
result.push(MessageValue::Unsigned(u32::from(value) + 100 * i));
|
|
result.push(MessageValue::Unsigned(0));
|
|
result.push(MessageValue::Unsigned(0));
|
|
result.push(MessageValue::Unsigned(0));
|
|
}
|
|
Ok(result)
|
|
}
|
|
}
|
|
|
|
impl FakeSensor {
|
|
#[allow(clippy::new_ret_no_self)]
|
|
pub fn new(properties: &DeviceProperties) -> MaybeDevice {
|
|
properties.check(&[], &["name"])?;
|
|
let sensor = Sensor::new(properties, "fake");
|
|
let fake_sensor = Self { sensor, value: 0 };
|
|
let sensor_device = SensorDevice(Box::new(fake_sensor));
|
|
Ok(Box::new(sensor_device))
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::assert_eq;
|
|
|
|
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");
|
|
}
|
|
|
|
fn device_properties() -> DeviceProperties {
|
|
DeviceProperties::new(vec![
|
|
("foo".to_owned(), "val1".to_owned()),
|
|
("def".to_owned(), "val2".to_owned()),
|
|
("bar".to_owned(), "val3".to_owned()),
|
|
])
|
|
}
|
|
|
|
#[test]
|
|
fn test_device_properties() {
|
|
let properties = device_properties();
|
|
assert_eq!(properties.get("bar"), Some("val3"));
|
|
assert_eq!(properties.get("baz"), None);
|
|
assert_eq!(properties.names(), HashSet::from(["foo", "def", "bar"]));
|
|
let expected = ["abc", "def", "ghi"];
|
|
let missing = properties.missing(&expected);
|
|
assert_eq!(missing, HashSet::from(["abc", "ghi"]));
|
|
let extra = properties.extra(&expected);
|
|
assert_eq!(extra, HashSet::from(["foo", "bar"]));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_device_properties() {
|
|
let properties = device_properties();
|
|
let result = properties.check(&["abc", "def", "ghi"], &["foo", "baz"]);
|
|
assert_eq!(
|
|
result,
|
|
Err(DeviceError::MissingDeviceProperties(vec![
|
|
"abc".to_owned(),
|
|
"ghi".to_owned()
|
|
]))
|
|
);
|
|
let result = properties.check(&["def"], &["foo", "baz"]);
|
|
assert_eq!(
|
|
result,
|
|
Err(DeviceError::UnexpectedDeviceProperties(vec![
|
|
"bar".to_owned()
|
|
]))
|
|
);
|
|
let result = properties.check(&["def"], &["foo", "bar"]);
|
|
assert_eq!(result, Ok(()));
|
|
}
|
|
}
|