From bd59ff2462f458f49154006c14c99cd6dd8c14b1 Mon Sep 17 00:00:00 2001 From: Priyansh Rathi Date: Mon, 24 Jul 2023 01:17:06 -0700 Subject: [PATCH 01/40] vsock: Allow optional fields in config file Currently need to provide all the fields in the yaml config file, otherwise the application panics. Modify this behaviour to allow not specifying the optional fields to make it consistent with specifying the configuration using only CLI arguments. Signed-off-by: Priyansh Rathi --- crates/vsock/src/main.rs | 45 ++++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/crates/vsock/src/main.rs b/crates/vsock/src/main.rs index 51f375943..69e23ed17 100644 --- a/crates/vsock/src/main.rs +++ b/crates/vsock/src/main.rs @@ -56,7 +56,7 @@ enum BackendError { CouldNotCreateDaemon(vhost_user_backend::Error), } -#[derive(Args, Clone, Debug, Deserialize)] +#[derive(Args, Clone, Debug)] struct VsockParam { /// Context identifier of the guest which uniquely identifies the device for its lifetime. #[arg( @@ -80,6 +80,14 @@ struct VsockParam { tx_buffer_size: u32, } +#[derive(Clone, Debug, Deserialize)] +struct ConfigFileVsockParam { + guest_cid: Option, + socket: String, + uds_path: String, + tx_buffer_size: Option, +} + #[derive(Parser, Debug)] #[command(version, about, long_about = None)] struct VsockArgs { @@ -137,16 +145,16 @@ impl VsockArgs { .add_source(config::File::new(c.as_str(), config::FileFormat::Yaml)) .build(); if let Ok(s) = b { - let mut v = s.get::>("vms").unwrap(); + let mut v = s.get::>("vms").unwrap(); if !v.is_empty() { let parsed: Vec = v .drain(..) .map(|p| { VsockConfig::new( - p.guest_cid, + p.guest_cid.unwrap_or(DEFAULT_GUEST_CID), p.socket.trim().to_string(), p.uds_path.trim().to_string(), - p.tx_buffer_size, + p.tx_buffer_size.unwrap_or(DEFAULT_TX_BUFFER_SIZE), ) }) .collect(); @@ -407,7 +415,7 @@ mod tests { - guest_cid: 4 socket: {} uds_path: {} - tx_buffer_size: 65536", + tx_buffer_size: 32768", socket_path.display(), uds_path.display(), ) @@ -423,8 +431,33 @@ mod tests { assert_eq!(config.get_guest_cid(), 4); assert_eq!(config.get_socket_path(), socket_path.display().to_string()); assert_eq!(config.get_uds_path(), uds_path.display().to_string()); - std::fs::remove_file(&config_path).unwrap(); + assert_eq!(config.get_tx_buffer_size(), 32768); + + // Now test that optional parameters are correctly set to their default values. + let mut yaml = File::create(&config_path).unwrap(); + yaml.write_all( + format!( + "vms: + - socket: {} + uds_path: {}", + socket_path.display(), + uds_path.display(), + ) + .as_bytes(), + ) + .unwrap(); + let args = VsockArgs::from_file(&config_path.display().to_string()); + let configs = Vec::::try_from(args).unwrap(); + assert_eq!(configs.len(), 1); + + let config = &configs[0]; + assert_eq!(config.get_guest_cid(), DEFAULT_GUEST_CID); + assert_eq!(config.get_socket_path(), socket_path.display().to_string()); + assert_eq!(config.get_uds_path(), uds_path.display().to_string()); + assert_eq!(config.get_tx_buffer_size(), DEFAULT_TX_BUFFER_SIZE); + + std::fs::remove_file(&config_path).unwrap(); test_dir.close().unwrap(); } From decfd8bd7338574f5d370eece3f5a4355fedfdbf Mon Sep 17 00:00:00 2001 From: Priyansh Rathi Date: Mon, 24 Jul 2023 01:07:18 -0700 Subject: [PATCH 02/40] vsock: Add VM groups in sibling communication Restrict the VMs a given VM can communicate with by introducing VM groups. A group is simply a list of names assigned to the device in the configuration. A VM can communicate with another VM only if the list of group names assigned to their devices have atleast one group name in common. Signed-off-by: Priyansh Rathi --- crates/vsock/README.md | 26 ++++++++---- crates/vsock/src/main.rs | 63 ++++++++++++++++++++++++---- crates/vsock/src/thread_backend.rs | 45 ++++++++++++++++++-- crates/vsock/src/vhu_vsock.rs | 27 ++++++++++-- crates/vsock/src/vhu_vsock_thread.rs | 18 +++++++- 5 files changed, 157 insertions(+), 22 deletions(-) diff --git a/crates/vsock/README.md b/crates/vsock/README.md index f4e946dc6..241397195 100644 --- a/crates/vsock/README.md +++ b/crates/vsock/README.md @@ -43,17 +43,18 @@ Run the vhost-device-vsock device: vhost-device-vsock --guest-cid= \ --socket= \ --uds-path= \ - [--tx-buffer-size=host packets)>] + [--tx-buffer-size=host packets)>] \ + [--groups=] ``` or ``` -vhost-device-vsock --vm guest_cid=,socket=,uds-path=[,tx-buffer-size=host packets)>] +vhost-device-vsock --vm guest_cid=,socket=,uds-path=[,tx-buffer-size=host packets)>][,groups=] ``` Specify the `--vm` argument multiple times to specify multiple devices like this: ``` vhost-device-vsock \ ---vm guest-cid=3,socket=/tmp/vhost3.socket,uds-path=/tmp/vm3.vsock \ +--vm guest-cid=3,socket=/tmp/vhost3.socket,uds-path=/tmp/vm3.vsock,groups=group1+groupA \ --vm guest-cid=4,socket=/tmp/vhost4.socket,uds-path=/tmp/vm4.vsock,tx-buffer-size=32768 ``` @@ -69,10 +70,12 @@ vms: socket: /tmp/vhost3.socket uds_path: /tmp/vm3.sock tx_buffer_size: 65536 + groups: group1+groupA - guest_cid: 4 socket: /tmp/vhost4.socket uds_path: /tmp/vm4.sock tx_buffer_size: 32768 + groups: group2+groupB ``` Run VMM (e.g. QEMU): @@ -144,12 +147,17 @@ guest$ nc --vsock 2 1234 ### Sibling VM communication -If you add multiple VMs, they can communicate with each other. For example, if you have two VMs with -CID 3 and 4, you can run the following commands to make them communicate: +If you add multiple VMs with their devices configured with at least one common group name, they can communicate with +each other. If you don't explicitly specify a group name, a default group will be assigned to the device with name +`default`, and all such devices will be able to communicate with each other. Or you can choose a different list of +group names for each device, and only devices with the at least one group in commmon will be able to communicate with +each other. + +For example, if you have two VMs with CID 3 and 4, you can run the following commands to make them communicate: ```sh -shell1$ vhost-device-vsock --vm guest-cid=3,uds-path=/tmp/vm3.vsock,socket=/tmp/vhost3.socket \ - --vm guest-cid=4,uds-path=/tmp/vm4.vsock,socket=/tmp/vhost4.socket +shell1$ vhost-device-vsock --vm guest-cid=3,uds-path=/tmp/vm3.vsock,socket=/tmp/vhost3.socket,groups=group1+group2 \ + --vm guest-cid=4,uds-path=/tmp/vm4.vsock,socket=/tmp/vhost4.socket,groups=group1 shell2$ qemu-system-x86_64 \ -drive file=vm1.qcow2,format=qcow2,if=virtio -smp 2 -m 512M -mem-prealloc \ -object memory-backend-file,share=on,id=mem0,size=512M,mem-path="/dev/hugepages" \ @@ -164,6 +172,10 @@ shell3$ qemu-system-x86_64 \ -device vhost-user-vsock-pci,chardev=char0 ``` +Please note that here the `groups` parameter is specified just for clarity, but it is not necessary to specify it if you want +to use the default group and make all the devices communicate with one another. It is useful to specify a list of groups +when you want fine-grained control over which devices can communicate with each other. + ```sh # nc-vsock patched to set `.svm_flags = VMADDR_FLAG_TO_HOST` guest_cid3$ nc-vsock -l 1234 diff --git a/crates/vsock/src/main.rs b/crates/vsock/src/main.rs index 69e23ed17..fa38bfb64 100644 --- a/crates/vsock/src/main.rs +++ b/crates/vsock/src/main.rs @@ -27,6 +27,7 @@ use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; const DEFAULT_GUEST_CID: u64 = 3; const DEFAULT_TX_BUFFER_SIZE: u32 = 64 * 1024; +const DEFAULT_GROUP_NAME: &str = "default"; #[derive(Debug, ThisError)] enum CliError { @@ -78,6 +79,17 @@ struct VsockParam { /// The size of the buffer used for the TX virtqueue #[clap(long, default_value_t = DEFAULT_TX_BUFFER_SIZE, conflicts_with = "config", conflicts_with = "vm")] tx_buffer_size: u32, + + /// The list of group names to which the device belongs. + /// A group is a set of devices that allow sibling communication between their guests. + #[arg( + long, + default_value_t = String::from(DEFAULT_GROUP_NAME), + conflicts_with = "config", + conflicts_with = "vm", + verbatim_doc_comment + )] + groups: String, } #[derive(Clone, Debug, Deserialize)] @@ -86,6 +98,7 @@ struct ConfigFileVsockParam { socket: String, uds_path: String, tx_buffer_size: Option, + groups: Option, } #[derive(Parser, Debug)] @@ -95,8 +108,9 @@ struct VsockArgs { param: Option, /// Device parameters corresponding to a VM in the form of comma separated key=value pairs. - /// The allowed keys are: guest_cid, socket, uds_path and tx_buffer_size - /// Example: --vm guest-cid=3,socket=/tmp/vhost3.socket,uds-path=/tmp/vm3.vsock,tx-buffer-size=65536 + /// The allowed keys are: guest_cid, socket, uds_path, tx_buffer_size and group. + /// Example: + /// --vm guest-cid=3,socket=/tmp/vhost3.socket,uds-path=/tmp/vm3.vsock,tx-buffer-size=65536,groups=group1+group2 /// Multiple instances of this argument can be provided to configure devices for multiple guests. #[arg(long, conflicts_with = "config", verbatim_doc_comment, value_parser = parse_vm_params)] vm: Option>, @@ -111,6 +125,7 @@ fn parse_vm_params(s: &str) -> Result { let mut socket = None; let mut uds_path = None; let mut tx_buffer_size = None; + let mut groups = None; for arg in s.trim().split(',') { let mut parts = arg.split('='); @@ -126,6 +141,7 @@ fn parse_vm_params(s: &str) -> Result { "tx_buffer_size" | "tx-buffer-size" => { tx_buffer_size = Some(val.parse().map_err(VmArgsParseError::ParseInteger)?) } + "groups" => groups = Some(val.split('+').map(String::from).collect()), _ => return Err(VmArgsParseError::InvalidKey(key.to_string())), } } @@ -135,6 +151,7 @@ fn parse_vm_params(s: &str) -> Result { socket.ok_or_else(|| VmArgsParseError::RequiredKeyNotFound("socket".to_string()))?, uds_path.ok_or_else(|| VmArgsParseError::RequiredKeyNotFound("uds-path".to_string()))?, tx_buffer_size.unwrap_or(DEFAULT_TX_BUFFER_SIZE), + groups.unwrap_or(vec![DEFAULT_GROUP_NAME.to_string()]), )) } @@ -155,6 +172,9 @@ impl VsockArgs { p.socket.trim().to_string(), p.uds_path.trim().to_string(), p.tx_buffer_size.unwrap_or(DEFAULT_TX_BUFFER_SIZE), + p.groups.map_or(vec![DEFAULT_GROUP_NAME.to_string()], |g| { + g.trim().split('+').map(String::from).collect() + }), ) }) .collect(); @@ -185,6 +205,7 @@ impl TryFrom for Vec { p.socket.trim().to_string(), p.uds_path.trim().to_string(), p.tx_buffer_size, + p.groups.trim().split('+').map(String::from).collect(), )]) }), }, @@ -289,13 +310,20 @@ mod tests { use tempfile::tempdir; impl VsockArgs { - fn from_args(guest_cid: u64, socket: &str, uds_path: &str, tx_buffer_size: u32) -> Self { + fn from_args( + guest_cid: u64, + socket: &str, + uds_path: &str, + tx_buffer_size: u32, + groups: &str, + ) -> Self { VsockArgs { param: Some(VsockParam { guest_cid, socket: socket.to_string(), uds_path: uds_path.to_string(), tx_buffer_size, + groups: groups.to_string(), }), vm: None, config: None, @@ -316,7 +344,7 @@ mod tests { let socket_path = test_dir.path().join("vhost4.socket").display().to_string(); let uds_path = test_dir.path().join("vm4.vsock").display().to_string(); - let args = VsockArgs::from_args(3, &socket_path, &uds_path, 64 * 1024); + let args = VsockArgs::from_args(3, &socket_path, &uds_path, 64 * 1024, "group1"); let configs = Vec::::try_from(args); assert!(configs.is_ok()); @@ -329,6 +357,7 @@ mod tests { assert_eq!(config.get_socket_path(), socket_path); assert_eq!(config.get_uds_path(), uds_path); assert_eq!(config.get_tx_buffer_size(), 64 * 1024); + assert_eq!(config.get_groups(), vec!["group1".to_string()]); test_dir.close().unwrap(); } @@ -349,8 +378,8 @@ mod tests { ]; let params = format!( "--vm socket={vhost3_socket},uds_path={vm3_vsock} \ - --vm socket={vhost4_socket},uds-path={vm4_vsock},guest-cid=4,tx_buffer_size=65536 \ - --vm guest-cid=5,socket={vhost5_socket},uds_path={vm5_vsock},tx-buffer-size=32768", + --vm socket={vhost4_socket},uds-path={vm4_vsock},guest-cid=4,tx_buffer_size=65536,groups=group1 \ + --vm groups=group2+group3,guest-cid=5,socket={vhost5_socket},uds_path={vm5_vsock},tx-buffer-size=32768", vhost3_socket = socket_paths[0].display(), vhost4_socket = socket_paths[1].display(), vhost5_socket = socket_paths[2].display(), @@ -378,6 +407,7 @@ mod tests { ); assert_eq!(config.get_uds_path(), uds_paths[0].display().to_string()); assert_eq!(config.get_tx_buffer_size(), 65536); + assert_eq!(config.get_groups(), vec![DEFAULT_GROUP_NAME.to_string()]); let config = configs.get(1).unwrap(); assert_eq!(config.get_guest_cid(), 4); @@ -387,6 +417,7 @@ mod tests { ); assert_eq!(config.get_uds_path(), uds_paths[1].display().to_string()); assert_eq!(config.get_tx_buffer_size(), 65536); + assert_eq!(config.get_groups(), vec!["group1".to_string()]); let config = configs.get(2).unwrap(); assert_eq!(config.get_guest_cid(), 5); @@ -396,6 +427,10 @@ mod tests { ); assert_eq!(config.get_uds_path(), uds_paths[2].display().to_string()); assert_eq!(config.get_tx_buffer_size(), 32768); + assert_eq!( + config.get_groups(), + vec!["group2".to_string(), "group3".to_string()] + ); test_dir.close().unwrap(); } @@ -415,7 +450,8 @@ mod tests { - guest_cid: 4 socket: {} uds_path: {} - tx_buffer_size: 32768", + tx_buffer_size: 32768 + groups: group1+group2", socket_path.display(), uds_path.display(), ) @@ -432,6 +468,10 @@ mod tests { assert_eq!(config.get_socket_path(), socket_path.display().to_string()); assert_eq!(config.get_uds_path(), uds_path.display().to_string()); assert_eq!(config.get_tx_buffer_size(), 32768); + assert_eq!( + config.get_groups(), + vec!["group1".to_string(), "group2".to_string()] + ); // Now test that optional parameters are correctly set to their default values. let mut yaml = File::create(&config_path).unwrap(); @@ -456,6 +496,7 @@ mod tests { assert_eq!(config.get_socket_path(), socket_path.display().to_string()); assert_eq!(config.get_uds_path(), uds_path.display().to_string()); assert_eq!(config.get_tx_buffer_size(), DEFAULT_TX_BUFFER_SIZE); + assert_eq!(config.get_groups(), vec![DEFAULT_GROUP_NAME.to_string()]); std::fs::remove_file(&config_path).unwrap(); test_dir.close().unwrap(); @@ -479,7 +520,13 @@ mod tests { .display() .to_string(); - let config = VsockConfig::new(CID, vhost_socket_path, vsock_socket_path, CONN_TX_BUF_SIZE); + let config = VsockConfig::new( + CID, + vhost_socket_path, + vsock_socket_path, + CONN_TX_BUF_SIZE, + vec![DEFAULT_GROUP_NAME.to_string()], + ); let cid_map: Arc> = Arc::new(RwLock::new(HashMap::new())); diff --git a/crates/vsock/src/thread_backend.rs b/crates/vsock/src/thread_backend.rs index 136912286..6d5e80ee0 100644 --- a/crates/vsock/src/thread_backend.rs +++ b/crates/vsock/src/thread_backend.rs @@ -2,6 +2,7 @@ use std::{ collections::{HashMap, HashSet, VecDeque}, + ops::Deref, os::unix::{ net::UnixStream, prelude::{AsRawFd, RawFd}, @@ -70,6 +71,8 @@ pub(crate) struct VsockThreadBackend { pub cid_map: Arc>, /// Queue of raw vsock packets recieved from sibling VMs to be sent to the guest. pub raw_pkts_queue: Arc>, + /// Set of groups assigned to the device which it is allowed to communicate with. + groups_set: Arc>>, } impl VsockThreadBackend { @@ -79,6 +82,7 @@ impl VsockThreadBackend { epoll_fd: i32, guest_cid: u64, tx_buffer_size: u32, + groups_set: Arc>>, cid_map: Arc>, ) -> Self { Self { @@ -95,6 +99,7 @@ impl VsockThreadBackend { tx_buffer_size, cid_map, raw_pkts_queue: Arc::new(RwLock::new(VecDeque::new())), + groups_set, } } @@ -180,7 +185,21 @@ impl VsockThreadBackend { if dst_cid != VSOCK_HOST_CID { let cid_map = self.cid_map.read().unwrap(); if cid_map.contains_key(&dst_cid) { - let (sibling_raw_pkts_queue, sibling_event_fd) = cid_map.get(&dst_cid).unwrap(); + let (sibling_raw_pkts_queue, sibling_groups_set, sibling_event_fd) = + cid_map.get(&dst_cid).unwrap(); + + if self + .groups_set + .read() + .unwrap() + .is_disjoint(sibling_groups_set.read().unwrap().deref()) + { + info!( + "vsock: dropping packet for cid: {:?} due to group mismatch", + dst_cid + ); + return Ok(()); + } sibling_raw_pkts_queue .write() @@ -337,6 +356,7 @@ mod tests { const DATA_LEN: usize = 16; const CONN_TX_BUF_SIZE: u32 = 64 * 1024; + const GROUP_NAME: &str = "default"; #[test] fn test_vsock_thread_backend() { @@ -352,6 +372,8 @@ mod tests { let epoll_fd = epoll::create(false).unwrap(); + let groups_set: HashSet = vec![GROUP_NAME.to_string()].into_iter().collect(); + let cid_map: Arc> = Arc::new(RwLock::new(HashMap::new())); let mut vtp = VsockThreadBackend::new( @@ -359,6 +381,7 @@ mod tests { epoll_fd, CID, CONN_TX_BUF_SIZE, + Arc::new(RwLock::new(groups_set)), cid_map, ); @@ -434,14 +457,30 @@ mod tests { sibling_vhost_socket_path, sibling_vsock_socket_path, CONN_TX_BUF_SIZE, + vec!["group1", "group2", "group3"] + .into_iter() + .map(String::from) + .collect(), ); let sibling_backend = Arc::new(VhostUserVsockBackend::new(sibling_config, cid_map.clone()).unwrap()); let epoll_fd = epoll::create(false).unwrap(); - let mut vtp = - VsockThreadBackend::new(vsock_socket_path, epoll_fd, CID, CONN_TX_BUF_SIZE, cid_map); + + let groups_set: HashSet = vec!["groupA", "groupB", "group3"] + .into_iter() + .map(String::from) + .collect(); + + let mut vtp = VsockThreadBackend::new( + vsock_socket_path, + epoll_fd, + CID, + CONN_TX_BUF_SIZE, + Arc::new(RwLock::new(groups_set)), + cid_map, + ); assert!(!vtp.pending_raw_pkts()); diff --git a/crates/vsock/src/vhu_vsock.rs b/crates/vsock/src/vhu_vsock.rs index c64f11fe2..0d25b386b 100644 --- a/crates/vsock/src/vhu_vsock.rs +++ b/crates/vsock/src/vhu_vsock.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause use std::{ - collections::HashMap, + collections::{HashMap, HashSet}, io::{self, Result as IoResult}, sync::{Arc, Mutex, RwLock}, u16, u32, u64, u8, @@ -24,7 +24,8 @@ use vmm_sys_util::{ use crate::thread_backend::RawPktsQ; use crate::vhu_vsock_thread::*; -pub(crate) type CidMap = HashMap>, EventFd)>; +pub(crate) type CidMap = + HashMap>, Arc>>, EventFd)>; const NUM_QUEUES: usize = 3; const QUEUE_SIZE: usize = 256; @@ -150,17 +151,25 @@ pub(crate) struct VsockConfig { socket: String, uds_path: String, tx_buffer_size: u32, + groups: Vec, } impl VsockConfig { /// Create a new instance of the VsockConfig struct, containing the /// parameters to be fed into the vsock-backend server. - pub fn new(guest_cid: u64, socket: String, uds_path: String, tx_buffer_size: u32) -> Self { + pub fn new( + guest_cid: u64, + socket: String, + uds_path: String, + tx_buffer_size: u32, + groups: Vec, + ) -> Self { Self { guest_cid, socket, uds_path, tx_buffer_size, + groups, } } @@ -184,6 +193,10 @@ impl VsockConfig { pub fn get_tx_buffer_size(&self) -> u32 { self.tx_buffer_size } + + pub fn get_groups(&self) -> Vec { + self.groups.clone() + } } /// A local port and peer port pair used to retrieve @@ -227,6 +240,7 @@ impl VhostUserVsockBackend { config.get_uds_path(), config.get_guest_cid(), config.get_tx_buffer_size(), + config.get_groups(), cid_map, )?); let queues_per_thread = vec![QUEUE_MASK]; @@ -364,6 +378,8 @@ mod tests { fn test_vsock_backend() { const CID: u64 = 3; + let groups_list: Vec = vec![String::from("default")]; + let test_dir = tempdir().expect("Could not create a temp test directory."); let vhost_socket_path = test_dir @@ -382,6 +398,7 @@ mod tests { vhost_socket_path.to_string(), vsock_socket_path.to_string(), CONN_TX_BUF_SIZE, + groups_list, ); let cid_map: Arc> = Arc::new(RwLock::new(HashMap::new())); @@ -451,6 +468,8 @@ mod tests { fn test_vsock_backend_failures() { const CID: u64 = 3; + let groups: Vec = vec![String::from("default")]; + let test_dir = tempdir().expect("Could not create a temp test directory."); let vhost_socket_path = test_dir @@ -469,6 +488,7 @@ mod tests { "/sys/not_allowed.socket".to_string(), "/sys/not_allowed.vsock".to_string(), CONN_TX_BUF_SIZE, + groups.clone(), ); let cid_map: Arc> = Arc::new(RwLock::new(HashMap::new())); @@ -481,6 +501,7 @@ mod tests { vhost_socket_path.to_string(), vsock_socket_path.to_string(), CONN_TX_BUF_SIZE, + groups, ); let backend = VhostUserVsockBackend::new(config, cid_map).unwrap(); diff --git a/crates/vsock/src/vhu_vsock_thread.rs b/crates/vsock/src/vhu_vsock_thread.rs index 726e2b64d..0c2ab2f3c 100644 --- a/crates/vsock/src/vhu_vsock_thread.rs +++ b/crates/vsock/src/vhu_vsock_thread.rs @@ -1,9 +1,11 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause use std::{ + collections::HashSet, fs::File, io, io::Read, + iter::FromIterator, num::Wrapping, ops::Deref, os::unix::{ @@ -79,6 +81,7 @@ impl VhostUserVsockThread { uds_path: String, guest_cid: u64, tx_buffer_size: u32, + groups: Vec, cid_map: Arc>, ) -> Result { // TODO: better error handling, maybe add a param to force the unlink @@ -93,6 +96,10 @@ impl VhostUserVsockThread { let host_raw_fd = host_sock.as_raw_fd(); + let mut groups = groups; + let groups_set: Arc>> = + Arc::new(RwLock::new(HashSet::from_iter(groups.drain(..)))); + let sibling_event_fd = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?; let thread_backend = VsockThreadBackend::new( @@ -100,6 +107,7 @@ impl VhostUserVsockThread { epoll_fd, guest_cid, tx_buffer_size, + groups_set.clone(), cid_map.clone(), ); @@ -107,6 +115,7 @@ impl VhostUserVsockThread { guest_cid, ( thread_backend.raw_pkts_queue.clone(), + groups_set, sibling_event_fd.try_clone().unwrap(), ), ); @@ -723,6 +732,8 @@ mod tests { #[test] fn test_vsock_thread() { + let groups: Vec = vec![String::from("default")]; + let cid_map: Arc> = Arc::new(RwLock::new(HashMap::new())); let test_dir = tempdir().expect("Could not create a temp test directory."); @@ -735,6 +746,7 @@ mod tests { .to_string(), 3, CONN_TX_BUF_SIZE, + groups, cid_map, ); assert!(t.is_ok()); @@ -792,6 +804,8 @@ mod tests { #[test] fn test_vsock_thread_failures() { + let groups: Vec = vec![String::from("default")]; + let cid_map: Arc> = Arc::new(RwLock::new(HashMap::new())); let test_dir = tempdir().expect("Could not create a temp test directory."); @@ -800,6 +814,7 @@ mod tests { "/sys/not_allowed.vsock".to_string(), 3, CONN_TX_BUF_SIZE, + groups.clone(), cid_map.clone(), ); assert!(t.is_err()); @@ -810,7 +825,8 @@ mod tests { .display() .to_string(); let mut t = - VhostUserVsockThread::new(vsock_socket_path, 3, CONN_TX_BUF_SIZE, cid_map).unwrap(); + VhostUserVsockThread::new(vsock_socket_path, 3, CONN_TX_BUF_SIZE, groups, cid_map) + .unwrap(); assert!(VhostUserVsockThread::epoll_register(-1, -1, epoll::Events::EPOLLIN).is_err()); assert!(VhostUserVsockThread::epoll_modify(-1, -1, epoll::Events::EPOLLIN).is_err()); assert!(VhostUserVsockThread::epoll_unregister(-1, -1).is_err()); From eea36eefa5a40de982f4595137acae7ef24e836a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Aug 2023 04:36:17 +0000 Subject: [PATCH 03/40] build(deps): bump equivalent from 1.0.0 to 1.0.1 Bumps [equivalent](https://github.com/cuviper/equivalent) from 1.0.0 to 1.0.1. - [Commits](https://github.com/cuviper/equivalent/compare/v1.0.0...v1.0.1) --- updated-dependencies: - dependency-name: equivalent dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 453d80380..9cd79c1b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -319,9 +319,9 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" From 87e51d55f036f23b6628dc5b8916771923e7672e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Aug 2023 04:36:07 +0000 Subject: [PATCH 04/40] build(deps): bump pest_meta from 2.7.0 to 2.7.1 Bumps [pest_meta](https://github.com/pest-parser/pest) from 2.7.0 to 2.7.1. - [Release notes](https://github.com/pest-parser/pest/releases) - [Commits](https://github.com/pest-parser/pest/compare/v2.7.0...v2.7.1) --- updated-dependencies: - dependency-name: pest_meta dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9cd79c1b9..1837cc17c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -778,9 +778,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01f71cb40bd8bb94232df14b946909e14660e33fc05db3e50ae2a82d7ea0ca0" +checksum = "2674c66ebb4b4d9036012091b537aae5878970d6999f81a265034d85b136b341" dependencies = [ "once_cell", "pest", From ace68f6722026584325cd7843570b2aab88629eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Aug 2023 04:35:58 +0000 Subject: [PATCH 05/40] build(deps): bump pest_generator from 2.7.0 to 2.7.1 Bumps [pest_generator](https://github.com/pest-parser/pest) from 2.7.0 to 2.7.1. - [Release notes](https://github.com/pest-parser/pest/releases) - [Commits](https://github.com/pest-parser/pest/compare/v2.7.0...v2.7.1) --- updated-dependencies: - dependency-name: pest_generator dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1837cc17c..ae478915f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -765,9 +765,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e8cba4ec22bada7fc55ffe51e2deb6a0e0db2d0b7ab0b103acc80d2510c190" +checksum = "99d490fe7e8556575ff6911e45567ab95e71617f43781e5c05490dc8d75c965c" dependencies = [ "pest", "pest_meta", From d8fdbb79cd4ba1a5f41ad6e153097c007d5fc7d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 04:26:19 +0000 Subject: [PATCH 06/40] build(deps): bump serde_json from 1.0.100 to 1.0.104 Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.100 to 1.0.104. - [Release notes](https://github.com/serde-rs/json/releases) - [Commits](https://github.com/serde-rs/json/compare/v1.0.100...v1.0.104) --- updated-dependencies: - dependency-name: serde_json dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ae478915f..bc3724f59 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -989,9 +989,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.100" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c" +checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" dependencies = [ "itoa", "ryu", From 89362cf3b5b77a1ff744de7e5e5fc2b5d8904b6b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 04:26:05 +0000 Subject: [PATCH 07/40] build(deps): bump either from 1.8.1 to 1.9.0 Bumps [either](https://github.com/bluss/either) from 1.8.1 to 1.9.0. - [Commits](https://github.com/bluss/either/compare/1.8.1...1.9.0) --- updated-dependencies: - dependency-name: either dependency-type: indirect update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bc3724f59..7b7561b52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -290,9 +290,9 @@ checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "env_logger" From 6754ca091f99632d431a3a917256fb06337c7147 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 04:25:56 +0000 Subject: [PATCH 08/40] build(deps): bump aho-corasick from 1.0.2 to 1.0.3 Bumps [aho-corasick](https://github.com/BurntSushi/aho-corasick) from 1.0.2 to 1.0.3. - [Commits](https://github.com/BurntSushi/aho-corasick/compare/1.0.2...1.0.3) --- updated-dependencies: - dependency-name: aho-corasick dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7b7561b52..049052dec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,9 +15,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +checksum = "86b8f9420f797f2d9e935edf629310eb938a0d839f984e25327f3c7eed22300c" dependencies = [ "memchr", ] From 033a0ed6147ec96023d89cf15200634c96a8caa6 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Tue, 8 Aug 2023 15:11:54 +0530 Subject: [PATCH 09/40] Update cargo dependencies The main target of this update is vm-memory to a newer stable version, but lets update everything anyway. Signed-off-by: Viresh Kumar --- Cargo.lock | 177 ++++++++++++++++++++--------------------------------- 1 file changed, 68 insertions(+), 109 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 049052dec..d9006c533 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,13 +85,13 @@ checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] name = "async-trait" -version = "0.1.71" +version = "0.1.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" +checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.28", ] [[package]] @@ -157,9 +157,12 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cc" -version = "1.0.79" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" +dependencies = [ + "libc", +] [[package]] name = "cexpr" @@ -189,9 +192,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.19" +version = "4.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d" +checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd" dependencies = [ "clap_builder", "clap_derive", @@ -200,9 +203,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.19" +version = "4.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1" +checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa" dependencies = [ "anstream", "anstyle", @@ -219,7 +222,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.28", ] [[package]] @@ -336,9 +339,9 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", @@ -357,12 +360,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.9.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" [[package]] name = "futures" @@ -421,7 +421,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.28", ] [[package]] @@ -533,32 +533,12 @@ dependencies = [ "hashbrown 0.14.0", ] -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - [[package]] name = "intmap" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee87fd093563344074bacf24faa0bb0227fb6969fb223e922db798516de924d6" -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys", -] - [[package]] name = "is-terminal" version = "0.4.9" @@ -566,15 +546,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", - "rustix 0.38.3", + "rustix", "windows-sys", ] [[package]] name = "itoa" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "json5" @@ -646,15 +626,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - -[[package]] -name = "linux-raw-sys" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "log" @@ -712,7 +686,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.28", ] [[package]] @@ -745,9 +719,9 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "pest" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d2d1d55045829d65aad9d389139882ad623b33b904e7c9f1b10c5b8927298e5" +checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" dependencies = [ "thiserror", "ucd-trie", @@ -755,9 +729,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef623c9bbfa0eedf5a0efba11a5ee83209c326653ca31ff019bec3a95bfff2b" +checksum = "666d00490d4ac815001da55838c500eafb0320019bbaa44444137c48b443a853" dependencies = [ "pest", "pest_generator", @@ -765,22 +739,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d490fe7e8556575ff6911e45567ab95e71617f43781e5c05490dc8d75c965c" +checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.28", ] [[package]] name = "pest_meta" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2674c66ebb4b4d9036012091b537aae5878970d6999f81a265034d85b136b341" +checksum = "56af0a30af74d0445c0bf6d9d051c979b516a1a5af790d251daee76005420a48" dependencies = [ "once_cell", "pest", @@ -789,9 +763,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" +checksum = "2c516611246607d0c04186886dbb3a754368ef82c79e9827a802c6d836dd111c" [[package]] name = "pin-utils" @@ -832,9 +806,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.29" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" dependencies = [ "proc-macro2", ] @@ -880,9 +854,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" dependencies = [ "aho-corasick", "memchr", @@ -892,9 +866,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.2" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" dependencies = [ "aho-corasick", "memchr", @@ -936,28 +910,14 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.37.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" -dependencies = [ - "bitflags 1.3.2", - "errno 0.3.1", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys", -] - -[[package]] -name = "rustix" -version = "0.38.3" +version = "0.38.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac5ffa1efe7548069688cd7028f32591853cd7b5b756d41bcffd2353e4fc75b4" +checksum = "172891ebdceb05aa0005f533a6cbfca599ddd7d966f6f5d4d9b2e70478e70399" dependencies = [ "bitflags 2.3.3", - "errno 0.3.1", + "errno 0.3.2", "libc", - "linux-raw-sys 0.4.3", + "linux-raw-sys", "windows-sys", ] @@ -969,22 +929,22 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "serde" -version = "1.0.168" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d614f89548720367ded108b3c843be93f3a341e22d5674ca0dd5cd57f34926af" +checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.168" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fe589678c688e44177da4f27152ee2d190757271dc7f1d5b6b9f68d869d641" +checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.28", ] [[package]] @@ -1000,9 +960,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.22" +version = "0.9.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "452e67b9c20c37fa79df53201dc03839651086ed9bbe92b3ca585ca9fdaa7d85" +checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" dependencies = [ "indexmap", "itoa", @@ -1074,9 +1034,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.23" +version = "2.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2", "quote", @@ -1100,15 +1060,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.6.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" dependencies = [ - "autocfg", "cfg-if", "fastrand", "redox_syscall", - "rustix 0.37.23", + "rustix", "windows-sys", ] @@ -1138,7 +1097,7 @@ checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.28", ] [[package]] @@ -1158,9 +1117,9 @@ checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" [[package]] name = "toml_edit" -version = "0.19.12" +version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ "indexmap", "toml_datetime", @@ -1193,9 +1152,9 @@ checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unsafe-libyaml" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1865806a559042e51ab5414598446a5871b561d21b6764f2eabb0dd481d880a6" +checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" [[package]] name = "utf8parse" @@ -1217,9 +1176,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vhost" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73832f4d8d636d63d9b145e8ef22a2c50b93f4d24eb7a99c9e6781b1b08549cf" +checksum = "61957aeb36daf0b00b87fff9c10dd28a161bd35ab157553d340d183b3d8756e6" dependencies = [ "bitflags 1.3.2", "libc", @@ -1375,9 +1334,9 @@ dependencies = [ [[package]] name = "vm-memory" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77c7a0891cbac53618f5f6eec650ed1dc4f7e506bbe14877aff49d94b8408b0" +checksum = "3750e9b70da7f2ce2f7bf942c886d45f9bae064135c398f05635bf77e926a2ef" dependencies = [ "arc-swap", "bitflags 1.3.2", @@ -1513,9 +1472,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.4.7" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca0ace3845f0d96209f0375e6d367e3eb87eb65d27d445bdc9f1843a26f39448" +checksum = "acaaa1190073b2b101e15083c38ee8ec891b5e05cbee516521e94ec008f61e64" dependencies = [ "memchr", ] From 4fa44ea06cd6ee9b64911a2432b881f862cc7d0e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Aug 2023 04:20:05 +0000 Subject: [PATCH 10/40] build(deps): bump serde_json from 1.0.104 to 1.0.105 Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.104 to 1.0.105. - [Release notes](https://github.com/serde-rs/json/releases) - [Commits](https://github.com/serde-rs/json/compare/v1.0.104...v1.0.105) --- updated-dependencies: - dependency-name: serde_json dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d9006c533..d826b5121 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -949,9 +949,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.104" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" dependencies = [ "itoa", "ryu", From a9770822f96b693bfb4014f7d9b8fe9091b30c75 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Aug 2023 04:19:58 +0000 Subject: [PATCH 11/40] build(deps): bump serde from 1.0.183 to 1.0.184 Bumps [serde](https://github.com/serde-rs/serde) from 1.0.183 to 1.0.184. - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.183...v1.0.184) --- updated-dependencies: - dependency-name: serde dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d826b5121..18401f459 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -929,18 +929,18 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "serde" -version = "1.0.183" +version = "1.0.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" +checksum = "2c911f4b04d7385c9035407a4eff5903bf4fe270fa046fda448b69e797f4fff0" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.183" +version = "1.0.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" +checksum = "c1df27f5b29406ada06609b2e2f77fb34f6dbb104a457a671cc31dbed237e09e" dependencies = [ "proc-macro2", "quote", From 4718ef80ad0b5bf065468135ee53d4e2534d3593 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Aug 2023 04:19:50 +0000 Subject: [PATCH 12/40] build(deps): bump log from 0.4.19 to 0.4.20 Bumps [log](https://github.com/rust-lang/log) from 0.4.19 to 0.4.20. - [Release notes](https://github.com/rust-lang/log/releases) - [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-lang/log/compare/0.4.19...0.4.20) --- updated-dependencies: - dependency-name: log dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 18401f459..d2b706971 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -632,9 +632,9 @@ checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "log" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" From 8da7657df03cdadcd16f75e22a7a2a1fc15f94a8 Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Tue, 22 Aug 2023 10:18:57 +0200 Subject: [PATCH 13/40] Update vhost-user-backend package dependency vhost-user-backend v0.10.0 introduced an issue that affects all vhost-user backends. I easily reproduced the problem with vhost-device-vsock: just restart the guest kernel and the device no longer works. vhost-user-backend v0.10.1 includes the fix [1] for that issue. [1] https://github.com/rust-vmm/vhost/pull/180 Signed-off-by: Stefano Garzarella --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d2b706971..16cb81acd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1290,9 +1290,9 @@ dependencies = [ [[package]] name = "vhost-user-backend" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ea9d5e8ec847cde4df1c04e586698a479706fd6beca37323f9d425b24b4c2f" +checksum = "ab069cdedaf18a0673766eb0a07a0f4ee3ed1b8e17fbfe4aafe5b988e2de1d01" dependencies = [ "libc", "log", From 05cfece5f8f6de4377b6da911e1cd363f4a6cfbc Mon Sep 17 00:00:00 2001 From: Bilal Elmoussaoui Date: Wed, 23 Aug 2023 12:29:27 +0200 Subject: [PATCH 14/40] i2c: Don't take a mut ref for reqs Fixing a nightly clippy warning Signed-off-by: Bilal Elmoussaoui --- crates/i2c/src/i2c.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/i2c/src/i2c.rs b/crates/i2c/src/i2c.rs index ce55a3029..e214b3bbd 100644 --- a/crates/i2c/src/i2c.rs +++ b/crates/i2c/src/i2c.rs @@ -183,7 +183,7 @@ impl SmbusMsg { /// /// These smbus related functions try to reverse what Linux does, only /// support basic modes (up to word transfer). - fn new(reqs: &mut [I2cReq]) -> Result { + fn new(reqs: &[I2cReq]) -> Result { let mut data = I2cSmbusData { block: [0; I2C_SMBUS_BLOCK_MAX + 2], }; From 38624410db3eff26be70a7399374c392b47e746b Mon Sep 17 00:00:00 2001 From: Bilal Elmoussaoui Date: Wed, 23 Aug 2023 12:32:03 +0200 Subject: [PATCH 15/40] misc: Set workspace resolver to 2 cargo complains with the following otherwise: some crates are on edition 2021 which defaults to resolver = 2, but virtual workspaces default to resolver = 1 Signed-off-by: Bilal Elmoussaoui --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 633fa4d3e..514eca805 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +resolver = "2" members = [ "crates/gpio", From fad00d3e3cd50663703620bb1c8cde618fca14a6 Mon Sep 17 00:00:00 2001 From: Bilal Elmoussaoui Date: Wed, 23 Aug 2023 12:34:55 +0200 Subject: [PATCH 16/40] vsock: Set edition to 2021 Similar to other crates Signed-off-by: Bilal Elmoussaoui --- crates/vsock/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/vsock/Cargo.toml b/crates/vsock/Cargo.toml index 2b37c5613..46d4e166f 100644 --- a/crates/vsock/Cargo.toml +++ b/crates/vsock/Cargo.toml @@ -7,7 +7,7 @@ repository = "https://github.com/rust-vmm/vhost-device" readme = "README.md" keywords = ["vhost", "vsock"] license = "Apache-2.0 OR BSD-3-Clause" -edition = "2018" +edition = "2021" [features] xen = ["vm-memory/xen", "vhost/xen", "vhost-user-backend/xen"] From ee2abe6df06bae46db2d3ed4a4c2754055c23333 Mon Sep 17 00:00:00 2001 From: Bilal Elmoussaoui Date: Wed, 23 Aug 2023 12:36:27 +0200 Subject: [PATCH 17/40] scsi: Bump num_enum Signed-off-by: Bilal Elmoussaoui --- Cargo.lock | 8 ++++---- crates/scsi/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 16cb81acd..5b5bdcac2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -670,18 +670,18 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" +checksum = "70bf6736f74634d299d00086f02986875b3c2d924781a6a2cb6c201e73da0ceb" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" +checksum = "56ea360eafe1022f7cc56cd7b869ed57330fb2453d0c7831d99b74c65d2f5597" dependencies = [ "proc-macro-crate", "proc-macro2", diff --git a/crates/scsi/Cargo.toml b/crates/scsi/Cargo.toml index ae3ff8ab4..3cba3c96d 100644 --- a/crates/scsi/Cargo.toml +++ b/crates/scsi/Cargo.toml @@ -19,7 +19,7 @@ clap = { version = "4.3", features = ["derive"] } env_logger = "0.10" epoll = "4.3" log = "0.4" -num_enum = "0.6" +num_enum = "0.7" thiserror = "1.0" vhost = { version = "0.8", features = ["vhost-user-slave"] } vhost-user-backend = "0.10" From 2fa80555d242f5aaa1d188cf79f982263c8bfd51 Mon Sep 17 00:00:00 2001 From: Bilal Elmoussaoui Date: Wed, 23 Aug 2023 12:37:41 +0200 Subject: [PATCH 18/40] misc: Update dependencies lockfile Following up the update num_enum dependency Signed-off-by: Bilal Elmoussaoui --- Cargo.lock | 122 ++++++++++++++++++++++++++--------------------------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5b5bdcac2..b627a2cd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,9 +15,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8f9420f797f2d9e935edf629310eb938a0d839f984e25327f3c7eed22300c" +checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" dependencies = [ "memchr", ] @@ -63,9 +63,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" dependencies = [ "anstyle", "windows-sys", @@ -85,13 +85,13 @@ checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] name = "async-trait" -version = "0.1.72" +version = "0.1.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -136,9 +136,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "block-buffer" @@ -157,9 +157,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cc" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "libc", ] @@ -192,9 +192,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.21" +version = "4.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd" +checksum = "03aef18ddf7d879c15ce20f04826ef8418101c7e528014c3eeea13321047dca3" dependencies = [ "clap_builder", "clap_derive", @@ -203,9 +203,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.21" +version = "4.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa" +checksum = "f8ce6fffb678c9b80a70b6b6de0aad31df727623a70fd9a842c30cd573e2fa98" dependencies = [ "anstream", "anstyle", @@ -222,7 +222,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -316,7 +316,7 @@ version = "4.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74351c3392ea1ff6cd2628e0042d268ac2371cb613252ff383b6dfa50d22fa79" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.0", "libc", ] @@ -421,7 +421,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -686,7 +686,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -747,7 +747,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -763,9 +763,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c516611246607d0c04186886dbb3a754368ef82c79e9827a802c6d836dd111c" +checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" [[package]] name = "pin-utils" @@ -806,9 +806,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.32" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -910,11 +910,11 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.7" +version = "0.38.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "172891ebdceb05aa0005f533a6cbfca599ddd7d966f6f5d4d9b2e70478e70399" +checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.0", "errno 0.3.2", "libc", "linux-raw-sys", @@ -929,22 +929,22 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "serde" -version = "1.0.184" +version = "1.0.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c911f4b04d7385c9035407a4eff5903bf4fe270fa046fda448b69e797f4fff0" +checksum = "be9b6f69f1dfd54c3b568ffa45c310d6973a5e5148fd40cf515acaf38cf5bc31" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.184" +version = "1.0.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1df27f5b29406ada06609b2e2f77fb34f6dbb104a457a671cc31dbed237e09e" +checksum = "dc59dfdcbad1437773485e0367fea4b090a2e0a16d9ffc46af47764536a298ec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -990,9 +990,9 @@ checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] @@ -1034,9 +1034,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.28" +version = "2.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" dependencies = [ "proc-macro2", "quote", @@ -1060,9 +1060,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.7.1" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", "fastrand", @@ -1082,22 +1082,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.44" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" +checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.44" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" +checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -1415,9 +1415,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -1430,51 +1430,51 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.5.4" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acaaa1190073b2b101e15083c38ee8ec891b5e05cbee516521e94ec008f61e64" +checksum = "d09770118a7eb1ccaf4a594a221334119a44a814fcb0d31c5b85e83e97227a97" dependencies = [ "memchr", ] From 9926e75c41906484478a122743a71befd01572a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Aug 2023 04:14:45 +0000 Subject: [PATCH 19/40] build(deps): bump winnow from 0.5.14 to 0.5.15 Bumps [winnow](https://github.com/winnow-rs/winnow) from 0.5.14 to 0.5.15. - [Changelog](https://github.com/winnow-rs/winnow/blob/main/CHANGELOG.md) - [Commits](https://github.com/winnow-rs/winnow/compare/v0.5.14...v0.5.15) --- updated-dependencies: - dependency-name: winnow dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b627a2cd2..6663effe6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1472,9 +1472,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d09770118a7eb1ccaf4a594a221334119a44a814fcb0d31c5b85e83e97227a97" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" dependencies = [ "memchr", ] From e7d0e80282f1e62c0cff3ae3be779f112e2c684b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Aug 2023 14:33:57 +0000 Subject: [PATCH 20/40] build(deps): bump regex-syntax from 0.7.4 to 0.7.5 Bumps [regex-syntax](https://github.com/rust-lang/regex) from 0.7.4 to 0.7.5. - [Release notes](https://github.com/rust-lang/regex/releases) - [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-lang/regex/commits) --- updated-dependencies: - dependency-name: regex-syntax dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6663effe6..2d56cb064 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -877,9 +877,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "ron" From 9513ed841cb9301062888704343b77a7a1c330db Mon Sep 17 00:00:00 2001 From: Milan Zamazal Date: Wed, 7 Jun 2023 18:50:02 +0200 Subject: [PATCH 21/40] scmi: Initial skeleton MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds support for a SCMI vhost-user device. It implements the basic skeleton of the vhost-user daemon and of SCMI processing. It doesn’t provide any real functionality yet, adding it will be the subject of followup patches. Signed-off-by: Milan Zamazal --- Cargo.lock | 17 + Cargo.toml | 1 + crates/scmi/Cargo.toml | 26 + crates/scmi/LICENSE-APACHE | 1 + crates/scmi/LICENSE-BSD-3-Clause | 1 + crates/scmi/README.md | 64 +++ crates/scmi/src/main.rs | 98 ++++ crates/scmi/src/scmi.rs | 281 +++++++++++ crates/scmi/src/vhu_scmi.rs | 787 +++++++++++++++++++++++++++++++ 9 files changed, 1276 insertions(+) create mode 100644 crates/scmi/Cargo.toml create mode 120000 crates/scmi/LICENSE-APACHE create mode 120000 crates/scmi/LICENSE-BSD-3-Clause create mode 100644 crates/scmi/README.md create mode 100644 crates/scmi/src/main.rs create mode 100644 crates/scmi/src/scmi.rs create mode 100644 crates/scmi/src/vhu_scmi.rs diff --git a/Cargo.lock b/Cargo.lock index 2d56cb064..3155b5f9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1244,6 +1244,23 @@ dependencies = [ "vmm-sys-util", ] +[[package]] +name = "vhost-device-scmi" +version = "0.1.0" +dependencies = [ + "assert_matches", + "clap", + "env_logger", + "log", + "thiserror", + "vhost", + "vhost-user-backend", + "virtio-bindings", + "virtio-queue", + "vm-memory", + "vmm-sys-util", +] + [[package]] name = "vhost-device-scsi" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 514eca805..72e3adfe6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,6 @@ members = [ "crates/i2c", "crates/rng", "crates/scsi", + "crates/scmi", "crates/vsock", ] diff --git a/crates/scmi/Cargo.toml b/crates/scmi/Cargo.toml new file mode 100644 index 000000000..b5e0a42f1 --- /dev/null +++ b/crates/scmi/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "vhost-device-scmi" +version = "0.1.0" +authors = ["Milan Zamazal "] +description = "vhost-user SCMI backend device" +repository = "https://github.com/rust-vmm/vhost-device" +readme = "README.md" +keywords = ["scmi", "vhost", "virt", "backend"] +license = "Apache-2.0 OR BSD-3-Clause" +edition = "2021" + +[dependencies] +clap = { version = "4.3", features = ["derive"] } +env_logger = "0.10" +log = "0.4" +thiserror = "1.0" +vhost = { version = "0.8", features = ["vhost-user-slave"] } +vhost-user-backend = "0.10" +virtio-bindings = "0.2" +virtio-queue = "0.9" +vm-memory = "0.12" +vmm-sys-util = "0.11" + +[dev-dependencies] +assert_matches = "1.5" +virtio-queue = { version = "0.9", features = ["test-utils"] } diff --git a/crates/scmi/LICENSE-APACHE b/crates/scmi/LICENSE-APACHE new file mode 120000 index 000000000..1cd601d0a --- /dev/null +++ b/crates/scmi/LICENSE-APACHE @@ -0,0 +1 @@ +../../LICENSE-APACHE \ No newline at end of file diff --git a/crates/scmi/LICENSE-BSD-3-Clause b/crates/scmi/LICENSE-BSD-3-Clause new file mode 120000 index 000000000..a60f1af6d --- /dev/null +++ b/crates/scmi/LICENSE-BSD-3-Clause @@ -0,0 +1 @@ +../../LICENSE-BSD-3-Clause \ No newline at end of file diff --git a/crates/scmi/README.md b/crates/scmi/README.md new file mode 100644 index 000000000..b555e7bcd --- /dev/null +++ b/crates/scmi/README.md @@ -0,0 +1,64 @@ +# vhost-device-scmi + +This program is a vhost-user backend for a VirtIO SCMI device. +It provides SCMI access to various entities on the host; not +necessarily only those providing an SCMI interface themselves. + +It is tested with QEMU's `-device vhost-user-scmi-pci` but should work +with any virtual machine monitor (VMM) that supports vhost-user. See +the Examples section below. + +The currently supported SCMI protocols are: + +- base protocol + +The currently supported SCMI connections on the host are: + +- none + +## Synopsis + +**vhost-device-scmi** [*OPTIONS*] + +## Options + +.. program:: vhost-device-scmi + +.. option:: -h, --help + + Print help. + +.. option:: -s, --socket-path=PATH + + Location of the vhost-user Unix domain sockets. + +You can set `RUST_LOG` environment variable to `debug` to get maximum +messages on the standard error output. + +## Examples + +The daemon should be started first: + +:: + + host# vhost-device-scmi --socket-path=scmi.sock + +The QEMU invocation needs to create a chardev socket the device can +use to communicate as well as share the guests memory over a memfd: + +:: + + host# qemu-system \ + -chardev socket,path=scmi.sock,id=scmi \ + -device vhost-user-scmi-pci,chardev=vscmi,id=scmi \ + -machine YOUR-MACHINE-OPTIONS,memory-backend=mem \ + -m 4096 \ + -object memory-backend-file,id=mem,size=4G,mem-path=/dev/shm,share=on \ + ... + +## License + +This project is licensed under either of + +- [Apache License](http://www.apache.org/licenses/LICENSE-2.0), Version 2.0 +- [BSD-3-Clause License](https://opensource.org/licenses/BSD-3-Clause) diff --git a/crates/scmi/src/main.rs b/crates/scmi/src/main.rs new file mode 100644 index 000000000..02b8e2051 --- /dev/null +++ b/crates/scmi/src/main.rs @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: Red Hat, Inc. +// SPDX-License-Identifier: Apache-2.0 +// Based on implementation of other devices here, Copyright by Linaro Ltd. + +mod scmi; +mod vhu_scmi; + +use std::process::exit; +use std::sync::{Arc, RwLock}; + +use clap::Parser; +use log::{debug, error, info, warn}; + +use vhost::vhost_user; +use vhost::vhost_user::Listener; +use vhost_user_backend::VhostUserDaemon; +use vhu_scmi::{VuScmiBackend, VuScmiError}; +use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; + +type Result = std::result::Result; + +#[derive(Parser)] +struct ScmiArgs { + // Location of vhost-user Unix domain socket. + #[clap(short, long, help = "vhost-user socket to use")] + socket_path: String, +} + +struct VuScmiConfig { + socket_path: String, +} + +impl TryFrom for VuScmiConfig { + type Error = VuScmiError; + + fn try_from(cmd_args: ScmiArgs) -> Result { + let socket_path = cmd_args.socket_path.trim().to_string(); + Ok(Self { socket_path }) + } +} + +fn start_backend(config: VuScmiConfig) -> Result<()> { + loop { + debug!("Starting backend"); + let backend = Arc::new(RwLock::new(VuScmiBackend::new().unwrap())); + let listener = Listener::new(config.socket_path.clone(), true).unwrap(); + let mut daemon = VhostUserDaemon::new( + "vhost-device-scmi".to_owned(), + backend.clone(), + GuestMemoryAtomic::new(GuestMemoryMmap::new()), + ) + .unwrap(); + + daemon.start(listener).unwrap(); + + match daemon.wait() { + Ok(()) => { + info!("Stopping cleanly"); + } + Err(vhost_user_backend::Error::HandleRequest(vhost_user::Error::PartialMessage)) => { + info!( + "vhost-user connection closed with partial message. + If the VM is shutting down, this is expected behavior; + otherwise, it might be a bug." + ); + } + Err(e) => { + warn!("Error running daemon: {:?}", e); + } + } + + // No matter the result, we need to shut down the worker thread. + backend.read().unwrap().exit_event.write(1).unwrap(); + debug!("Finishing backend"); + } +} + +fn main() { + env_logger::init(); + if let Err(error) = start_backend(VuScmiConfig::try_from(ScmiArgs::parse()).unwrap()) { + error!("{error}"); + exit(1); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_command_line() { + let path = "/foo/scmi.sock".to_owned(); + let command_line = format!("-s {path}"); + let args: ScmiArgs = Parser::parse_from(["", &command_line]); + let config: VuScmiConfig = args.try_into().unwrap(); + assert_eq!(config.socket_path, path); + } +} diff --git a/crates/scmi/src/scmi.rs b/crates/scmi/src/scmi.rs new file mode 100644 index 000000000..9429e0c94 --- /dev/null +++ b/crates/scmi/src/scmi.rs @@ -0,0 +1,281 @@ +// SPDX-FileCopyrightText: Red Hat, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use log::debug; + +pub type MessageHeader = u32; +// SCMI specification talks about Le32 parameter and return values. +// VirtIO SCMI specification talks about u8 SCMI values. +// Let's stick with SCMI specification for implementation simplicity. +#[derive(Clone, Debug, PartialEq)] +#[allow(dead_code)] +enum MessageValue { + Signed(i32), + Unsigned(u32), + String(String, usize), // string, expected characters +} +type MessageValues = Vec; + +#[derive(Debug, PartialEq)] +enum MessageType { + // 4-bit unsigned integer + Command, // 0 + Unsupported, // anything else +} +type MessageId = u8; +type ProtocolId = u8; +type NParameters = u8; + +#[derive(Clone, Copy)] +// Not all the codes are currently used but let's have a complete return status +// enumeration from the SCMI specification here. +#[allow(dead_code)] +enum ReturnStatus { + // 32-bit signed integer + Success = 0, + NotSupported = -1, + InvalidParameters = -2, + Denied = -3, + NotFound = -4, + OutOfRange = -5, + Busy = -6, + CommsError = -7, + GenericError = -8, + HardwareError = -9, + ProtocolError = -10, + // -11..-127: reserved + // <-127: vendor specific +} + +impl ReturnStatus { + const fn as_value(&self) -> MessageValue { + MessageValue::Signed(*self as i32) + } +} + +struct Response { + values: MessageValues, +} + +impl From for Response { + fn from(value: ReturnStatus) -> Self { + Self { + values: vec![value.as_value()], + } + } +} + +impl From<&MessageValues> for Response { + #[allow(dead_code)] + fn from(value: &MessageValues) -> Self { + let mut response_values = vec![ReturnStatus::Success.as_value()]; + response_values.extend_from_slice(value.as_slice()); + Self { + values: response_values, + } + } +} + +#[derive(Debug)] +pub struct ScmiResponse { + header: MessageHeader, + ret_bytes: Vec, +} + +impl ScmiResponse { + fn from(header: MessageHeader, response: Response) -> Self { + debug!("response arguments: {:?}", response.values); + let mut ret_bytes: Vec = vec![]; + ret_bytes.extend_from_slice(&header.to_le_bytes()); + for v in response.values { + let mut bytes = match v { + MessageValue::Signed(n) => n.to_le_bytes().to_vec(), + MessageValue::Unsigned(n) => n.to_le_bytes().to_vec(), + // Strings can be UTF-8 or ASCII and they must be + // null-terminated in either case. Let's put the + // null-terminator here rather than having to put it + // to all the strings anywhere. + MessageValue::String(s, size) => { + let mut v = s.as_bytes().to_vec(); + let v_len = v.len(); + // The string must be NULL terminated, at least one NULL must be present. + assert!( + v_len < size, + "String longer than specified: {v_len} >= {size}" + ); + v.resize(size, b'\0'); + v + } + }; + ret_bytes.append(&mut bytes) + } + debug!("ret bytes: {:?}", ret_bytes); + Self { header, ret_bytes } + } + + pub(crate) fn as_slice(&self) -> &[u8] { + self.ret_bytes.as_slice() + } + + pub(crate) fn len(&self) -> usize { + self.ret_bytes.len() + } + + pub(crate) fn communication_error(&self) -> Self { + Self::from(self.header, Response::from(ReturnStatus::CommsError)) + } +} + +pub struct ScmiHandler {} + +impl ScmiHandler { + pub const fn new() -> Self { + Self {} + } + + pub fn handle(&mut self, request: ScmiRequest) -> ScmiResponse { + let response = match request.message_type { + // TODO: Implement a mechanism to invoke the proper protocol & + // message handling on command requests. + MessageType::Command => Response::from(ReturnStatus::NotSupported), + MessageType::Unsupported => Response::from(ReturnStatus::NotSupported), + }; + ScmiResponse::from(request.header, response) + } + + pub fn number_of_parameters(&self, _request: &ScmiRequest) -> Option { + // TODO: Implement. + Some(0) + } + + pub fn store_parameters(&self, _request: &mut ScmiRequest, _buffer: &[u8]) { + // TODO: Implement (depends on knowledge of the number of parameters). + } +} + +#[allow(dead_code)] +pub struct ScmiRequest { + header: MessageHeader, // 32-bit unsigned integer, split below: + message_id: MessageId, // bits 7:0 + message_type: MessageType, // bits 9:8 + protocol_id: ProtocolId, // bits 17:10 + // token: u16, // bits 27:18 + // bits 31:28 are reserved, must be 0 + _parameters: Option, // set later based on the number of parameters +} + +impl ScmiRequest { + pub(crate) fn new(header: MessageHeader) -> Self { + let protocol_id: u8 = ((header >> 10) & 0xFF).try_into().unwrap(); + let message_id: u8 = (header & 0xFF).try_into().unwrap(); + // Token is an arbitrary info, the Linux SCMI driver uses it as a sequence number. + // No actual meaning for vhost except copying the unchanged header in the response + // as required by SCMI specification. We extract it here only for debugging purposes. + let token: u16 = ((header >> 18) & 0x3FF).try_into().unwrap(); + let message_type = match (header >> 8) & 0x3 { + 0 => MessageType::Command, + _ => MessageType::Unsupported, + }; + debug!( + "SCMI request: protocol id={}, message id={}, message_type={:?}, token={}", + protocol_id, message_id, message_type, token + ); + Self { + header, + message_id, + message_type, + protocol_id, + _parameters: None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_response_from_status() { + let status = ReturnStatus::Busy; + let response = Response::from(status); + assert_eq!(response.values.len(), 1); + assert_eq!(response.values[0], MessageValue::Signed(status as i32)); + } + + #[test] + fn test_response_from_values() { + let status = ReturnStatus::Success; + let values = vec![ + MessageValue::Signed(-2), + MessageValue::Unsigned(8), + MessageValue::String("foo".to_owned(), 16), + ]; + let len = values.len() + 1; + let response = Response::from(&values); + assert_eq!(response.values.len(), len); + assert_eq!(response.values[0], MessageValue::Signed(status as i32)); + for i in 1..len { + assert_eq!(response.values[i], values[i - 1]); + } + } + + fn make_response(header: MessageHeader) -> ScmiResponse { + let values = vec![ + MessageValue::Signed(-2), + MessageValue::Unsigned(800_000_000), + MessageValue::String("foo".to_owned(), 16), + ]; + let response = Response::from(&values); + ScmiResponse::from(header, response) + } + + #[test] + fn test_response() { + let header: MessageHeader = 1_000_000; + let scmi_response = make_response(header); + assert_eq!(scmi_response.header, header); + let bytes: Vec = vec![ + 0x40, 0x42, 0x0F, 0x00, // header + 0x00, 0x00, 0x00, 0x00, // SUCCESS + 0xFE, 0xFF, 0xFF, 0xFF, // -2 + 0x00, 0x08, 0xAF, 0x2F, // 800 000 000 + 0x66, 0x6F, 0x6F, 0x00, // "foo" + NULLs + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + assert_eq!(scmi_response.ret_bytes, bytes); + assert_eq!(scmi_response.len(), bytes.len()); + assert_eq!(scmi_response.as_slice(), bytes.as_slice()); + } + + #[test] + fn test_communication_error_response() { + let header: MessageHeader = 1_000_000; + let scmi_response = make_response(header).communication_error(); + assert_eq!(scmi_response.header, header); + let bytes: Vec = vec![ + 0x40, 0x42, 0x0F, 0x00, // header + 0xF9, 0xFF, 0xFF, 0xFF, // ComsError + ]; + assert_eq!(scmi_response.ret_bytes, bytes); + } + + #[test] + fn test_request() { + let header: MessageHeader = 0x000304AB; + let request = ScmiRequest::new(header); + assert_eq!(request.header, header); + assert_eq!(request.message_id, 0xAB); + assert_eq!(request.message_type, MessageType::Command); + assert_eq!(request.protocol_id, 0xC1); + } + + #[test] + fn test_request_unsupported() { + let header: MessageHeader = 0x000102AB; + let request = ScmiRequest::new(header); + assert_eq!(request.header, header); + assert_eq!(request.message_id, 0xAB); + assert_eq!(request.message_type, MessageType::Unsupported); + assert_eq!(request.protocol_id, 0x40); + } +} diff --git a/crates/scmi/src/vhu_scmi.rs b/crates/scmi/src/vhu_scmi.rs new file mode 100644 index 000000000..0128666fa --- /dev/null +++ b/crates/scmi/src/vhu_scmi.rs @@ -0,0 +1,787 @@ +// SPDX-FileCopyrightText: Red Hat, Inc. +// SPDX-License-Identifier: Apache-2.0 +// Based on https://github.com/rust-vmm/vhost-device, Copyright by Linaro Ltd. + +use log::{debug, error, warn}; +use std::io; +use std::io::Result as IoResult; +use std::mem::size_of; +use thiserror::Error as ThisError; +use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; +use vhost_user_backend::{VhostUserBackendMut, VringRwLock, VringT}; +use virtio_bindings::bindings::virtio_config::{VIRTIO_F_NOTIFY_ON_EMPTY, VIRTIO_F_VERSION_1}; +use virtio_bindings::bindings::virtio_ring::{ + VIRTIO_RING_F_EVENT_IDX, VIRTIO_RING_F_INDIRECT_DESC, +}; +use virtio_queue::{DescriptorChain, QueueOwnedT}; +use vm_memory::{ + Bytes, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryLoadGuard, GuestMemoryMmap, +}; +use vmm_sys_util::epoll::EventSet; +use vmm_sys_util::eventfd::{EventFd, EFD_NONBLOCK}; + +use crate::scmi::{MessageHeader, ScmiHandler, ScmiRequest}; + +// QUEUE_SIZE must be apparently at least 1024 for MMIO. +// There is probably a maximum size per descriptor defined in the kernel. +const QUEUE_SIZE: usize = 1024; +const NUM_QUEUES: usize = 2; + +const COMMAND_QUEUE: u16 = 0; +const EVENT_QUEUE: u16 = 1; + +const VIRTIO_SCMI_F_P2A_CHANNELS: u16 = 0; + +#[derive(Debug, PartialEq, Eq, ThisError)] +pub enum VuScmiError { + #[error("Descriptor not found")] + DescriptorNotFound, + #[error("Descriptor read failed")] + DescriptorReadFailed, + #[error("Descriptor write failed")] + DescriptorWriteFailed, + #[error("Failed to create new EventFd")] + EventFdFailed, + #[error("Failed to handle event, didn't match EPOLLIN")] + HandleEventNotEpollIn, + #[error("Failed to handle unknown event")] + HandleEventUnknownEvent, + #[error("Isufficient descriptor size, required: {0}, found: {1}")] + InsufficientDescriptorSize(usize, usize), + #[error("Failed to send notification")] + SendNotificationFailed, + #[error("Invalid descriptor count {0}")] + UnexpectedDescriptorCount(usize), + #[error("Invalid descriptor size, expected: {0}, found: {1}")] + UnexpectedDescriptorSize(usize, usize), + #[error("Invalid descriptor size, expected at least: {0}, found: {1}")] + UnexpectedMinimumDescriptorSize(usize, usize), + #[error("Received unexpected readable descriptor at index {0}")] + UnexpectedReadableDescriptor(usize), + #[error("Received unexpected write only descriptor at index {0}")] + UnexpectedWriteOnlyDescriptor(usize), +} + +impl From for io::Error { + fn from(e: VuScmiError) -> Self { + Self::new(io::ErrorKind::Other, e) + } +} + +type Result = std::result::Result; + +type ScmiDescriptorChain = DescriptorChain>>; + +pub struct VuScmiBackend { + event_idx: bool, + pub exit_event: EventFd, + mem: Option>, + // Event vring and descriptors serve for asynchronous responses and notifications. + // They are obtained from the driver and we store them here for later use. + // (We currently don't implement asynchronous responses or notifications but we support + // the event queue because the Linux VIRTIO SCMI driver seems to be unhappy if it is not + // present. And it doesn't harm to be ready for possible event queue use in future.) + event_vring: Option, + event_descriptors: Vec>>, + // The abstraction of request handling, with all the needed information stored inside. + scmi_handler: ScmiHandler, +} + +impl VuScmiBackend { + pub fn new() -> Result { + Ok(Self { + event_idx: false, + exit_event: EventFd::new(EFD_NONBLOCK).map_err(|_| VuScmiError::EventFdFailed)?, + mem: None, + event_vring: None, + event_descriptors: vec![], + scmi_handler: ScmiHandler::new(), + }) + } + + pub fn process_requests( + &mut self, + requests: Vec, + vring: &VringRwLock, + ) -> Result { + if requests.is_empty() { + return Ok(true); + } + + for desc_chain in requests { + let descriptors: Vec<_> = desc_chain.clone().collect(); + if descriptors.len() != 2 { + return Err(VuScmiError::UnexpectedDescriptorCount(descriptors.len())); + } + + let desc_request = descriptors[0]; + if desc_request.is_write_only() { + return Err(VuScmiError::UnexpectedWriteOnlyDescriptor(0)); + } + + let read_desc_len: usize = desc_request.len() as usize; + let header_size = size_of::(); + if read_desc_len < header_size { + return Err(VuScmiError::UnexpectedMinimumDescriptorSize( + header_size, + read_desc_len, + )); + } + + let header = desc_chain + .memory() + .read_obj::(desc_request.addr()) + .map_err(|_| VuScmiError::DescriptorReadFailed)?; + let mut scmi_request = ScmiRequest::new(header); + let n_parameters = self.scmi_handler.number_of_parameters(&scmi_request); + debug!("SCMI request with n parameters: {:?}", n_parameters); + let value_size = 4; + if let Some(expected_parameters) = n_parameters { + if expected_parameters > 0 { + let param_bytes = (expected_parameters as usize) * value_size; + let total_size = value_size + param_bytes; + if read_desc_len != total_size { + return Err(VuScmiError::UnexpectedDescriptorSize( + total_size, + read_desc_len, + )); + } + let mut buffer: Vec = vec![0; header_size + param_bytes]; + desc_chain + .memory() + .read_slice(&mut buffer, desc_request.addr()) + .map_err(|_| VuScmiError::DescriptorReadFailed)?; + self.scmi_handler + .store_parameters(&mut scmi_request, &buffer[header_size..]); + } else if read_desc_len != value_size { + return Err(VuScmiError::UnexpectedDescriptorSize( + value_size, + read_desc_len, + )); + } + } + + debug!("Calling SCMI request handler"); + let mut response = self.scmi_handler.handle(scmi_request); + debug!("SCMI response: {:?}", response); + + let desc_response = descriptors[1]; + if !desc_response.is_write_only() { + return Err(VuScmiError::UnexpectedReadableDescriptor(1)); + } + + let write_desc_len: usize = desc_response.len() as usize; + if response.len() > write_desc_len { + error!( + "Response of length {} cannot fit into the descriptor size {}", + response.len(), + write_desc_len + ); + response = response.communication_error(); + if response.len() > write_desc_len { + return Err(VuScmiError::InsufficientDescriptorSize( + response.len(), + write_desc_len, + )); + } + } + desc_chain + .memory() + .write_slice(response.as_slice(), desc_response.addr()) + .map_err(|_| VuScmiError::DescriptorWriteFailed)?; + + if vring + .add_used(desc_chain.head_index(), response.len() as u32) + .is_err() + { + error!("Couldn't return used descriptors to the ring"); + } + } + Ok(true) + } + + fn process_command_queue(&mut self, vring: &VringRwLock) -> Result<()> { + debug!("Processing command queue"); + let requests: Vec<_> = vring + .get_mut() + .get_queue_mut() + .iter(self.mem.as_ref().unwrap().memory()) + .map_err(|_| VuScmiError::DescriptorNotFound)? + .collect(); + + debug!("Requests to process: {}", requests.len()); + match self.process_requests(requests, vring) { + Ok(_) => { + // Send notification once all the requests are processed + debug!("Sending processed request notification"); + vring + .signal_used_queue() + .map_err(|_| VuScmiError::SendNotificationFailed)?; + debug!("Notification sent"); + } + Err(err) => { + warn!("Failed SCMI request: {}", err); + return Err(err); + } + } + debug!("Processing command queue finished"); + Ok(()) + } + + fn start_event_queue(&mut self, vring: &VringRwLock) { + if self.event_vring.is_none() { + self.event_vring = Some(vring.clone()); + } + } + + pub fn process_event_requests( + &mut self, + requests: Vec, + _vring: &VringRwLock, + ) -> Result { + // The requests here are notifications from the guest about adding + // fresh buffers for the used ring. The Linux driver allocates 256 + // buffers for the event queue initially (arriving here in several + // batches) and then adds a free buffer after each message delivered + // through the event queue. + for desc_chain in requests { + let descriptors: Vec<_> = desc_chain.clone().collect(); + debug!( + "SCMI event request with n descriptors: {}", + descriptors.len() + ); + if descriptors.len() != 1 { + return Err(VuScmiError::UnexpectedDescriptorCount(descriptors.len())); + } + + let desc = descriptors[0]; + if !desc.is_write_only() { + return Err(VuScmiError::UnexpectedReadableDescriptor(0)); + } + debug!("SCMI event request avail descriptor length: {}", desc.len()); + + self.event_descriptors.push(desc_chain); + } + Ok(true) + } + + fn process_event_queue(&mut self, vring: &VringRwLock) -> Result<()> { + debug!("Processing event queue"); + + let requests: Vec<_> = vring + .get_mut() + .get_queue_mut() + .iter(self.mem.as_ref().unwrap().memory()) + .map_err(|_| VuScmiError::DescriptorNotFound)? + .collect(); + debug!("Requests to process: {}", requests.len()); + match self.process_event_requests(requests, vring) { + Ok(_) => { + // Send notification once all the requests are processed + debug!("Sending processed request notification"); + vring + .signal_used_queue() + .map_err(|_| VuScmiError::SendNotificationFailed)?; + debug!("Notification sent"); + } + Err(err) => { + warn!("Failed SCMI request: {}", err); + return Err(err); + } + } + self.start_event_queue(vring); + debug!("Processing event queue finished"); + Ok(()) + } +} + +/// VhostUserBackend trait methods +impl VhostUserBackendMut for VuScmiBackend { + fn num_queues(&self) -> usize { + debug!("Num queues called"); + NUM_QUEUES + } + + fn max_queue_size(&self) -> usize { + debug!("Max queue size called"); + QUEUE_SIZE + } + + fn features(&self) -> u64 { + debug!("Features called"); + 1 << VIRTIO_F_VERSION_1 + | 1 << VIRTIO_F_NOTIFY_ON_EMPTY + | 1 << VIRTIO_RING_F_INDIRECT_DESC + | 1 << VIRTIO_RING_F_EVENT_IDX + | 1 << VIRTIO_SCMI_F_P2A_CHANNELS + | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() + } + + fn protocol_features(&self) -> VhostUserProtocolFeatures { + debug!("Protocol features called"); + VhostUserProtocolFeatures::MQ + } + + fn set_event_idx(&mut self, enabled: bool) { + self.event_idx = enabled; + debug!("Event idx set to: {}", enabled); + } + + fn update_memory(&mut self, mem: GuestMemoryAtomic) -> IoResult<()> { + debug!("Update memory called"); + self.mem = Some(mem); + Ok(()) + } + + fn handle_event( + &mut self, + device_event: u16, + evset: EventSet, + vrings: &[VringRwLock], + _thread_id: usize, + ) -> IoResult { + debug!("Handle event called"); + if evset != EventSet::IN { + warn!("Non-input event"); + return Err(VuScmiError::HandleEventNotEpollIn.into()); + } + + match device_event { + COMMAND_QUEUE => { + let vring = &vrings[COMMAND_QUEUE as usize]; + + if self.event_idx { + // vm-virtio's Queue implementation only checks avail_index + // once, so to properly support EVENT_IDX we need to keep + // calling process_queue() until it stops finding new + // requests on the queue. + loop { + vring.disable_notification().unwrap(); + self.process_command_queue(vring)?; + if !vring.enable_notification().unwrap() { + break; + } + } + } else { + // Without EVENT_IDX, a single call is enough. + self.process_command_queue(vring)?; + } + } + + EVENT_QUEUE => { + let vring = &vrings[EVENT_QUEUE as usize]; + + if self.event_idx { + // vm-virtio's Queue implementation only checks avail_index + // once, so to properly support EVENT_IDX we need to keep + // calling process_queue() until it stops finding new + // requests on the queue. + loop { + vring.disable_notification().unwrap(); + self.process_event_queue(vring)?; + if !vring.enable_notification().unwrap() { + break; + } + } + } else { + // Without EVENT_IDX, a single call is enough. + self.process_event_queue(vring)?; + } + } + + _ => { + warn!("unhandled device_event: {}", device_event); + return Err(VuScmiError::HandleEventUnknownEvent.into()); + } + } + debug!("Handle event finished"); + Ok(false) + } + + fn exit_event(&self, _thread_index: usize) -> Option { + debug!("Exit event called"); + self.exit_event.try_clone().ok() + } +} + +#[cfg(test)] +mod tests { + use virtio_bindings::virtio_ring::{VRING_DESC_F_NEXT, VRING_DESC_F_WRITE}; + use virtio_queue::{mock::MockSplitQueue, Descriptor, Queue}; + use vm_memory::{Address, GuestAddress, GuestMemoryAtomic, GuestMemoryMmap}; + + use super::*; + + fn build_event_desc_chain() -> ScmiDescriptorChain { + let mem = &GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(); + let vq = MockSplitQueue::new(mem, 16); + let next_addr = vq.desc_table().total_size() + 0x100; + + // Descriptor for the SCMI event + let desc_response = Descriptor::new(next_addr, 0x100, VRING_DESC_F_WRITE as u16, 0); + vq.desc_table().store(0, desc_response).unwrap(); + + // Put the descriptor index 0 in the first available ring position. + mem.write_obj(0u16, vq.avail_addr().unchecked_add(4)) + .unwrap(); + // Set `avail_idx` to 1. + mem.write_obj(1u16, vq.avail_addr().unchecked_add(2)) + .unwrap(); + // Create descriptor chain from pre-filled memory. + vq.create_queue::() + .unwrap() + .iter(GuestMemoryAtomic::new(mem.clone()).memory()) + .unwrap() + .next() + .unwrap() + } + + // Build just empty descriptors + struct DescParameters { + addr: Option, + flags: u16, + len: u32, + } + fn build_dummy_desc_chain(parameters: Vec<&DescParameters>) -> ScmiDescriptorChain { + let mem = &GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(); + let vq = MockSplitQueue::new(mem, 16); + + for (i, p) in parameters.iter().enumerate() { + let mut f: u16 = if i == parameters.len() - 1 { + 0 + } else { + VRING_DESC_F_NEXT as u16 + }; + f |= p.flags; + let offset = match p.addr { + Some(addr) => addr, + _ => 0x100, + }; + let desc = Descriptor::new(offset, p.len, f, (i + 1) as u16); + vq.desc_table().store(i as u16, desc).unwrap(); + } + + // Put the descriptor index 0 in the first available ring position. + mem.write_obj(0u16, vq.avail_addr().unchecked_add(4)) + .unwrap(); + // Set `avail_idx` to 1. + mem.write_obj(1u16, vq.avail_addr().unchecked_add(2)) + .unwrap(); + // Create descriptor chain from pre-filled memory + vq.create_queue::() + .unwrap() + .iter(GuestMemoryAtomic::new(mem.clone()).memory()) + .unwrap() + .next() + .unwrap() + } + + #[test] + fn test_process_requests_failure() { + let mut backend = VuScmiBackend::new().unwrap(); + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(), + ); + let vring = VringRwLock::new(mem, 0x1000).unwrap(); + let default = DescParameters { + addr: None, + flags: 0, + len: 0, + }; + + // 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) + ); + + // 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) + ); + + // Write only descriptors. + let p = DescParameters { + addr: None, + flags: VRING_DESC_F_WRITE as u16, + len: 0, + }; + 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) + ); + + // Invalid request address. + let parameters = vec![ + &DescParameters { + addr: Some(0x10000), + flags: 0, + len: 4, + }, + &DescParameters { + addr: None, + flags: VRING_DESC_F_WRITE as u16, + len: 4, + }, + ]; + let desc_chain = build_dummy_desc_chain(parameters); + assert_eq!( + backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err(), + VuScmiError::DescriptorReadFailed + ); + + // Invalid request length (very small). + let parameters = vec![ + &DescParameters { + addr: None, + flags: 0, + len: 2, + }, + &DescParameters { + addr: None, + flags: VRING_DESC_F_WRITE as u16, + len: 4, + }, + ]; + let desc_chain = build_dummy_desc_chain(parameters); + assert_eq!( + backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err(), + VuScmiError::UnexpectedMinimumDescriptorSize(4, 2) + ); + + // Read only descriptors. + let p = DescParameters { + addr: None, + flags: 0, + len: 4, + }; + 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) + ); + + // Invalid response address. + let parameters = vec![ + &DescParameters { + addr: None, + flags: 0, + len: 4, + }, + &DescParameters { + addr: Some(0x10000), + flags: VRING_DESC_F_WRITE as u16, + len: 8, + }, + ]; + let desc_chain = build_dummy_desc_chain(parameters); + assert_eq!( + backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err(), + VuScmiError::DescriptorWriteFailed + ); + + // Invalid response length. + let parameters = vec![ + &DescParameters { + addr: None, + flags: 0, + len: 4, + }, + &DescParameters { + addr: None, + flags: VRING_DESC_F_WRITE as u16, + len: 6, + }, + ]; + let desc_chain = build_dummy_desc_chain(parameters); + assert_eq!( + backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err(), + VuScmiError::InsufficientDescriptorSize(8, 6) + ); + } + + #[test] + fn test_event_requests() { + let mut backend = VuScmiBackend::new().unwrap(); + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(), + ); + let vring = VringRwLock::new(mem, 0x1000).unwrap(); + + // Descriptor chain size zero, shouldn't fail and should be no-op + backend + .process_event_requests(Vec::::new(), &vring) + .unwrap(); + assert_eq!(backend.event_descriptors.len(), 0); + + // Valid event descriptors, should get stored + let desc_chains = vec![build_event_desc_chain(), build_event_desc_chain()]; + backend.process_event_requests(desc_chains, &vring).unwrap(); + assert_eq!(backend.event_descriptors.len(), 2); + + // Some more event descriptors + let desc_chains = vec![ + build_event_desc_chain(), + build_event_desc_chain(), + build_event_desc_chain(), + ]; + backend.process_event_requests(desc_chains, &vring).unwrap(); + assert_eq!(backend.event_descriptors.len(), 5); + } + + #[test] + fn test_event_requests_failure() { + let mut backend = VuScmiBackend::new().unwrap(); + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(), + ); + let vring = VringRwLock::new(mem, 0x1000).unwrap(); + + // Invalid number of desc chains + let p = DescParameters { + addr: None, + flags: 0, + 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) + ); + + // Read only descriptor + let p = DescParameters { + addr: None, + flags: 0, + 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) + ); + } + + #[test] + fn test_backend() { + let mut backend = VuScmiBackend::new().unwrap(); + + assert_eq!(backend.num_queues(), NUM_QUEUES); + assert_eq!(backend.max_queue_size(), QUEUE_SIZE); + assert_eq!(backend.features(), 0x171000001); + assert_eq!(backend.protocol_features(), VhostUserProtocolFeatures::MQ); + + assert_eq!(backend.queues_per_thread(), vec![0xffff_ffff]); + + backend.set_event_idx(true); + assert!(backend.event_idx); + + assert!(backend.exit_event(0).is_some()); + + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(), + ); + backend.update_memory(mem.clone()).unwrap(); + + let vring_request = VringRwLock::new(mem.clone(), 0x1000).unwrap(); + vring_request.set_queue_info(0x100, 0x200, 0x300).unwrap(); + vring_request.set_queue_ready(true); + + let vring_event = VringRwLock::new(mem, 0x1000).unwrap(); + vring_event.set_queue_info(0x100, 0x200, 0x300).unwrap(); + vring_event.set_queue_ready(true); + + assert_eq!( + backend + .handle_event( + 0, + EventSet::OUT, + &[vring_request.clone(), vring_event.clone()], + 0, + ) + .unwrap_err() + .kind(), + io::ErrorKind::Other + ); + + assert_eq!( + backend + .handle_event( + 2, + EventSet::IN, + &[vring_request.clone(), vring_event.clone()], + 0, + ) + .unwrap_err() + .kind(), + io::ErrorKind::Other + ); + + // Hit the loop part + backend.set_event_idx(true); + backend + .handle_event( + 0, + EventSet::IN, + &[vring_request.clone(), vring_event.clone()], + 0, + ) + .unwrap(); + + // Hit the non-loop part + backend.set_event_idx(false); + backend + .handle_event( + 0, + EventSet::IN, + &[vring_request.clone(), vring_event.clone()], + 0, + ) + .unwrap(); + + // Hit the loop part + backend.set_event_idx(true); + backend + .handle_event( + 1, + EventSet::IN, + &[vring_request.clone(), vring_event.clone()], + 0, + ) + .unwrap(); + + // Hit the non-loop part + backend.set_event_idx(false); + backend + .handle_event(1, EventSet::IN, &[vring_request, vring_event], 0) + .unwrap(); + } +} From 5b0b8c3753203cababee4970045057096a2946d5 Mon Sep 17 00:00:00 2001 From: Milan Zamazal Date: Sat, 10 Jun 2023 18:02:53 +0200 Subject: [PATCH 22/40] scmi: Implement SCMI base protocol Implementation of the mandatory parts of the SCMI base protocol. This allows the daemon to communicate with the guest SCMI VIRTIO device, although not yet providing any useful functionality. Signed-off-by: Milan Zamazal --- Cargo.lock | 10 + crates/scmi/Cargo.toml | 1 + crates/scmi/src/scmi.rs | 476 +++++++++++++++++++++++++++++++++--- crates/scmi/src/vhu_scmi.rs | 123 ++++++++++ 4 files changed, 578 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3155b5f9f..7258ce15c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -550,6 +550,15 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.9" @@ -1251,6 +1260,7 @@ dependencies = [ "assert_matches", "clap", "env_logger", + "itertools", "log", "thiserror", "vhost", diff --git a/crates/scmi/Cargo.toml b/crates/scmi/Cargo.toml index b5e0a42f1..be2929fbe 100644 --- a/crates/scmi/Cargo.toml +++ b/crates/scmi/Cargo.toml @@ -12,6 +12,7 @@ edition = "2021" [dependencies] clap = { version = "4.3", features = ["derive"] } env_logger = "0.10" +itertools = "0.10" log = "0.4" thiserror = "1.0" vhost = { version = "0.8", features = ["vhost-user-slave"] } diff --git a/crates/scmi/src/scmi.rs b/crates/scmi/src/scmi.rs index 9429e0c94..d8fd96b5a 100644 --- a/crates/scmi/src/scmi.rs +++ b/crates/scmi/src/scmi.rs @@ -1,6 +1,9 @@ // SPDX-FileCopyrightText: Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 +use std::collections::HashMap; + +use itertools::Itertools; use log::debug; pub type MessageHeader = u32; @@ -8,7 +11,6 @@ pub type MessageHeader = u32; // VirtIO SCMI specification talks about u8 SCMI values. // Let's stick with SCMI specification for implementation simplicity. #[derive(Clone, Debug, PartialEq)] -#[allow(dead_code)] enum MessageValue { Signed(i32), Unsigned(u32), @@ -65,8 +67,15 @@ impl From for Response { } } +impl From for Response { + fn from(value: MessageValue) -> Self { + Self { + values: vec![ReturnStatus::Success.as_value(), value], + } + } +} + impl From<&MessageValues> for Response { - #[allow(dead_code)] fn from(value: &MessageValues) -> Self { let mut response_values = vec![ReturnStatus::Success.as_value()]; response_values.extend_from_slice(value.as_slice()); @@ -126,34 +135,6 @@ impl ScmiResponse { } } -pub struct ScmiHandler {} - -impl ScmiHandler { - pub const fn new() -> Self { - Self {} - } - - pub fn handle(&mut self, request: ScmiRequest) -> ScmiResponse { - let response = match request.message_type { - // TODO: Implement a mechanism to invoke the proper protocol & - // message handling on command requests. - MessageType::Command => Response::from(ReturnStatus::NotSupported), - MessageType::Unsupported => Response::from(ReturnStatus::NotSupported), - }; - ScmiResponse::from(request.header, response) - } - - pub fn number_of_parameters(&self, _request: &ScmiRequest) -> Option { - // TODO: Implement. - Some(0) - } - - pub fn store_parameters(&self, _request: &mut ScmiRequest, _buffer: &[u8]) { - // TODO: Implement (depends on knowledge of the number of parameters). - } -} - -#[allow(dead_code)] pub struct ScmiRequest { header: MessageHeader, // 32-bit unsigned integer, split below: message_id: MessageId, // bits 7:0 @@ -161,7 +142,7 @@ pub struct ScmiRequest { protocol_id: ProtocolId, // bits 17:10 // token: u16, // bits 27:18 // bits 31:28 are reserved, must be 0 - _parameters: Option, // set later based on the number of parameters + parameters: Option, // set later based on the number of parameters } impl ScmiRequest { @@ -185,7 +166,256 @@ impl ScmiRequest { message_id, message_type, protocol_id, - _parameters: None, + parameters: None, + } + } + + fn get_unsigned(&self, parameter: usize) -> u32 { + match self.parameters.as_ref().expect("Missing parameters")[parameter] { + MessageValue::Unsigned(value) => value, + _ => panic!("Wrong parameter"), + } + } +} + +const BASE_PROTOCOL_ID: ProtocolId = 0x10; +const BASE_VERSION: MessageId = 0x0; +const BASE_PROTOCOL_ATTRIBUTES: MessageId = 0x1; +const BASE_MESSAGE_ATTRIBUTES: MessageId = 0x2; +const BASE_DISCOVER_VENDOR: MessageId = 0x3; +const BASE_DISCOVER_IMPLEMENTATION_VERSION: MessageId = 0x5; +const BASE_DISCOVER_LIST_PROTOCOLS: MessageId = 0x6; + +enum ParameterType { + _SignedInt32, + UnsignedInt32, +} +type ParameterSpecification = Vec; + +type HandlerFunction = fn(&ScmiHandler, &ScmiRequest) -> Response; +struct HandlerInfo { + name: String, + parameters: ParameterSpecification, + function: HandlerFunction, +} + +// HandlerMap layout is suboptimal but let's prefer simplicity for now. +struct HandlerMap(HashMap<(ProtocolId, MessageId), HandlerInfo>); + +impl HandlerMap { + fn new() -> Self { + let mut map = Self(HashMap::new()); + map.make_base_handlers(); + map + } + + fn keys(&self) -> std::collections::hash_map::Keys<(u8, u8), HandlerInfo> { + self.0.keys() + } + + fn get(&self, protocol_id: ProtocolId, message_id: MessageId) -> Option<&HandlerInfo> { + self.0.get(&(protocol_id, message_id)) + } + + fn bind( + &mut self, + protocol_id: ProtocolId, + message_id: MessageId, + name: &str, + parameters: ParameterSpecification, + function: HandlerFunction, + ) { + assert!( + self.get(protocol_id, message_id).is_none(), + "Multiple handlers defined for SCMI message {}/{}", + protocol_id, + message_id + ); + self.0.insert( + (protocol_id, message_id), + HandlerInfo { + name: name.to_string(), + parameters, + function, + }, + ); + } + + fn make_base_handlers(&mut self) { + self.bind( + BASE_PROTOCOL_ID, + BASE_VERSION, + "base/version", + vec![], + |_, _| -> Response { + // 32-bit unsigned integer + // major: upper 16 bits + // minor: lower 16 bits + Response::from(MessageValue::Unsigned(0x20000)) + }, + ); + self.bind( + BASE_PROTOCOL_ID, + BASE_PROTOCOL_ATTRIBUTES, + "base/protocol_attributes", + vec![], + |handler, _| -> Response { + // The base protocol doesn't count. + Response::from(MessageValue::Unsigned(handler.number_of_protocols() - 1)) + }, + ); + self.bind( + BASE_PROTOCOL_ID, + BASE_MESSAGE_ATTRIBUTES, + "base/message_attributes", + vec![ParameterType::UnsignedInt32], + ScmiHandler::message_attributes, + ); + self.bind( + BASE_PROTOCOL_ID, + BASE_DISCOVER_VENDOR, + "base/discover_vendor", + vec![], + |_, _| -> Response { Response::from(MessageValue::String("rust-vmm".to_string(), 16)) }, + ); + self.bind( + BASE_PROTOCOL_ID, + BASE_DISCOVER_IMPLEMENTATION_VERSION, + "base/discover_implementation_version", + vec![], + |_, _| -> Response { Response::from(MessageValue::Unsigned(0)) }, + ); + self.bind( + BASE_PROTOCOL_ID, + BASE_DISCOVER_LIST_PROTOCOLS, + "base/discover_list_protocols", + vec![ParameterType::UnsignedInt32], + ScmiHandler::discover_list_protocols, + ); + } +} + +pub struct ScmiHandler { + handlers: HandlerMap, +} + +impl ScmiHandler { + pub fn new() -> Self { + Self { + handlers: HandlerMap::new(), + } + } + + fn request_handler(&self, request: &ScmiRequest) -> Option<&HandlerInfo> { + self.handlers.get(request.protocol_id, request.message_id) + } + + pub fn handle(&mut self, request: ScmiRequest) -> ScmiResponse { + let response = match request.message_type { + MessageType::Command => match self.request_handler(&request) { + Some(info) => { + debug!( + "Calling handler for {}({:?})", + info.name, + request.parameters.as_ref().unwrap_or(&vec![]) + ); + (info.function)(self, &request) + } + _ => Response::from(ReturnStatus::NotSupported), + }, + MessageType::Unsupported => Response::from(ReturnStatus::NotSupported), + }; + ScmiResponse::from(request.header, response) + } + + pub fn number_of_parameters(&self, request: &ScmiRequest) -> Option { + self.request_handler(request).map(|info| { + info.parameters + .len() + .try_into() + .expect("Invalid parameter specification") + }) + } + + pub fn store_parameters(&self, request: &mut ScmiRequest, buffer: &[u8]) { + let handler = &self + .request_handler(request) + .expect("Attempt to process an unsupported SCMI message"); + let n_parameters = handler.parameters.len(); + debug!( + "SCMI request {}/{} parameters length: {}, buffer length: {}", + request.message_id, + request.protocol_id, + n_parameters, + buffer.len() + ); + let value_size = 4; + assert!( + buffer.len() == n_parameters * value_size, + "Unexpected parameters buffer size: buffer={} parameters={}", + buffer.len(), + n_parameters + ); + let mut values: MessageValues = Vec::with_capacity(n_parameters); + for n in 0..n_parameters { + let slice: [u8; 4] = buffer[4 * n..4 * (n + 1)] + .try_into() + .expect("Insufficient data for parameters"); + let v = match handler.parameters[n] { + ParameterType::_SignedInt32 => MessageValue::Signed(i32::from_le_bytes(slice)), + ParameterType::UnsignedInt32 => MessageValue::Unsigned(u32::from_le_bytes(slice)), + }; + debug!("SCMI parameter {}: {:?}", n, v); + values.push(v); + } + request.parameters = Some(values); + } + + fn number_of_protocols(&self) -> u32 { + let n: usize = self.handlers.keys().unique_by(|k| k.0).count(); + n.try_into() + .expect("Impossibly large number of SCMI protocols") + } + + fn discover_list_protocols(&self, request: &ScmiRequest) -> Response { + // Base protocol is skipped + let skip: usize = request + .get_unsigned(0) + .try_into() + .expect("Extremely many protocols"); + let protocols: Vec = self + .handlers + .keys() + .filter(|(protocol_id, _)| *protocol_id != BASE_PROTOCOL_ID) + .map(|(protocol_id, _)| *protocol_id) + .unique() + .sorted() + .skip(skip) + .collect(); + let n_protocols = protocols.len(); + debug!("Number of listed protocols after {}: {}", skip, n_protocols); + let mut values: Vec = vec![MessageValue::Unsigned(n_protocols as u32)]; + if n_protocols > 0 { + let mut compressed: Vec = vec![0; 1 + (n_protocols - 1) / 4]; + for i in 0..n_protocols { + debug!("Adding protocol: {}", protocols[i]); + compressed[i % 4] |= u32::from(protocols[i]) << ((i % 4) * 8); + } + for item in compressed { + values.push(MessageValue::Unsigned(item)); + } + } + Response::from(&values) + } + + fn message_attributes(&self, request: &ScmiRequest) -> Response { + let message_id: Result = request.get_unsigned(0).try_into(); + if message_id.is_err() { + return Response::from(ReturnStatus::InvalidParameters); + } + match self.handlers.get(request.protocol_id, message_id.unwrap()) { + Some(_) => Response::from(MessageValue::Unsigned(0)), + None => Response::from(ReturnStatus::NotFound), } } } @@ -202,6 +432,16 @@ mod tests { assert_eq!(response.values[0], MessageValue::Signed(status as i32)); } + #[test] + fn test_response_from_value() { + let value = MessageValue::Unsigned(28); + let status = ReturnStatus::Success; + let response = Response::from(value.clone()); + assert_eq!(response.values.len(), 2); + assert_eq!(response.values[0], MessageValue::Signed(status as i32)); + assert_eq!(response.values[1], value); + } + #[test] fn test_response_from_values() { let status = ReturnStatus::Success; @@ -278,4 +518,176 @@ mod tests { assert_eq!(request.message_type, MessageType::Unsupported); assert_eq!(request.protocol_id, 0x40); } + + fn make_request(protocol_id: ProtocolId, message_id: MessageId) -> ScmiRequest { + let header: MessageHeader = u32::from(message_id) | (u32::from(protocol_id) << 10); + ScmiRequest::new(header) + } + + fn store_parameters( + handler: &ScmiHandler, + request: &mut ScmiRequest, + parameters: &[MessageValue], + ) { + let mut bytes: Vec = vec![]; + for p in parameters { + let value = match p { + MessageValue::Unsigned(n) => u32::to_le_bytes(*n), + MessageValue::Signed(n) => i32::to_le_bytes(*n), + _ => panic!("Unsupported parameter type"), + }; + bytes.append(&mut value.to_vec()); + } + handler.store_parameters(request, bytes.as_slice()); + } + + #[test] + fn test_handler_parameters() { + let handler = ScmiHandler::new(); + let mut request = make_request(BASE_PROTOCOL_ID, BASE_DISCOVER_LIST_PROTOCOLS); + assert_eq!(handler.number_of_parameters(&request), Some(1)); + + let value: u32 = 1234567890; + let parameters = [MessageValue::Unsigned(value)]; + store_parameters(&handler, &mut request, ¶meters); + assert_eq!(request.parameters, Some(parameters.to_vec())); + assert_eq!(request.get_unsigned(0), value); + } + + fn test_message( + protocol_id: ProtocolId, + message_id: MessageId, + parameters: Vec, + result_code: ReturnStatus, + result_values: Vec, + ) { + let mut handler = ScmiHandler::new(); + let mut request = make_request(protocol_id, message_id); + let header = request.header; + if !parameters.is_empty() { + let parameter_slice = parameters.as_slice(); + store_parameters(&handler, &mut request, parameter_slice); + } + let response = handler.handle(request); + assert_eq!(response.header, header); + let mut bytes: Vec = vec![]; + bytes.append(&mut header.to_le_bytes().to_vec()); + bytes.append(&mut (result_code as i32).to_le_bytes().to_vec()); + for value in result_values { + let mut value_vec = match value { + MessageValue::Unsigned(n) => n.to_le_bytes().to_vec(), + MessageValue::Signed(n) => n.to_le_bytes().to_vec(), + MessageValue::String(s, size) => { + let mut v = s.as_bytes().to_vec(); + let v_len = v.len(); + assert!( + v_len < size, + "String longer than specified: {v_len} >= {size}" + ); + v.resize(size, b'\0'); + v + } + }; + bytes.append(&mut value_vec); + } + assert_eq!(response.ret_bytes, bytes.as_slice()); + } + + #[test] + fn test_base_version() { + let values = vec![MessageValue::Unsigned(0x20000)]; + test_message( + BASE_PROTOCOL_ID, + BASE_VERSION, + vec![], + ReturnStatus::Success, + values, + ); + } + + #[test] + fn test_base_protocol_attributes() { + let result = vec![MessageValue::Unsigned(0)]; + test_message( + BASE_PROTOCOL_ID, + BASE_PROTOCOL_ATTRIBUTES, + vec![], + ReturnStatus::Success, + result, + ); + } + + #[test] + fn test_base_protocol_message_attributes_supported() { + let parameters = vec![MessageValue::Unsigned(u32::from(BASE_DISCOVER_VENDOR))]; + let result = vec![MessageValue::Unsigned(0)]; + test_message( + BASE_PROTOCOL_ID, + BASE_MESSAGE_ATTRIBUTES, + parameters, + ReturnStatus::Success, + result, + ); + } + + #[test] + fn test_base_protocol_message_attributes_unsupported() { + let parameters = vec![MessageValue::Unsigned(0x4)]; + test_message( + BASE_PROTOCOL_ID, + BASE_MESSAGE_ATTRIBUTES, + parameters, + ReturnStatus::NotFound, + vec![], + ); + } + + #[test] + fn test_base_protocol_message_attributes_invalid() { + let parameters = vec![MessageValue::Unsigned(0x100)]; + test_message( + BASE_PROTOCOL_ID, + BASE_MESSAGE_ATTRIBUTES, + parameters, + ReturnStatus::InvalidParameters, + vec![], + ); + } + + #[test] + fn test_base_discover_vendor() { + let result = vec![MessageValue::String(String::from("rust-vmm"), 16)]; + test_message( + BASE_PROTOCOL_ID, + BASE_DISCOVER_VENDOR, + vec![], + ReturnStatus::Success, + result, + ); + } + + #[test] + fn test_base_discover_implementation_version() { + let values = vec![MessageValue::Unsigned(0)]; + test_message( + BASE_PROTOCOL_ID, + BASE_DISCOVER_IMPLEMENTATION_VERSION, + vec![], + ReturnStatus::Success, + values, + ); + } + + #[test] + fn test_base_discover_list_protocols() { + let parameters = vec![MessageValue::Unsigned(0)]; + let result = vec![MessageValue::Unsigned(0)]; + test_message( + BASE_PROTOCOL_ID, + BASE_DISCOVER_LIST_PROTOCOLS, + parameters, + ReturnStatus::Success, + result, + ); + } } diff --git a/crates/scmi/src/vhu_scmi.rs b/crates/scmi/src/vhu_scmi.rs index 0128666fa..e3f37b522 100644 --- a/crates/scmi/src/vhu_scmi.rs +++ b/crates/scmi/src/vhu_scmi.rs @@ -412,6 +412,54 @@ mod tests { use super::*; + fn scmi_header(message_id: u8, protocol_id: u8) -> u32 { + u32::from(message_id) | u32::from(protocol_id) << 10 + } + + fn build_cmd_desc_chain( + protocol_id: u8, + message_id: u8, + parameters: Vec, + ) -> ScmiDescriptorChain { + let mem = &GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(); + let vq = MockSplitQueue::new(mem, 16); + let mut next_addr = vq.desc_table().total_size() + 0x100; + let mut index = 0; + let request_size: u32 = (4 + parameters.len() * 4) as u32; + + // Descriptor for the SCMI request + let desc_request = + Descriptor::new(next_addr, request_size, VRING_DESC_F_NEXT as u16, index + 1); + let mut bytes: Vec = vec![]; + bytes.append(&mut scmi_header(message_id, protocol_id).to_le_bytes().to_vec()); + for p in parameters { + bytes.append(&mut p.to_le_bytes().to_vec()); + } + mem.write_slice(bytes.as_slice(), desc_request.addr()) + .unwrap(); + vq.desc_table().store(index, desc_request).unwrap(); + next_addr += u64::from(desc_request.len()); + index += 1; + + // Descriptor for the SCMI response + let desc_response = Descriptor::new(next_addr, 0x100, VRING_DESC_F_WRITE as u16, 0); + vq.desc_table().store(index, desc_response).unwrap(); + + // Put the descriptor index 0 in the first available ring position. + mem.write_obj(0u16, vq.avail_addr().unchecked_add(4)) + .unwrap(); + // Set `avail_idx` to 1. + mem.write_obj(1u16, vq.avail_addr().unchecked_add(2)) + .unwrap(); + // Create descriptor chain from pre-filled memory. + vq.create_queue::() + .unwrap() + .iter(GuestMemoryAtomic::new(mem.clone()).memory()) + .unwrap() + .next() + .unwrap() + } + fn build_event_desc_chain() -> ScmiDescriptorChain { let mem = &GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(); let vq = MockSplitQueue::new(mem, 16); @@ -476,6 +524,63 @@ mod tests { .unwrap() } + fn validate_desc_chains( + desc_chains: &[ScmiDescriptorChain], + chain_index: usize, + protocol_id: u8, + message_id: u8, + status: i32, + data: Vec, + ) { + let desc_chain = &desc_chains[chain_index]; + let descriptors: Vec<_> = desc_chain.clone().collect(); + let mut response = vec![0; descriptors[1].len() as usize]; + + desc_chain + .memory() + .read(&mut response, descriptors[1].addr()) + .unwrap(); + + let mut result: Vec = scmi_header(message_id, protocol_id).to_le_bytes().to_vec(); + result.append(&mut status.to_le_bytes().to_vec()); + for d in &data { + result.append(&mut d.to_le_bytes().to_vec()); + } + assert_eq!(response[0..result.len()], result); + } + + #[test] + fn test_process_requests() { + let mut backend = VuScmiBackend::new().unwrap(); + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(), + ); + let vring = VringRwLock::new(mem, 0x1000).unwrap(); + + // Descriptor chain size zero, shouldn't fail + backend + .process_requests(Vec::::new(), &vring) + .unwrap(); + + // Valid single SCMI request: base protocol version + let desc_chains = vec![build_cmd_desc_chain(0x10, 0x0, vec![])]; + backend + .process_requests(desc_chains.clone(), &vring) + .unwrap(); + validate_desc_chains(&desc_chains, 0, 0x10, 0x0, 0, vec![0x20000]); + + // Valid multi SCMI request: base protocol version + implementation version + let desc_chains = vec![ + build_cmd_desc_chain(0x10, 0x0, vec![]), + build_cmd_desc_chain(0x10, 0x5, vec![]), + ]; + backend + .process_requests(desc_chains.clone(), &vring) + .unwrap(); + validate_desc_chains(&desc_chains, 0, 0x10, 0x0, 0, vec![0x20000]); + validate_desc_chains(&desc_chains, 1, 0x10, 0x5, 0, vec![0]); + } + #[test] fn test_process_requests_failure() { let mut backend = VuScmiBackend::new().unwrap(); @@ -566,6 +671,24 @@ mod tests { VuScmiError::UnexpectedMinimumDescriptorSize(4, 2) ); + // 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) + ); + + // 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) + ); + // Read only descriptors. let p = DescParameters { addr: None, From bc7faf7685725f3597893b6e0488e349aa7b274f Mon Sep 17 00:00:00 2001 From: Milan Zamazal Date: Thu, 29 Jun 2023 18:16:50 +0200 Subject: [PATCH 23/40] scmi: Add sensor management protocol This patch implements the necessary parts of the SCMI sensor management protocol, required either by the SCMI standard or by Linux VIRTIO SCMI drivers to function correctly. A part of this implementation is a fake sensor device, which is useful for both unit testing here and a testing with a real guest OS. Signed-off-by: Milan Zamazal --- crates/scmi/README.md | 18 +- crates/scmi/src/devices.rs | 119 +++++++ crates/scmi/src/main.rs | 1 + crates/scmi/src/scmi.rs | 619 +++++++++++++++++++++++++++++++++++-- 4 files changed, 731 insertions(+), 26 deletions(-) create mode 100644 crates/scmi/src/devices.rs diff --git a/crates/scmi/README.md b/crates/scmi/README.md index b555e7bcd..49f1b70f9 100644 --- a/crates/scmi/README.md +++ b/crates/scmi/README.md @@ -8,14 +8,6 @@ It is tested with QEMU's `-device vhost-user-scmi-pci` but should work with any virtual machine monitor (VMM) that supports vhost-user. See the Examples section below. -The currently supported SCMI protocols are: - -- base protocol - -The currently supported SCMI connections on the host are: - -- none - ## Synopsis **vhost-device-scmi** [*OPTIONS*] @@ -56,6 +48,16 @@ use to communicate as well as share the guests memory over a memfd: -object memory-backend-file,id=mem,size=4G,mem-path=/dev/shm,share=on \ ... +## Supported SCMI protocols + +The currently supported SCMI protocols are: + +- base +- sensor management + +Basically only the mandatory and necessary parts of the protocols are +implemented. + ## License This project is licensed under either of diff --git a/crates/scmi/src/devices.rs b/crates/scmi/src/devices.rs new file mode 100644 index 000000000..174aa39da --- /dev/null +++ b/crates/scmi/src/devices.rs @@ -0,0 +1,119 @@ +// SPDX-FileCopyrightText: Red Hat, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use log::debug; + +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, +}; + +pub struct FakeSensor { + enabled: bool, + value: u8, + name: String, +} + +impl FakeSensor { + const NUMBER_OF_AXES: u32 = 3; + + pub const fn new(name: String) -> Self { + Self { + enabled: false, + value: 0, + name, + } + } +} + +impl ScmiDevice for FakeSensor { + fn protocol(&self) -> ProtocolId { + SENSOR_PROTOCOL_ID + } + + fn handle(&mut self, message_id: MessageId, parameters: &[MessageValue]) -> DeviceResult { + match message_id { + SENSOR_DESCRIPTION_GET => { + // Continuous update required by Linux SCMI IIO driver + let low = 1 << 30; + let high = Self::NUMBER_OF_AXES << 16 | 1 << 8; + let name = self.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) + } + SENSOR_AXIS_DESCRIPTION_GET => { + let axis_desc_index = parameters[0].get_unsigned(); + if axis_desc_index >= Self::NUMBER_OF_AXES { + return Result::Err(ScmiDeviceError::InvalidParameters); + } + let mut values = vec![MessageValue::Unsigned( + Self::NUMBER_OF_AXES - axis_desc_index, + )]; + for i in axis_desc_index..Self::NUMBER_OF_AXES { + values.push(MessageValue::Unsigned(i)); // axis id + values.push(MessageValue::Unsigned(0)); // attributes low + + // 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). + values.push(MessageValue::Unsigned( + SENSOR_UNIT_METERS_PER_SECOND_SQUARED, + )); // attributes high + + // Name in the recommended format, 16 bytes: + let axis = match i { + 0 => 'X', + 1 => 'Y', + 2 => 'Z', + _ => 'N', // shouldn't be reached currently + }; + values.push(MessageValue::String(format!("acc_{axis}").to_string(), 16)); + } + Ok(values) + } + SENSOR_CONFIG_GET => { + let config = u32::from(self.enabled); + Ok(vec![MessageValue::Unsigned(config)]) + } + SENSOR_CONFIG_SET => { + let config = parameters[0].get_unsigned(); + if config & 0xFFFFFFFE != 0 { + return Result::Err(ScmiDeviceError::UnsupportedRequest); + } + self.enabled = config != 0; + debug!("Sensor enabled: {}", self.enabled); + Ok(vec![]) + } + 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.enabled { + return Result::Err(ScmiDeviceError::NotEnabled); + } + 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) + } + _ => Result::Err(ScmiDeviceError::UnsupportedRequest), + } + } +} diff --git a/crates/scmi/src/main.rs b/crates/scmi/src/main.rs index 02b8e2051..2cf890633 100644 --- a/crates/scmi/src/main.rs +++ b/crates/scmi/src/main.rs @@ -4,6 +4,7 @@ mod scmi; mod vhu_scmi; +mod devices; use std::process::exit; use std::sync::{Arc, RwLock}; diff --git a/crates/scmi/src/scmi.rs b/crates/scmi/src/scmi.rs index d8fd96b5a..e168654fc 100644 --- a/crates/scmi/src/scmi.rs +++ b/crates/scmi/src/scmi.rs @@ -1,22 +1,40 @@ // SPDX-FileCopyrightText: Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 -use std::collections::HashMap; +use std::{ + cmp::min, + collections::HashMap, + sync::{Arc, Mutex}, +}; use itertools::Itertools; -use log::debug; +use log::{debug, error, info}; +use thiserror::Error as ThisError; pub type MessageHeader = u32; + +pub const MAX_SIMPLE_STRING_LENGTH: usize = 16; // incl. NULL terminator + // SCMI specification talks about Le32 parameter and return values. // VirtIO SCMI specification talks about u8 SCMI values. // Let's stick with SCMI specification for implementation simplicity. -#[derive(Clone, Debug, PartialEq)] -enum MessageValue { +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum MessageValue { Signed(i32), Unsigned(u32), String(String, usize), // string, expected characters } -type MessageValues = Vec; + +impl MessageValue { + pub(crate) fn get_unsigned(&self) -> u32 { + match self { + Self::Unsigned(value) => *value, + _ => panic!("Wrong parameter"), + } + } +} + +pub type MessageValues = Vec; #[derive(Debug, PartialEq)] enum MessageType { @@ -24,8 +42,8 @@ enum MessageType { Command, // 0 Unsupported, // anything else } -type MessageId = u8; -type ProtocolId = u8; +pub type MessageId = u8; +pub type ProtocolId = u8; type NParameters = u8; #[derive(Clone, Copy)] @@ -171,10 +189,11 @@ impl ScmiRequest { } fn get_unsigned(&self, parameter: usize) -> u32 { - match self.parameters.as_ref().expect("Missing parameters")[parameter] { - MessageValue::Unsigned(value) => value, - _ => panic!("Wrong parameter"), - } + self.parameters.as_ref().expect("Missing parameters")[parameter].get_unsigned() + } + + fn get_usize(&self, parameter: usize) -> usize { + self.get_unsigned(parameter) as usize } } @@ -186,6 +205,19 @@ const BASE_DISCOVER_VENDOR: MessageId = 0x3; const BASE_DISCOVER_IMPLEMENTATION_VERSION: MessageId = 0x5; const BASE_DISCOVER_LIST_PROTOCOLS: MessageId = 0x6; +pub const SENSOR_PROTOCOL_ID: ProtocolId = 0x15; +const SENSOR_VERSION: MessageId = 0x0; +const SENSOR_ATTRIBUTES: MessageId = 0x1; +const SENSOR_MESSAGE_ATTRIBUTES: MessageId = 0x2; +pub const SENSOR_DESCRIPTION_GET: MessageId = 0x3; +pub const SENSOR_READING_GET: MessageId = 0x6; +pub const SENSOR_AXIS_DESCRIPTION_GET: MessageId = 0x7; +pub const SENSOR_CONFIG_GET: MessageId = 0x9; +pub const SENSOR_CONFIG_SET: MessageId = 0xA; +pub const SENSOR_CONTINUOUS_UPDATE_NOTIFY: MessageId = 0xB; + +pub const SENSOR_UNIT_METERS_PER_SECOND_SQUARED: u32 = 89; + enum ParameterType { _SignedInt32, UnsignedInt32, @@ -206,6 +238,7 @@ impl HandlerMap { fn new() -> Self { let mut map = Self(HashMap::new()); map.make_base_handlers(); + map.make_sensor_handlers(); map } @@ -293,16 +326,227 @@ impl HandlerMap { ScmiHandler::discover_list_protocols, ); } + + fn make_sensor_handlers(&mut self) { + self.bind( + SENSOR_PROTOCOL_ID, + SENSOR_VERSION, + "sensor/version", + vec![], + |_, _| -> Response { + // 32-bit unsigned integer + // major: upper 16 bits + // minor: lower 16 bits + Response::from(MessageValue::Unsigned(0x30000)) + }, + ); + + self.bind( + SENSOR_PROTOCOL_ID, + SENSOR_ATTRIBUTES, + "sensor/attributes", + vec![], + |handler: &ScmiHandler, _| -> Response { + let n_sensors = u32::from(handler.devices.number_of_devices(SENSOR_PROTOCOL_ID)); + let values: MessageValues = vec![ + MessageValue::Unsigned(n_sensors), // # of sensors, no async commands + MessageValue::Unsigned(0), // lower shared memory address -- not supported + MessageValue::Unsigned(0), // higer shared memory address -- not supported + MessageValue::Unsigned(0), // length of shared memory -- not supported + ]; + Response::from(&values) + }, + ); + + self.bind( + SENSOR_PROTOCOL_ID, + SENSOR_MESSAGE_ATTRIBUTES, + "sensor/message_attributes", + vec![ParameterType::UnsignedInt32], + ScmiHandler::message_attributes, + ); + + self.bind( + SENSOR_PROTOCOL_ID, + SENSOR_DESCRIPTION_GET, + "sensor/description_get", + vec![ParameterType::UnsignedInt32], + |handler: &ScmiHandler, request: &ScmiRequest| -> Response { + let first_index = request.get_usize(0); + let n_sensors = handler.devices.number_of_devices(SENSOR_PROTOCOL_ID) as usize; + if first_index >= n_sensors { + return Response::from(ReturnStatus::InvalidParameters); + } + // Let's use something reasonable to fit into the available VIRTIO buffers: + let max_sensors_to_return = 256; + let sensors_to_return = min(n_sensors - first_index, max_sensors_to_return); + let last_non_returned_sensor = first_index + sensors_to_return; + let remaining_sensors = if n_sensors > last_non_returned_sensor { + n_sensors - last_non_returned_sensor + } else { + 0 + }; + let mut values = vec![MessageValue::Unsigned( + sensors_to_return as u32 | (remaining_sensors as u32) << 16, + )]; + for index in first_index..last_non_returned_sensor { + values.push(MessageValue::Unsigned(index as u32)); + let result = handler.handle_device( + index, + SENSOR_PROTOCOL_ID, + SENSOR_DESCRIPTION_GET, + &[], + ); + if result.is_err() { + return handler.device_response(result, index); + } + let mut sensor_values = result.unwrap(); + values.append(&mut sensor_values); + } + Response::from(&values) + }, + ); + + self.bind( + SENSOR_PROTOCOL_ID, + SENSOR_READING_GET, + "sensor/reading_get", + vec![ParameterType::UnsignedInt32, ParameterType::UnsignedInt32], + |handler: &ScmiHandler, request: &ScmiRequest| -> Response { + // Check flags + if request.get_unsigned(1) != 0 { + // Asynchronous reporting not supported + return Response::from(ReturnStatus::NotSupported); + } + handler.handle_device_response(request, &[]) + }, + ); + + self.bind( + SENSOR_PROTOCOL_ID, + SENSOR_AXIS_DESCRIPTION_GET, + "sensor/axis_description_get", + vec![ParameterType::UnsignedInt32, ParameterType::UnsignedInt32], + |handler: &ScmiHandler, request: &ScmiRequest| -> Response { + handler.handle_device_response(request, &[1]) + }, + ); + + self.bind( + SENSOR_PROTOCOL_ID, + SENSOR_CONFIG_GET, + "sensor/config_get", + vec![ParameterType::UnsignedInt32], + |handler: &ScmiHandler, request: &ScmiRequest| -> Response { + handler.handle_device_response(request, &[]) + }, + ); + + self.bind( + SENSOR_PROTOCOL_ID, + SENSOR_CONFIG_SET, + "sensor/config_set", + vec![ParameterType::UnsignedInt32, ParameterType::UnsignedInt32], + |handler: &ScmiHandler, request: &ScmiRequest| -> Response { + handler.handle_device_response(request, &[1]) + }, + ); + + // Linux VIRTIO SCMI seems to insist on presence of this: + self.bind( + SENSOR_PROTOCOL_ID, + SENSOR_CONTINUOUS_UPDATE_NOTIFY, + "sensor/continuous_update_notify", + vec![ParameterType::UnsignedInt32, ParameterType::UnsignedInt32], + |handler: &ScmiHandler, request: &ScmiRequest| -> Response { + handler.handle_device_response(request, &[1]) + }, + ); + } +} + +#[derive(Debug, PartialEq, Eq, ThisError)] +pub enum ScmiDeviceError { + #[error("Invalid parameters")] + InvalidParameters, + #[error("No such device")] + NoSuchDevice, + #[error("Device not enabled")] + NotEnabled, + #[error("Unsupported request")] + UnsupportedRequest, +} + +pub trait ScmiDevice: Send { + fn protocol(&self) -> ProtocolId; + fn handle( + &mut self, + message_id: MessageId, + parameters: &[MessageValue], + ) -> Result; } +type DeviceList = Vec>; + +struct DeviceMap(Arc>>); + +impl DeviceMap { + fn new() -> Self { + Self(Arc::new(Mutex::new(HashMap::new()))) + } + + // This is the maximum number of the remaining sensors + // SENSOR_DESCRIPTION_GET supports -- the upper 16 bits of the response. + const MAX_NUMBER_OF_PROTOCOL_DEVICES: usize = 0xFFFF; + + fn insert(&mut self, device: Box) { + let mut device_map = self.0.lock().unwrap(); + let devices = device_map.entry(device.protocol()).or_default(); + if devices.len() >= Self::MAX_NUMBER_OF_PROTOCOL_DEVICES { + panic!( + "Too many devices defined for protocol {}", + device.protocol() + ); + } + devices.push(device); + } + + fn number_of_devices(&self, protocol_id: ProtocolId) -> u16 { + match self.0.lock().unwrap().get(&protocol_id) { + Some(devices) => devices.len() as u16, + None => 0, + } + } + + fn handle( + &self, + device_index: usize, + protocol_id: ProtocolId, + message_id: MessageId, + parameters: &[MessageValue], + ) -> Result { + match self.0.lock().unwrap().get_mut(&protocol_id) { + Some(devices) => match devices.get_mut(device_index) { + Some(device) => device.handle(message_id, parameters), + None => Result::Err(ScmiDeviceError::NoSuchDevice), + }, + None => Result::Err(ScmiDeviceError::NoSuchDevice), + } + } +} + +pub type DeviceResult = Result; + pub struct ScmiHandler { handlers: HandlerMap, + devices: DeviceMap, } impl ScmiHandler { pub fn new() -> Self { Self { handlers: HandlerMap::new(), + devices: DeviceMap::new(), } } @@ -377,6 +621,56 @@ impl ScmiHandler { .expect("Impossibly large number of SCMI protocols") } + pub fn register_device(&mut self, device: Box) { + self.devices.insert(device); + } + + fn handle_device( + &self, + device_index: usize, + protocol_id: ProtocolId, + message_id: MessageId, + parameters: &[MessageValue], + ) -> DeviceResult { + self.devices + .handle(device_index, protocol_id, message_id, parameters) + } + + fn device_response(&self, result: DeviceResult, device_index: usize) -> Response { + match result { + Ok(values) => Response::from(&values), + Err(error) => match error { + ScmiDeviceError::NoSuchDevice + | ScmiDeviceError::NotEnabled + | ScmiDeviceError::InvalidParameters => { + info!("Invalid device access: {}, {}", device_index, error); + Response::from(ReturnStatus::InvalidParameters) + } + ScmiDeviceError::UnsupportedRequest => { + info!("Unsupported request for {}", device_index); + Response::from(ReturnStatus::NotSupported) + } + }, + } + } + + fn handle_device_response(&self, request: &ScmiRequest, parameters: &[usize]) -> Response { + let device_index = request.get_usize(0); + let protocol_id = request.protocol_id; + let message_id = request.message_id; + let parameter_values: Vec = parameters + .iter() + .map(|i| MessageValue::Unsigned(request.get_unsigned(*i))) + .collect(); + let result = self.handle_device( + device_index, + protocol_id, + message_id, + parameter_values.as_slice(), + ); + self.device_response(result, device_index) + } + fn discover_list_protocols(&self, request: &ScmiRequest) -> Response { // Base protocol is skipped let skip: usize = request @@ -422,6 +716,8 @@ impl ScmiHandler { #[cfg(test)] mod tests { + use crate::devices; + use super::*; #[test] @@ -448,7 +744,7 @@ mod tests { let values = vec![ MessageValue::Signed(-2), MessageValue::Unsigned(8), - MessageValue::String("foo".to_owned(), 16), + MessageValue::String("foo".to_owned(), MAX_SIMPLE_STRING_LENGTH), ]; let len = values.len() + 1; let response = Response::from(&values); @@ -463,7 +759,7 @@ mod tests { let values = vec![ MessageValue::Signed(-2), MessageValue::Unsigned(800_000_000), - MessageValue::String("foo".to_owned(), 16), + MessageValue::String("foo".to_owned(), MAX_SIMPLE_STRING_LENGTH), ]; let response = Response::from(&values); ScmiResponse::from(header, response) @@ -554,6 +850,22 @@ mod tests { assert_eq!(request.get_unsigned(0), value); } + #[test] + fn test_unsupported_parameters() { + let handler = ScmiHandler::new(); + let request = make_request(BASE_PROTOCOL_ID, 0x4); + assert_eq!(handler.number_of_parameters(&request), None); + } + + fn make_handler() -> ScmiHandler { + let mut handler = ScmiHandler::new(); + for i in 0..2 { + let fake_sensor = Box::new(devices::FakeSensor::new(format!("fake{i}"))); + handler.register_device(fake_sensor); + } + handler + } + fn test_message( protocol_id: ProtocolId, message_id: MessageId, @@ -561,13 +873,32 @@ mod tests { result_code: ReturnStatus, result_values: Vec, ) { - let mut handler = ScmiHandler::new(); + let mut handler = make_handler(); + test_message_with_handler( + protocol_id, + message_id, + parameters, + result_code, + result_values, + &mut handler, + ); + } + + fn test_message_with_handler( + protocol_id: ProtocolId, + message_id: MessageId, + parameters: Vec, + result_code: ReturnStatus, + result_values: Vec, + handler: &mut ScmiHandler, + ) { let mut request = make_request(protocol_id, message_id); let header = request.header; if !parameters.is_empty() { let parameter_slice = parameters.as_slice(); - store_parameters(&handler, &mut request, parameter_slice); + store_parameters(handler, &mut request, parameter_slice); } + let response = handler.handle(request); assert_eq!(response.header, header); let mut bytes: Vec = vec![]; @@ -607,7 +938,7 @@ mod tests { #[test] fn test_base_protocol_attributes() { - let result = vec![MessageValue::Unsigned(0)]; + let result = vec![MessageValue::Unsigned(1)]; test_message( BASE_PROTOCOL_ID, BASE_PROTOCOL_ATTRIBUTES, @@ -656,7 +987,10 @@ mod tests { #[test] fn test_base_discover_vendor() { - let result = vec![MessageValue::String(String::from("rust-vmm"), 16)]; + let result = vec![MessageValue::String( + "rust-vmm".to_owned(), + MAX_SIMPLE_STRING_LENGTH, + )]; test_message( BASE_PROTOCOL_ID, BASE_DISCOVER_VENDOR, @@ -681,7 +1015,7 @@ mod tests { #[test] fn test_base_discover_list_protocols() { let parameters = vec![MessageValue::Unsigned(0)]; - let result = vec![MessageValue::Unsigned(0)]; + let result = vec![MessageValue::Unsigned(1), MessageValue::Unsigned(21)]; test_message( BASE_PROTOCOL_ID, BASE_DISCOVER_LIST_PROTOCOLS, @@ -690,4 +1024,253 @@ mod tests { result, ); } + + #[test] + fn test_sensor_version() { + let values = vec![MessageValue::Unsigned(0x30000)]; + test_message( + SENSOR_PROTOCOL_ID, + SENSOR_VERSION, + vec![], + ReturnStatus::Success, + values, + ); + } + + #[test] + fn test_sensor_attributes() { + let result = vec![ + MessageValue::Unsigned(2), + MessageValue::Unsigned(0), + MessageValue::Unsigned(0), + MessageValue::Unsigned(0), + ]; + test_message( + SENSOR_PROTOCOL_ID, + SENSOR_ATTRIBUTES, + vec![], + ReturnStatus::Success, + result, + ); + } + + #[test] + fn test_sensor_message_attributes_supported() { + let parameters = vec![MessageValue::Unsigned(u32::from(SENSOR_DESCRIPTION_GET))]; + let result = vec![MessageValue::Unsigned(0)]; + test_message( + SENSOR_PROTOCOL_ID, + SENSOR_MESSAGE_ATTRIBUTES, + parameters, + ReturnStatus::Success, + result, + ); + } + + #[test] + fn test_sensor_message_attributes_unsupported() { + let parameters = vec![MessageValue::Unsigned(0x5)]; + test_message( + SENSOR_PROTOCOL_ID, + SENSOR_MESSAGE_ATTRIBUTES, + parameters, + ReturnStatus::NotFound, + vec![], + ); + } + + #[test] + fn test_sensor_protocol_message_attributes_invalid() { + let parameters = vec![MessageValue::Unsigned(0x100)]; + test_message( + SENSOR_PROTOCOL_ID, + SENSOR_MESSAGE_ATTRIBUTES, + parameters, + ReturnStatus::InvalidParameters, + vec![], + ); + } + + fn check_sensor_description(sensor_index: u32) { + let n_sensors = 2; + let parameters = vec![MessageValue::Unsigned(sensor_index)]; + let mut result = vec![MessageValue::Unsigned(n_sensors - sensor_index)]; + for i in sensor_index..n_sensors { + let mut description = vec![ + MessageValue::Unsigned(i), + MessageValue::Unsigned(1 << 30), + MessageValue::Unsigned(3 << 16 | 1 << 8), + MessageValue::String(format!("fake{i}"), MAX_SIMPLE_STRING_LENGTH), + ]; + result.append(&mut description); + } + test_message( + SENSOR_PROTOCOL_ID, + SENSOR_DESCRIPTION_GET, + parameters, + ReturnStatus::Success, + result, + ); + } + + #[test] + fn test_sensor_description_get() { + check_sensor_description(0); + check_sensor_description(1); + } + + #[test] + fn test_sensor_description_get_invalid() { + let parameters = vec![MessageValue::Unsigned(2)]; + test_message( + SENSOR_PROTOCOL_ID, + SENSOR_DESCRIPTION_GET, + parameters, + ReturnStatus::InvalidParameters, + vec![], + ); + } + + fn check_sensor_axis_description(axis_index: u32) { + let n_axes = 3; + let parameters = vec![ + MessageValue::Unsigned(0), + MessageValue::Unsigned(axis_index), + ]; + let mut result = vec![MessageValue::Unsigned(n_axes - axis_index)]; + for i in axis_index..n_axes { + let name = format!("acc_{}", char::from_u32('X' as u32 + i).unwrap()).to_string(); + let mut description = vec![ + MessageValue::Unsigned(i), + MessageValue::Unsigned(0), + MessageValue::Unsigned(SENSOR_UNIT_METERS_PER_SECOND_SQUARED), + MessageValue::String(name, MAX_SIMPLE_STRING_LENGTH), + ]; + result.append(&mut description); + } + test_message( + SENSOR_PROTOCOL_ID, + SENSOR_AXIS_DESCRIPTION_GET, + parameters, + ReturnStatus::Success, + result, + ); + } + + #[test] + fn test_sensor_axis_description_get() { + check_sensor_axis_description(0); + check_sensor_axis_description(1); + check_sensor_axis_description(2); + } + + #[test] + fn test_sensor_axis_description_get_invalid() { + let parameters = vec![MessageValue::Unsigned(0), MessageValue::Unsigned(3)]; + test_message( + SENSOR_PROTOCOL_ID, + SENSOR_AXIS_DESCRIPTION_GET, + parameters, + ReturnStatus::InvalidParameters, + vec![], + ); + } + + fn check_enabled(sensor: u32, enabled: bool, handler: &mut ScmiHandler) { + let enabled_flag = u32::from(enabled); + let parameters = vec![MessageValue::Unsigned(sensor)]; + let result = vec![MessageValue::Unsigned(enabled_flag)]; + test_message_with_handler( + SENSOR_PROTOCOL_ID, + SENSOR_CONFIG_GET, + parameters, + ReturnStatus::Success, + result, + handler, + ); + } + + #[test] + fn test_sensor_config_get() { + let mut handler = make_handler(); + check_enabled(0, false, &mut handler); + } + + fn enable_sensor(sensor: u32, enable: bool, handler: &mut ScmiHandler) { + let enable_flag = u32::from(enable); + let parameters = vec![ + MessageValue::Unsigned(sensor), + MessageValue::Unsigned(enable_flag), + ]; + let result = vec![]; + test_message_with_handler( + SENSOR_PROTOCOL_ID, + SENSOR_CONFIG_SET, + parameters, + ReturnStatus::Success, + result, + handler, + ); + } + + #[test] + fn test_sensor_config_set() { + let mut handler = make_handler(); + enable_sensor(0, true, &mut handler); + check_enabled(0, true, &mut handler); + check_enabled(1, false, &mut handler); + enable_sensor(1, true, &mut handler); + check_enabled(1, true, &mut handler); + enable_sensor(0, true, &mut handler); + check_enabled(0, true, &mut handler); + enable_sensor(0, false, &mut handler); + check_enabled(0, false, &mut handler); + } + + #[test] + fn test_sensor_config_set_invalid() { + let parameters = vec![MessageValue::Unsigned(0), MessageValue::Unsigned(3)]; + test_message( + SENSOR_PROTOCOL_ID, + SENSOR_CONFIG_SET, + parameters, + ReturnStatus::NotSupported, + vec![], + ); + } + + #[test] + fn test_sensor_reading_get() { + let mut handler = make_handler(); + for sensor in 0..2 { + enable_sensor(sensor, true, &mut handler); + } + for iteration in 0..2 { + for sensor in 0..2 { + let parameters = vec![MessageValue::Unsigned(sensor), MessageValue::Unsigned(0)]; + let result = vec![ + MessageValue::Unsigned(iteration), + MessageValue::Unsigned(0), + MessageValue::Unsigned(0), + MessageValue::Unsigned(0), + MessageValue::Unsigned(iteration + 100), + MessageValue::Unsigned(0), + MessageValue::Unsigned(0), + MessageValue::Unsigned(0), + MessageValue::Unsigned(iteration + 200), + MessageValue::Unsigned(0), + MessageValue::Unsigned(0), + MessageValue::Unsigned(0), + ]; + test_message_with_handler( + SENSOR_PROTOCOL_ID, + SENSOR_READING_GET, + parameters, + ReturnStatus::Success, + result, + &mut handler, + ); + } + } + } } From 3d1cc5b37000fda723e378846fb7c23fb2d0315a Mon Sep 17 00:00:00 2001 From: Milan Zamazal Date: Fri, 30 Jun 2023 18:00:28 +0200 Subject: [PATCH 24/40] scmi: Add a command line option for defining devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The code already contains support for creating devices that can serve as SCMI-accessible sensors and a sample fake devices. But to actually use the device, the code must be modified. This patch adds a command line option to define the devices on start. The format of the option value is in the QEMU style: DEVICE,PROPERTY=VALUE,… For example: --device fake,name=fake1 fake,name=fake2 Signed-off-by: Milan Zamazal --- crates/scmi/README.md | 9 +++- crates/scmi/src/devices.rs | 45 ++++++++++++++---- crates/scmi/src/main.rs | 91 +++++++++++++++++++++++++++++++------ crates/scmi/src/scmi.rs | 9 +++- crates/scmi/src/vhu_scmi.rs | 46 ++++++++++++++++--- 5 files changed, 167 insertions(+), 33 deletions(-) diff --git a/crates/scmi/README.md b/crates/scmi/README.md index 49f1b70f9..8c1f9c494 100644 --- a/crates/scmi/README.md +++ b/crates/scmi/README.md @@ -24,6 +24,13 @@ the Examples section below. Location of the vhost-user Unix domain sockets. +.. option:: -d, --device=SPEC + + SCMI device specification in the format `ID,PROPERTY=VALUE,...`. + 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. + You can set `RUST_LOG` environment variable to `debug` to get maximum messages on the standard error output. @@ -33,7 +40,7 @@ The daemon should be started first: :: - host# vhost-device-scmi --socket-path=scmi.sock + host# vhost-device-scmi --socket-path=scmi.sock --device fake,name=foo The QEMU invocation needs to create a chardev socket the device can use to communicate as well as share the guests memory over a memfd: diff --git a/crates/scmi/src/devices.rs b/crates/scmi/src/devices.rs index 174aa39da..39dd67a45 100644 --- a/crates/scmi/src/devices.rs +++ b/crates/scmi/src/devices.rs @@ -1,15 +1,29 @@ // SPDX-FileCopyrightText: Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 +use std::collections::HashMap; + use log::debug; -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, +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, + }, + DeviceProperties, }; +type DeviceSpecification = fn() -> Box; +type NameDeviceMapping = HashMap; + +pub fn available_devices() -> NameDeviceMapping { + let mut devices: NameDeviceMapping = HashMap::new(); + devices.insert(String::from("fake"), FakeSensor::new); + devices +} + pub struct FakeSensor { enabled: bool, value: u8, @@ -19,16 +33,31 @@ pub struct FakeSensor { impl FakeSensor { const NUMBER_OF_AXES: u32 = 3; - pub const fn new(name: String) -> Self { - Self { + #[allow(clippy::new_ret_no_self)] + pub fn new() -> Box { + let name = String::from("fake"); + let sensor = Self { enabled: false, value: 0, name, - } + }; + Box::new(sensor) } } impl ScmiDevice for FakeSensor { + fn configure(&mut self, properties: &DeviceProperties) -> Result<(), String> { + for (k, v) in properties { + if k == "name" { + // TODO: Check for duplicate names + self.name = String::from(v); + } else { + return Result::Err(format!("Invalid device option: {k}")); + } + } + Ok(()) + } + fn protocol(&self) -> ProtocolId { SENSOR_PROTOCOL_ID } diff --git a/crates/scmi/src/main.rs b/crates/scmi/src/main.rs index 2cf890633..c9e176e22 100644 --- a/crates/scmi/src/main.rs +++ b/crates/scmi/src/main.rs @@ -2,48 +2,79 @@ // SPDX-License-Identifier: Apache-2.0 // Based on implementation of other devices here, Copyright by Linaro Ltd. +mod devices; mod scmi; mod vhu_scmi; -mod devices; -use std::process::exit; -use std::sync::{Arc, RwLock}; +use std::{ + process::exit, + sync::{Arc, RwLock}, +}; use clap::Parser; +use itertools::Itertools; use log::{debug, error, info, warn}; use vhost::vhost_user; use vhost::vhost_user::Listener; use vhost_user_backend::VhostUserDaemon; -use vhu_scmi::{VuScmiBackend, VuScmiError}; +use vhu_scmi::VuScmiBackend; use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; -type Result = std::result::Result; +type Result = std::result::Result; #[derive(Parser)] struct ScmiArgs { // Location of vhost-user Unix domain socket. #[clap(short, long, help = "vhost-user socket to use")] socket_path: String, + // Specification of SCMI devices to create. + #[clap(short, long)] + #[arg(num_args(1..))] + device: Vec, } -struct VuScmiConfig { +// [(NAME, [(PROPERTY, VALUE), ...]), ...] +type DeviceDescription = Vec<(String, DeviceProperties)>; +type DeviceProperties = Vec<(String, String)>; + +pub struct VuScmiConfig { socket_path: String, + devices: DeviceDescription, } impl TryFrom for VuScmiConfig { - type Error = VuScmiError; + type Error = String; fn try_from(cmd_args: ScmiArgs) -> Result { let socket_path = cmd_args.socket_path.trim().to_string(); - Ok(Self { socket_path }) + let device_iterator = cmd_args.device.iter(); + let mut devices: DeviceDescription = vec![]; + for d in device_iterator { + let mut split = d.split(','); + let name = split.next().unwrap().to_owned(); + let mut properties: DeviceProperties = vec![]; + for s in split { + if let Some((key, value)) = s.split('=').collect_tuple() { + properties.push((key.to_owned(), value.to_owned())); + } else { + return Result::Err(format!("Invalid device {name} property format: {s}")); + } + } + devices.push((name, properties)); + } + Ok(Self { + socket_path, + devices, + }) } } fn start_backend(config: VuScmiConfig) -> Result<()> { loop { debug!("Starting backend"); - let backend = Arc::new(RwLock::new(VuScmiBackend::new().unwrap())); + // TODO: Print a nice error message on backend configuration failure. + let backend = Arc::new(RwLock::new(VuScmiBackend::new(&config).unwrap())); let listener = Listener::new(config.socket_path.clone(), true).unwrap(); let mut daemon = VhostUserDaemon::new( "vhost-device-scmi".to_owned(), @@ -78,9 +109,17 @@ fn start_backend(config: VuScmiConfig) -> Result<()> { fn main() { env_logger::init(); - if let Err(error) = start_backend(VuScmiConfig::try_from(ScmiArgs::parse()).unwrap()) { - error!("{error}"); - exit(1); + match VuScmiConfig::try_from(ScmiArgs::parse()) { + Ok(config) => { + if let Err(error) = start_backend(config) { + error!("{error}"); + exit(1); + } + } + Err(message) => { + println!("{message}"); + // TODO: print help + } } } @@ -91,9 +130,31 @@ mod tests { #[test] fn test_command_line() { let path = "/foo/scmi.sock".to_owned(); - let command_line = format!("-s {path}"); - let args: ScmiArgs = Parser::parse_from(["", &command_line]); - let config: VuScmiConfig = args.try_into().unwrap(); + let params_string = format!( + "binary \ + --device dummy \ + -s {path} \ + --device fake,name=foo,prop=value \ + -d fake,name=bar" + ); + let params: Vec<&str> = params_string.split_whitespace().collect(); + let args: ScmiArgs = Parser::parse_from(params); + let config = VuScmiConfig::try_from(args).unwrap(); assert_eq!(config.socket_path, path); + let devices = vec![ + ("dummy".to_owned(), vec![]), + ( + "fake".to_owned(), + vec![ + ("name".to_owned(), "foo".to_owned()), + ("prop".to_owned(), "value".to_owned()), + ], + ), + ( + "fake".to_owned(), + vec![("name".to_owned(), "bar".to_owned())], + ), + ]; + assert_eq!(config.devices, devices); } } diff --git a/crates/scmi/src/scmi.rs b/crates/scmi/src/scmi.rs index e168654fc..d4b982b21 100644 --- a/crates/scmi/src/scmi.rs +++ b/crates/scmi/src/scmi.rs @@ -11,6 +11,8 @@ use itertools::Itertools; use log::{debug, error, info}; use thiserror::Error as ThisError; +use crate::DeviceProperties; + pub type MessageHeader = u32; pub const MAX_SIMPLE_STRING_LENGTH: usize = 16; // incl. NULL terminator @@ -478,6 +480,7 @@ pub enum ScmiDeviceError { } pub trait ScmiDevice: Send { + fn configure(&mut self, properties: &DeviceProperties) -> Result<(), String>; fn protocol(&self) -> ProtocolId; fn handle( &mut self, @@ -716,7 +719,7 @@ impl ScmiHandler { #[cfg(test)] mod tests { - use crate::devices; + use crate::devices::FakeSensor; use super::*; @@ -860,7 +863,9 @@ mod tests { fn make_handler() -> ScmiHandler { let mut handler = ScmiHandler::new(); for i in 0..2 { - let fake_sensor = Box::new(devices::FakeSensor::new(format!("fake{i}"))); + let properties = vec![("name".to_owned(), format!("fake{i}"))]; + let mut fake_sensor = FakeSensor::new(); + fake_sensor.configure(&properties).unwrap(); handler.register_device(fake_sensor); } handler diff --git a/crates/scmi/src/vhu_scmi.rs b/crates/scmi/src/vhu_scmi.rs index e3f37b522..20493831c 100644 --- a/crates/scmi/src/vhu_scmi.rs +++ b/crates/scmi/src/vhu_scmi.rs @@ -20,7 +20,10 @@ use vm_memory::{ use vmm_sys_util::epoll::EventSet; use vmm_sys_util::eventfd::{EventFd, EFD_NONBLOCK}; +use crate::devices::available_devices; +use crate::scmi::ScmiDevice; use crate::scmi::{MessageHeader, ScmiHandler, ScmiRequest}; +use crate::VuScmiConfig; // QUEUE_SIZE must be apparently at least 1024 for MMIO. // There is probably a maximum size per descriptor defined in the kernel. @@ -40,6 +43,8 @@ pub enum VuScmiError { DescriptorReadFailed, #[error("Descriptor write failed")] DescriptorWriteFailed, + #[error("Error when configuring device {0}: {1}")] + DeviceConfigurationError(String, String), #[error("Failed to create new EventFd")] EventFdFailed, #[error("Failed to handle event, didn't match EPOLLIN")] @@ -60,6 +65,8 @@ pub enum VuScmiError { UnexpectedReadableDescriptor(usize), #[error("Received unexpected write only descriptor at index {0}")] UnexpectedWriteOnlyDescriptor(usize), + #[error("Unknown device requested: {0}")] + UnknownDeviceRequested(String), } impl From for io::Error { @@ -88,14 +95,31 @@ pub struct VuScmiBackend { } impl VuScmiBackend { - pub fn new() -> Result { + pub fn new(config: &VuScmiConfig) -> Result { + let mut handler = ScmiHandler::new(); + let device_mapping = available_devices(); + for (name, properties) in config.devices.iter() { + match device_mapping.get(name) { + Some(constructor) => { + let mut device: Box = constructor(); + if let Err(message) = device.configure(properties) { + return Result::Err(VuScmiError::DeviceConfigurationError( + name.clone(), + message, + )); + } + handler.register_device(device); + } + None => return Result::Err(VuScmiError::UnknownDeviceRequested(name.clone())), + }; + } Ok(Self { event_idx: false, exit_event: EventFd::new(EFD_NONBLOCK).map_err(|_| VuScmiError::EventFdFailed)?, mem: None, event_vring: None, event_descriptors: vec![], - scmi_handler: ScmiHandler::new(), + scmi_handler: handler, }) } @@ -549,9 +573,17 @@ mod tests { assert_eq!(response[0..result.len()], result); } + fn make_backend() -> VuScmiBackend { + let config = VuScmiConfig { + socket_path: "/foo/scmi.sock".to_owned(), + devices: vec![], + }; + VuScmiBackend::new(&config).unwrap() + } + #[test] fn test_process_requests() { - let mut backend = VuScmiBackend::new().unwrap(); + let mut backend = make_backend(); let mem = GuestMemoryAtomic::new( GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(), ); @@ -583,7 +615,7 @@ mod tests { #[test] fn test_process_requests_failure() { - let mut backend = VuScmiBackend::new().unwrap(); + let mut backend = make_backend(); let mem = GuestMemoryAtomic::new( GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(), ); @@ -749,7 +781,7 @@ mod tests { #[test] fn test_event_requests() { - let mut backend = VuScmiBackend::new().unwrap(); + let mut backend = make_backend(); let mem = GuestMemoryAtomic::new( GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(), ); @@ -778,7 +810,7 @@ mod tests { #[test] fn test_event_requests_failure() { - let mut backend = VuScmiBackend::new().unwrap(); + let mut backend = make_backend(); let mem = GuestMemoryAtomic::new( GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(), ); @@ -815,7 +847,7 @@ mod tests { #[test] fn test_backend() { - let mut backend = VuScmiBackend::new().unwrap(); + let mut backend = make_backend(); assert_eq!(backend.num_queues(), NUM_QUEUES); assert_eq!(backend.max_queue_size(), QUEUE_SIZE); From e0fdfa4c383d79f5b107f823951a8dfc4a39a339 Mon Sep 17 00:00:00 2001 From: Milan Zamazal Date: Fri, 21 Jul 2023 20:43:58 +0200 Subject: [PATCH 25/40] scmi: Refactor devices.rs for sensor code reuse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Different sensors will have similar handling. Let’s extract generic parts from FakeSensor implementation into a reusable code, within the limits of Rust. Signed-off-by: Milan Zamazal --- crates/scmi/src/devices.rs | 261 +++++++++++++++++++++++++------------ 1 file changed, 180 insertions(+), 81 deletions(-) diff --git a/crates/scmi/src/devices.rs b/crates/scmi/src/devices.rs index 39dd67a45..be865070f 100644 --- a/crates/scmi/src/devices.rs +++ b/crates/scmi/src/devices.rs @@ -20,107 +20,141 @@ type NameDeviceMapping = HashMap; pub fn available_devices() -> NameDeviceMapping { let mut devices: NameDeviceMapping = HashMap::new(); - devices.insert(String::from("fake"), FakeSensor::new); + devices.insert("fake".to_owned(), FakeSensor::new); devices } -pub struct FakeSensor { - enabled: bool, - value: u8, +// Common sensor infrastructure + +pub struct Sensor { name: String, + enabled: bool, } -impl FakeSensor { - const NUMBER_OF_AXES: u32 = 3; - - #[allow(clippy::new_ret_no_self)] - pub fn new() -> Box { - let name = String::from("fake"); - let sensor = Self { +impl Sensor { + const fn new(default_name: String) -> Self { + Self { + name: default_name, enabled: false, - value: 0, - name, - }; - Box::new(sensor) + } } } -impl ScmiDevice for FakeSensor { +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: &String) -> Result<(), String> { + Result::Err(format!("Invalid device option: {name}")) + } + + fn process_property(&mut self, name: &String, _value: &str) -> Result<(), String> { + self.invalid_property(name) + } + fn configure(&mut self, properties: &DeviceProperties) -> Result<(), String> { - for (k, v) in properties { - if k == "name" { + for (name, value) in properties { + if name == "name" { // TODO: Check for duplicate names - self.name = String::from(v); + self.sensor_mut().name = String::from(value); } else { - return Result::Err(format!("Invalid device option: {k}")); + self.process_property(name, value)?; } } Ok(()) } - fn protocol(&self) -> ProtocolId { - SENSOR_PROTOCOL_ID + 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 { + 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 => { - // Continuous update required by Linux SCMI IIO driver - let low = 1 << 30; - let high = Self::NUMBER_OF_AXES << 16 | 1 << 8; - let name = self.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) - } + 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 >= Self::NUMBER_OF_AXES { + if axis_desc_index >= n_sensor_axes { return Result::Err(ScmiDeviceError::InvalidParameters); } - let mut values = vec![MessageValue::Unsigned( - Self::NUMBER_OF_AXES - axis_desc_index, - )]; - for i in axis_desc_index..Self::NUMBER_OF_AXES { - values.push(MessageValue::Unsigned(i)); // axis id - values.push(MessageValue::Unsigned(0)); // attributes low - - // 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). - values.push(MessageValue::Unsigned( - SENSOR_UNIT_METERS_PER_SECOND_SQUARED, - )); // attributes high - - // Name in the recommended format, 16 bytes: - let axis = match i { - 0 => 'X', - 1 => 'Y', - 2 => 'Z', - _ => 'N', // shouldn't be reached currently - }; - values.push(MessageValue::String(format!("acc_{axis}").to_string(), 16)); + 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 => { - let config = u32::from(self.enabled); - Ok(vec![MessageValue::Unsigned(config)]) - } + SENSOR_CONFIG_GET => self.config_get(), SENSOR_CONFIG_SET => { let config = parameters[0].get_unsigned(); - if config & 0xFFFFFFFE != 0 { - return Result::Err(ScmiDeviceError::UnsupportedRequest); - } - self.enabled = config != 0; - debug!("Sensor enabled: {}", self.enabled); - Ok(vec![]) + self.config_set(config) } SENSOR_CONTINUOUS_UPDATE_NOTIFY => { // Linux VIRTIO SCMI insists on this. @@ -128,21 +162,86 @@ impl ScmiDevice for FakeSensor { Ok(vec![]) } SENSOR_READING_GET => { - if !self.enabled { + if !self.sensor().enabled { return Result::Err(ScmiDeviceError::NotEnabled); } - 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) + 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); + +impl ScmiDevice for SensorDevice { + fn configure(&mut self, properties: &DeviceProperties) -> Result<(), String> { + self.0.configure(properties) + } + + 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() -> Box { + let sensor = Sensor::new("fake".to_owned()); + let fake_sensor = Self { sensor, value: 0 }; + let sensor_device = SensorDevice(Box::new(fake_sensor)); + Box::new(sensor_device) + } +} From 4014a6366f4815e5b96a384dc1515e4004253d29 Mon Sep 17 00:00:00 2001 From: Milan Zamazal Date: Wed, 26 Jul 2023 14:43:03 +0200 Subject: [PATCH 26/40] scmi: Kernel instructions and changes for IIO added MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Followup patches will allow connecting vhost-user-scmi to industrial I/O devices. On host without IIO devices, it’s possible to use emulated devices for testing. This patch documents how to use them and also provides a slightly customized IIO dummy kernel module. Signed-off-by: Milan Zamazal --- crates/scmi/README.md | 8 + crates/scmi/kernel/iio-dummy/.gitignore | 7 + crates/scmi/kernel/iio-dummy/Makefile | 19 + crates/scmi/kernel/iio-dummy/README.md | 184 +++++ .../scmi/kernel/iio-dummy/iio-dummy-fix.patch | 55 ++ .../kernel/iio-dummy/iio_modified_dummy.c | 693 ++++++++++++++++++ .../kernel/iio-dummy/iio_modified_dummy.h | 68 ++ 7 files changed, 1034 insertions(+) create mode 100644 crates/scmi/kernel/iio-dummy/.gitignore create mode 100644 crates/scmi/kernel/iio-dummy/Makefile create mode 100644 crates/scmi/kernel/iio-dummy/README.md create mode 100644 crates/scmi/kernel/iio-dummy/iio-dummy-fix.patch create mode 100644 crates/scmi/kernel/iio-dummy/iio_modified_dummy.c create mode 100644 crates/scmi/kernel/iio-dummy/iio_modified_dummy.h diff --git a/crates/scmi/README.md b/crates/scmi/README.md index 8c1f9c494..b7e3dcdd3 100644 --- a/crates/scmi/README.md +++ b/crates/scmi/README.md @@ -65,9 +65,17 @@ The currently supported SCMI protocols are: Basically only the mandatory and necessary parts of the protocols are implemented. +## Kernel support for testing + +`kernel` subdirectory contains +[instructions](kernel/iio-dummy/README.md) how to create emulated +industrial I/O devices for testing. + ## License This project is licensed under either of - [Apache License](http://www.apache.org/licenses/LICENSE-2.0), Version 2.0 - [BSD-3-Clause License](https://opensource.org/licenses/BSD-3-Clause) + +unless specified in particular files otherwise. diff --git a/crates/scmi/kernel/iio-dummy/.gitignore b/crates/scmi/kernel/iio-dummy/.gitignore new file mode 100644 index 000000000..a2337affb --- /dev/null +++ b/crates/scmi/kernel/iio-dummy/.gitignore @@ -0,0 +1,7 @@ +*.cmd +*.ko +*.mod +*.mod.[co] +*.o +Module.symvers +modules.order diff --git a/crates/scmi/kernel/iio-dummy/Makefile b/crates/scmi/kernel/iio-dummy/Makefile new file mode 100644 index 000000000..f602b91cd --- /dev/null +++ b/crates/scmi/kernel/iio-dummy/Makefile @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the IIO Dummy Driver +# +# Modified by Milan Zamazal in 2023 for out of +# tree compilation. +# + +obj-m += iio_modified_dummy.o + +on_nixos = $(wildcard /etc/NIXOS) +ifeq ($(on_nixos), /etc/NIXOS) +nix_prefix = $(shell nix-build -E '(import {}).linux.dev' --no-out-link) +endif + +all: + make -C $(nix_prefix)/lib/modules/$(shell uname -r)/build M=$(PWD) modules +clean: + make -C $(nix_prefix)/lib/modules/$(shell uname -r)/build M=$(PWD) clean diff --git a/crates/scmi/kernel/iio-dummy/README.md b/crates/scmi/kernel/iio-dummy/README.md new file mode 100644 index 000000000..ed8710117 --- /dev/null +++ b/crates/scmi/kernel/iio-dummy/README.md @@ -0,0 +1,184 @@ +# Using emulated industrial I/O devices + +This is a modified version of the Linux [industrial I/O dummy +driver](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/iio/dummy). +Both the original driver and this modification can provide emulated +industrial I/O devices for testing vhost-device-scmi. + +## Modifications in this module + +If the stock industrial I/O dummy driver is enough for you, use it +(but you may still want to read the instructions below). + +Otherwise, this alternative is provided with the following changes: + +- Simplified Makefile for out of tree compilation. +- The accelerometer has three axes instead of just one. + +Of course, you can modified it further for your liking if needed. + +## How to create emulated industrial I/O devices + +Make sure your kernel supports software industrial I/O devices and +industrial I/O with configfs. You can check this by running `modprobe +industrialio_sw_device && modprobe industrialio_configfs`. If any of +the modules is not present, follow the [instructions for recompiling +kernel](#recompiling-kernel-with-industrial-io) below. + +Make sure you have the right kernel version. Since Linux 5.19, the +dummy industrial I/O driver is broken. This will be probably fixed in +Linux 6.6. + +If you have a broken kernel version, apply the +[fix](./iio-dummy-fix.patch) and compile and install the modified +kernel. + +If you want to use the modified module from here, compile it. In +order to do this, you must have kernel development environment +installed, for example: + +- Fedora or derivatives: `dnf install kernel-devel kernel-modules make` +- Debian or derivatives: `apt install linux-headers-$(uname -r) make` +- NixOS: `nix-shell '' -A linux.dev` + +Then you can compile the module, simply running `make` should work on +most distributions. + +Insert a dummy industrial I/O kernel module. Either the stock one: + +``` +# modprobe iio-dummy +``` + +or the modified one from here: + +``` +# modprobe industrialio +# modprobe industrialio_configfs +# modprobe industrialio_sw_device +# insmod ./iio-dummy-modified.ko +``` + +Find out where configfs is mounted: `mount | grep configfs`. It's +typically `/sys/kernel/config`. If configfs is not mounted, mount it +somewhere: `mount -t configfs none MOUNTPOINT`. + +Now you can create emulated industrial I/O devices with the stock driver: + +``` +# mkdir /sys/kernel/config/iio/devices/dummy/my-device +``` + +And/or with the modified driver from here: + +``` +# mkdir /sys/kernel/config/iio/devices/dummy-modified/my-device +``` + +If everything is OK then you can find the device in +`/sys/bus/iio/devices/`. + +## Recompiling kernel with industrial I/O + +Making a custom kernel is different on each GNU/Linux distribution. +The corresponding documentation can be found for example here: + +- Fedora: [https://fedoraproject.org/wiki/Building_a_custom_kernel](https://fedoraproject.org/wiki/Building_a_custom_kernel) +- CentOS Stream: [https://wiki.centos.org/HowTos/BuildingKernelModules](https://wiki.centos.org/HowTos/BuildingKernelModules) + (looks more useful for Fedora builds than CentOS) +- Debian: [https://kernel-team.pages.debian.net/kernel-handbook/ch-common-tasks.html#s-common-official](https://kernel-team.pages.debian.net/kernel-handbook/ch-common-tasks.html#s-common-official) +- NixOS: [https://nixos.wiki/wiki/Linux_kernel](https://nixos.wiki/wiki/Linux_kernel) + +Here are instructions for Fedora, similar steps can be used for other +distributions, with distribution specifics as described in the links +above. This is not necessarily the most official or the best way to +do it but it's a way that *actually works* for me. + +Note on CentOS Stream 9: The kernel there doesn't contain the needed +modules. Recompiling the kernel on CentOS Stream may be challenging +due to missing build dependencies. If it doesn't work for you, you +can try to use Fedora kernel and modules on CentOS Stream, including +the dummy module compiled on Fedora. + +### Install kernel sources + +``` +# dnf install 'dnf-command(download)' +$ dnf download --source kernel +$ rpm -i kernel-*.src.rpm +# dnf builddep ~/rpmbuild/SPECS/kernel.spec +``` + +### Change kernel configuration + +Not needed for current Fedora but may be needed for e.g. CentOS Stream. + +``` +# dnf install kernel-devel kernel-modules make rpm-build python3-devel ncurses-devel +$ rpmbuild -bp ~/rpmbuild/SPECS/kernel.spec +$ cd ~/rpmbuild/BUILD/kernel-*/linux-*/ +$ cp configs/kernel-VERSION-YOURARCH.config .config +$ make nconfig +``` + +Configuration options that must be enabled: + +- Device Drivers -> Industrial I/O Support -> Enable IIO configuration via configfs +- Device Drivers -> Industrial I/O Support -> Enable software IIO device support + +Optionally (you can use the alternative driver from here instead): + +- Device Drivers -> Industrial I/O Support -> IIO dummy drive -> An example driver with no hardware requirements + +Then copy `.config` back to its original file and don't forget to add +the original architecture specification line there. + +### Apply the kernel fix + +If the kernel fix from here is needed, copy it to the sources: + +``` +cp .../iio-dummy-fix.patch ~/rpmbuild/SOURCES/ +``` + +Edit `~/rpmbuild/SPECS/kernel.spec`: + +- Uncomment: `%define buildid .local`. + +- Add the patch file before: `Patch999999: linux-kernel-test.patch`. + +- Add the patch file before: `ApplyOptionalPatch linux-kernel-test.patch`. + +### Build the kernel + +You can use different options, if you don't need anything extra then +the following builds the most important rpm's: + +``` +$ rpmbuild -bb --with baseonly --without debug --without debuginfo ~/rpmbuild/SPECS/kernel.spec +``` + +## Adding industrial I/O dummy module to your kernel + +If all you need is to add a missing stock I/O dummy module, you can +try to compile just the module. Switch to kernel sources and run: + +``` +$ make oldconfig +$ make prepare +$ make modules_prepare +$ make M=drivers/iio/dummy +``` + +And insert the module: + +``` +# modprobe industrialio +# modprobe industrialio_configfs +# modprobe industrialio_sw_device +# insmod ./drivers/iio/dummy/iio-dummy.ko +``` + +If this fails, inspect `dmesg` output and try to figure out what's +wrong. If this fails too, rebuild the whole kernel with the given +module enabled. diff --git a/crates/scmi/kernel/iio-dummy/iio-dummy-fix.patch b/crates/scmi/kernel/iio-dummy/iio-dummy-fix.patch new file mode 100644 index 000000000..d74b7d5b6 --- /dev/null +++ b/crates/scmi/kernel/iio-dummy/iio-dummy-fix.patch @@ -0,0 +1,55 @@ +Commit 813665564b3d ("iio: core: Convert to use firmware node handle +instead of OF node") switched the kind of nodes to use for label +retrieval in device registration. Probably an unwanted change in that +commit was that if the device has no parent then NULL pointer is +accessed. This is what happens in the stock IIO dummy driver when a +new entry is created in configfs: + + # mkdir /sys/kernel/config/iio/devices/dummy/foo + BUG: kernel NULL pointer dereference, address: ... + ... + Call Trace: + __iio_device_register + iio_dummy_probe + +Since there seems to be no reason to make a parent device of an IIO +dummy device mandatory, let’s prevent the invalid memory access in +__iio_device_register when the parent device is NULL. With this +change, the IIO dummy driver works fine with configfs. + +Fixes: 813665564b3d ("iio: core: Convert to use firmware node handle instead of OF node") +Reviewed-by: Andy Shevchenko +Signed-off-by: Milan Zamazal +--- + drivers/iio/industrialio-core.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c +index c117f50d0cf3..adcba832e6fa 100644 +--- a/drivers/iio/industrialio-core.c ++++ b/drivers/iio/industrialio-core.c +@@ -1888,7 +1888,7 @@ static const struct iio_buffer_setup_ops noop_ring_setup_ops; + int __iio_device_register(struct iio_dev *indio_dev, struct module *this_mod) + { + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); +- struct fwnode_handle *fwnode; ++ struct fwnode_handle *fwnode = NULL; + int ret; + + if (!indio_dev->info) +@@ -1899,7 +1899,8 @@ int __iio_device_register(struct iio_dev *indio_dev, struct module *this_mod) + /* If the calling driver did not initialize firmware node, do it here */ + if (dev_fwnode(&indio_dev->dev)) + fwnode = dev_fwnode(&indio_dev->dev); +- else ++ /* The default dummy IIO device has no parent */ ++ else if (indio_dev->dev.parent) + fwnode = dev_fwnode(indio_dev->dev.parent); + device_set_node(&indio_dev->dev, fwnode); + +-- + +2.40.1 + + + diff --git a/crates/scmi/kernel/iio-dummy/iio_modified_dummy.c b/crates/scmi/kernel/iio-dummy/iio_modified_dummy.c new file mode 100644 index 000000000..750448901 --- /dev/null +++ b/crates/scmi/kernel/iio-dummy/iio_modified_dummy.c @@ -0,0 +1,693 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2011 Jonathan Cameron + * + * A reference industrial I/O driver to illustrate the functionality available. + * + * There are numerous real drivers to illustrate the finer points. + * The purpose of this driver is to provide a driver with far more comments + * and explanatory notes than any 'real' driver would have. + * Anyone starting out writing an IIO driver should first make sure they + * understand all of this driver except those bits specifically marked + * as being present to allow us to 'fake' the presence of hardware. + * + * Changes by Milan Zamazal 2023, for testing + * with vhost-device-scmi: + * + * - Dropped conditional parts. + * - Use 3 axes in the accelerometer device. + */ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "iio_modified_dummy.h" + +static const struct config_item_type iio_dummy_type = { + .ct_owner = THIS_MODULE, +}; + +/** + * struct iio_dummy_accel_calibscale - realworld to register mapping + * @val: first value in read_raw - here integer part. + * @val2: second value in read_raw etc - here micro part. + * @regval: register value - magic device specific numbers. + */ +struct iio_dummy_accel_calibscale { + int val; + int val2; + int regval; /* what would be written to hardware */ +}; + +static const struct iio_dummy_accel_calibscale dummy_scales[] = { + { 0, 100, 0x8 }, /* 0.000100 */ + { 0, 133, 0x7 }, /* 0.000133 */ + { 733, 13, 0x9 }, /* 733.000013 */ +}; + +/* + * iio_dummy_channels - Description of available channels + * + * This array of structures tells the IIO core about what the device + * actually provides for a given channel. + */ +static const struct iio_chan_spec iio_dummy_channels[] = { + /* indexed ADC channel in_voltage0_raw etc */ + { + .type = IIO_VOLTAGE, + /* Channel has a numeric index of 0 */ + .indexed = 1, + .channel = 0, + /* What other information is available? */ + .info_mask_separate = + /* + * in_voltage0_raw + * Raw (unscaled no bias removal etc) measurement + * from the device. + */ + BIT(IIO_CHAN_INFO_RAW) | + /* + * in_voltage0_offset + * Offset for userspace to apply prior to scale + * when converting to standard units (microvolts) + */ + BIT(IIO_CHAN_INFO_OFFSET) | + /* + * in_voltage0_scale + * Multipler for userspace to apply post offset + * when converting to standard units (microvolts) + */ + BIT(IIO_CHAN_INFO_SCALE), + /* + * sampling_frequency + * The frequency in Hz at which the channels are sampled + */ + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ), + /* The ordering of elements in the buffer via an enum */ + .scan_index = DUMMY_INDEX_VOLTAGE_0, + .scan_type = { /* Description of storage in buffer */ + .sign = 'u', /* unsigned */ + .realbits = 13, /* 13 bits */ + .storagebits = 16, /* 16 bits used for storage */ + .shift = 0, /* zero shift */ + }, + }, + /* Differential ADC channel in_voltage1-voltage2_raw etc*/ + { + .type = IIO_VOLTAGE, + .differential = 1, + /* + * Indexing for differential channels uses channel + * for the positive part, channel2 for the negative. + */ + .indexed = 1, + .channel = 1, + .channel2 = 2, + /* + * in_voltage1-voltage2_raw + * Raw (unscaled no bias removal etc) measurement + * from the device. + */ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + /* + * in_voltage-voltage_scale + * Shared version of scale - shared by differential + * input channels of type IIO_VOLTAGE. + */ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + /* + * sampling_frequency + * The frequency in Hz at which the channels are sampled + */ + .scan_index = DUMMY_INDEX_DIFFVOLTAGE_1M2, + .scan_type = { /* Description of storage in buffer */ + .sign = 's', /* signed */ + .realbits = 12, /* 12 bits */ + .storagebits = 16, /* 16 bits used for storage */ + .shift = 0, /* zero shift */ + }, + }, + /* Differential ADC channel in_voltage3-voltage4_raw etc*/ + { + .type = IIO_VOLTAGE, + .differential = 1, + .indexed = 1, + .channel = 3, + .channel2 = 4, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = DUMMY_INDEX_DIFFVOLTAGE_3M4, + .scan_type = { + .sign = 's', + .realbits = 11, + .storagebits = 16, + .shift = 0, + }, + }, + /* + * 'modified' (i.e. axis specified) acceleration channel + * in_accel_[xyz]_raw + */ + { + .type = IIO_ACCEL, + .modified = 1, + /* Channel 2 is use for modifiers */ + .channel2 = IIO_MOD_X, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + /* + * Internal bias and gain correction values. Applied + * by the hardware or driver prior to userspace + * seeing the readings. Typically part of hardware + * calibration. + */ + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = DUMMY_INDEX_ACCEL_X, + .scan_type = { /* Description of storage in buffer */ + .sign = 's', /* signed */ + .realbits = 16, /* 16 bits */ + .storagebits = 16, /* 16 bits used for storage */ + .shift = 0, /* zero shift */ + }, + }, + { + .type = IIO_ACCEL, + .modified = 1, + /* Channel 2 is use for modifiers */ + .channel2 = IIO_MOD_Y, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = DUMMY_INDEX_ACCEL_Y, + .scan_type = { /* Description of storage in buffer */ + .sign = 's', /* signed */ + .realbits = 16, /* 16 bits */ + .storagebits = 16, /* 16 bits used for storage */ + .shift = 0, /* zero shift */ + }, + }, + { + .type = IIO_ACCEL, + .modified = 1, + /* Channel 2 is use for modifiers */ + .channel2 = IIO_MOD_Z, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = DUMMY_INDEX_ACCEL_Z, + .scan_type = { /* Description of storage in buffer */ + .sign = 's', /* signed */ + .realbits = 16, /* 16 bits */ + .storagebits = 16, /* 16 bits used for storage */ + .shift = 0, /* zero shift */ + }, + }, + /* + * Convenience macro for timestamps. 4 is the index in + * the buffer. + */ + IIO_CHAN_SOFT_TIMESTAMP(4), + /* DAC channel out_voltage0_raw */ + { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_index = -1, /* No buffer support */ + .output = 1, + .indexed = 1, + .channel = 0, + }, + { + .type = IIO_STEPS, + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_ENABLE) | + BIT(IIO_CHAN_INFO_CALIBHEIGHT), + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = -1, /* No buffer support */ + }, + { + .type = IIO_ACTIVITY, + .modified = 1, + .channel2 = IIO_MOD_RUNNING, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = -1, /* No buffer support */ + }, + { + .type = IIO_ACTIVITY, + .modified = 1, + .channel2 = IIO_MOD_WALKING, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = -1, /* No buffer support */ + }, +}; + +/** + * iio_dummy_read_raw() - data read function. + * @indio_dev: the struct iio_dev associated with this device instance + * @chan: the channel whose data is to be read + * @val: first element of returned value (typically INT) + * @val2: second element of returned value (typically MICRO) + * @mask: what we actually want to read as per the info_mask_* + * in iio_chan_spec. + */ +static int iio_dummy_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + int ret = -EINVAL; + + mutex_lock(&st->lock); + switch (mask) { + case IIO_CHAN_INFO_RAW: /* magic value - channel value read */ + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->output) { + /* Set integer part to cached value */ + *val = st->dac_val; + ret = IIO_VAL_INT; + } else if (chan->differential) { + if (chan->channel == 1) + *val = st->differential_adc_val[0]; + else + *val = st->differential_adc_val[1]; + ret = IIO_VAL_INT; + } else { + *val = st->single_ended_adc_val; + ret = IIO_VAL_INT; + } + break; + case IIO_ACCEL: + switch(chan->scan_index) { + case DUMMY_INDEX_ACCEL_X: + *val = st->accel_val[0]; + break; + case DUMMY_INDEX_ACCEL_Y: + *val = st->accel_val[1]; + break; + case DUMMY_INDEX_ACCEL_Z: + *val = st->accel_val[2]; + break; + default: + *val = 0; + } + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_STEPS: + *val = st->steps; + ret = IIO_VAL_INT; + break; + case IIO_ACTIVITY: + switch (chan->channel2) { + case IIO_MOD_RUNNING: + *val = st->activity_running; + ret = IIO_VAL_INT; + break; + case IIO_MOD_WALKING: + *val = st->activity_walking; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + default: + break; + } + break; + case IIO_CHAN_INFO_OFFSET: + /* only single ended adc -> 7 */ + *val = 7; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + switch (chan->differential) { + case 0: + /* only single ended adc -> 0.001333 */ + *val = 0; + *val2 = 1333; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case 1: + /* all differential adc -> 0.000001344 */ + *val = 0; + *val2 = 1344; + ret = IIO_VAL_INT_PLUS_NANO; + } + break; + default: + break; + } + break; + case IIO_CHAN_INFO_CALIBBIAS: + /* only the acceleration axis - read from cache */ + *val = st->accel_calibbias; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_CALIBSCALE: + *val = st->accel_calibscale->val; + *val2 = st->accel_calibscale->val2; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = 3; + *val2 = 33; + ret = IIO_VAL_INT_PLUS_NANO; + break; + case IIO_CHAN_INFO_ENABLE: + switch (chan->type) { + case IIO_STEPS: + *val = st->steps_enabled; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + case IIO_CHAN_INFO_CALIBHEIGHT: + switch (chan->type) { + case IIO_STEPS: + *val = st->height; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + + default: + break; + } + mutex_unlock(&st->lock); + return ret; +} + +/** + * iio_dummy_write_raw() - data write function. + * @indio_dev: the struct iio_dev associated with this device instance + * @chan: the channel whose data is to be written + * @val: first element of value to set (typically INT) + * @val2: second element of value to set (typically MICRO) + * @mask: what we actually want to write as per the info_mask_* + * in iio_chan_spec. + * + * Note that all raw writes are assumed IIO_VAL_INT and info mask elements + * are assumed to be IIO_INT_PLUS_MICRO unless the callback write_raw_get_fmt + * in struct iio_info is provided by the driver. + */ +static int iio_dummy_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + int i; + int ret = 0; + struct iio_dummy_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->output == 0) + return -EINVAL; + + /* Locking not required as writing single value */ + mutex_lock(&st->lock); + st->dac_val = val; + mutex_unlock(&st->lock); + return 0; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_STEPS: + mutex_lock(&st->lock); + st->steps = val; + mutex_unlock(&st->lock); + return 0; + case IIO_ACTIVITY: + if (val < 0) + val = 0; + if (val > 100) + val = 100; + switch (chan->channel2) { + case IIO_MOD_RUNNING: + st->activity_running = val; + return 0; + case IIO_MOD_WALKING: + st->activity_walking = val; + return 0; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBSCALE: + mutex_lock(&st->lock); + /* Compare against table - hard matching here */ + for (i = 0; i < ARRAY_SIZE(dummy_scales); i++) + if (val == dummy_scales[i].val && + val2 == dummy_scales[i].val2) + break; + if (i == ARRAY_SIZE(dummy_scales)) + ret = -EINVAL; + else + st->accel_calibscale = &dummy_scales[i]; + mutex_unlock(&st->lock); + return ret; + case IIO_CHAN_INFO_CALIBBIAS: + mutex_lock(&st->lock); + st->accel_calibbias = val; + mutex_unlock(&st->lock); + return 0; + case IIO_CHAN_INFO_ENABLE: + switch (chan->type) { + case IIO_STEPS: + mutex_lock(&st->lock); + st->steps_enabled = val; + mutex_unlock(&st->lock); + return 0; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBHEIGHT: + switch (chan->type) { + case IIO_STEPS: + st->height = val; + return 0; + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +/* + * Device type specific information. + */ +static const struct iio_info iio_dummy_info = { + .read_raw = &iio_dummy_read_raw, + .write_raw = &iio_dummy_write_raw, +}; + +/** + * iio_dummy_init_device() - device instance specific init + * @indio_dev: the iio device structure + * + * Most drivers have one of these to set up default values, + * reset the device to known state etc. + */ +static int iio_dummy_init_device(struct iio_dev *indio_dev) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + st->dac_val = 0; + st->single_ended_adc_val = 73; + st->differential_adc_val[0] = 33; + st->differential_adc_val[1] = -34; + st->accel_val[0] = 34; + st->accel_val[1] = 37; + st->accel_val[2] = 40; + st->accel_calibbias = -7; + st->accel_calibscale = &dummy_scales[0]; + st->steps = 47; + st->activity_running = 98; + st->activity_walking = 4; + + return 0; +} + +/** + * iio_dummy_probe() - device instance probe + * @name: name of this instance. + * + * Arguments are bus type specific. + * I2C: iio_dummy_probe(struct i2c_client *client, + * const struct i2c_device_id *id) + * SPI: iio_dummy_probe(struct spi_device *spi) + */ +static struct iio_sw_device *iio_dummy_probe(const char *name) +{ + int ret; + struct iio_dev *indio_dev; + struct iio_dummy_state *st; + struct iio_sw_device *swd; + struct device *parent; + + /* + * With hardware: Set the parent device. + * parent = &spi->dev; + * parent = &client->dev; + */ + + swd = kzalloc(sizeof(*swd), GFP_KERNEL); + if (!swd) + return ERR_PTR(-ENOMEM); + + /* + * Allocate an IIO device. + * + * This structure contains all generic state + * information about the device instance. + * It also has a region (accessed by iio_priv() + * for chip specific state information. + */ + indio_dev = iio_device_alloc(parent, sizeof(*st)); + if (!indio_dev) { + ret = -ENOMEM; + goto error_free_swd; + } + + st = iio_priv(indio_dev); + mutex_init(&st->lock); + + iio_dummy_init_device(indio_dev); + + /* + * Make the iio_dev struct available to remove function. + * Bus equivalents + * i2c_set_clientdata(client, indio_dev); + * spi_set_drvdata(spi, indio_dev); + */ + swd->device = indio_dev; + + /* + * Set the device name. + * + * This is typically a part number and obtained from the module + * id table. + * e.g. for i2c and spi: + * indio_dev->name = id->name; + * indio_dev->name = spi_get_device_id(spi)->name; + */ + indio_dev->name = kstrdup(name, GFP_KERNEL); + if (!indio_dev->name) { + ret = -ENOMEM; + goto error_free_device; + } + + /* Provide description of available channels */ + indio_dev->channels = iio_dummy_channels; + indio_dev->num_channels = ARRAY_SIZE(iio_dummy_channels); + + /* + * Provide device type specific interface functions and + * constant data. + */ + indio_dev->info = &iio_dummy_info; + + /* Specify that device provides sysfs type interfaces */ + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto error_free_name; + + iio_swd_group_init_type_name(swd, name, &iio_dummy_type); + + return swd; +error_free_name: + kfree(indio_dev->name); +error_free_device: + iio_device_free(indio_dev); +error_free_swd: + kfree(swd); + return ERR_PTR(ret); +} + +/** + * iio_dummy_remove() - device instance removal function + * @swd: pointer to software IIO device abstraction + * + * Parameters follow those of iio_dummy_probe for buses. + */ +static int iio_dummy_remove(struct iio_sw_device *swd) +{ + /* + * Get a pointer to the device instance iio_dev structure + * from the bus subsystem. E.g. + * struct iio_dev *indio_dev = i2c_get_clientdata(client); + * struct iio_dev *indio_dev = spi_get_drvdata(spi); + */ + struct iio_dev *indio_dev = swd->device; + + /* Unregister the device */ + iio_device_unregister(indio_dev); + + /* Free all structures */ + kfree(indio_dev->name); + iio_device_free(indio_dev); + + return 0; +} + +/* + * module_iio_sw_device_driver() - device driver registration + * + * Varies depending on bus type of the device. As there is no device + * here, call probe directly. For information on device registration + * i2c: + * Documentation/i2c/writing-clients.rst + * spi: + * Documentation/spi/spi-summary.rst + */ +static const struct iio_sw_device_ops iio_dummy_device_ops = { + .probe = iio_dummy_probe, + .remove = iio_dummy_remove, +}; + +static struct iio_sw_device_type iio_dummy_device = { + .name = "dummy-modified", + .owner = THIS_MODULE, + .ops = &iio_dummy_device_ops, +}; + +module_iio_sw_device_driver(iio_dummy_device); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("IIO dummy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/crates/scmi/kernel/iio-dummy/iio_modified_dummy.h b/crates/scmi/kernel/iio-dummy/iio_modified_dummy.h new file mode 100644 index 000000000..9b0b8b171 --- /dev/null +++ b/crates/scmi/kernel/iio-dummy/iio_modified_dummy.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/** + * Copyright (c) 2011 Jonathan Cameron + * + * Join together the various functionality of iio_modified_dummy driver + * + * Changes by Milan Zamazal 2023, for testing + * with vhost-device-scmi: + * + * - Dropped conditional parts. + * - Use 3 axes in the accelerometer device. + */ + +#ifndef _IIO_MODIFIED_DUMMY_H_ +#define _IIO_MODIFIED_DUMMY_H_ +#include + +struct iio_dummy_accel_calibscale; +struct iio_dummy_regs; + +/** + * struct iio_dummy_state - device instance specific state. + * @dac_val: cache for dac value + * @single_ended_adc_val: cache for single ended adc value + * @differential_adc_val: cache for differential adc value + * @accel_val: cache for acceleration value + * @accel_calibbias: cache for acceleration calibbias + * @accel_calibscale: cache for acceleration calibscale + * @lock: lock to ensure state is consistent + * @event_irq: irq number for event line (faked) + * @event_val: cache for event threshold value + * @event_en: cache of whether event is enabled + */ +struct iio_dummy_state { + int dac_val; + int single_ended_adc_val; + int differential_adc_val[2]; + int accel_val[3]; + int accel_calibbias; + int activity_running; + int activity_walking; + const struct iio_dummy_accel_calibscale *accel_calibscale; + struct mutex lock; + struct iio_dummy_regs *regs; + int steps_enabled; + int steps; + int height; +}; + +/** + * enum iio_modified_dummy_scan_elements - scan index enum + * @DUMMY_INDEX_VOLTAGE_0: the single ended voltage channel + * @DUMMY_INDEX_DIFFVOLTAGE_1M2: first differential channel + * @DUMMY_INDEX_DIFFVOLTAGE_3M4: second differential channel + * @DUMMY_INDEX_ACCELX: acceleration channel + * + * Enum provides convenient numbering for the scan index. + */ +enum iio_modified_dummy_scan_elements { + DUMMY_INDEX_VOLTAGE_0, + DUMMY_INDEX_DIFFVOLTAGE_1M2, + DUMMY_INDEX_DIFFVOLTAGE_3M4, + DUMMY_INDEX_ACCEL_X, + DUMMY_INDEX_ACCEL_Y, + DUMMY_INDEX_ACCEL_Z, +}; + +#endif /* _IIO_MODIFIED_DUMMY_H_ */ From 73c536df2e425f74ade8a953d17b4232d2030950 Mon Sep 17 00:00:00 2001 From: Milan Zamazal Date: Mon, 31 Jul 2023 10:17:06 +0200 Subject: [PATCH 27/40] scmi: Provide help for devices Writing `--device help' on the command line will list all the available devices and their parameters. Signed-off-by: Milan Zamazal --- crates/scmi/README.md | 1 + crates/scmi/src/devices.rs | 97 ++++++++++++++++++++++++++++++++++++- crates/scmi/src/main.rs | 6 ++- crates/scmi/src/scmi.rs | 3 ++ crates/scmi/src/vhu_scmi.rs | 4 ++ 5 files changed, 108 insertions(+), 3 deletions(-) diff --git a/crates/scmi/README.md b/crates/scmi/README.md index b7e3dcdd3..1e347d700 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 be865070f..50b8d14e9 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 c9e176e22..bc91842e8 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 d4b982b21..87534dc6e 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 20493831c..47fc1e37d 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(); From ca8f181bcdc22917a8bf7e81bbeddca82d32dbfb Mon Sep 17 00:00:00 2001 From: Milan Zamazal Date: Mon, 31 Jul 2023 16:17:10 +0200 Subject: [PATCH 28/40] scmi: Refactor device specification and creation 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 --- crates/scmi/src/devices.rs | 254 +++++++++++++++++++++++++----------- crates/scmi/src/main.rs | 18 ++- crates/scmi/src/scmi.rs | 13 +- crates/scmi/src/vhu_scmi.rs | 21 ++- 4 files changed, 199 insertions(+), 107 deletions(-) diff --git a/crates/scmi/src/devices.rs b/crates/scmi/src/devices.rs index 50b8d14e9..3ec35da58 100644 --- a/crates/scmi/src/devices.rs +++ b/crates/scmi/src/devices.rs @@ -1,42 +1,147 @@ // SPDX-FileCopyrightText: Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 -use log::debug; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::fmt::Write; use std::process::exit; -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, - }, - DeviceProperties, +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, } -type DeviceSpecification = fn() -> Box; -type NameDeviceMapping = HashMap; +#[derive(Debug, PartialEq, Eq, ThisError)] +pub enum DeviceError { + #[error("Invalid device parameter: {0}")] + InvalidProperty(String), + #[error("Missing device parameters: {}", .0.join(", "))] + MissingDeviceProperties(Vec), + #[error("Unexpected device parameters: {}", .0.join(", "))] + UnexpectedDeviceProperties(Vec), +} + +// [(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::>(), + )); + } + 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::>(), + )); + } + Ok(()) + } +} + +type MaybeDevice = Result, DeviceError>; +type DeviceConstructor = fn(&DeviceProperties) -> MaybeDevice; + +pub struct DeviceSpecification { + pub(crate) constructor: DeviceConstructor, + short_help: String, + long_help: String, + parameters_help: Vec, +} + +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::>(), + } + } +} + +type NameDeviceMapping = HashMap<&'static str, DeviceSpecification>; pub fn available_devices() -> NameDeviceMapping { let mut devices: NameDeviceMapping = HashMap::new(); - devices.insert("fake".to_owned(), FakeSensor::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, 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(); + 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(); @@ -57,7 +162,7 @@ fn devices_help() -> String { help } -pub(crate) fn print_devices_help() { +pub fn print_devices_help() { let help = devices_help(); println!("{}", help); exit(ExitCodes::Help as i32); @@ -71,9 +176,10 @@ pub struct Sensor { } impl Sensor { - const fn new(default_name: String) -> Self { + fn new(properties: &DeviceProperties, default_name: &str) -> Self { + let name = properties.get("name").unwrap_or(default_name); Self { - name: default_name, + name: name.to_owned(), enabled: false, } } @@ -83,42 +189,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 } - fn invalid_property(&self, name: &String) -> Result<(), String> { - Result::Err(format!("Invalid device option: {name}")) + fn invalid_property(&self, name: &str) -> Result<(), DeviceError> { + Result::Err(DeviceError::InvalidProperty(name.to_owned())) } - fn process_property(&mut self, name: &String, _value: &str) -> Result<(), String> { + fn process_property(&mut self, name: &str, _value: &str) -> Result<(), DeviceError> { self.invalid_property(name) } - fn configure(&mut self, properties: &DeviceProperties) -> Result<(), String> { - for (name, value) in properties { - if name == "name" { - // TODO: Check for duplicate names - self.sensor_mut().name = String::from(value); - } else { - self.process_property(name, value)?; - } - } - Ok(()) - } - fn number_of_axes(&self) -> u32 { 1 } @@ -229,22 +311,6 @@ trait SensorT: Send { struct SensorDevice(Box); impl ScmiDevice for SensorDevice { - fn configure(&mut self, properties: &DeviceProperties) -> Result<(), String> { - 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() } @@ -270,14 +336,6 @@ 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 } @@ -309,16 +367,19 @@ impl SensorT for FakeSensor { impl FakeSensor { #[allow(clippy::new_ret_no_self)] - pub fn new() -> Box { - let sensor = Sensor::new("fake".to_owned()); + 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)); - Box::new(sensor_device) + Ok(Box::new(sensor_device)) } } #[cfg(test)] mod tests { + use std::assert_eq; + use super::*; #[test] @@ -337,4 +398,47 @@ mod tests { 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(())); + } } diff --git a/crates/scmi/src/main.rs b/crates/scmi/src/main.rs index bc91842e8..04719837d 100644 --- a/crates/scmi/src/main.rs +++ b/crates/scmi/src/main.rs @@ -6,6 +6,8 @@ mod devices; mod scmi; mod vhu_scmi; +use devices::{DeviceDescription, DeviceProperties}; + use std::{ process::exit, sync::{Arc, RwLock}, @@ -38,10 +40,6 @@ struct ScmiArgs { device: Vec, } -// [(NAME, [(PROPERTY, VALUE), ...]), ...] -type DeviceDescription = Vec<(String, DeviceProperties)>; -type DeviceProperties = Vec<(String, String)>; - pub struct VuScmiConfig { socket_path: String, devices: DeviceDescription, @@ -57,7 +55,7 @@ impl TryFrom for VuScmiConfig { for d in device_iterator { let mut split = d.split(','); let name = split.next().unwrap().to_owned(); - let mut properties: DeviceProperties = vec![]; + let mut properties = vec![]; for s in split { if let Some((key, value)) = s.split('=').collect_tuple() { properties.push((key.to_owned(), value.to_owned())); @@ -65,7 +63,7 @@ impl TryFrom for VuScmiConfig { return Result::Err(format!("Invalid device {name} property format: {s}")); } } - devices.push((name, properties)); + devices.push((name, DeviceProperties::new(properties))); } Ok(Self { socket_path, @@ -146,17 +144,17 @@ mod tests { let config = VuScmiConfig::try_from(args).unwrap(); assert_eq!(config.socket_path, path); let devices = vec![ - ("dummy".to_owned(), vec![]), + ("dummy".to_owned(), DeviceProperties::new(vec![])), ( "fake".to_owned(), - vec![ + DeviceProperties::new(vec![ ("name".to_owned(), "foo".to_owned()), ("prop".to_owned(), "value".to_owned()), - ], + ]), ), ( "fake".to_owned(), - vec![("name".to_owned(), "bar".to_owned())], + DeviceProperties::new(vec![("name".to_owned(), "bar".to_owned())]), ), ]; assert_eq!(config.devices, devices); diff --git a/crates/scmi/src/scmi.rs b/crates/scmi/src/scmi.rs index 87534dc6e..ae7c5a0ce 100644 --- a/crates/scmi/src/scmi.rs +++ b/crates/scmi/src/scmi.rs @@ -11,8 +11,6 @@ use itertools::Itertools; use log::{debug, error, info}; use thiserror::Error as ThisError; -use crate::DeviceProperties; - pub type MessageHeader = u32; pub const MAX_SIMPLE_STRING_LENGTH: usize = 16; // incl. NULL terminator @@ -480,10 +478,6 @@ 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( &mut self, @@ -722,7 +716,7 @@ impl ScmiHandler { #[cfg(test)] mod tests { - use crate::devices::FakeSensor; + use crate::devices::{DeviceProperties, FakeSensor}; use super::*; @@ -866,9 +860,8 @@ mod tests { fn make_handler() -> ScmiHandler { let mut handler = ScmiHandler::new(); for i in 0..2 { - let properties = vec![("name".to_owned(), format!("fake{i}"))]; - let mut fake_sensor = FakeSensor::new(); - fake_sensor.configure(&properties).unwrap(); + let properties = DeviceProperties::new(vec![("name".to_owned(), format!("fake{i}"))]); + let fake_sensor = FakeSensor::new(&properties).unwrap(); handler.register_device(fake_sensor); } handler diff --git a/crates/scmi/src/vhu_scmi.rs b/crates/scmi/src/vhu_scmi.rs index 47fc1e37d..7821c3d0c 100644 --- a/crates/scmi/src/vhu_scmi.rs +++ b/crates/scmi/src/vhu_scmi.rs @@ -20,9 +20,7 @@ use vm_memory::{ 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::devices::{available_devices, print_devices_help, DeviceError}; use crate::scmi::{MessageHeader, ScmiHandler, ScmiRequest}; use crate::VuScmiConfig; @@ -45,7 +43,7 @@ pub enum VuScmiError { #[error("Descriptor write failed")] DescriptorWriteFailed, #[error("Error when configuring device {0}: {1}")] - DeviceConfigurationError(String, String), + DeviceConfigurationError(String, DeviceError), #[error("Failed to create new EventFd")] EventFdFailed, #[error("Failed to handle event, didn't match EPOLLIN")] @@ -103,17 +101,16 @@ impl VuScmiBackend { if name == "help" { print_devices_help(); } - match device_mapping.get(name) { - Some(constructor) => { - let mut device: Box = constructor(); - if let Err(message) = device.configure(properties) { + match device_mapping.get(name.as_str()) { + Some(specification) => match (specification.constructor)(properties) { + Ok(device) => handler.register_device(device), + Err(error) => { return Result::Err(VuScmiError::DeviceConfigurationError( name.clone(), - message, - )); + error, + )) } - handler.register_device(device); - } + }, None => return Result::Err(VuScmiError::UnknownDeviceRequested(name.clone())), }; } From 58ddb26d5b514d312f752709e53638e991593119 Mon Sep 17 00:00:00 2001 From: Milan Zamazal Date: Wed, 2 Aug 2023 19:16:52 +0200 Subject: [PATCH 29/40] scmi: Use separate modules for device implementations For a better readability once we start adding more devices. Signed-off-by: Milan Zamazal --- .../src/{devices.rs => devices/common.rs} | 76 +++---------------- crates/scmi/src/devices/fake.rs | 62 +++++++++++++++ crates/scmi/src/devices/mod.rs | 5 ++ crates/scmi/src/main.rs | 2 +- crates/scmi/src/scmi.rs | 2 +- crates/scmi/src/vhu_scmi.rs | 4 +- 6 files changed, 82 insertions(+), 69 deletions(-) rename crates/scmi/src/{devices.rs => devices/common.rs} (85%) create mode 100644 crates/scmi/src/devices/fake.rs create mode 100644 crates/scmi/src/devices/mod.rs diff --git a/crates/scmi/src/devices.rs b/crates/scmi/src/devices/common.rs similarity index 85% rename from crates/scmi/src/devices.rs rename to crates/scmi/src/devices/common.rs index 3ec35da58..b93fa85a4 100644 --- a/crates/scmi/src/devices.rs +++ b/crates/scmi/src/devices/common.rs @@ -13,9 +13,11 @@ 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, + SENSOR_READING_GET, }; +use super::fake; + enum ExitCodes { Help = 1, } @@ -42,7 +44,7 @@ impl DeviceProperties { Self(properties) } - fn get(&self, name: &str) -> Option<&str> { + pub(crate) fn get(&self, name: &str) -> Option<&str> { self.0 .iter() .find(|(n, _)| n == name) @@ -63,7 +65,7 @@ impl DeviceProperties { required_set.difference(&self.names()).copied().collect() } - fn check(&self, required: &[&str], optional: &[&str]) -> Result<(), DeviceError> { + pub(crate) fn check(&self, required: &[&str], optional: &[&str]) -> Result<(), DeviceError> { let missing = self.missing(required); if !missing.is_empty() { return Err(DeviceError::MissingDeviceProperties( @@ -90,7 +92,7 @@ impl DeviceProperties { } } -type MaybeDevice = Result, DeviceError>; +pub type MaybeDevice = Result, DeviceError>; type DeviceConstructor = fn(&DeviceProperties) -> MaybeDevice; pub struct DeviceSpecification { @@ -126,7 +128,7 @@ pub fn available_devices() -> NameDeviceMapping { devices.insert( "fake", DeviceSpecification::new( - FakeSensor::new, + fake::FakeSensor::new, "fake accelerometer", "A simple 3-axes sensor providing fake pre-defined values.", &["name: an optional name of the sensor, max. 15 characters"], @@ -171,12 +173,12 @@ pub fn print_devices_help() { // Common sensor infrastructure pub struct Sensor { - name: String, + pub name: String, enabled: bool, } impl Sensor { - fn new(properties: &DeviceProperties, default_name: &str) -> Self { + pub fn new(properties: &DeviceProperties, default_name: &str) -> Self { let name = properties.get("name").unwrap_or(default_name); Self { name: name.to_owned(), @@ -185,7 +187,7 @@ impl Sensor { } } -trait SensorT: Send { +pub trait SensorT: Send { fn sensor(&self) -> &Sensor; fn sensor_mut(&mut self) -> &mut Sensor; @@ -308,7 +310,7 @@ trait SensorT: Send { // 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); +pub struct SensorDevice(pub(crate) Box); impl ScmiDevice for SensorDevice { fn protocol(&self) -> ProtocolId { @@ -320,62 +322,6 @@ impl ScmiDevice for SensorDevice { } } -// 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; diff --git a/crates/scmi/src/devices/fake.rs b/crates/scmi/src/devices/fake.rs new file mode 100644 index 000000000..cc47d6381 --- /dev/null +++ b/crates/scmi/src/devices/fake.rs @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: Red Hat, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Fake sensor + +use crate::scmi::{DeviceResult, MessageValue, SENSOR_UNIT_METERS_PER_SECOND_SQUARED}; + +use super::common::{DeviceProperties, MaybeDevice, Sensor, SensorDevice, SensorT}; + +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)) + } +} diff --git a/crates/scmi/src/devices/mod.rs b/crates/scmi/src/devices/mod.rs new file mode 100644 index 000000000..a118d1b8b --- /dev/null +++ b/crates/scmi/src/devices/mod.rs @@ -0,0 +1,5 @@ +// SPDX-FileCopyrightText: Red Hat, Inc. +// SPDX-License-Identifier: Apache-2.0 + +pub mod common; +pub mod fake; diff --git a/crates/scmi/src/main.rs b/crates/scmi/src/main.rs index 04719837d..b61281b50 100644 --- a/crates/scmi/src/main.rs +++ b/crates/scmi/src/main.rs @@ -6,7 +6,7 @@ mod devices; mod scmi; mod vhu_scmi; -use devices::{DeviceDescription, DeviceProperties}; +use devices::common::{DeviceDescription, DeviceProperties}; use std::{ process::exit, diff --git a/crates/scmi/src/scmi.rs b/crates/scmi/src/scmi.rs index ae7c5a0ce..616e4cdf8 100644 --- a/crates/scmi/src/scmi.rs +++ b/crates/scmi/src/scmi.rs @@ -716,7 +716,7 @@ impl ScmiHandler { #[cfg(test)] mod tests { - use crate::devices::{DeviceProperties, FakeSensor}; + use crate::devices::{common::DeviceProperties, fake::FakeSensor}; use super::*; diff --git a/crates/scmi/src/vhu_scmi.rs b/crates/scmi/src/vhu_scmi.rs index 7821c3d0c..b993fe759 100644 --- a/crates/scmi/src/vhu_scmi.rs +++ b/crates/scmi/src/vhu_scmi.rs @@ -20,7 +20,7 @@ use vm_memory::{ use vmm_sys_util::epoll::EventSet; use vmm_sys_util::eventfd::{EventFd, EFD_NONBLOCK}; -use crate::devices::{available_devices, print_devices_help, DeviceError}; +use crate::devices::common::{available_devices, print_devices_help, DeviceError}; use crate::scmi::{MessageHeader, ScmiHandler, ScmiRequest}; use crate::VuScmiConfig; @@ -108,7 +108,7 @@ impl VuScmiBackend { return Result::Err(VuScmiError::DeviceConfigurationError( name.clone(), error, - )) + )); } }, None => return Result::Err(VuScmiError::UnknownDeviceRequested(name.clone())), From cbb0449d433a36f0d22045f9324bc214bb8639d2 Mon Sep 17 00:00:00 2001 From: Milan Zamazal Date: Sat, 5 Aug 2023 17:38:31 +0200 Subject: [PATCH 30/40] scmi: Refactor unit handling Make unit handling better SCMI compliant. Let's distinguish between scalar sensors and axis sensors and report the units in the appropriate SCMI commands. Also, let's change the unit type to u8 to correspond to the number of unit bits in SCMI. Signed-off-by: Milan Zamazal --- crates/scmi/src/devices/common.rs | 29 ++++++++++++++++++++++------- crates/scmi/src/devices/fake.rs | 6 +++--- crates/scmi/src/scmi.rs | 12 +++++++++--- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/crates/scmi/src/devices/common.rs b/crates/scmi/src/devices/common.rs index b93fa85a4..ac3a80ac4 100644 --- a/crates/scmi/src/devices/common.rs +++ b/crates/scmi/src/devices/common.rs @@ -10,9 +10,9 @@ 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, + self, 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, }; @@ -204,13 +204,22 @@ pub trait SensorT: Send { } fn number_of_axes(&self) -> u32 { - 1 + 0 + } + + fn format_unit(&self, axis: u32) -> u32 { + (self.unit_exponent(axis) as u32 & 0x1F) << 11 | u32::from(self.unit()) } 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 n_axes = self.number_of_axes(); + let high = if n_axes > 0 { + n_axes << 16 | 1 << 8 + } else { + self.format_unit(0) + }; let name = self.sensor().name.clone(); let values: MessageValues = vec![ // attributes low @@ -223,7 +232,13 @@ pub trait SensorT: Send { Ok(values) } - fn axis_unit(&self) -> u32; + fn unit(&self) -> u8 { + scmi::SENSOR_UNIT_UNSPECIFIED + } + + fn unit_exponent(&self, _axis: u32) -> i8 { + 0 + } fn axis_name_prefix(&self) -> String { "axis".to_owned() @@ -242,7 +257,7 @@ pub trait SensorT: Send { 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 + values.push(MessageValue::Unsigned(self.format_unit(axis))); // attributes high // Name in the recommended format, 16 bytes: let prefix = self.axis_name_prefix(); diff --git a/crates/scmi/src/devices/fake.rs b/crates/scmi/src/devices/fake.rs index cc47d6381..5cde468c8 100644 --- a/crates/scmi/src/devices/fake.rs +++ b/crates/scmi/src/devices/fake.rs @@ -3,7 +3,7 @@ // Fake sensor -use crate::scmi::{DeviceResult, MessageValue, SENSOR_UNIT_METERS_PER_SECOND_SQUARED}; +use crate::scmi::{self, DeviceResult, MessageValue}; use super::common::{DeviceProperties, MaybeDevice, Sensor, SensorDevice, SensorT}; @@ -25,11 +25,11 @@ impl SensorT for FakeSensor { 3 } - fn axis_unit(&self) -> u32 { + fn unit(&self) -> u8 { // 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 + scmi::SENSOR_UNIT_METERS_PER_SECOND_SQUARED } fn axis_name_prefix(&self) -> String { diff --git a/crates/scmi/src/scmi.rs b/crates/scmi/src/scmi.rs index 616e4cdf8..82185b6b6 100644 --- a/crates/scmi/src/scmi.rs +++ b/crates/scmi/src/scmi.rs @@ -216,7 +216,8 @@ pub const SENSOR_CONFIG_GET: MessageId = 0x9; pub const SENSOR_CONFIG_SET: MessageId = 0xA; pub const SENSOR_CONTINUOUS_UPDATE_NOTIFY: MessageId = 0xB; -pub const SENSOR_UNIT_METERS_PER_SECOND_SQUARED: u32 = 89; +pub const SENSOR_UNIT_UNSPECIFIED: u8 = 1; +pub const SENSOR_UNIT_METERS_PER_SECOND_SQUARED: u8 = 89; enum ParameterType { _SignedInt32, @@ -309,7 +310,12 @@ impl HandlerMap { BASE_DISCOVER_VENDOR, "base/discover_vendor", vec![], - |_, _| -> Response { Response::from(MessageValue::String("rust-vmm".to_string(), 16)) }, + |_, _| -> Response { + Response::from(MessageValue::String( + "rust-vmm".to_string(), + MAX_SIMPLE_STRING_LENGTH, + )) + }, ); self.bind( BASE_PROTOCOL_ID, @@ -1144,7 +1150,7 @@ mod tests { let mut description = vec![ MessageValue::Unsigned(i), MessageValue::Unsigned(0), - MessageValue::Unsigned(SENSOR_UNIT_METERS_PER_SECOND_SQUARED), + MessageValue::Unsigned(u32::from(SENSOR_UNIT_METERS_PER_SECOND_SQUARED)), MessageValue::String(name, MAX_SIMPLE_STRING_LENGTH), ]; result.append(&mut description); From 789288c372f01feb7a80d78a6c80a41d4f3f0c5d Mon Sep 17 00:00:00 2001 From: Milan Zamazal Date: Sat, 5 Aug 2023 17:42:17 +0200 Subject: [PATCH 31/40] scmi: Add sensor device initialization function Implementation accessing real sensors will need to set up the device instance. Signed-off-by: Milan Zamazal --- crates/scmi/src/devices/common.rs | 8 ++++++++ crates/scmi/src/scmi.rs | 3 +++ crates/scmi/src/vhu_scmi.rs | 10 +++++++++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/crates/scmi/src/devices/common.rs b/crates/scmi/src/devices/common.rs index ac3a80ac4..d5568c8b5 100644 --- a/crates/scmi/src/devices/common.rs +++ b/crates/scmi/src/devices/common.rs @@ -191,6 +191,10 @@ pub trait SensorT: Send { fn sensor(&self) -> &Sensor; fn sensor_mut(&mut self) -> &mut Sensor; + fn initialize(&mut self) -> Result<(), DeviceError> { + Ok(()) + } + fn protocol(&self) -> ProtocolId { SENSOR_PROTOCOL_ID } @@ -328,6 +332,10 @@ pub trait SensorT: Send { pub struct SensorDevice(pub(crate) Box); impl ScmiDevice for SensorDevice { + fn initialize(&mut self) -> Result<(), DeviceError> { + self.0.initialize() + } + fn protocol(&self) -> ProtocolId { self.0.protocol() } diff --git a/crates/scmi/src/scmi.rs b/crates/scmi/src/scmi.rs index 82185b6b6..87d678928 100644 --- a/crates/scmi/src/scmi.rs +++ b/crates/scmi/src/scmi.rs @@ -11,6 +11,8 @@ use itertools::Itertools; use log::{debug, error, info}; use thiserror::Error as ThisError; +use crate::devices::common::DeviceError; + pub type MessageHeader = u32; pub const MAX_SIMPLE_STRING_LENGTH: usize = 16; // incl. NULL terminator @@ -484,6 +486,7 @@ pub enum ScmiDeviceError { } pub trait ScmiDevice: Send { + fn initialize(&mut self) -> Result<(), DeviceError>; fn protocol(&self) -> ProtocolId; fn handle( &mut self, diff --git a/crates/scmi/src/vhu_scmi.rs b/crates/scmi/src/vhu_scmi.rs index b993fe759..3a2559706 100644 --- a/crates/scmi/src/vhu_scmi.rs +++ b/crates/scmi/src/vhu_scmi.rs @@ -103,7 +103,15 @@ impl VuScmiBackend { } match device_mapping.get(name.as_str()) { Some(specification) => match (specification.constructor)(properties) { - Ok(device) => handler.register_device(device), + Ok(mut device) => { + if let Err(error) = device.initialize() { + return Result::Err(VuScmiError::DeviceConfigurationError( + name.clone(), + error, + )); + } + handler.register_device(device); + } Err(error) => { return Result::Err(VuScmiError::DeviceConfigurationError( name.clone(), From c1637e94b81b2f8ee69a44fc0031daecbd7d5287 Mon Sep 17 00:00:00 2001 From: Milan Zamazal Date: Mon, 7 Aug 2023 20:52:53 +0200 Subject: [PATCH 32/40] scmi: Use new_device for ScmiDevice constructors Instead of `new', which should be reserved for direct constructors. Signed-off-by: Milan Zamazal --- crates/scmi/src/devices/common.rs | 2 +- crates/scmi/src/devices/fake.rs | 3 +-- crates/scmi/src/scmi.rs | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/scmi/src/devices/common.rs b/crates/scmi/src/devices/common.rs index d5568c8b5..c4db9caef 100644 --- a/crates/scmi/src/devices/common.rs +++ b/crates/scmi/src/devices/common.rs @@ -128,7 +128,7 @@ pub fn available_devices() -> NameDeviceMapping { devices.insert( "fake", DeviceSpecification::new( - fake::FakeSensor::new, + fake::FakeSensor::new_device, "fake accelerometer", "A simple 3-axes sensor providing fake pre-defined values.", &["name: an optional name of the sensor, max. 15 characters"], diff --git a/crates/scmi/src/devices/fake.rs b/crates/scmi/src/devices/fake.rs index 5cde468c8..bc760b83a 100644 --- a/crates/scmi/src/devices/fake.rs +++ b/crates/scmi/src/devices/fake.rs @@ -51,8 +51,7 @@ impl SensorT for FakeSensor { } impl FakeSensor { - #[allow(clippy::new_ret_no_self)] - pub fn new(properties: &DeviceProperties) -> MaybeDevice { + pub fn new_device(properties: &DeviceProperties) -> MaybeDevice { properties.check(&[], &["name"])?; let sensor = Sensor::new(properties, "fake"); let fake_sensor = Self { sensor, value: 0 }; diff --git a/crates/scmi/src/scmi.rs b/crates/scmi/src/scmi.rs index 87d678928..2c525d338 100644 --- a/crates/scmi/src/scmi.rs +++ b/crates/scmi/src/scmi.rs @@ -870,7 +870,7 @@ mod tests { let mut handler = ScmiHandler::new(); for i in 0..2 { let properties = DeviceProperties::new(vec![("name".to_owned(), format!("fake{i}"))]); - let fake_sensor = FakeSensor::new(&properties).unwrap(); + let fake_sensor = FakeSensor::new_device(&properties).unwrap(); handler.register_device(fake_sensor); } handler From 458f168639605216843bf3fdc2bca9020d428b39 Mon Sep 17 00:00:00 2001 From: Milan Zamazal Date: Sat, 5 Aug 2023 17:46:16 +0200 Subject: [PATCH 33/40] 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 --- crates/scmi/README.md | 3 +- crates/scmi/src/devices/common.rs | 56 ++- crates/scmi/src/devices/iio.rs | 782 ++++++++++++++++++++++++++++++ crates/scmi/src/devices/mod.rs | 1 + crates/scmi/src/scmi.rs | 26 +- crates/scmi/src/vhu_scmi.rs | 158 +++--- 6 files changed, 932 insertions(+), 94 deletions(-) create mode 100644 crates/scmi/src/devices/iio.rs diff --git a/crates/scmi/README.md b/crates/scmi/README.md index 1e347d700..4c65023ef 100644 --- a/crates/scmi/README.md +++ b/crates/scmi/README.md @@ -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. diff --git a/crates/scmi/src/devices/common.rs b/crates/scmi/src/devices/common.rs index c4db9caef..11d4b08f8 100644 --- a/crates/scmi/src/devices/common.rs +++ b/crates/scmi/src/devices/common.rs @@ -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), #[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), + } } } diff --git a/crates/scmi/src/devices/iio.rs b/crates/scmi/src/devices/iio.rs new file mode 100644 index 000000000..e5dcfe902 --- /dev/null +++ b/crates/scmi/src/devices/iio.rs @@ -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, +} + +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 = 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(path: &Path) -> Result, 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 { + 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, 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) { + 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( + &self, + path: &OsStr, + name: &str, + ) -> Result, 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 = read_number_from_file(value_path)?; + if value.is_some() { + return Ok(value); + } + } + Ok(None) + } + + fn read_axis_offset(&self, path: &OsStr) -> Result, ScmiDeviceError> { + self.read_axis_file(path, "offset") + } + + fn read_axis_scale(&self, path: &OsStr) -> Result, ScmiDeviceError> { + self.read_axis_file(path, "scale") + } + + fn read_axis(&self, axis: &Axis) -> Result { + 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 = 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 = 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) -> 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) -> IIOSensor { + let properties = device_properties(path, channel, name); + IIOSensor::new(&properties).unwrap() + } + + fn make_iio_sensor( + directory: &IIODirectory, + channel: String, + name: Option, + ) -> 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, + ) -> MaybeDevice { + let properties = device_properties(path, channel, name); + IIOSensor::new_device(&properties) + } + + fn make_scmi_sensor( + directory: &IIODirectory, + channel: String, + name: Option, + ) -> Box { + 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)); + } + } + } +} diff --git a/crates/scmi/src/devices/mod.rs b/crates/scmi/src/devices/mod.rs index a118d1b8b..a776667f7 100644 --- a/crates/scmi/src/devices/mod.rs +++ b/crates/scmi/src/devices/mod.rs @@ -3,3 +3,4 @@ pub mod common; pub mod fake; +pub mod iio; diff --git a/crates/scmi/src/scmi.rs b/crates/scmi/src/scmi.rs index 2c525d338..5bcca1e5e 100644 --- a/crates/scmi/src/scmi.rs +++ b/crates/scmi/src/scmi.rs @@ -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) + } }, } } diff --git a/crates/scmi/src/vhu_scmi.rs b/crates/scmi/src/vhu_scmi.rs index 3a2559706..45d9cd0a2 100644 --- a/crates/scmi/src/vhu_scmi.rs +++ b/crates/scmi/src/vhu_scmi.rs @@ -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] From b84bf46d68dc7f4b933daa9df2df07ef7a82be92 Mon Sep 17 00:00:00 2001 From: Milan Zamazal Date: Tue, 8 Aug 2023 12:58:06 +0200 Subject: [PATCH 34/40] scmi: Add scale & offset to the accelerator in the kernel To be able to test scaling in guests running on hosts with the dummy IIO module. Signed-off-by: Milan Zamazal --- crates/scmi/kernel/iio-dummy/README.md | 1 + crates/scmi/kernel/iio-dummy/iio_modified_dummy.c | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/crates/scmi/kernel/iio-dummy/README.md b/crates/scmi/kernel/iio-dummy/README.md index ed8710117..59e16f46d 100644 --- a/crates/scmi/kernel/iio-dummy/README.md +++ b/crates/scmi/kernel/iio-dummy/README.md @@ -14,6 +14,7 @@ Otherwise, this alternative is provided with the following changes: - Simplified Makefile for out of tree compilation. - The accelerometer has three axes instead of just one. +- The Y axis of the accelerometer has offset and scale. Of course, you can modified it further for your liking if needed. diff --git a/crates/scmi/kernel/iio-dummy/iio_modified_dummy.c b/crates/scmi/kernel/iio-dummy/iio_modified_dummy.c index 750448901..dd5e59468 100644 --- a/crates/scmi/kernel/iio-dummy/iio_modified_dummy.c +++ b/crates/scmi/kernel/iio-dummy/iio_modified_dummy.c @@ -16,6 +16,7 @@ * * - Dropped conditional parts. * - Use 3 axes in the accelerometer device. + * - Define offset and scale for some of the accelerometer axes. */ #include #include @@ -184,6 +185,9 @@ static const struct iio_chan_spec iio_dummy_channels[] = { /* Channel 2 is use for modifiers */ .channel2 = IIO_MOD_Y, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_CALIBSCALE) | BIT(IIO_CHAN_INFO_CALIBBIAS), .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ), @@ -352,6 +356,15 @@ static int iio_dummy_read_raw(struct iio_dev *indio_dev, *val2 = 1344; ret = IIO_VAL_INT_PLUS_NANO; } + break; + case IIO_ACCEL: + switch(chan->scan_index) { + case DUMMY_INDEX_ACCEL_Y: + *val = 0; + *val2 = 1344; + break; + } + ret = IIO_VAL_INT_PLUS_MICRO; break; default: break; From 847b3f44a6ccec291d59ad2c40c9971550bce866 Mon Sep 17 00:00:00 2001 From: Milan Zamazal Date: Wed, 9 Aug 2023 18:50:11 +0200 Subject: [PATCH 35/40] scmi: Add source code docstrings Signed-off-by: Milan Zamazal --- crates/scmi/README.md | 3 + crates/scmi/src/devices/common.rs | 136 ++++++++++++++++++++++++++++++ crates/scmi/src/devices/fake.rs | 8 +- crates/scmi/src/devices/iio.rs | 67 ++++++++++++--- crates/scmi/src/devices/mod.rs | 7 ++ crates/scmi/src/main.rs | 30 +++++++ crates/scmi/src/scmi.rs | 70 +++++++++++++++ crates/scmi/src/vhu_scmi.rs | 15 ++-- 8 files changed, 318 insertions(+), 18 deletions(-) diff --git a/crates/scmi/README.md b/crates/scmi/README.md index 4c65023ef..5b9727594 100644 --- a/crates/scmi/README.md +++ b/crates/scmi/README.md @@ -67,6 +67,9 @@ The currently supported SCMI protocols are: Basically only the mandatory and necessary parts of the protocols are implemented. +See source code (`scmi` crate) documentation for details and how to +add more protocols, host device bindings or other functionality. + ## Kernel support for testing `kernel` subdirectory contains diff --git a/crates/scmi/src/devices/common.rs b/crates/scmi/src/devices/common.rs index 11d4b08f8..d79dcd144 100644 --- a/crates/scmi/src/devices/common.rs +++ b/crates/scmi/src/devices/common.rs @@ -1,6 +1,14 @@ // SPDX-FileCopyrightText: Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 +//! Common functionality for SCMI bindings to host devices. +//! +//! A new kind of devices can be added in [available_devices] using +//! [DeviceSpecification::new] calls. +//! +//! The module also defines common infrastructure to provide sensor devices to +//! SCMI, see [SensorT]. + use std::collections::{HashMap, HashSet}; use std::ffi::OsString; use std::fmt::Write; @@ -19,6 +27,8 @@ use crate::scmi::{ use super::{fake, iio}; +/// Enumeration of vhost-device-scmi exit codes. +// TODO: It should be better placed elsewhere but it's currently used only here. enum ExitCodes { Help = 1, } @@ -100,14 +110,36 @@ impl DeviceProperties { pub type MaybeDevice = Result, DeviceError>; type DeviceConstructor = fn(&DeviceProperties) -> MaybeDevice; +/// Definition of a device kind. +/// +/// Use [DeviceSpecification::new] to create it. pub struct DeviceSpecification { + /// Function to call to create the device. + /// + /// The device properties are those provided on the command line by the + /// user. pub(crate) constructor: DeviceConstructor, + /// Short description of the device. + /// + /// Single line, not a complete sentence. short_help: String, + /// Long description of the device. + /// + /// Complete sentences, can span multiple lines. long_help: String, + /// Description of the device parameters available to the user. + /// + /// Each item in the vector corresponds to a single parameter description + /// and should start with the parameter name and a followup colon. parameters_help: Vec, } impl DeviceSpecification { + /// Creates a new device specification. + /// + /// See [DeviceSpecification] for the meaning of the arguments. + /// The device specification must be used in [available_devices] to + /// actually add the device. fn new( constructor: DeviceConstructor, short_help: &str, @@ -126,8 +158,16 @@ impl DeviceSpecification { } } +/// Mapping of device identifiers (names) to device specifications. +/// +/// The string keys correspond to device identifiers specified on the command +/// line. type NameDeviceMapping = HashMap<&'static str, DeviceSpecification>; +/// Creates device mapping and adds all the supported devices to it. +/// +/// If you want to introduce a new kind of host device bindings, insert a +/// device identifier + [DeviceSpecification] to [NameDeviceMapping] here. pub fn available_devices() -> NameDeviceMapping { let mut devices: NameDeviceMapping = HashMap::new(); devices.insert( @@ -190,9 +230,17 @@ pub fn print_devices_help() { // Common sensor infrastructure +/// Basic information about the sensor. +/// +/// It is typically used as a field in structs implementing sensor devices. #[derive(Debug)] pub struct Sensor { + /// The sensor name (possibly truncated) as reported to the guest. pub name: String, + /// Whether the sensor is enabled. + /// + /// Sensors can be enabled and disabled using SCMI. [Sensor]s created + /// using [Sensor::new] are disabled initially. enabled: bool, } @@ -206,34 +254,92 @@ impl Sensor { } } +/// Common base that sensor devices can use to simplify their implementation. +/// +/// To add a new kind of sensor bindings, you must implement +/// [crate::scmi::ScmiDevice], define [DeviceSpecification] and add it to +/// [NameDeviceMapping] created in [available_devices]. You can do it fully +/// yourself or use this trait to simplify the implementation. +/// +/// The trait is typically used as follows: +/// +/// ```rust +/// struct MySensor { +/// sensor: Sensor, +/// // other fields as needed +/// } +/// +/// impl SensorT for MySensor { +/// // provide trait functions implementation as needed +/// } +/// +/// impl MySensor { +/// pub fn new_device(properties: &DeviceProperties) -> MaybeDevice { +/// check_device_properties(properties, &[], &["name"])?; +/// let sensor = Sensor::new(properties, "mydevice"); +/// let my_sensor = MySensor { sensor }; +/// let sensor_device = SensorDevice(Box::new(my_sensor)); +/// Ok(Box::new(sensor_device)) +/// } +/// } +/// ``` +/// +/// See [crate::devices::fake::FakeSensor] implementation for an example. pub trait SensorT: Send { + /// Returns the inner [Sensor] instance, immutable. fn sensor(&self) -> &Sensor; + /// Returns the inner [Sensor] instance, mutable. fn sensor_mut(&mut self) -> &mut Sensor; + /// Performs any non-default initialization on the sensor. + /// + /// If the initialization fails, a corresponding error message is + /// returned. fn initialize(&mut self) -> Result<(), DeviceError> { Ok(()) } + /// Returns the id of the SCMI protocol used to communicate with the + /// sensor. + /// + /// Usually no need to redefine this. fn protocol(&self) -> ProtocolId { SENSOR_PROTOCOL_ID } + /// Returns an error message about invalid property `name`. + /// + /// Usually no need to redefine this. fn invalid_property(&self, name: &str) -> Result<(), DeviceError> { Result::Err(DeviceError::InvalidProperty(name.to_owned())) } + /// Processes a device property specified on the command line. + /// + /// The function is called on all the device properties from the command line. fn process_property(&mut self, name: &str, _value: &str) -> Result<(), DeviceError> { self.invalid_property(name) } + /// Returns the number of axes of the given sensor. + /// + /// If the sensor provides just a scalar value, 0 must be returned (the + /// default return value here). Otherwise a non-zero value must be + /// returned, even for vector sensors with a single access. fn number_of_axes(&self) -> u32 { 0 } + /// Formats the unit of the given `axis` for SCMI protocol. + /// + /// Usually no need to redefine this. fn format_unit(&self, axis: u32) -> u32 { (self.unit_exponent(axis) as u32 & 0x1F) << 11 | u32::from(self.unit()) } + /// Returns SCMI description of the sensor. + /// + /// Usually no need to redefine this. fn description_get(&self) -> DeviceResult { // Continuous update required by Linux SCMI IIO driver let low = 1 << 30; @@ -255,18 +361,26 @@ pub trait SensorT: Send { Ok(values) } + /// Returns the SCMI unit of the sensor. fn unit(&self) -> u8 { scmi::SENSOR_UNIT_UNSPECIFIED } + /// Returns the decadic exponent to apply to the sensor values. fn unit_exponent(&self, _axis: u32) -> i8 { 0 } + /// Returns the prefix of axes names. + /// + /// Usually no need to redefine this. fn axis_name_prefix(&self) -> String { "axis".to_owned() } + /// Returns the suffix of the given axis. + /// + /// Usually no need to redefine this. fn axis_name_suffix(&self, axis: u32) -> char { match axis { 0 => 'X', @@ -276,6 +390,9 @@ pub trait SensorT: Send { } } + /// Returns the SCMI description of the given axis. + /// + /// Usually no need to redefine this. fn axis_description(&self, axis: u32) -> Vec { let mut values = vec![]; values.push(MessageValue::Unsigned(axis)); // axis id @@ -292,11 +409,19 @@ pub trait SensorT: Send { values } + /// Returns the SCMI configuration of the sensor. + /// + /// The default implementation here returns just whether the sensor is + /// enabled or not. fn config_get(&self) -> DeviceResult { let config = u32::from(self.sensor().enabled); Ok(vec![MessageValue::Unsigned(config)]) } + /// Processes the SCMI configuration of the sensor. + /// + /// The default implementation here permits and implements only enabling + /// and disabling the sensor. fn config_set(&mut self, config: u32) -> DeviceResult { if config & 0xFFFFFFFE != 0 { return Result::Err(ScmiDeviceError::UnsupportedRequest); @@ -306,8 +431,19 @@ pub trait SensorT: Send { Ok(vec![]) } + /// Returns SCMI reading of the sensor values. + /// + /// It is a sequence of [MessageValue::Unsigned] values, 4 of them for each + /// sensor axis. See the SCMI standard for the exact specification of the + /// result. fn reading_get(&mut self) -> DeviceResult; + /// Handles the given protocol message with the given parameters. + /// + /// Usually no need to redefine this, unless more than the basic + /// functionality is needed, in which case it would be probably better to + /// enhance this trait with additional functions and improved + /// implementation. fn handle(&mut self, message_id: MessageId, parameters: &[MessageValue]) -> DeviceResult { match message_id { SENSOR_DESCRIPTION_GET => self.description_get(), diff --git a/crates/scmi/src/devices/fake.rs b/crates/scmi/src/devices/fake.rs index bc760b83a..c0936b424 100644 --- a/crates/scmi/src/devices/fake.rs +++ b/crates/scmi/src/devices/fake.rs @@ -1,7 +1,13 @@ // SPDX-FileCopyrightText: Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 -// Fake sensor +//! Fake sensor implementation. +//! +//! The fake sensor is completely implemented here rather than bound to a host +//! device. It emulates a dummy accelerometer device that increments an axis +//! reading value on each its retrieval. Useful for initial testing and +//! arranging SCMI virtualization setup without the need to bind real host +//! devices. use crate::scmi::{self, DeviceResult, MessageValue}; diff --git a/crates/scmi/src/devices/iio.rs b/crates/scmi/src/devices/iio.rs index e5dcfe902..4b5522e20 100644 --- a/crates/scmi/src/devices/iio.rs +++ b/crates/scmi/src/devices/iio.rs @@ -1,7 +1,13 @@ // SPDX-FileCopyrightText: Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 -// Industrial I/O sensors +//! Industrial I/O (IIO) sensors bindings. +//! +//! Basic functionality for exposing `/sys/bus/iio/devices/` stuff as guest +//! SCMI devices. Only some typical cases are supported. If you want more +//! functionality, you must enhance the implementation here. +//! +//! For some entry points, see [IIOSensor] and [Axis]. use std::cmp::{max, min}; use std::ffi::{OsStr, OsString}; @@ -16,14 +22,30 @@ use crate::scmi::{self, DeviceResult, MessageValue, ScmiDeviceError, MAX_SIMPLE_ use super::common::{DeviceError, DeviceProperties, MaybeDevice, Sensor, SensorDevice, SensorT}; +/// Information about units used by the given Linux IIO channel. struct UnitMapping<'a> { + /// IIO sysfs channel prefix, e.g. "in_accel". channel: &'a str, + /// One of the SCMI unit constants from [crate::scmi] (enum is not used to + /// avoid type conversions everywhere). unit: u8, + /// Decadic exponent to be used to convert the given unit to the SCMI unit. + /// For example, the exponent is 0 for no conversion, -3 to convert + /// milliamps here to amps in SCMI, or 3 to convert kilopascals here to + /// pascals in SCMI. unit_exponent: i8, // max. 5 bits actually } -// Incomplete, just a sample. -// TODO: Make some macro(s) for this. +/// Specification of IIO channel units. +/// +/// Based on +/// . +/// Not everything from there is present -- channels here with more complicated +/// unit transformations (beyond using a decadic exponent; e.g. degrees to +/// radians or units not defined in SCMI) are omitted. If an IIO channel +/// doesn't have unit specification here, it can be still used by the unit +/// reported in SCMI will be [crate::scmi::SENSOR_UNIT_UNSPECIFIED]. +// TODO: Make some macro(s) for this? const UNIT_MAPPING: &[UnitMapping] = &[ UnitMapping { channel: "in_accel", @@ -137,23 +159,46 @@ const UNIT_MAPPING: &[UnitMapping] = &[ }, ]; +/// Representation of an IIO channel axis. +/// +/// Used also for scalar values. #[derive(PartialEq, Debug)] struct Axis { + /// Full sysfs path to the axis value file stripped of "_raw". path: OsString, // without "_raw" suffix + /// Axis unit exponent, see [UnitMapping::unit_exponent] and [UNIT_MAPPING]. unit_exponent: i8, + /// Additional exponent to apply to the axis values. It is computed from + /// the axis value scaling (see [IIOSensor::custom_exponent] to provide a + /// sufficiently accurate SCMI value that is represented by an integer (not + /// a float) + decadic exponent. custom_exponent: i8, } +/// Particular IIO sensor specification. +/// +/// An IIO sensor is specified by an IIO sysfs device directory and a channel +/// prefix within the directory (i.e. more devices can be defined for a single +/// IIO device directory). All other information about the sensor is retrieved +/// from the device directory and from [UNIT_MAPPING]. #[derive(Debug)] -pub(crate) struct IIOSensor { +pub struct IIOSensor { + /// Common sensor instance. sensor: Sensor, - // Full /sys path to the device directory + /// Full sysfs path to the device directory. + /// + /// Provided by the user. path: OsString, - // Prefix of the device type in the device directory, e.g. "in_accel" + /// Prefix of the device type in the device directory, e.g. "in_accel". + /// + /// Provided by the user. channel: OsString, - // Whether the sensor is scalar or has one or more axes + /// Whether the sensor is scalar or has one or more axes. + /// + /// Determined automatically by looking for presence of `*_[xyz]_raw` files + /// with the given channel prefix. scalar: bool, - // Paths to "_raw" files + /// Axes descriptions, see [Axis] for more details. axes: Vec, } @@ -282,10 +327,10 @@ fn read_number_from_file(path: &Path) -> Result, ScmiDevic impl IIOSensor { #[allow(clippy::new_ret_no_self)] - pub fn new(properties: &DeviceProperties) -> Result { + pub fn new(properties: &DeviceProperties) -> Result { properties.check(&["path", "channel"], &["name"])?; let sensor = Sensor::new(properties, "iio"); - Ok(IIOSensor { + Ok(Self { sensor, path: OsString::from(properties.get("path").unwrap()), channel: OsString::from(properties.get("channel").unwrap()), @@ -295,7 +340,7 @@ impl IIOSensor { } pub fn new_device(properties: &DeviceProperties) -> MaybeDevice { - let iio_sensor = IIOSensor::new(properties)?; + let iio_sensor = Self::new(properties)?; let sensor_device = SensorDevice(Box::new(iio_sensor)); Ok(Box::new(sensor_device)) } diff --git a/crates/scmi/src/devices/mod.rs b/crates/scmi/src/devices/mod.rs index a776667f7..5b7ea6138 100644 --- a/crates/scmi/src/devices/mod.rs +++ b/crates/scmi/src/devices/mod.rs @@ -1,6 +1,13 @@ // SPDX-FileCopyrightText: Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 +//! Implementation of SCMI bindings to host devices. +//! +//! The general infrastructure is implemented in [crate::devices::common] module. +//! Access to particular kinds of devices is implemented in the other modules: +//! - [crate::devices::fake] provides a fake sensor. +//! - [crate::devices::iio] implements access to industrial I/O (IIO) devices. + pub mod common; pub mod fake; pub mod iio; diff --git a/crates/scmi/src/main.rs b/crates/scmi/src/main.rs index b61281b50..60b5072e1 100644 --- a/crates/scmi/src/main.rs +++ b/crates/scmi/src/main.rs @@ -2,6 +2,36 @@ // SPDX-License-Identifier: Apache-2.0 // Based on implementation of other devices here, Copyright by Linaro Ltd. +//! vhost-user daemon implementation for +//! [System Control and Management Interface](https://developer.arm.com/Architectures/System%20Control%20and%20Management%20Interface) +//! (SCMI). +//! +//! Currently, the mandatory parts of the following SCMI protocols are implemented: +//! +//! - base +//! - sensor management +//! +//! As for sensor management, support for industrial I/O (IIO) Linux devices +//! and a fake sensor device is implemented. +//! +//! The daemon listens on a socket that is specified using `--socket-path` +//! command line option. Usually at least one exposed device is specified, +//! which is done using `--device` command line option. It can be used more +//! than once, for different devices. `--device help` lists the available +//! devices and their options. +//! +//! The daemon normally logs info and higher messages to the standard error +//! output. To log more messages, you can set `RUST_LOG` environment variable, +//! e.g. to `debug`. +//! +//! Here is an example command line invocation of the daemon: +//! +//! ```sh +//! RUST_LOG=debug vhost-device-scmi \ +//! --socket ~/tmp/scmi.sock \ +//! --device iio,path=/sys/bus/iio/devices/iio:device0,channel=in_accel +//! ``` + mod devices; mod scmi; mod vhu_scmi; diff --git a/crates/scmi/src/scmi.rs b/crates/scmi/src/scmi.rs index 5bcca1e5e..365352872 100644 --- a/crates/scmi/src/scmi.rs +++ b/crates/scmi/src/scmi.rs @@ -1,6 +1,15 @@ // SPDX-FileCopyrightText: Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 +//! Implementation of SCMI and some of its protocols. +//! +//! This module implements SCMI infrastructure and some of the SCMI protocols. +//! See [HandlerMap::new] how to add support for another SCMI protocol or to add +//! more functionality to an already implemented SCMI protocol. +//! +//! If you want to add new devices (e.g. SCMI bindings to some kinds of host +//! devices), see [crate::devices] modules. + use std::{ cmp::min, collections::HashMap, @@ -17,6 +26,9 @@ pub type MessageHeader = u32; pub const MAX_SIMPLE_STRING_LENGTH: usize = 16; // incl. NULL terminator +/// Wrapper around SCMI values of the basic types SCMI defines. +/// +/// Everything communicating to/from SCMI must be composed of them. // SCMI specification talks about Le32 parameter and return values. // VirtIO SCMI specification talks about u8 SCMI values. // Let's stick with SCMI specification for implementation simplicity. @@ -38,6 +50,9 @@ impl MessageValue { pub type MessageValues = Vec; +/// Enumeration of SCMI message types, mapped to the corresponding SCMI codes. +/// +/// The only one we currently support is [MessageType::Command]. #[derive(Debug, PartialEq)] enum MessageType { // 4-bit unsigned integer @@ -48,6 +63,7 @@ pub type MessageId = u8; pub type ProtocolId = u8; type NParameters = u8; +/// Mapping of return values to SCMI return status codes. #[derive(Clone, Copy)] // Not all the codes are currently used but let's have a complete return status // enumeration from the SCMI specification here. @@ -75,6 +91,11 @@ impl ReturnStatus { } } +/// Representation of [MessageValue] sequence used to construct [ScmiResponse]. +/// +/// The sequence includes the response code (see the helper constructors for +/// adding them) but it doesn't include the SCMI message header. The header is +/// added in [ScmiResponse]. struct Response { values: MessageValues, } @@ -105,6 +126,9 @@ impl From<&MessageValues> for Response { } } +/// SCMI response in SCMI representation byte. +/// +/// Use [ScmiResponse::from] function to construct it. #[derive(Debug)] pub struct ScmiResponse { header: MessageHeader, @@ -112,6 +136,8 @@ pub struct ScmiResponse { } impl ScmiResponse { + /// Creates [ScmiResponse] instance from the (unchanged) SCMI request + /// `header` and a [Response] composed of [MessageValue]s. fn from(header: MessageHeader, response: Response) -> Self { debug!("response arguments: {:?}", response.values); let mut ret_bytes: Vec = vec![]; @@ -155,6 +181,10 @@ impl ScmiResponse { } } +/// Representation of a parsed SCMI request. +/// +/// Use [ScmiRequest::get_unsigned] and [ScmiRequest::get_usize] functions to +/// retrieve its parameters as `u32` and `usize` values respectively. pub struct ScmiRequest { header: MessageHeader, // 32-bit unsigned integer, split below: message_id: MessageId, // bits 7:0 @@ -246,12 +276,20 @@ enum ParameterType { type ParameterSpecification = Vec; type HandlerFunction = fn(&ScmiHandler, &ScmiRequest) -> Response; + +/// Specification of an SCMI message handler. +/// +/// No need to create this directly, use [HandlerMap::bind] to add message +/// handlers. struct HandlerInfo { name: String, parameters: ParameterSpecification, function: HandlerFunction, } +/// Mapping of SCMI protocols and messages to handlers. +/// +/// See [HandlerMap::new] and [HandlerMap::bind] how to add new handlers. // HandlerMap layout is suboptimal but let's prefer simplicity for now. struct HandlerMap(HashMap<(ProtocolId, MessageId), HandlerInfo>); @@ -271,6 +309,13 @@ impl HandlerMap { self.0.get(&(protocol_id, message_id)) } + /// Add a handler for a SCMI protocol message. + /// + /// `protocol_id` & `message_id` specify the corresponding SCMI protocol + /// and message codes identifying the request to handle using `function`. + /// Expected SCMI parameters (unsigned or signed 32-bit integers) are + /// specified in `parameters`. `name` serves just for identifying the + /// handlers easily in logs and error messages. fn bind( &mut self, protocol_id: ProtocolId, @@ -295,6 +340,7 @@ impl HandlerMap { ); } + /// Adds SCMI base protocol handlers. fn make_base_handlers(&mut self) { self.bind( BASE_PROTOCOL_ID, @@ -353,6 +399,7 @@ impl HandlerMap { ); } + /// Adds SCMI sensor protocol handlers. fn make_sensor_handlers(&mut self) { self.bind( SENSOR_PROTOCOL_ID, @@ -505,9 +552,24 @@ pub enum ScmiDeviceError { UnsupportedRequest, } +/// The highest representation of an SCMI device. +/// +/// A device is an entity bound to a SCMI protocol that can take an SCMI +/// message id and parameters and respond with [MessageValue]s. See +/// [crate::devices] how devices are defined and created. pub trait ScmiDevice: Send { + /// Initializes the device (if needed). + /// + /// If any error occurs preventing the operation of the device, a + /// corresponding error message must be returned. fn initialize(&mut self) -> Result<(), DeviceError>; + /// Returns the SCMI protocol id that the device is attached to. fn protocol(&self) -> ProtocolId; + /// Handles an SCMI request. + /// + /// `message_id` is an SCMI message id from the + /// given SCMI protocol and `parameters` are the SCMI request parameters + /// already represented as [MessageValue]s. fn handle( &mut self, message_id: MessageId, @@ -517,6 +579,7 @@ pub trait ScmiDevice: Send { type DeviceList = Vec>; +/// Mapping of SCMI protocols to devices that can handle them. struct DeviceMap(Arc>>); impl DeviceMap { @@ -572,6 +635,13 @@ pub struct ScmiHandler { } impl ScmiHandler { + /// Creates an instance for handling SCMI requests. + /// + /// The function also defines handlers for particular SCMI protocols. + /// It creates a [HandlerMap] and then adds SCMI message handlers to + /// it using [HandlerMap::bind] function. This is the place (i.e. the + /// functions called from here) where to add bindings for SCMI protocols and + /// messages. pub fn new() -> Self { Self { handlers: HandlerMap::new(), diff --git a/crates/scmi/src/vhu_scmi.rs b/crates/scmi/src/vhu_scmi.rs index 45d9cd0a2..fd8b443f1 100644 --- a/crates/scmi/src/vhu_scmi.rs +++ b/crates/scmi/src/vhu_scmi.rs @@ -2,6 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 // Based on https://github.com/rust-vmm/vhost-device, Copyright by Linaro Ltd. +//! General part of the vhost-user SCMI backend. Nothing very different from +//! the other rust-vmm backends. + use log::{debug, error, warn}; use std::io; use std::io::Result as IoResult; @@ -82,14 +85,14 @@ pub struct VuScmiBackend { event_idx: bool, pub exit_event: EventFd, mem: Option>, - // Event vring and descriptors serve for asynchronous responses and notifications. - // They are obtained from the driver and we store them here for later use. - // (We currently don't implement asynchronous responses or notifications but we support - // the event queue because the Linux VIRTIO SCMI driver seems to be unhappy if it is not - // present. And it doesn't harm to be ready for possible event queue use in future.) + /// Event vring and descriptors serve for asynchronous responses and notifications. + /// They are obtained from the driver and we store them here for later use. + /// (We currently don't implement asynchronous responses or notifications but we support + /// the event queue because the Linux VIRTIO SCMI driver seems to be unhappy if it is not + /// present. And it doesn't harm to be ready for possible event queue use in future.) event_vring: Option, event_descriptors: Vec>>, - // The abstraction of request handling, with all the needed information stored inside. + /// The abstraction of request handling, with all the needed information stored inside. scmi_handler: ScmiHandler, } From 0261d315ac1537dfc3af9fdc70554a2ed99b4385 Mon Sep 17 00:00:00 2001 From: Milan Zamazal Date: Mon, 14 Aug 2023 17:13:41 +0200 Subject: [PATCH 36/40] scmi: Improve command line processing When a device help is requested with `-d help', the socket argument is still required. This patch: - replaces `-d help' with --help-devices; - stops requiring the socket argument in such a case; - prints help in case of command line parsing errors. Signed-off-by: Milan Zamazal --- crates/scmi/README.md | 2 +- crates/scmi/src/devices/common.rs | 9 +--- crates/scmi/src/main.rs | 75 ++++++++++++++++++++++--------- crates/scmi/src/vhu_scmi.rs | 5 +-- 4 files changed, 56 insertions(+), 35 deletions(-) diff --git a/crates/scmi/README.md b/crates/scmi/README.md index 5b9727594..1d0c5501d 100644 --- a/crates/scmi/README.md +++ b/crates/scmi/README.md @@ -31,7 +31,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. + Use `--help-devices` 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/common.rs b/crates/scmi/src/devices/common.rs index d79dcd144..ec51b2cf5 100644 --- a/crates/scmi/src/devices/common.rs +++ b/crates/scmi/src/devices/common.rs @@ -12,7 +12,6 @@ use std::collections::{HashMap, HashSet}; use std::ffi::OsString; use std::fmt::Write; -use std::process::exit; use itertools::Itertools; use log::debug; @@ -27,12 +26,7 @@ use crate::scmi::{ use super::{fake, iio}; -/// Enumeration of vhost-device-scmi exit codes. -// TODO: It should be better placed elsewhere but it's currently used only here. -enum ExitCodes { - Help = 1, -} - +/// Non-SCMI related device errors. #[derive(Debug, ThisError)] pub enum DeviceError { #[error("{0}")] @@ -225,7 +219,6 @@ fn devices_help() -> String { pub fn print_devices_help() { let help = devices_help(); println!("{}", help); - exit(ExitCodes::Help as i32); } // Common sensor infrastructure diff --git a/crates/scmi/src/main.rs b/crates/scmi/src/main.rs index 60b5072e1..36c9561be 100644 --- a/crates/scmi/src/main.rs +++ b/crates/scmi/src/main.rs @@ -17,7 +17,7 @@ //! The daemon listens on a socket that is specified using `--socket-path` //! command line option. Usually at least one exposed device is specified, //! which is done using `--device` command line option. It can be used more -//! than once, for different devices. `--device help` lists the available +//! than once, for different devices. `--help-devices` lists the available //! devices and their options. //! //! The daemon normally logs info and higher messages to the standard error @@ -36,14 +36,14 @@ mod devices; mod scmi; mod vhu_scmi; -use devices::common::{DeviceDescription, DeviceProperties}; +use devices::common::{print_devices_help, DeviceDescription, DeviceProperties}; use std::{ process::exit, sync::{Arc, RwLock}, }; -use clap::Parser; +use clap::{CommandFactory, Parser}; use itertools::Itertools; use log::{debug, error, info, warn}; @@ -58,16 +58,15 @@ type Result = std::result::Result; #[derive(Parser)] struct ScmiArgs { // Location of vhost-user Unix domain socket. - #[clap(short, long, help = "vhost-user socket to use")] - socket_path: String, + // Required, unless one of the --help options is used. + #[clap(short, long, help = "vhost-user socket to use (required)")] + socket_path: Option, // Specification of SCMI devices to create. - #[clap( - short, - long, - help = "Devices to expose (use `help' device for more info)" - )] + #[clap(short, long, help = "Devices to expose")] #[arg(num_args(1..))] device: Vec, + #[clap(long, help = "Print help on available devices")] + help_devices: bool, } pub struct VuScmiConfig { @@ -79,9 +78,12 @@ impl TryFrom for VuScmiConfig { type Error = String; fn try_from(cmd_args: ScmiArgs) -> Result { - let socket_path = cmd_args.socket_path.trim().to_string(); - let device_iterator = cmd_args.device.iter(); + if cmd_args.socket_path.is_none() { + return Result::Err("Required argument socket-path was not provided".to_string()); + } + let socket_path = cmd_args.socket_path.unwrap().trim().to_string(); let mut devices: DeviceDescription = vec![]; + let device_iterator = cmd_args.device.iter(); for d in device_iterator { let mut split = d.split(','); let name = split.next().unwrap().to_owned(); @@ -139,18 +141,32 @@ fn start_backend(config: VuScmiConfig) -> Result<()> { } } +fn process_args(args: ScmiArgs) -> Option { + if args.help_devices { + print_devices_help(); + None + } else { + Some(args) + } +} + +fn print_help(message: &String) { + println!("{message}\n"); + let mut command = ScmiArgs::command(); + command.print_help().unwrap(); +} + fn main() { env_logger::init(); - match VuScmiConfig::try_from(ScmiArgs::parse()) { - Ok(config) => { - if let Err(error) = start_backend(config) { - error!("{error}"); - exit(1); + if let Some(args) = process_args(ScmiArgs::parse()) { + match VuScmiConfig::try_from(args) { + Ok(config) => { + if let Err(error) = start_backend(config) { + error!("{error}"); + exit(1); + } } - } - Err(message) => { - println!("{message}"); - // TODO: print help + Err(message) => print_help(&message), } } } @@ -170,7 +186,7 @@ mod tests { -d fake,name=bar" ); let params: Vec<&str> = params_string.split_whitespace().collect(); - let args: ScmiArgs = Parser::parse_from(params); + let args: ScmiArgs = process_args(Parser::parse_from(params)).unwrap(); let config = VuScmiConfig::try_from(args).unwrap(); assert_eq!(config.socket_path, path); let devices = vec![ @@ -189,4 +205,19 @@ mod tests { ]; assert_eq!(config.devices, devices); } + + #[test] + fn test_device_help_processing() { + let params_string = "binary --help-devices".to_string(); + let params: Vec<&str> = params_string.split_whitespace().collect(); + let args: ScmiArgs = Parser::parse_from(params); + let processed = process_args(args); + assert!(processed.is_none()); + } + + #[test] + fn test_help() { + // No way known to me to capture print_help() output from clap. + print_help(&String::from("test")); + } } diff --git a/crates/scmi/src/vhu_scmi.rs b/crates/scmi/src/vhu_scmi.rs index fd8b443f1..856a1b169 100644 --- a/crates/scmi/src/vhu_scmi.rs +++ b/crates/scmi/src/vhu_scmi.rs @@ -23,7 +23,7 @@ use vm_memory::{ use vmm_sys_util::epoll::EventSet; use vmm_sys_util::eventfd::{EventFd, EFD_NONBLOCK}; -use crate::devices::common::{available_devices, print_devices_help, DeviceError}; +use crate::devices::common::{available_devices, DeviceError}; use crate::scmi::{MessageHeader, ScmiHandler, ScmiRequest}; use crate::VuScmiConfig; @@ -101,9 +101,6 @@ 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.as_str()) { Some(specification) => match (specification.constructor)(properties) { Ok(mut device) => { From 07cbe73dc83ea3c15a3b835b432e26c3502c3908 Mon Sep 17 00:00:00 2001 From: Milan Zamazal Date: Wed, 30 Aug 2023 16:34:36 +0200 Subject: [PATCH 37/40] scmi: Improve output on backend configuration error Print a properly formatted error to both the log and terminal. Signed-off-by: Milan Zamazal --- crates/scmi/src/main.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/scmi/src/main.rs b/crates/scmi/src/main.rs index 36c9561be..8a925d8f1 100644 --- a/crates/scmi/src/main.rs +++ b/crates/scmi/src/main.rs @@ -107,8 +107,12 @@ impl TryFrom for VuScmiConfig { fn start_backend(config: VuScmiConfig) -> Result<()> { loop { debug!("Starting backend"); - // TODO: Print a nice error message on backend configuration failure. - let backend = Arc::new(RwLock::new(VuScmiBackend::new(&config).unwrap())); + let backend_instance = VuScmiBackend::new(&config); + if let Err(error) = backend_instance { + return Err(error.to_string()); + } + + let backend = Arc::new(RwLock::new(backend_instance.unwrap())); let listener = Listener::new(config.socket_path.clone(), true).unwrap(); let mut daemon = VhostUserDaemon::new( "vhost-device-scmi".to_owned(), @@ -163,6 +167,7 @@ fn main() { Ok(config) => { if let Err(error) = start_backend(config) { error!("{error}"); + println!("{error}"); exit(1); } } From 58650819ff745ad15d8af6b5a1adf05bdc1e6cd1 Mon Sep 17 00:00:00 2001 From: Milan Zamazal Date: Mon, 21 Aug 2023 17:53:16 +0200 Subject: [PATCH 38/40] Increase coverage score CI complains not only when the score is too low but also when it is too high. Accommodate the increased average coverage caused by adding vhost-device-scmi and its unit tests. Signed-off-by: Milan Zamazal --- coverage_config_x86_64.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coverage_config_x86_64.json b/coverage_config_x86_64.json index 1773554ff..0586cbaae 100644 --- a/coverage_config_x86_64.json +++ b/coverage_config_x86_64.json @@ -1,5 +1,5 @@ { - "coverage_score": 68.2, + "coverage_score": 71.0, "exclude_path": "", "crate_features": "" } From 923fa4312aff0c8b918d35dd4eb4836167d96afb Mon Sep 17 00:00:00 2001 From: Milan Zamazal Date: Mon, 4 Sep 2023 10:45:57 +0200 Subject: [PATCH 39/40] scmi: Add CHANGELOG.md Signed-off-by: Milan Zamazal --- crates/scmi/CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 crates/scmi/CHANGELOG.md diff --git a/crates/scmi/CHANGELOG.md b/crates/scmi/CHANGELOG.md new file mode 100644 index 000000000..51d3f040d --- /dev/null +++ b/crates/scmi/CHANGELOG.md @@ -0,0 +1,15 @@ +# Changelog +## [Unreleased] + +### Added + +### Changed + +### Fixed + +### Deprecated + +## [0.1.0] + +First release + From c0a5c39b99787d67c0121f620f78a3f38e214e72 Mon Sep 17 00:00:00 2001 From: Milan Zamazal Date: Mon, 4 Sep 2023 11:16:32 +0200 Subject: [PATCH 40/40] scmi: Add some notes on testing to README Signed-off-by: Milan Zamazal --- crates/scmi/README.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/crates/scmi/README.md b/crates/scmi/README.md index 1d0c5501d..d16c090d8 100644 --- a/crates/scmi/README.md +++ b/crates/scmi/README.md @@ -70,7 +70,25 @@ implemented. See source code (`scmi` crate) documentation for details and how to add more protocols, host device bindings or other functionality. -## Kernel support for testing +## Testing + +SCMI is supported only on Arm in Linux. This restriction doesn't +apply to the host, which can be any architecture as long as the guest +is Arm. + +The easiest way to test it on the guest side is using the Linux SCMI +Industrial I/O driver there. If an 3-axes accelerometer or gyroscope +VirtIO SCMI device is present and the guest kernel is compiled with +`CONFIG_IIO_SCMI` enabled then the device should be available in +`/sys/bus/iio/devices/`. The vhost-device-scmi fake device is +suitable for this. + +Of course, other means of accessing SCMI devices can be used too. The +following Linux kernel command line can be useful to obtain SCMI trace +information, in addition to SCMI related messages in dmesg: +`trace_event=scmi:* ftrace=function ftrace_filter=scmi*`. + +### Kernel support for testing `kernel` subdirectory contains [instructions](kernel/iio-dummy/README.md) how to create emulated