vhost-device-can: Add initial implementation

This implementation supports the exchange of CAN/CANFD messages
virtual ("vcan") and real HW CAN device ("can0"). It was tested with:
a) virtio-can driver found in the following patch serie:
    - https://lwn.net/Articles/934187/

b) QEMU's vhost-user-can device:
    1) Option 1: Upstream QEMU's vhost-user-device

        qemu-system-x86_64  \
                -m 4096 \
                -numa node,memdev=mem \
                -object memory-backend-file,id=mem,size=4G,mem-path=/dev/shm,share=on \
                -chardev socket,id=char1,path=/tmp/can.sock \
                -device vhost-user-device-pci,chardev=char1,virtio-id=36,num_vqs=3,config_size=16 \
                ...

    2) Option 2: A new QEMU vhost-user-can device can be found in the
       following repo:
        - https://github.com/virtualopensystems/qemu/tree/vhu-can-rfc

For more information, please check the README.md file under
"staging/vhost-device-can/".

Signed-off-by: Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com>
This commit is contained in:
Timos Ampelikiotis 2024-01-09 14:38:17 +00:00 committed by Stefano Garzarella
parent f0fe2b5a30
commit 42fa1204ec
13 changed files with 3254 additions and 1 deletions

View File

@ -47,6 +47,7 @@ More information may be found in its [README file](./staging/README.md).
Here is the list of device backends in **staging**:
- [Video](https://github.com/rust-vmm/vhost-device/blob/main/staging/vhost-device-video/README.md)
- [Can](https://github.com/rust-vmm/vhost-device/blob/main/staging/vhost-device-can/README.md)
<!--
Template:

View File

@ -2,4 +2,5 @@
resolver = "2"
members = [
"vhost-device-video",
"vhost-device-can",
]

View File

@ -1,5 +1,5 @@
{
"coverage_score": 62.15,
"coverage_score": 63.00,
"exclude_path": "",
"crate_features": ""
}

View File

@ -0,0 +1,15 @@
# Changelog
## Unreleased
### Added
### Changed
### Fixed
### Deprecated
## v0.1.0
First release

View File

@ -0,0 +1,41 @@
[package]
name = "vhost-device-can"
version = "0.1.0"
authors = ["Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com>"]
description = "vhost can backend device"
repository = "https://github.com/rust-vmm/vhost-device"
readme = "README.md"
keywords = ["can", "vhost", "virt", "backend"]
license = "Apache-2.0 OR BSD-3-Clause"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
xen = ["vm-memory/xen", "vhost/xen", "vhost-user-backend/xen"]
[dependencies]
clap = { version = "4.4", features = ["derive"] }
env_logger = "0.11"
log = "0.4"
thiserror = "1.0"
queues = "1.0.2"
# TODO: Update socketcan to version "v3.4.0" when this is released.
# Socketcan "v3.3.0" includes the following issue: https://github.com/socketcan-rs/socketcan-rs/pull/61.
# The version was set to the commit "f004ee91e142a" where that issue has been resolved. As soon as, a
# newer version is released we need to point socketcan dependency to it.
# NOTE: If you are using rust version "1.80", the compiler might complain about "std::mem::size_of".
# The solution to that problem is described in the following link:
# - https://github.com/socketcan-rs/socketcan-rs/pull/72
socketcan = { git = "https://github.com/socketcan-rs/socketcan-rs.git", rev = "f004ee91e142a37fea36c5d719a57852c7076e87" }
vhost = { version = "0.11", features = ["vhost-user-backend"] }
vhost-user-backend = { version = "0.15" }
virtio-bindings = "0.2.2"
virtio-queue = "0.12"
vm-memory = "0.14.1"
vmm-sys-util = "0.12"
[dev-dependencies]
assert_matches = "1.5"
virtio-queue = { version = "0.12", features = ["test-utils"] }
vm-memory = { version = "0.14.1", features = ["backend-mmap", "backend-atomic"] }

View File

@ -0,0 +1 @@
../../LICENSE-APACHE

View File

@ -0,0 +1 @@
../../LICENSE-BSD-3-Clause

View File

@ -0,0 +1,147 @@
# vhost-device-can - CAN emulation backend daemon
## Description
This program is a vhost-user backend that emulates a VirtIO CAN device.
The device's binary takes two (2) parameters: a socket, a 'can-devices' list.
The socket is commonly used across all vhost-devices to communicate with
the vhost-user frontend device. The 'can-devices' represents a list of
CAN/FD devices appears in the host system which vhost-device-can will
forward messages to and from the frontend side.
This program is tested with QEMU's `vhost-user-device-pci` device.
Examples' section below.
## Synopsis
```
**vhost-device-can** [*OPTIONS*]
````
## Options
.. program:: vhost-device-can
.. option:: -h, --help
Print help.
.. option:: -s, --socket-path=PATH
Location of vhost-user Unix domain sockets, this path will be suffixed with
0,1,2..socket_count-1.
.. option:: -c, --socket-count=INT
Number of guests (sockets) to attach to, default set to 1.
.. option:: -d, --can-devices='CAN/FD interfaces'
CAN/FD device list at the host OS in the format:
<can-_X_0> [<can_in_X_1>] ... [<can_in_X_N-1>]
Note 1: Where N (the number of CAN/FD interfaces) is equal with the number
provided via *socket_count* parameter.
Example: --can-devices "can0 can1 can2"
## Features
This device is still work-in-progress (WIP) and on [virtio-spec v1.4](https://github.com/oasis-tcs/virtio-spec/blob/virtio-1.4/device-types/can/) is based
on virtio-can Linux's driver and QEMU's device presented in the following RFC:
- https://lwn.net/Articles/934187/
Vhost-device-can have be been tested in scenarios with multiple QEMU's VMs using
host's *CAN/FD* devices.
## Limitations
1) The transmission of a CAN/FD frame to a host interface always is done
synchronously. This means that regardless the negotiation or not of the
feature *VIRTIO_CAN_F_LATE_TX_ACK*, the backend will always wait for the
transmission of the frame and after will mark the transmission request
as used.
2) Does not check for undefined flags in CAN/FD frame when send and receive
a CAN/FD frame from the frontend (QEMU device).
3) The host's CAN/FD devices should be already in *UP* state before staring
the vhost-device-can (by using `ip link set can0 [up,down]`).
- The control messages does not actually change host's device state
4) Current version of the device has been tested only with *vcan* device.
## Examples
### Dependencies
For testing the device the required dependencies are:
- Linux:
- Integrate *virtio-can* driver implemented by OpenSynergy:
- https://lwn.net/Articles/934187/
- Set `CONFIG_VIRTIO_CAN=y`
- QEMU
- Integrate *virtio-can* device implemented by OpenSynergy:
- https://lwn.net/Articles/934187/
- Clone vhost-user-can QEMU device (optional):
- A new vhost-user-can device has been implemented in the following repo:
- https://github.com/virtualopensystems/qemu/tree/vhu-can-rfc
### Test the device
The daemon should be started first:
```shell
host# vhost-device-can --socket-path=can.sock --can-devices="vcan0"
```
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.
There are two option for running QEMU with vhost-device-can:
1) Using `vhost-user-device-pci` available upstream since QEMU `v8.2.0`:
```text
host# qemu-system \
-m 4096 \
-numa node,memdev=mem \
-object memory-backend-memfd,id=mem,size=4G,share=on \
-chardev socket,id=can0,path=/tmp/can.sock \
-device vhost-user-device-pci,chardev=can0,virtio-id=36,num_vqs=3,config_size=16 \
...
```
2) Using `vhost-user-can-pci`:
```text
host# qemu-system \
-m 4096 \
-numa node,memdev=mem \
-object memory-backend-memfd,id=mem,size=4G,share=on \
-chardev socket,path=/tmp/can.sock,id=can0 \
-device vhost-user-can-pci,chardev=can0,id=can \
...
```
> Note: For testing this scenario the reader needs to clone the QEMU version
> from the following repo which implements `vhost-user-can` device:
> - https://github.com/virtualopensystems/qemu/tree/vhu-can-rfc
### Multi-Guest case
Run vhost-device-can as:
```text
./vhost-device-can --socket-path /tmp/can.sock --socket-count 2 --can-devices "vcan0 vcan1"
```
This command will start the device and create two new sockets: */tmp/can.sock0* and */tmp/can.sock1*.
From the other side we run two QEMU instances (VMs) with vhost-user-can:
```text
host# qemu-system \
-m 4096 \
-numa node,memdev=mem \
-object memory-backend-memfd,id=mem,size=4G,share=on \
-chardev socket,path=<SOCKET_PATH>,id=can0 \
-device vhost-user-can-pci,chardev=can0,id=can \
...
```
In the first instance of QEMU *SOCKET_PATH* would be: */tmp/can.sock0*,
and will use *can0* (host interface) as sender and receiver. The second
QEMU VM would have: *SOCKET_PATH* = */tmp/can.sock1*, and will use *can1*
as receiver and *can2* as sender.
## 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)

View File

@ -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))
);
}
}

View File

@ -0,0 +1,393 @@
// CAN backend device
//
// 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, trace, warn};
use std::sync::{Arc, RwLock};
use std::thread::{spawn, JoinHandle};
use thiserror::Error as ThisError;
use vmm_sys_util::eventfd::{EventFd, EFD_NONBLOCK};
extern crate queues;
use queues::*;
extern crate socketcan;
use crate::virtio_can::{
VirtioCanConfig, VirtioCanFrame, CAN_CS_STARTED, CAN_CS_STOPPED, CAN_EFF_FLAG,
CAN_FRMF_TYPE_FD, VIRTIO_CAN_RX,
};
use socketcan::{
CanAnyFrame, CanDataFrame, CanFdFrame, CanFdSocket, EmbeddedFrame, ExtendedId, Frame, Id,
Socket, StandardId,
};
type Result<T> = std::result::Result<T, Error>;
#[derive(Copy, Clone, Debug, PartialEq, ThisError)]
/// Errors related to low level can helpers
pub(crate) enum Error {
#[error("Can open socket operation failed")]
SocketOpen,
#[error("Can write socket operation failed")]
SocketWrite,
#[error("Can read socket operation failed")]
SocketRead,
#[error("Pop can element operation failed")]
PopFailed,
#[error("Queue is empty")]
QueueEmpty,
#[error("Creating Eventfd for CAN events failed")]
EventFdFailed,
#[error("Push can element operation failed")]
PushFailed,
#[error("No output interface available")]
NoOutputInterface,
}
#[derive(Debug)]
pub(crate) struct CanController {
pub config: VirtioCanConfig,
pub can_name: String,
pub can_socket: Option<CanFdSocket>,
pub rx_event_fd: EventFd,
rx_fifo: Queue<VirtioCanFrame>,
pub status: bool,
pub ctrl_state: u8,
}
impl CanController {
// Creates a new controller corresponding to `device`.
pub(crate) fn new(can_name: String) -> Result<CanController> {
let can_name = can_name.to_owned();
info!("can_name: {:?}", can_name);
let rx_fifo = Queue::new();
let rx_efd = EventFd::new(EFD_NONBLOCK).map_err(|_| Error::EventFdFailed)?;
Ok(CanController {
config: VirtioCanConfig { status: 0x0.into() },
can_name,
can_socket: None,
rx_event_fd: rx_efd,
rx_fifo,
status: true,
ctrl_state: CAN_CS_STOPPED,
})
}
pub fn print_can_frame(canframe: VirtioCanFrame) {
trace!("canframe.msg_type 0x{:x}", canframe.msg_type.to_native());
trace!("canframe.can_id 0x{:x}", canframe.can_id.to_native());
trace!("canframe.length {}", canframe.length.to_native());
trace!("canframe.flags 0x{:x}", canframe.flags.to_native());
if canframe.length.to_native() == 0 {
trace!("[]");
return;
}
trace!("[");
let last_elem = canframe.length.to_native() as usize - 1;
for (index, sdu) in canframe.sdu.iter().enumerate() {
if index == last_elem {
trace!("0x{:x}", sdu);
break;
}
trace!("0x{:x}, ", sdu);
}
trace!("]");
}
pub fn start_read_thread(controller: Arc<RwLock<CanController>>) -> JoinHandle<Result<()>> {
spawn(move || CanController::read_can_socket(controller))
}
pub fn push(&mut self, rx_elem: VirtioCanFrame) -> Result<()> {
match self.rx_fifo.add(rx_elem) {
Ok(_) => Ok(()),
_ => Err(Error::PushFailed),
}
}
pub fn rx_is_empty(&mut self) -> bool {
self.rx_fifo.size() == 0
}
pub fn pop(&mut self) -> Result<VirtioCanFrame> {
if self.rx_fifo.size() < 1 {
return Err(Error::QueueEmpty);
}
match self.rx_fifo.remove() {
Ok(item) => Ok(item),
_ => Err(Error::PopFailed),
}
}
pub fn open_can_socket(&mut self) -> Result<()> {
self.can_socket = match CanFdSocket::open(&self.can_name) {
Ok(socket) => Some(socket),
Err(_) => {
warn!("Error opening CAN socket");
return Err(Error::SocketOpen);
}
};
Ok(())
}
// Helper function to process frame
fn process_frame<F: Frame>(frame: F, is_fd: bool) -> VirtioCanFrame {
VirtioCanFrame {
msg_type: VIRTIO_CAN_RX.into(),
can_id: frame.id_word().into(),
length: (frame.data().len() as u16).into(),
reserved: 0.into(),
flags: if is_fd {
CAN_FRMF_TYPE_FD.into()
} else {
0.into()
},
sdu: {
let mut sdu_data: [u8; 64] = [0; 64];
sdu_data[..frame.data().len()].copy_from_slice(frame.data());
sdu_data
},
}
}
pub fn read_can_socket(controller: Arc<RwLock<CanController>>) -> Result<()> {
let can_name = &controller.read().unwrap().can_name.clone();
dbg!("Start reading from {} socket!", &can_name);
let socket = match CanFdSocket::open(can_name) {
Ok(socket) => socket,
Err(_) => {
warn!("Error opening CAN socket");
return Err(Error::SocketOpen);
}
};
// Set non-blocking otherwise the device will not restart immediatelly
// when the VM closes, and a new canfd message needs to be received for
// restart to happen.
// This caused by the fact that the thread is stacked in read function
// and does not go to the next loop to check the status condition.
socket
.set_nonblocking(true)
.expect("Cannot set nonblocking");
// Receive CAN messages
loop {
// If the status variable is false then break and exit.
if !controller.read().unwrap().status {
dbg!("exit read can thread");
return Ok(());
}
if let Ok(frame) = socket.read_frame() {
// If ctrl_state is stopped, consume the received CAN/FD frame
// and loop till the ctrl_state changes to started or the thread
// to exit.
if controller.read().unwrap().ctrl_state != CAN_CS_STARTED {
trace!("CAN/FD frame is received but not saved!");
continue;
}
// Match and process frame variants
let read_can_frame = match frame {
CanAnyFrame::Normal(frame) => {
trace!("Received CAN frame: {:?}", frame);
Self::process_frame(frame, false)
}
CanAnyFrame::Fd(frame) => {
trace!("Received CAN FD frame: {:?}", frame);
Self::process_frame(frame, true)
}
CanAnyFrame::Remote(frame) => {
trace!("Received Remote CAN frame: {:?}", frame);
Self::process_frame(frame, false)
}
CanAnyFrame::Error(frame) => {
trace!("Received Error frame: {:?}", frame);
Self::process_frame(frame, false)
}
};
match controller.write().unwrap().push(read_can_frame) {
Ok(_) => warn!("New Can frame was received"),
Err(_) => {
warn!("Error read/push CAN frame");
return Err(Error::SocketRead);
}
};
controller
.write()
.unwrap()
.rx_event_fd
.write(1)
.expect("Fail to write on rx_event_fd");
}
}
}
pub(crate) fn exit_read_thread(&mut self) {
trace!("Exit can read thread\n");
self.status = false;
}
pub(crate) fn config(&mut self) -> &VirtioCanConfig {
&self.config
}
pub(crate) fn can_out(&self, tx_request: VirtioCanFrame) -> Result<()> {
// Create a CAN frame with a specific CAN-ID and the data buffer
let can_id: Id = if (tx_request.can_id.to_native() & CAN_EFF_FLAG) != 0 {
// SAFETY: Use new_unchecked cause checks have been taken place
// to prior stage. Also flags have beem already added on can_id
// so tnew will fail (can_id + can_flags) > 29 bits
unsafe { Id::Extended(ExtendedId::new_unchecked(tx_request.can_id.into())) }
} else {
// SAFETY: Use new_unchecked cause checks have been taken place
// to prior stage. Also flags have beem already added on can_id
// so tnew will fail (can_id + can_flags) > 11 bits
unsafe {
Id::Standard(StandardId::new_unchecked(
tx_request.can_id.to_native() as u16
))
}
};
// Grab the data to be tranfered
let data_len = tx_request.length.to_native() as usize;
let data: Vec<u8> = tx_request.sdu.iter().cloned().take(data_len).collect();
// Format CAN/FD frame
let frame: CanAnyFrame = if (tx_request.flags.to_native() & CAN_FRMF_TYPE_FD) != 0 {
CanAnyFrame::Fd(CanFdFrame::new(can_id, &data).expect("Fail to create CanFdFrame"))
} else {
CanAnyFrame::Normal(CanDataFrame::new(can_id, &data).expect("Fail to create CanFrame"))
};
// Send the CAN/FD frame
let socket = self.can_socket.as_ref().ok_or("No available device");
match socket {
Ok(socket) => match socket.write_frame(&frame) {
Ok(_) => Ok(()),
Err(_) => {
warn!("Error write CAN socket");
Err(Error::SocketWrite)
}
},
Err(_) => Err(Error::NoOutputInterface),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vhu_can::VhostUserCanBackend;
use std::sync::{Arc, RwLock};
#[test]
fn test_can_controller_creation() {
let can_name = "can".to_string();
let controller = CanController::new(can_name.clone()).unwrap();
assert_eq!(controller.can_name, can_name);
}
#[test]
fn test_can_controller_push_and_pop() {
let can_name = "can".to_string();
let mut controller = CanController::new(can_name.clone()).unwrap();
let frame = VirtioCanFrame {
msg_type: VIRTIO_CAN_RX.into(),
can_id: 123.into(),
length: 64.into(),
reserved: 0.into(),
flags: 0.into(),
sdu: [0; 64],
};
// Test push
controller.push(frame).unwrap();
// Test pop
let pop_result = controller.pop().unwrap();
assert_eq!(pop_result, frame);
}
#[test]
fn test_can_controller_config() {
let can_name = "can".to_string();
let mut controller = CanController::new(can_name.clone()).unwrap();
// Test config
let config = controller.config();
assert_eq!(config.status.to_native(), 0);
}
#[test]
fn test_can_controller_operation() {
let can_name = "can".to_string();
let mut controller = CanController::new(can_name.clone()).unwrap();
let frame = VirtioCanFrame {
msg_type: VIRTIO_CAN_RX.into(),
can_id: 123.into(),
length: 64.into(),
reserved: 0.into(),
flags: 0.into(),
sdu: [0; 64],
};
match controller.open_can_socket() {
Ok(_) => {
// Test operation
let operation_result = controller.can_out(frame);
assert!(operation_result.is_ok());
}
Err(_) => warn!("There is no CAN interface with {} name", can_name),
}
}
#[test]
fn test_can_controller_start_read_thread() {
let can_name = "can".to_string();
let controller = CanController::new(can_name.clone()).unwrap();
let arc_controller = Arc::new(RwLock::new(controller));
// Test start_read_thread
let thread_handle = CanController::start_read_thread(arc_controller.clone());
assert!(thread_handle.join().is_ok());
}
#[test]
fn test_can_open_socket_fail() {
let controller =
CanController::new("can0".to_string()).expect("Could not build controller");
let controller = Arc::new(RwLock::new(controller));
VhostUserCanBackend::new(controller.clone()).expect("Could not build vhucan device");
assert_eq!(
controller.write().unwrap().open_can_socket(),
Err(Error::SocketOpen)
);
}
#[test]
fn test_can_read_socket_fail() {
let controller =
CanController::new("can0".to_string()).expect("Could not build controller");
let controller = Arc::new(RwLock::new(controller));
VhostUserCanBackend::new(controller.clone()).expect("Could not build vhucan device");
assert_eq!(
CanController::read_can_socket(controller),
Err(Error::SocketOpen)
);
}
}

View File

@ -0,0 +1,96 @@
// 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
mod backend;
mod can;
mod vhu_can;
mod virtio_can;
use clap::Parser;
use log::{error, info};
use socketcan::{CanSocket, Socket};
use std::convert::TryFrom;
use std::path::PathBuf;
use std::process::exit;
pub(crate) type Result<T> = std::result::Result<T, Error>;
use crate::backend::{start_backend, Error, VuCanConfig};
#[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, value_name = "SOCKET")]
socket_path: PathBuf,
/// A can device name to be used for reading (ex. vcan, can0, can1, ... etc.)
#[clap(short = 'd', long)]
can_devices: String,
/// Number of guests (sockets) to connect to.
#[clap(short = 'c', long, default_value_t = 1)]
socket_count: u32,
}
fn check_can_devices(can_devices: &[String]) -> Result<()> {
for can_dev in can_devices {
if CanSocket::open(can_dev).is_err() {
info!("There is no interface with the following name {}", can_dev);
return Err(Error::CouldNotFindCANDevs);
}
}
Ok(())
}
fn parse_can_devices(input: &CanArgs) -> Result<Vec<String>> {
let can_devices_vec: Vec<&str> = input.can_devices.split_whitespace().collect();
let can_devices: Vec<_> = can_devices_vec.iter().map(|x| x.to_string()).collect();
if (can_devices.len() as u32) != input.socket_count {
info!(
"Number of CAN/FD devices ({}) not equal with socket count {}",
input.can_devices, input.socket_count
);
return Err(Error::SocketCountInvalid(
input.socket_count.try_into().unwrap(),
));
}
match check_can_devices(&can_devices) {
Ok(_) => Ok(can_devices),
Err(_) => Err(Error::CouldNotFindCANDevs),
}
}
impl TryFrom<CanArgs> for VuCanConfig {
type Error = Error;
fn try_from(args: CanArgs) -> Result<Self> {
if args.socket_count == 0 {
return Err(Self::Error::SocketCountInvalid(0));
}
let can_devices = match parse_can_devices(&args) {
Ok(can_devs) => can_devs,
Err(e) => return Err(e),
};
Ok(VuCanConfig {
socket_path: args.socket_path,
socket_count: args.socket_count,
can_devices,
})
}
}
fn main() {
env_logger::init();
if let Err(e) = VuCanConfig::try_from(CanArgs::parse()).and_then(start_backend) {
error!("{e}");
exit(1);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,144 @@
// CAN virtio bindings
//
// 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 vm_memory::{ByteValued, Le16, Le32};
/// CAN FRAME Flags and Masks
pub(crate) const CAN_EFF_FLAG: u32 = 0x80000000; // EFF/SFF is set in the MSB
pub(crate) const CAN_RTR_FLAG: u32 = 0x40000000; // remote transmission request
pub(crate) const CAN_ERR_FLAG: u32 = 0x20000000; // error message frame
pub(crate) const CAN_SFF_MASK: u32 = 0x000007FF; // standard frame format (SFF)
pub(crate) const CAN_EFF_MASK: u32 = 0x1FFFFFFF; // extended frame format (EFF)
#[allow(dead_code)]
pub(crate) const CAN_FRMF_BRS: u32 = 0x01; // bit rate switch (2nd bitrate for data)
#[allow(dead_code)]
pub(crate) const CAN_FRMF_ESI: u32 = 0x02; // error state ind. of transmitting node
pub(crate) const CAN_FRMF_TYPE_FD: u32 = 0x10; // internal bit ind. of CAN FD frame
pub(crate) const CAN_ERR_BUSOFF: u32 = 0x00000040; // bus off
/// CANFD frame's valid data lengths
pub(crate) const CANFD_VALID_LENGTHS: [u32; 7] = [12, 16, 20, 24, 32, 48, 64];
/// CAN controller states
pub(crate) const CAN_CS_STARTED: u8 = 0x01;
pub(crate) const CAN_CS_STOPPED: u8 = 0x02;
/// CAN flags to determine type of CAN FRAME Id
pub(crate) const VIRTIO_CAN_FLAGS_EXTENDED: u32 = 0x8000;
pub(crate) const VIRTIO_CAN_FLAGS_FD: u32 = 0x4000;
pub(crate) const VIRTIO_CAN_FLAGS_RTR: u32 = 0x2000;
pub(crate) const VIRTIO_CAN_FLAGS_VALID_MASK: u32 =
VIRTIO_CAN_FLAGS_EXTENDED | VIRTIO_CAN_FLAGS_FD | VIRTIO_CAN_FLAGS_RTR;
pub(crate) const VIRTIO_CAN_TX: u16 = 0x0001;
pub(crate) const VIRTIO_CAN_RX: u16 = 0x0101;
/// Feature bit numbers
pub const VIRTIO_CAN_F_CAN_CLASSIC: u16 = 0;
pub const VIRTIO_CAN_F_CAN_FD: u16 = 1;
pub const VIRTIO_CAN_S_CTRL_BUSOFF: u16 = 2; /* Controller BusOff */
#[allow(dead_code)]
pub const VIRTIO_CAN_F_LATE_TX_ACK: u16 = 2;
pub const VIRTIO_CAN_F_RTR_FRAMES: u16 = 3;
/// Possible values of the status field
pub const VIRTIO_CAN_RESULT_OK: u8 = 0x0;
pub const VIRTIO_CAN_RESULT_NOT_OK: u8 = 0x1;
/// CAN Control messages
pub const VIRTIO_CAN_SET_CTRL_MODE_START: u16 = 0x0201;
pub const VIRTIO_CAN_SET_CTRL_MODE_STOP: u16 = 0x0202;
/// Virtio Can Configuration
#[derive(Copy, Clone, Debug, Default, PartialEq)]
#[repr(C)]
pub(crate) struct VirtioCanConfig {
/// CAN controller status
pub(crate) status: Le16,
}
// SAFETY: The layout of the structure is fixed and can be initialized by
// reading its content from byte array.
unsafe impl ByteValued for VirtioCanConfig {}
/// Virtio CAN Request / Response messages
///
/// The response message is a stream of bytes, where first byte represents the
/// status, and rest is message specific data.
#[derive(Copy, Clone, Default)]
#[repr(C)]
pub struct VirtioCanTxResponse {
pub result: i8,
}
// SAFETY: The layout of the structure is fixed and can be initialized by
// reading its content from byte array.
unsafe impl ByteValued for VirtioCanTxResponse {}
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(C)]
pub struct VirtioCanFrame {
pub msg_type: Le16,
pub length: Le16, /* 0..8 CC, 0..64 CAN­FD, 0..2048 CAN­XL, 12 bits */
pub reserved: Le32, /* May be needed in part for CAN XL priority */
pub flags: Le32,
pub can_id: Le32,
pub sdu: [u8; 64],
}
// SAFETY: The layout of the structure is fixed and can be initialized by
// reading its content from byte array.
unsafe impl ByteValued for VirtioCanFrame {}
impl Default for VirtioCanFrame {
fn default() -> Self {
VirtioCanFrame {
msg_type: Le16::default(),
length: Le16::default(),
reserved: Le32::default(),
flags: Le32::default(),
can_id: Le32::default(),
sdu: [0; 64], // Initialize "sdu" with default value (0 in this case)
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Default)]
#[repr(C)]
pub struct VirtioCanHeader {
pub msg_type: Le16,
pub length: Le16, /* 0..8 CC, 0..64 CAN­FD, 0..2048 CAN­XL, 12 bits */
pub reserved: Le32, /* May be needed in part for CAN XL priority */
pub flags: Le32,
pub can_id: Le32,
}
// SAFETY: The layout of the structure is fixed and can be initialized by
// reading its content from byte array.
unsafe impl ByteValued for VirtioCanHeader {}
#[derive(Copy, Clone, Default)]
#[repr(C)]
pub struct VirtioCanCtrlRequest {
pub msg_type: Le16,
}
// SAFETY: The layout of the structure is fixed and can be initialized by
// reading its content from byte array.
unsafe impl ByteValued for VirtioCanCtrlRequest {}
#[derive(Copy, Clone, Default)]
#[repr(C)]
pub struct VirtioCanCtrlResponse {
pub result: i8,
}
// SAFETY: The layout of the structure is fixed and can be initialized by
// reading its content from byte array.
unsafe impl ByteValued for VirtioCanCtrlResponse {}