diff options
Diffstat (limited to 'meta-egvirt/recipes-extended/vhost-device-can/files/vhost-device-can-0.1.0/src/backend.rs')
-rwxr-xr-x | meta-egvirt/recipes-extended/vhost-device-can/files/vhost-device-can-0.1.0/src/backend.rs | 268 |
1 files changed, 268 insertions, 0 deletions
diff --git a/meta-egvirt/recipes-extended/vhost-device-can/files/vhost-device-can-0.1.0/src/backend.rs b/meta-egvirt/recipes-extended/vhost-device-can/files/vhost-device-can-0.1.0/src/backend.rs new file mode 100755 index 00000000..ce8d8511 --- /dev/null +++ b/meta-egvirt/recipes-extended/vhost-device-can/files/vhost-device-can-0.1.0/src/backend.rs @@ -0,0 +1,268 @@ +// VIRTIO CAN Emulation via vhost-user +// +// Copyright 2023-2024 VIRTUAL OPEN SYSTEMS SAS. All Rights Reserved. +// Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com> +// +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + +use log::{error, info, warn}; +use std::any::Any; +use std::collections::HashMap; +use std::path::PathBuf; +use std::sync::{Arc, RwLock}; +use std::thread; + +use thiserror::Error as ThisError; +use vhost_user_backend::VhostUserDaemon; +use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; + +use crate::can::CanController; +use crate::vhu_can::VhostUserCanBackend; + +pub(crate) type Result<T> = std::result::Result<T, Error>; + +#[derive(Debug, ThisError)] +/// Errors related to low level CAN helpers +pub(crate) enum Error { + #[error("Invalid socket count: {0}")] + SocketCountInvalid(usize), + #[error("Could not find can devices")] + CouldNotFindCANDevs, + #[error("Could not create can controller: {0}")] + CouldNotCreateCanController(crate::can::Error), + #[error("Could not create can controller output socket: {0}")] + FailCreateCanControllerSocket(crate::can::Error), + #[error("Could not create can backend: {0}")] + CouldNotCreateBackend(crate::vhu_can::Error), + #[error("Could not create daemon: {0}")] + CouldNotCreateDaemon(vhost_user_backend::Error), + #[error("Fatal error: {0}")] + ServeFailed(vhost_user_backend::Error), + #[error("Thread `{0}` panicked")] + ThreadPanic(String, Box<dyn Any + Send>), +} + +#[derive(PartialEq, Debug)] +pub struct VuCanConfig { + pub socket_path: PathBuf, + pub socket_count: u32, + pub can_devices: Vec<String>, +} + +impl VuCanConfig { + pub fn generate_socket_paths(&self) -> Vec<PathBuf> { + let socket_file_name = self + .socket_path + .file_name() + .expect("socket_path has no filename."); + let socket_file_parent = self + .socket_path + .parent() + .expect("socket_path has no parent directory."); + + let make_socket_path = |i: u32| -> PathBuf { + let mut file_name = socket_file_name.to_os_string(); + file_name.push(std::ffi::OsStr::new(&i.to_string())); + socket_file_parent.join(&file_name) + }; + + (0..self.socket_count).map(make_socket_path).collect() + } +} + +/// This is the public API through which an external program starts the +/// vhost-device-can backend server. +pub(crate) fn start_backend_server(socket: PathBuf, can_devs: String) -> Result<()> { + loop { + let controller = + CanController::new(can_devs.clone()).map_err(Error::CouldNotCreateCanController)?; + let lockable_controller = Arc::new(RwLock::new(controller)); + let vu_can_backend = Arc::new(RwLock::new( + VhostUserCanBackend::new(lockable_controller.clone()) + .map_err(Error::CouldNotCreateBackend)?, + )); + lockable_controller + .write() + .unwrap() + .open_can_socket() + .map_err(Error::FailCreateCanControllerSocket)?; + + let read_handle = CanController::start_read_thread(lockable_controller.clone()); + + let mut daemon = VhostUserDaemon::new( + String::from("vhost-device-can-backend"), + vu_can_backend.clone(), + GuestMemoryAtomic::new(GuestMemoryMmap::new()), + ) + .map_err(Error::CouldNotCreateDaemon)?; + + // Start the read thread -- need to handle it after termination + let vring_workers = daemon.get_epoll_handlers(); + vu_can_backend + .read() + .unwrap() + .set_vring_worker(&vring_workers[0]); + + daemon.serve(&socket).map_err(Error::ServeFailed)?; + + // Terminate the thread which reads CAN messages from "can_devs" + lockable_controller.write().unwrap().exit_read_thread(); + + // Wait for read thread to exit + match read_handle.join() { + Ok(_) => info!("The read thread returned successfully"), + Err(e) => warn!("The read thread returned the error: {:?}", e), + } + } +} + +pub fn start_backend(config: VuCanConfig) -> Result<()> { + let mut handles = HashMap::new(); + let (senders, receiver) = std::sync::mpsc::channel(); + + for (thread_id, (socket, can_devs)) in config + .generate_socket_paths() + .into_iter() + .zip(config.can_devices.iter().cloned()) + .map(|(a, b)| (a, b.to_string())) + .enumerate() + { + println!( + "thread_id: {}, socket: {:?}, can_devs: {:?}", + thread_id, socket, can_devs, + ); + + let name = format!("vhu-can-{}", can_devs); + let sender = senders.clone(); + let handle = thread::Builder::new() + .name(name.clone()) + .spawn(move || { + let result = + std::panic::catch_unwind(move || start_backend_server(socket, can_devs)); + + // Notify the main thread that we are done. + sender.send(thread_id).unwrap(); + + result.map_err(|e| Error::ThreadPanic(name, e))? + }) + .unwrap(); + handles.insert(thread_id, handle); + } + + while !handles.is_empty() { + let thread_id = receiver.recv().unwrap(); + handles + .remove(&thread_id) + .unwrap() + .join() + .map_err(std::panic::resume_unwind) + .unwrap()?; + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::backend::Error::FailCreateCanControllerSocket; + use crate::can::Error::SocketOpen; + use crate::CanArgs; + use assert_matches::assert_matches; + + #[test] + fn test_can_valid_configuration() { + let valid_args = CanArgs { + socket_path: "/tmp/vhost.sock".to_string().into(), + can_devices: "can0".to_string(), + socket_count: 1, + }; + + assert_matches!( + VuCanConfig::try_from(valid_args), + Err(Error::CouldNotFindCANDevs) + ); + } + + #[test] + fn test_can_valid_mult_device_configuration() { + let valid_args = CanArgs { + socket_path: "/tmp/vhost.sock".to_string().into(), + can_devices: "can0 can1".to_string(), + socket_count: 2, + }; + + assert_matches!( + VuCanConfig::try_from(valid_args), + Err(Error::CouldNotFindCANDevs) + ); + } + + #[test] + fn test_can_invalid_socket_configuration() { + let invalid_args = CanArgs { + socket_path: "/tmp/vhost.sock".to_string().into(), + can_devices: "can0".to_string(), + socket_count: 0, + }; + + assert_matches!( + VuCanConfig::try_from(invalid_args), + Err(Error::SocketCountInvalid(0)) + ); + } + + #[test] + fn test_can_invalid_mult_socket_configuration_1() { + let invalid_args = CanArgs { + socket_path: "/tmp/vhost.sock".to_string().into(), + can_devices: "can0".to_string(), + socket_count: 2, + }; + + assert_matches!( + VuCanConfig::try_from(invalid_args), + Err(Error::SocketCountInvalid(2)) + ); + } + + #[test] + fn test_can_invalid_mult_socket_configuration_2() { + let invalid_args = CanArgs { + socket_path: "/tmp/vhost.sock".to_string().into(), + can_devices: "can0 can1".to_string(), + socket_count: 1, + }; + + assert_matches!( + VuCanConfig::try_from(invalid_args), + Err(Error::SocketCountInvalid(1)) + ); + } + + #[test] + fn test_can_valid_configuration_start_backend_fail() { + // Instantiate the struct with the provided values + let config = VuCanConfig { + socket_path: PathBuf::from("/tmp/vhost.sock"), + socket_count: 1, + can_devices: vec!["can0".to_string()], + }; + + assert_matches!( + start_backend(config), + Err(FailCreateCanControllerSocket(SocketOpen)) + ); + } + + #[test] + fn test_can_valid_configuration_start_backend_server_fail() { + let socket_path = PathBuf::from("/tmp/vhost.sock"); + let can_devs = "can0".to_string(); + + assert_matches!( + start_backend_server(socket_path, can_devs), + Err(FailCreateCanControllerSocket(SocketOpen)) + ); + } +} |