mirror of
https://github.com/rust-vmm/vhost-device.git
synced 2026-01-07 03:03:44 +00:00
scmi: Add support for industrial I/O devices
Industrial I/O (IIO) devices are present in /sys/bus/iio/devices/ on Linux. This patch makes them accessible to the guests via the sensor SCMI protocol. The implementation no way covers all the possible IIO devices. It supports some basic stuff, other sensors can be added as needed. Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
This commit is contained in:
parent
c1637e94b8
commit
458f168639
@ -26,7 +26,8 @@ the Examples section below.
|
||||
|
||||
.. option:: -d, --device=SPEC
|
||||
|
||||
SCMI device specification in the format `ID,PROPERTY=VALUE,...`.
|
||||
SCMI device specification in the format `ID,PROPERTY=VALUE,...`.
|
||||
For example: `-d iio,path=/sys/bus/iio/devices/iio:device0,channel=in_accel`.
|
||||
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.
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::ffi::OsString;
|
||||
use std::fmt::Write;
|
||||
use std::process::exit;
|
||||
|
||||
@ -16,16 +17,20 @@ use crate::scmi::{
|
||||
SENSOR_READING_GET,
|
||||
};
|
||||
|
||||
use super::fake;
|
||||
use super::{fake, iio};
|
||||
|
||||
enum ExitCodes {
|
||||
Help = 1,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, ThisError)]
|
||||
#[derive(Debug, ThisError)]
|
||||
pub enum DeviceError {
|
||||
#[error("{0}")]
|
||||
GenericError(String),
|
||||
#[error("Invalid device parameter: {0}")]
|
||||
InvalidProperty(String),
|
||||
#[error("I/O error on {0:?}: {1}")]
|
||||
IOError(OsString, std::io::Error),
|
||||
#[error("Missing device parameters: {}", .0.join(", "))]
|
||||
MissingDeviceProperties(Vec<String>),
|
||||
#[error("Unexpected device parameters: {}", .0.join(", "))]
|
||||
@ -134,6 +139,19 @@ pub fn available_devices() -> NameDeviceMapping {
|
||||
&["name: an optional name of the sensor, max. 15 characters"],
|
||||
),
|
||||
);
|
||||
devices.insert(
|
||||
"iio",
|
||||
DeviceSpecification::new(
|
||||
iio::IIOSensor::new_device,
|
||||
"industrial I/O sensor",
|
||||
"",
|
||||
&[
|
||||
"path: path to the device directory (e.g. /sys/bus/iio/devices/iio:device0)",
|
||||
"channel: prefix of the device type (e.g. in_accel)",
|
||||
"name: an optional name of the sensor, max. 15 characters",
|
||||
],
|
||||
),
|
||||
);
|
||||
devices
|
||||
}
|
||||
|
||||
@ -172,6 +190,7 @@ pub fn print_devices_help() {
|
||||
|
||||
// Common sensor infrastructure
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Sensor {
|
||||
pub name: String,
|
||||
enabled: bool,
|
||||
@ -392,22 +411,21 @@ mod tests {
|
||||
#[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(()));
|
||||
match properties.check(&["abc", "def", "ghi"], &["foo", "baz"]) {
|
||||
Err(DeviceError::MissingDeviceProperties(missing)) => {
|
||||
assert_eq!(missing, vec!["abc".to_owned(), "ghi".to_owned()])
|
||||
}
|
||||
other => panic!("Unexpected result: {:?}", other),
|
||||
}
|
||||
match properties.check(&["def"], &["foo", "baz"]) {
|
||||
Err(DeviceError::UnexpectedDeviceProperties(unexpected)) => {
|
||||
assert_eq!(unexpected, vec!["bar".to_owned()])
|
||||
}
|
||||
other => panic!("Unexpected result: {:?}", other),
|
||||
}
|
||||
match properties.check(&["def"], &["foo", "bar"]) {
|
||||
Ok(()) => (),
|
||||
other => panic!("Unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
782
crates/scmi/src/devices/iio.rs
Normal file
782
crates/scmi/src/devices/iio.rs
Normal file
@ -0,0 +1,782 @@
|
||||
// SPDX-FileCopyrightText: Red Hat, Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Industrial I/O sensors
|
||||
|
||||
use std::cmp::{max, min};
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::fs;
|
||||
use std::io::ErrorKind;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
use log::{debug, error, warn};
|
||||
|
||||
use crate::scmi::{self, DeviceResult, MessageValue, ScmiDeviceError, MAX_SIMPLE_STRING_LENGTH};
|
||||
|
||||
use super::common::{DeviceError, DeviceProperties, MaybeDevice, Sensor, SensorDevice, SensorT};
|
||||
|
||||
struct UnitMapping<'a> {
|
||||
channel: &'a str,
|
||||
unit: u8,
|
||||
unit_exponent: i8, // max. 5 bits actually
|
||||
}
|
||||
|
||||
// Incomplete, just a sample.
|
||||
// TODO: Make some macro(s) for this.
|
||||
const UNIT_MAPPING: &[UnitMapping] = &[
|
||||
UnitMapping {
|
||||
channel: "in_accel",
|
||||
unit: scmi::SENSOR_UNIT_METERS_PER_SECOND_SQUARED,
|
||||
unit_exponent: 0,
|
||||
},
|
||||
UnitMapping {
|
||||
channel: "in_angle",
|
||||
unit: scmi::SENSOR_UNIT_RADIANS,
|
||||
unit_exponent: 0,
|
||||
},
|
||||
UnitMapping {
|
||||
channel: "in_anglevel",
|
||||
unit: scmi::SENSOR_UNIT_RADIANS_PER_SECOND,
|
||||
unit_exponent: 0,
|
||||
},
|
||||
UnitMapping {
|
||||
channel: "in_concentration",
|
||||
unit: scmi::SENSOR_UNIT_PERCENTAGE,
|
||||
unit_exponent: 0,
|
||||
},
|
||||
UnitMapping {
|
||||
channel: "in_current",
|
||||
unit: scmi::SENSOR_UNIT_AMPS,
|
||||
unit_exponent: -3,
|
||||
},
|
||||
UnitMapping {
|
||||
channel: "in_capacitance",
|
||||
unit: scmi::SENSOR_UNIT_FARADS,
|
||||
unit_exponent: -9,
|
||||
},
|
||||
UnitMapping {
|
||||
channel: "in_distance",
|
||||
unit: scmi::SENSOR_UNIT_METERS,
|
||||
unit_exponent: 0,
|
||||
},
|
||||
UnitMapping {
|
||||
channel: "in_electricalconductivity",
|
||||
unit: scmi::SENSOR_UNIT_SIEMENS, // per meter
|
||||
unit_exponent: 0,
|
||||
},
|
||||
UnitMapping {
|
||||
channel: "in_energy",
|
||||
unit: scmi::SENSOR_UNIT_JOULS,
|
||||
unit_exponent: 0,
|
||||
},
|
||||
UnitMapping {
|
||||
channel: "in_gravity",
|
||||
unit: scmi::SENSOR_UNIT_METERS_PER_SECOND_SQUARED,
|
||||
unit_exponent: 0,
|
||||
},
|
||||
UnitMapping {
|
||||
channel: "in_humidityrelative",
|
||||
unit: scmi::SENSOR_UNIT_PERCENTAGE,
|
||||
unit_exponent: -3,
|
||||
},
|
||||
UnitMapping {
|
||||
channel: "in_illuminance",
|
||||
unit: scmi::SENSOR_UNIT_LUX,
|
||||
unit_exponent: 0,
|
||||
},
|
||||
UnitMapping {
|
||||
channel: "in_magn",
|
||||
unit: scmi::SENSOR_UNIT_GAUSS,
|
||||
unit_exponent: 0,
|
||||
},
|
||||
UnitMapping {
|
||||
channel: "in_ph",
|
||||
unit: scmi::SENSOR_UNIT_UNSPECIFIED, // SCMI doesn't define pH
|
||||
unit_exponent: -3,
|
||||
},
|
||||
UnitMapping {
|
||||
channel: "in_positionrelative",
|
||||
unit: scmi::SENSOR_UNIT_PERCENTAGE,
|
||||
unit_exponent: -3,
|
||||
},
|
||||
UnitMapping {
|
||||
channel: "in_power",
|
||||
unit: scmi::SENSOR_UNIT_WATTS,
|
||||
unit_exponent: -3,
|
||||
},
|
||||
UnitMapping {
|
||||
channel: "in_pressure",
|
||||
unit: scmi::SENSOR_UNIT_PASCALS,
|
||||
unit_exponent: 3,
|
||||
},
|
||||
UnitMapping {
|
||||
channel: "in_proximity",
|
||||
unit: scmi::SENSOR_UNIT_METERS,
|
||||
unit_exponent: 0,
|
||||
},
|
||||
UnitMapping {
|
||||
channel: "in_resistance",
|
||||
unit: scmi::SENSOR_UNIT_OHMS,
|
||||
unit_exponent: 0,
|
||||
},
|
||||
UnitMapping {
|
||||
channel: "in_temp",
|
||||
unit: scmi::SENSOR_UNIT_DEGREES_C,
|
||||
unit_exponent: -3,
|
||||
},
|
||||
UnitMapping {
|
||||
channel: "in_velocity_sqrt(x^2+y^2+z^2)",
|
||||
unit: scmi::SENSOR_UNIT_METERS_PER_SECOND,
|
||||
unit_exponent: -3,
|
||||
},
|
||||
UnitMapping {
|
||||
channel: "in_voltage",
|
||||
unit: scmi::SENSOR_UNIT_VOLTS,
|
||||
unit_exponent: -3,
|
||||
},
|
||||
];
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
struct Axis {
|
||||
path: OsString, // without "_raw" suffix
|
||||
unit_exponent: i8,
|
||||
custom_exponent: i8,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct IIOSensor {
|
||||
sensor: Sensor,
|
||||
// Full /sys path to the device directory
|
||||
path: OsString,
|
||||
// Prefix of the device type in the device directory, e.g. "in_accel"
|
||||
channel: OsString,
|
||||
// Whether the sensor is scalar or has one or more axes
|
||||
scalar: bool,
|
||||
// Paths to "_raw" files
|
||||
axes: Vec<Axis>,
|
||||
}
|
||||
|
||||
impl SensorT for IIOSensor {
|
||||
// TODO: Define a macro for this boilerplate?
|
||||
fn sensor(&self) -> &Sensor {
|
||||
&self.sensor
|
||||
}
|
||||
fn sensor_mut(&mut self) -> &mut Sensor {
|
||||
&mut self.sensor
|
||||
}
|
||||
|
||||
fn initialize(&mut self) -> Result<(), DeviceError> {
|
||||
let mut axes: Vec<Axis> = vec![];
|
||||
match fs::read_dir(&self.path) {
|
||||
Ok(iter) => {
|
||||
for dir_entry in iter {
|
||||
match dir_entry {
|
||||
Ok(entry) => self.register_iio_file(entry, &mut axes),
|
||||
Err(error) => return Err(DeviceError::IOError(self.path.clone(), error)),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error) => return Err(DeviceError::IOError(self.path.clone(), error)),
|
||||
}
|
||||
if axes.is_empty() {
|
||||
return Err(DeviceError::GenericError(format!(
|
||||
"No {:?} channel found in {:?}",
|
||||
&self.channel, &self.path
|
||||
)));
|
||||
}
|
||||
axes.sort_by(|a1, a2| a1.path.cmp(&a2.path));
|
||||
self.axes = axes;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unit(&self) -> u8 {
|
||||
UNIT_MAPPING
|
||||
.iter()
|
||||
.find(|mapping| mapping.channel == self.channel)
|
||||
.map_or(scmi::SENSOR_UNIT_UNSPECIFIED, |mapping| mapping.unit)
|
||||
}
|
||||
|
||||
fn unit_exponent(&self, axis_index: u32) -> i8 {
|
||||
let axis: &Axis = self.axes.get(axis_index as usize).unwrap();
|
||||
axis.unit_exponent + axis.custom_exponent
|
||||
}
|
||||
|
||||
fn number_of_axes(&self) -> u32 {
|
||||
if self.scalar {
|
||||
0
|
||||
} else {
|
||||
self.axes.len() as u32
|
||||
}
|
||||
}
|
||||
|
||||
fn axis_name_prefix(&self) -> String {
|
||||
let channel = self.channel.to_str().unwrap();
|
||||
let in_prefix = "in_";
|
||||
let out_prefix = "out_";
|
||||
let name: &str = if channel.starts_with(in_prefix) {
|
||||
channel.strip_prefix(in_prefix).unwrap()
|
||||
} else if channel.starts_with(out_prefix) {
|
||||
channel.strip_prefix(out_prefix).unwrap()
|
||||
} else {
|
||||
channel
|
||||
};
|
||||
let len = min(name.len(), MAX_SIMPLE_STRING_LENGTH - 1);
|
||||
String::from(&name[..len])
|
||||
}
|
||||
|
||||
fn reading_get(&mut self) -> DeviceResult {
|
||||
let mut result = vec![];
|
||||
for axis in &self.axes {
|
||||
let value = self.read_axis(axis)?;
|
||||
result.push(MessageValue::Unsigned((value & 0xFFFFFFFF) as u32));
|
||||
result.push(MessageValue::Unsigned((value >> 32) as u32));
|
||||
result.push(MessageValue::Unsigned(0));
|
||||
result.push(MessageValue::Unsigned(0));
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
fn read_number_from_file<F: FromStr>(path: &Path) -> Result<Option<F>, ScmiDeviceError> {
|
||||
match fs::read_to_string(path) {
|
||||
Ok(string) => match string.trim().parse() {
|
||||
Ok(value) => Ok(Some(value)),
|
||||
_ => {
|
||||
error!(
|
||||
"Failed to parse IIO numeric value from {}: {string}",
|
||||
path.display()
|
||||
);
|
||||
Err(ScmiDeviceError::GenericError)
|
||||
}
|
||||
},
|
||||
Err(error) => match error.kind() {
|
||||
ErrorKind::NotFound => {
|
||||
let raw = path.ends_with("_raw");
|
||||
let format = || {
|
||||
format!(
|
||||
"IIO {} file {} not found",
|
||||
if raw { "value" } else { "data" },
|
||||
path.display()
|
||||
)
|
||||
};
|
||||
if raw {
|
||||
error!("{}", format());
|
||||
Err(ScmiDeviceError::GenericError)
|
||||
} else {
|
||||
debug!("{}", format());
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
other_error => {
|
||||
error!(
|
||||
"Failed to read IIO data from {}: {}",
|
||||
path.display(),
|
||||
other_error
|
||||
);
|
||||
Err(ScmiDeviceError::GenericError)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
impl IIOSensor {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(properties: &DeviceProperties) -> Result<IIOSensor, DeviceError> {
|
||||
properties.check(&["path", "channel"], &["name"])?;
|
||||
let sensor = Sensor::new(properties, "iio");
|
||||
Ok(IIOSensor {
|
||||
sensor,
|
||||
path: OsString::from(properties.get("path").unwrap()),
|
||||
channel: OsString::from(properties.get("channel").unwrap()),
|
||||
scalar: true,
|
||||
axes: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_device(properties: &DeviceProperties) -> MaybeDevice {
|
||||
let iio_sensor = IIOSensor::new(properties)?;
|
||||
let sensor_device = SensorDevice(Box::new(iio_sensor));
|
||||
Ok(Box::new(sensor_device))
|
||||
}
|
||||
|
||||
fn set_sensor_name_from_file(&mut self, path: &PathBuf) {
|
||||
match fs::read_to_string(path) {
|
||||
Ok(name) => self.sensor_mut().name = name,
|
||||
Err(error) => warn!(
|
||||
"Error reading IIO device name from {}: {}",
|
||||
path.display(),
|
||||
error
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn custom_exponent(&self, path: &OsStr, unit_exponent: i8) -> i8 {
|
||||
let mut custom_exponent: i8 = 0;
|
||||
if let Ok(Some(scale)) = self.read_axis_scale(path) {
|
||||
// Crash completely OK if *this* doesn't fit:
|
||||
custom_exponent = scale.log10() as i8;
|
||||
if scale < 1.0 {
|
||||
// The logarithm is truncated towards zero, we need floor
|
||||
custom_exponent -= 1;
|
||||
}
|
||||
// The SCMI exponent (unit_exponent + custom_exponent) can have max. 5 bits:
|
||||
custom_exponent = min(15 - unit_exponent, custom_exponent);
|
||||
custom_exponent = max(-16 - unit_exponent, custom_exponent);
|
||||
debug!(
|
||||
"Setting custom scaling coefficient for {:?}: {}",
|
||||
&path, custom_exponent
|
||||
);
|
||||
}
|
||||
custom_exponent
|
||||
}
|
||||
|
||||
fn add_axis(&mut self, axes: &mut Vec<Axis>, path: &OsStr) {
|
||||
let unit_exponent = UNIT_MAPPING
|
||||
.iter()
|
||||
.find(|mapping| mapping.channel == self.channel)
|
||||
.map_or(0, |mapping| mapping.unit_exponent);
|
||||
// To get meaningful integer values, we must adjust exponent to
|
||||
// the provided scale if any.
|
||||
let custom_exponent = self.custom_exponent(path, unit_exponent);
|
||||
axes.push(Axis {
|
||||
path: OsString::from(path),
|
||||
unit_exponent,
|
||||
custom_exponent,
|
||||
});
|
||||
}
|
||||
|
||||
fn register_iio_file(&mut self, file: fs::DirEntry, axes: &mut Vec<Axis>) {
|
||||
let channel = self.channel.to_str().unwrap();
|
||||
let os_file_name = file.file_name();
|
||||
let file_name = os_file_name.to_str().unwrap_or_default();
|
||||
let raw_suffix = "_raw";
|
||||
if file_name == "name" {
|
||||
self.set_sensor_name_from_file(&file.path());
|
||||
} else if file_name.starts_with(channel) && file_name.ends_with(raw_suffix) {
|
||||
let infix = &file_name[channel.len()..file_name.len() - raw_suffix.len()];
|
||||
let infix_len = infix.len();
|
||||
if infix_len == 0 || (infix_len == 2 && infix.starts_with('_')) {
|
||||
let raw_axis_path = Path::new(&self.path)
|
||||
.join(Path::new(&file_name))
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let axis_path = raw_axis_path.strip_suffix(raw_suffix).unwrap();
|
||||
self.add_axis(axes, &OsString::from(axis_path));
|
||||
if infix_len > 0 {
|
||||
self.scalar = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_axis_file<T: FromStr>(
|
||||
&self,
|
||||
path: &OsStr,
|
||||
name: &str,
|
||||
) -> Result<Option<T>, ScmiDeviceError> {
|
||||
for value_path in [
|
||||
Path::new(&(String::from(path.to_str().unwrap()) + "_" + name)),
|
||||
&Path::new(&path).parent().unwrap().join(name),
|
||||
]
|
||||
.iter()
|
||||
{
|
||||
let value: Option<T> = read_number_from_file(value_path)?;
|
||||
if value.is_some() {
|
||||
return Ok(value);
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn read_axis_offset(&self, path: &OsStr) -> Result<Option<i64>, ScmiDeviceError> {
|
||||
self.read_axis_file(path, "offset")
|
||||
}
|
||||
|
||||
fn read_axis_scale(&self, path: &OsStr) -> Result<Option<f64>, ScmiDeviceError> {
|
||||
self.read_axis_file(path, "scale")
|
||||
}
|
||||
|
||||
fn read_axis(&self, axis: &Axis) -> Result<i64, ScmiDeviceError> {
|
||||
let path_result = axis.path.clone().into_string();
|
||||
let mut value: i64 =
|
||||
read_number_from_file(Path::new(&(path_result.unwrap() + "_raw")))?.unwrap();
|
||||
let offset: Option<i64> = self.read_axis_offset(&axis.path)?;
|
||||
if let Some(offset_value) = offset {
|
||||
match value.checked_add(offset_value) {
|
||||
Some(new_value) => value = new_value,
|
||||
None => {
|
||||
error!(
|
||||
"IIO offset overflow in {:?}: {} + {}",
|
||||
&axis.path,
|
||||
value,
|
||||
offset.unwrap()
|
||||
);
|
||||
return Err(ScmiDeviceError::GenericError);
|
||||
}
|
||||
}
|
||||
}
|
||||
let scale: Option<f64> = self.read_axis_scale(&axis.path)?;
|
||||
if let Some(scale_value) = scale {
|
||||
let exponent_scale = 10.0_f64.powi(i32::from(axis.custom_exponent));
|
||||
value = (value as f64 * (scale_value / exponent_scale)).round() as i64;
|
||||
}
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::scmi::ScmiDevice;
|
||||
|
||||
use super::*;
|
||||
use std::{
|
||||
assert_eq, fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
fn make_directory(prefix: &str) -> PathBuf {
|
||||
for i in 1..100 {
|
||||
let path = Path::new(".").join(format!("{prefix}{i}"));
|
||||
if fs::create_dir(&path).is_ok() {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
panic!("Couldn't create test directory");
|
||||
}
|
||||
|
||||
struct IIODirectory {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl IIODirectory {
|
||||
fn new(files: &[(&str, &str)]) -> IIODirectory {
|
||||
let path = make_directory("_test");
|
||||
let directory = IIODirectory { path };
|
||||
for (file, content) in files.iter() {
|
||||
fs::write(&directory.path.join(file), content).unwrap();
|
||||
}
|
||||
directory
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for IIODirectory {
|
||||
fn drop(&mut self) {
|
||||
let _ = fs::remove_dir_all(&self.path);
|
||||
}
|
||||
}
|
||||
|
||||
fn directory_path(directory: &IIODirectory) -> String {
|
||||
directory
|
||||
.path
|
||||
.clone()
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn device_properties(path: String, channel: String, name: Option<String>) -> DeviceProperties {
|
||||
let mut pairs = vec![("path".to_owned(), path), ("channel".to_owned(), channel)];
|
||||
if let Some(name) = name {
|
||||
pairs.push(("name".to_owned(), name));
|
||||
}
|
||||
DeviceProperties::new(pairs)
|
||||
}
|
||||
|
||||
fn make_iio_sensor_from_path(path: String, channel: String, name: Option<String>) -> IIOSensor {
|
||||
let properties = device_properties(path, channel, name);
|
||||
IIOSensor::new(&properties).unwrap()
|
||||
}
|
||||
|
||||
fn make_iio_sensor(
|
||||
directory: &IIODirectory,
|
||||
channel: String,
|
||||
name: Option<String>,
|
||||
) -> IIOSensor {
|
||||
let path = directory_path(directory);
|
||||
make_iio_sensor_from_path(path, channel, name)
|
||||
}
|
||||
|
||||
fn make_scmi_sensor_from_path(
|
||||
path: String,
|
||||
channel: String,
|
||||
name: Option<String>,
|
||||
) -> MaybeDevice {
|
||||
let properties = device_properties(path, channel, name);
|
||||
IIOSensor::new_device(&properties)
|
||||
}
|
||||
|
||||
fn make_scmi_sensor(
|
||||
directory: &IIODirectory,
|
||||
channel: String,
|
||||
name: Option<String>,
|
||||
) -> Box<dyn ScmiDevice> {
|
||||
let path = directory_path(directory);
|
||||
make_scmi_sensor_from_path(path, channel, name).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_missing_property() {
|
||||
let properties = DeviceProperties::new(vec![("path".to_owned(), ".".to_owned())]);
|
||||
let result = IIOSensor::new(&properties);
|
||||
match result {
|
||||
Ok(_) => panic!("Should fail on a missing property"),
|
||||
Err(DeviceError::MissingDeviceProperties(missing)) => {
|
||||
assert_eq!(missing, vec!["channel".to_owned()])
|
||||
}
|
||||
other => panic!("Unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extra_property() {
|
||||
let properties = DeviceProperties::new(vec![
|
||||
("path".to_owned(), ".".to_owned()),
|
||||
("name".to_owned(), "test".to_owned()),
|
||||
("channel".to_owned(), "in_accel".to_owned()),
|
||||
("foo".to_owned(), "something".to_owned()),
|
||||
("bar".to_owned(), "baz".to_owned()),
|
||||
]);
|
||||
let result = IIOSensor::new(&properties);
|
||||
match result {
|
||||
Ok(_) => panic!("Should fail on an extra property"),
|
||||
Err(DeviceError::UnexpectedDeviceProperties(extra)) => {
|
||||
assert_eq!(extra, ["bar".to_owned(), "foo".to_owned()])
|
||||
}
|
||||
other => panic!("Unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iio_init() {
|
||||
let directory = IIODirectory::new(&[("foo", "bar"), ("in_accel_raw", "123")]);
|
||||
let mut sensor =
|
||||
make_scmi_sensor(&directory, "in_accel".to_owned(), Some("accel".to_owned()));
|
||||
sensor.initialize().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iio_init_no_directory() {
|
||||
let mut sensor =
|
||||
make_scmi_sensor_from_path("non-existent".to_owned(), "".to_owned(), None).unwrap();
|
||||
match sensor.initialize() {
|
||||
Ok(_) => panic!("Should fail on an inaccessible path"),
|
||||
Err(DeviceError::IOError(path, std::io::Error { .. })) => {
|
||||
assert_eq!(path, "non-existent")
|
||||
}
|
||||
other => panic!("Unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iio_init_no_channel() {
|
||||
let directory = IIODirectory::new(&[("foo", "bar")]);
|
||||
let mut sensor = make_scmi_sensor(&directory, "in_accel".to_owned(), None);
|
||||
match sensor.initialize() {
|
||||
Ok(_) => panic!("Should fail on an inaccessible channel"),
|
||||
Err(DeviceError::GenericError(message)) => {
|
||||
assert!(
|
||||
message.starts_with("No \"in_accel\" channel found in \"./_test"),
|
||||
"Unexpected error: {}",
|
||||
message
|
||||
)
|
||||
}
|
||||
other => panic!("Unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sensor_name_from_fs() {
|
||||
let directory = IIODirectory::new(&[("in_accel_raw", "123"), ("name", "foo")]);
|
||||
let mut sensor =
|
||||
make_iio_sensor(&directory, "in_accel".to_owned(), Some("accel".to_owned()));
|
||||
sensor.initialize().unwrap();
|
||||
assert_eq!(sensor.sensor.name, "foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sensor_name_from_params() {
|
||||
let directory = IIODirectory::new(&[("in_accel_raw", "123")]);
|
||||
let mut sensor = make_iio_sensor(&directory, "in_accel".to_owned(), Some("foo".to_owned()));
|
||||
sensor.initialize().unwrap();
|
||||
assert_eq!(sensor.sensor.name, "foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_sensor_name() {
|
||||
let directory = IIODirectory::new(&[("in_accel_raw", "123")]);
|
||||
let mut sensor = make_iio_sensor(&directory, "in_accel".to_owned(), None);
|
||||
sensor.initialize().unwrap();
|
||||
assert_eq!(sensor.sensor.name, "iio");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_units() {
|
||||
let directory = IIODirectory::new(&[
|
||||
("in_foo_raw", "123"),
|
||||
("in_accel_raw", "123"),
|
||||
("in_voltage_raw", "123"),
|
||||
]);
|
||||
for (name, unit) in [
|
||||
("foo", scmi::SENSOR_UNIT_UNSPECIFIED),
|
||||
("accel", scmi::SENSOR_UNIT_METERS_PER_SECOND_SQUARED),
|
||||
("voltage", scmi::SENSOR_UNIT_VOLTS),
|
||||
]
|
||||
.iter()
|
||||
{
|
||||
let sensor =
|
||||
make_iio_sensor(&directory, "in_".to_owned() + name, Some(name.to_string()));
|
||||
assert_eq!(sensor.unit(), *unit);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unit_exponent() {
|
||||
for (channel, scale, exponent) in [
|
||||
("in_accel", 1.23, 0),
|
||||
("in_accel", 0.000123, -4),
|
||||
("in_accel", 123.0, 2),
|
||||
("in_voltage", 123.0, -1),
|
||||
]
|
||||
.iter()
|
||||
{
|
||||
let raw_file = format!("{channel}_raw");
|
||||
let scale_file = format!("{channel}_scale");
|
||||
let directory =
|
||||
IIODirectory::new(&[(&raw_file, "123"), (&scale_file, &scale.to_string())]);
|
||||
let mut sensor = make_iio_sensor(&directory, channel.to_string(), None);
|
||||
sensor.initialize().unwrap();
|
||||
assert_eq!(sensor.unit_exponent(0), *exponent);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unit_exponent_multiple_axes() {
|
||||
let directory = IIODirectory::new(&[
|
||||
("in_accel_x_raw", "123"),
|
||||
("in_accel_x_scale", "0.123"),
|
||||
("in_accel_y_raw", "123"),
|
||||
("in_accel_y_scale", "12.3"),
|
||||
]);
|
||||
let mut sensor = make_iio_sensor(&directory, "in_accel".to_owned(), None);
|
||||
sensor.initialize().unwrap();
|
||||
assert_eq!(sensor.unit_exponent(0), -1);
|
||||
assert_eq!(sensor.unit_exponent(1), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unit_exponent_single_scale() {
|
||||
let directory = IIODirectory::new(&[("in_accel_raw", "123"), ("scale", "0.123")]);
|
||||
let mut sensor = make_iio_sensor(&directory, "in_accel".to_owned(), None);
|
||||
sensor.initialize().unwrap();
|
||||
assert_eq!(sensor.unit_exponent(0), -1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_number_of_axes_scalar() {
|
||||
let directory = IIODirectory::new(&[("in_accel_raw", "123"), ("in_accel_scale", "123")]);
|
||||
let mut sensor = make_iio_sensor(&directory, "in_accel".to_owned(), None);
|
||||
sensor.initialize().unwrap();
|
||||
assert_eq!(sensor.number_of_axes(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_number_of_axes_1() {
|
||||
let directory = IIODirectory::new(&[("in_accel_x_raw", "123"), ("in_accel_scale", "123")]);
|
||||
let mut sensor = make_iio_sensor(&directory, "in_accel".to_owned(), None);
|
||||
sensor.initialize().unwrap();
|
||||
assert_eq!(sensor.number_of_axes(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_number_of_axes_3() {
|
||||
let directory = IIODirectory::new(&[
|
||||
("in_accel_x_raw", "123"),
|
||||
("in_accel_y_raw", "123"),
|
||||
("in_accel_z_raw", "123"),
|
||||
("in_accel_x_scale", "123"),
|
||||
]);
|
||||
let mut sensor = make_iio_sensor(&directory, "in_accel".to_owned(), None);
|
||||
sensor.initialize().unwrap();
|
||||
assert_eq!(sensor.number_of_axes(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_axis_name_prefix() {
|
||||
for (channel, prefix) in [
|
||||
("in_accel", "accel"),
|
||||
("out_voltage", "voltage"),
|
||||
("foo", "foo"),
|
||||
("name-longer-than-fifteen-characters", "name-longer-tha"),
|
||||
]
|
||||
.iter()
|
||||
{
|
||||
let sensor = make_iio_sensor_from_path("".to_owned(), channel.to_string(), None);
|
||||
assert_eq!(&sensor.axis_name_prefix(), prefix);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iio_reading_scalar() {
|
||||
let directory = IIODirectory::new(&[
|
||||
("in_voltage_raw", "9876543210"),
|
||||
("in_voltage_offset", "123"),
|
||||
("in_voltage_scale", "456"),
|
||||
]);
|
||||
let mut sensor = make_iio_sensor(&directory, "in_voltage".to_owned(), None);
|
||||
sensor.initialize().unwrap();
|
||||
let result = sensor.reading_get().unwrap();
|
||||
// (9876543210 + 123) * 456 = 4503703759848
|
||||
// custom exponent = 2
|
||||
// applied and rounded: 45037037598 = 0xA7C6AA81E
|
||||
assert_eq!(result.len(), 4);
|
||||
assert_eq!(result.get(0).unwrap(), &MessageValue::Unsigned(0x7C6AA81E));
|
||||
assert_eq!(result.get(1).unwrap(), &MessageValue::Unsigned(0xA));
|
||||
assert_eq!(result.get(2).unwrap(), &MessageValue::Unsigned(0));
|
||||
assert_eq!(result.get(3).unwrap(), &MessageValue::Unsigned(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iio_reading_scalar_whitespace() {
|
||||
let directory = IIODirectory::new(&[
|
||||
("in_accel_raw", "10\n"),
|
||||
("in_accel_offset", "20\n"),
|
||||
("in_accel_scale", "0.3\n"),
|
||||
]);
|
||||
let mut sensor = make_iio_sensor(&directory, "in_accel".to_owned(), None);
|
||||
sensor.initialize().unwrap();
|
||||
let result = sensor.reading_get().unwrap();
|
||||
assert_eq!(result.len(), 4);
|
||||
assert_eq!(result.get(0).unwrap(), &MessageValue::Unsigned(0x5A));
|
||||
assert_eq!(result.get(1).unwrap(), &MessageValue::Unsigned(0));
|
||||
assert_eq!(result.get(2).unwrap(), &MessageValue::Unsigned(0));
|
||||
assert_eq!(result.get(3).unwrap(), &MessageValue::Unsigned(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iio_reading_axes() {
|
||||
let directory = IIODirectory::new(&[
|
||||
("in_accel_x_raw", "10"),
|
||||
("in_accel_x_offset", "1"),
|
||||
("in_accel_y_raw", "20"),
|
||||
("in_accel_y_offset", "10"),
|
||||
("in_accel_z_raw", "30"),
|
||||
("in_accel_z_offset", "20"),
|
||||
("in_accel_z_scale", "0.3"),
|
||||
("scale", "0.02"),
|
||||
]);
|
||||
let mut sensor = make_iio_sensor(&directory, "in_accel".to_owned(), None);
|
||||
sensor.initialize().unwrap();
|
||||
let result = sensor.reading_get().unwrap();
|
||||
assert_eq!(result.len(), 12);
|
||||
assert_eq!(result.get(0).unwrap(), &MessageValue::Unsigned(22));
|
||||
assert_eq!(result.get(4).unwrap(), &MessageValue::Unsigned(60));
|
||||
assert_eq!(result.get(8).unwrap(), &MessageValue::Unsigned(150));
|
||||
for i in 0..12 {
|
||||
if i % 4 != 0 {
|
||||
assert_eq!(result.get(i).unwrap(), &MessageValue::Unsigned(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,3 +3,4 @@
|
||||
|
||||
pub mod common;
|
||||
pub mod fake;
|
||||
pub mod iio;
|
||||
|
||||
@ -8,7 +8,7 @@ use std::{
|
||||
};
|
||||
|
||||
use itertools::Itertools;
|
||||
use log::{debug, error, info};
|
||||
use log::{debug, error, info, warn};
|
||||
use thiserror::Error as ThisError;
|
||||
|
||||
use crate::devices::common::DeviceError;
|
||||
@ -218,7 +218,25 @@ pub const SENSOR_CONFIG_GET: MessageId = 0x9;
|
||||
pub const SENSOR_CONFIG_SET: MessageId = 0xA;
|
||||
pub const SENSOR_CONTINUOUS_UPDATE_NOTIFY: MessageId = 0xB;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const SENSOR_UNIT_NONE: u8 = 0;
|
||||
pub const SENSOR_UNIT_UNSPECIFIED: u8 = 1;
|
||||
pub const SENSOR_UNIT_DEGREES_C: u8 = 2;
|
||||
pub const SENSOR_UNIT_VOLTS: u8 = 5;
|
||||
pub const SENSOR_UNIT_AMPS: u8 = 6;
|
||||
pub const SENSOR_UNIT_WATTS: u8 = 7;
|
||||
pub const SENSOR_UNIT_JOULS: u8 = 8;
|
||||
pub const SENSOR_UNIT_LUX: u8 = 13;
|
||||
pub const SENSOR_UNIT_METERS: u8 = 31;
|
||||
pub const SENSOR_UNIT_RADIANS: u8 = 36;
|
||||
pub const SENSOR_UNIT_GAUSS: u8 = 45;
|
||||
pub const SENSOR_UNIT_FARADS: u8 = 48;
|
||||
pub const SENSOR_UNIT_OHMS: u8 = 49;
|
||||
pub const SENSOR_UNIT_SIEMENS: u8 = 50;
|
||||
pub const SENSOR_UNIT_PERCENTAGE: u8 = 65;
|
||||
pub const SENSOR_UNIT_PASCALS: u8 = 66;
|
||||
pub const SENSOR_UNIT_RADIANS_PER_SECOND: u8 = 87;
|
||||
pub const SENSOR_UNIT_METERS_PER_SECOND: u8 = 90;
|
||||
pub const SENSOR_UNIT_METERS_PER_SECOND_SQUARED: u8 = 89;
|
||||
|
||||
enum ParameterType {
|
||||
@ -475,6 +493,8 @@ impl HandlerMap {
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, ThisError)]
|
||||
pub enum ScmiDeviceError {
|
||||
#[error("Generic error")]
|
||||
GenericError,
|
||||
#[error("Invalid parameters")]
|
||||
InvalidParameters,
|
||||
#[error("No such device")]
|
||||
@ -659,6 +679,10 @@ impl ScmiHandler {
|
||||
info!("Unsupported request for {}", device_index);
|
||||
Response::from(ReturnStatus::NotSupported)
|
||||
}
|
||||
ScmiDeviceError::GenericError => {
|
||||
warn!("Device error in {}", device_index);
|
||||
Response::from(ReturnStatus::GenericError)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ const EVENT_QUEUE: u16 = 1;
|
||||
|
||||
const VIRTIO_SCMI_F_P2A_CHANNELS: u16 = 0;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, ThisError)]
|
||||
#[derive(Debug, ThisError)]
|
||||
pub enum VuScmiError {
|
||||
#[error("Descriptor not found")]
|
||||
DescriptorNotFound,
|
||||
@ -638,22 +638,24 @@ mod tests {
|
||||
// Have only one descriptor, expected two.
|
||||
let parameters = vec![&default];
|
||||
let desc_chain = build_dummy_desc_chain(parameters);
|
||||
assert_eq!(
|
||||
backend
|
||||
.process_requests(vec![desc_chain], &vring)
|
||||
.unwrap_err(),
|
||||
VuScmiError::UnexpectedDescriptorCount(1)
|
||||
);
|
||||
match backend
|
||||
.process_requests(vec![desc_chain], &vring)
|
||||
.unwrap_err()
|
||||
{
|
||||
VuScmiError::UnexpectedDescriptorCount(1) => (),
|
||||
other => panic!("Unexpected result: {:?}", other),
|
||||
}
|
||||
|
||||
// Have three descriptors, expected two.
|
||||
let parameters = vec![&default, &default, &default];
|
||||
let desc_chain = build_dummy_desc_chain(parameters);
|
||||
assert_eq!(
|
||||
backend
|
||||
.process_requests(vec![desc_chain], &vring)
|
||||
.unwrap_err(),
|
||||
VuScmiError::UnexpectedDescriptorCount(3)
|
||||
);
|
||||
match backend
|
||||
.process_requests(vec![desc_chain], &vring)
|
||||
.unwrap_err()
|
||||
{
|
||||
VuScmiError::UnexpectedDescriptorCount(3) => (),
|
||||
other => panic!("Unexpected result: {:?}", other),
|
||||
}
|
||||
|
||||
// Write only descriptors.
|
||||
let p = DescParameters {
|
||||
@ -663,12 +665,13 @@ mod tests {
|
||||
};
|
||||
let parameters = vec![&p, &p];
|
||||
let desc_chain = build_dummy_desc_chain(parameters);
|
||||
assert_eq!(
|
||||
backend
|
||||
.process_requests(vec![desc_chain], &vring)
|
||||
.unwrap_err(),
|
||||
VuScmiError::UnexpectedWriteOnlyDescriptor(0)
|
||||
);
|
||||
match backend
|
||||
.process_requests(vec![desc_chain], &vring)
|
||||
.unwrap_err()
|
||||
{
|
||||
VuScmiError::UnexpectedWriteOnlyDescriptor(0) => (),
|
||||
other => panic!("Unexpected result: {:?}", other),
|
||||
}
|
||||
|
||||
// Invalid request address.
|
||||
let parameters = vec![
|
||||
@ -684,12 +687,13 @@ mod tests {
|
||||
},
|
||||
];
|
||||
let desc_chain = build_dummy_desc_chain(parameters);
|
||||
assert_eq!(
|
||||
backend
|
||||
.process_requests(vec![desc_chain], &vring)
|
||||
.unwrap_err(),
|
||||
VuScmiError::DescriptorReadFailed
|
||||
);
|
||||
match backend
|
||||
.process_requests(vec![desc_chain], &vring)
|
||||
.unwrap_err()
|
||||
{
|
||||
VuScmiError::DescriptorReadFailed => (),
|
||||
other => panic!("Unexpected result: {:?}", other),
|
||||
}
|
||||
|
||||
// Invalid request length (very small).
|
||||
let parameters = vec![
|
||||
@ -705,30 +709,33 @@ mod tests {
|
||||
},
|
||||
];
|
||||
let desc_chain = build_dummy_desc_chain(parameters);
|
||||
assert_eq!(
|
||||
backend
|
||||
.process_requests(vec![desc_chain], &vring)
|
||||
.unwrap_err(),
|
||||
VuScmiError::UnexpectedMinimumDescriptorSize(4, 2)
|
||||
);
|
||||
match backend
|
||||
.process_requests(vec![desc_chain], &vring)
|
||||
.unwrap_err()
|
||||
{
|
||||
VuScmiError::UnexpectedMinimumDescriptorSize(4, 2) => (),
|
||||
other => panic!("Unexpected result: {:?}", other),
|
||||
}
|
||||
|
||||
// Invalid request length (too small).
|
||||
let desc_chain = build_cmd_desc_chain(0x10, 0x2, vec![]);
|
||||
assert_eq!(
|
||||
backend
|
||||
.process_requests(vec![desc_chain], &vring)
|
||||
.unwrap_err(),
|
||||
VuScmiError::UnexpectedDescriptorSize(8, 4)
|
||||
);
|
||||
match backend
|
||||
.process_requests(vec![desc_chain], &vring)
|
||||
.unwrap_err()
|
||||
{
|
||||
VuScmiError::UnexpectedDescriptorSize(8, 4) => (),
|
||||
other => panic!("Unexpected result: {:?}", other),
|
||||
}
|
||||
|
||||
// Invalid request length (too large).
|
||||
let desc_chain = build_cmd_desc_chain(0x10, 0x0, vec![0]);
|
||||
assert_eq!(
|
||||
backend
|
||||
.process_requests(vec![desc_chain], &vring)
|
||||
.unwrap_err(),
|
||||
VuScmiError::UnexpectedDescriptorSize(4, 8)
|
||||
);
|
||||
match backend
|
||||
.process_requests(vec![desc_chain], &vring)
|
||||
.unwrap_err()
|
||||
{
|
||||
VuScmiError::UnexpectedDescriptorSize(4, 8) => (),
|
||||
other => panic!("Unexpected result: {:?}", other),
|
||||
}
|
||||
|
||||
// Read only descriptors.
|
||||
let p = DescParameters {
|
||||
@ -738,12 +745,13 @@ mod tests {
|
||||
};
|
||||
let parameters = vec![&p, &p];
|
||||
let desc_chain = build_dummy_desc_chain(parameters);
|
||||
assert_eq!(
|
||||
backend
|
||||
.process_requests(vec![desc_chain], &vring)
|
||||
.unwrap_err(),
|
||||
VuScmiError::UnexpectedReadableDescriptor(1)
|
||||
);
|
||||
match backend
|
||||
.process_requests(vec![desc_chain], &vring)
|
||||
.unwrap_err()
|
||||
{
|
||||
VuScmiError::UnexpectedReadableDescriptor(1) => (),
|
||||
other => panic!("Unexpected result: {:?}", other),
|
||||
}
|
||||
|
||||
// Invalid response address.
|
||||
let parameters = vec![
|
||||
@ -759,12 +767,13 @@ mod tests {
|
||||
},
|
||||
];
|
||||
let desc_chain = build_dummy_desc_chain(parameters);
|
||||
assert_eq!(
|
||||
backend
|
||||
.process_requests(vec![desc_chain], &vring)
|
||||
.unwrap_err(),
|
||||
VuScmiError::DescriptorWriteFailed
|
||||
);
|
||||
match backend
|
||||
.process_requests(vec![desc_chain], &vring)
|
||||
.unwrap_err()
|
||||
{
|
||||
VuScmiError::DescriptorWriteFailed => (),
|
||||
other => panic!("Unexpected result: {:?}", other),
|
||||
}
|
||||
|
||||
// Invalid response length.
|
||||
let parameters = vec![
|
||||
@ -780,12 +789,13 @@ mod tests {
|
||||
},
|
||||
];
|
||||
let desc_chain = build_dummy_desc_chain(parameters);
|
||||
assert_eq!(
|
||||
backend
|
||||
.process_requests(vec![desc_chain], &vring)
|
||||
.unwrap_err(),
|
||||
VuScmiError::InsufficientDescriptorSize(8, 6)
|
||||
);
|
||||
match backend
|
||||
.process_requests(vec![desc_chain], &vring)
|
||||
.unwrap_err()
|
||||
{
|
||||
VuScmiError::InsufficientDescriptorSize(8, 6) => (),
|
||||
other => panic!("Unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -832,12 +842,13 @@ mod tests {
|
||||
len: 0,
|
||||
};
|
||||
let desc_chain = build_dummy_desc_chain(vec![&p, &p]);
|
||||
assert_eq!(
|
||||
backend
|
||||
.process_event_requests(vec![desc_chain], &vring)
|
||||
.unwrap_err(),
|
||||
VuScmiError::UnexpectedDescriptorCount(2)
|
||||
);
|
||||
match backend
|
||||
.process_event_requests(vec![desc_chain], &vring)
|
||||
.unwrap_err()
|
||||
{
|
||||
VuScmiError::UnexpectedDescriptorCount(2) => (),
|
||||
other => panic!("Unexpected result: {:?}", other),
|
||||
}
|
||||
|
||||
// Read only descriptor
|
||||
let p = DescParameters {
|
||||
@ -846,12 +857,13 @@ mod tests {
|
||||
len: 0,
|
||||
};
|
||||
let desc_chain = build_dummy_desc_chain(vec![&p]);
|
||||
assert_eq!(
|
||||
backend
|
||||
.process_event_requests(vec![desc_chain], &vring)
|
||||
.unwrap_err(),
|
||||
VuScmiError::UnexpectedReadableDescriptor(0)
|
||||
);
|
||||
match backend
|
||||
.process_event_requests(vec![desc_chain], &vring)
|
||||
.unwrap_err()
|
||||
{
|
||||
VuScmiError::UnexpectedReadableDescriptor(0) => (),
|
||||
other => panic!("Unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user