diff options
Diffstat (limited to 'meta-egvirt/recipes-extended/vhost-device-can/vhost-device-can-0.1.0/src/backend.rs')
-rw-r--r-- | meta-egvirt/recipes-extended/vhost-device-can/vhost-device-can-0.1.0/src/backend.rs | 170 |
1 files changed, 170 insertions, 0 deletions
diff --git a/meta-egvirt/recipes-extended/vhost-device-can/vhost-device-can-0.1.0/src/backend.rs b/meta-egvirt/recipes-extended/vhost-device-can/vhost-device-can-0.1.0/src/backend.rs new file mode 100644 index 00000000..177c76cc --- /dev/null +++ b/meta-egvirt/recipes-extended/vhost-device-can/vhost-device-can-0.1.0/src/backend.rs @@ -0,0 +1,170 @@ +// VIRTIO CAN Emulation via vhost-user +// +// Copyright 2023 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::process::exit; +use std::sync::{Arc, RwLock}; +use std::thread::{spawn, JoinHandle}; + +use clap::Parser; +use thiserror::Error as ThisError; +use vhost::{vhost_user, vhost_user::Listener}; +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("Failed to join threads")] + FailedJoiningThreads, + #[error("Could not create can controller: {0}")] + CouldNotCreateCanController(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), +} + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct CanArgs { + /// Location of vhost-user Unix domain socket. This is suffixed by 0,1,2..socket_count-1. + #[clap(short, long)] + socket_path: String, + + /// A can device name to be used for reading (ex. vcan, can0, can1, ... etc.) + #[clap(short = 'i', long)] + can_in: String, + + /// A can device name to be used for writing (ex. vcan, can0, can1, ... etc.) + #[clap(short = 'o', long)] + can_out: String, + + /// Number of guests (sockets) to connect to. + #[clap(short = 'c', long, default_value_t = 1)] + socket_count: u32, +} + +#[derive(PartialEq, Debug)] +struct CanConfiguration { + socket_path: String, + socket_count: u32, + can_in: String, + can_out: String, +} + +impl TryFrom<CanArgs> for CanConfiguration { + type Error = Error; + + fn try_from(args: CanArgs) -> Result<Self> { + + if args.socket_count == 0 { + return Err(Error::SocketCountInvalid(0)); + } + + let can_in = args.can_in.trim().to_string(); + let can_out = args.can_out.trim().to_string(); + + Ok(CanConfiguration { + socket_path: args.socket_path, + socket_count: args.socket_count, + can_in, + can_out, + }) + } +} + +fn start_backend(args: CanArgs) -> Result<()> { + + println!("start_backend function!\n"); + + let config = CanConfiguration::try_from(args).unwrap(); + let mut handles = Vec::new(); + + for _ in 0..config.socket_count { + let socket = config.socket_path.to_owned(); + let can_in = config.can_in.to_owned(); + let can_out = config.can_out.to_owned(); + + let handle: JoinHandle<Result<()>> = spawn(move || loop { + // A separate thread is spawned for each socket and can connect to a separate guest. + // These are run in an infinite loop to not require the daemon to be restarted once a + // guest exits. + // + // There isn't much value in complicating code here to return an error from the + // threads, and so the code uses unwrap() instead. The panic on a thread won't cause + // trouble to other threads/guests or the main() function and should be safe for the + // daemon. + + let controller = + CanController::new(can_in.clone(), can_out.clone()).map_err(Error::CouldNotCreateCanController)?; + let shared_controller_1 = Arc::new(RwLock::new(controller)); + let shared_controller_2 = shared_controller_1.clone(); + let vu_can_backend = Arc::new(RwLock::new( + VhostUserCanBackend::new(shared_controller_1).map_err(Error::CouldNotCreateBackend)?, + )); + let _read_hanlde = CanController::start_read_thread(shared_controller_2); + + 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]); + + let listener = Listener::new(socket.clone(), true).unwrap(); + daemon.start(listener).unwrap(); + + match daemon.wait() { + Ok(()) => { + info!("Stopping cleanly."); + } + Err(vhost_user_backend::Error::HandleRequest( + vhost_user::Error::PartialMessage | vhost_user::Error::Disconnected, + )) => { + 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. + vu_can_backend.read().unwrap().exit_event.write(1).unwrap(); + }); + + handles.push(handle); + } + + for handle in handles { + handle.join().map_err(|_| Error::FailedJoiningThreads)??; + } + + Ok(()) +} + +pub(crate) fn can_init() { + env_logger::init(); + println!("Can_init function!"); + if let Err(e) = start_backend(CanArgs::parse()) { + error!("{e}"); + exit(1); + } +} |