// VIRTIO CAN Emulation via vhost-user // // Copyright 2023 VIRTUAL OPEN SYSTEMS SAS. All Rights Reserved. // Timos Ampelikiotis // // 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 = std::result::Result; #[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 for CanConfiguration { type Error = Error; fn try_from(args: CanArgs) -> Result { 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> = 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); } }