mirror of
https://github.com/rust-vmm/vhost-device.git
synced 2025-12-26 06:32:44 +00:00
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:
parent
f0fe2b5a30
commit
42fa1204ec
@ -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:
|
||||
|
||||
@ -2,4 +2,5 @@
|
||||
resolver = "2"
|
||||
members = [
|
||||
"vhost-device-video",
|
||||
"vhost-device-can",
|
||||
]
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"coverage_score": 62.15,
|
||||
"coverage_score": 63.00,
|
||||
"exclude_path": "",
|
||||
"crate_features": ""
|
||||
}
|
||||
|
||||
15
staging/vhost-device-can/CHANGELOG.md
Normal file
15
staging/vhost-device-can/CHANGELOG.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Changelog
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
### Changed
|
||||
|
||||
### Fixed
|
||||
|
||||
### Deprecated
|
||||
|
||||
## v0.1.0
|
||||
|
||||
First release
|
||||
|
||||
41
staging/vhost-device-can/Cargo.toml
Normal file
41
staging/vhost-device-can/Cargo.toml
Normal 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"] }
|
||||
1
staging/vhost-device-can/LICENSE-APACHE
Symbolic link
1
staging/vhost-device-can/LICENSE-APACHE
Symbolic link
@ -0,0 +1 @@
|
||||
../../LICENSE-APACHE
|
||||
1
staging/vhost-device-can/LICENSE-BSD-3-Clause
Symbolic link
1
staging/vhost-device-can/LICENSE-BSD-3-Clause
Symbolic link
@ -0,0 +1 @@
|
||||
../../LICENSE-BSD-3-Clause
|
||||
147
staging/vhost-device-can/README.md
Normal file
147
staging/vhost-device-can/README.md
Normal 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)
|
||||
268
staging/vhost-device-can/src/backend.rs
Normal file
268
staging/vhost-device-can/src/backend.rs
Normal 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))
|
||||
);
|
||||
}
|
||||
}
|
||||
393
staging/vhost-device-can/src/can.rs
Normal file
393
staging/vhost-device-can/src/can.rs
Normal 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)
|
||||
);
|
||||
}
|
||||
}
|
||||
96
staging/vhost-device-can/src/main.rs
Normal file
96
staging/vhost-device-can/src/main.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
2145
staging/vhost-device-can/src/vhu_can.rs
Normal file
2145
staging/vhost-device-can/src/vhu_can.rs
Normal file
File diff suppressed because it is too large
Load Diff
144
staging/vhost-device-can/src/virtio_can.rs
Normal file
144
staging/vhost-device-can/src/virtio_can.rs
Normal 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 CANFD, 0..2048 CANXL, 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 CANFD, 0..2048 CANXL, 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 {}
|
||||
Loading…
Reference in New Issue
Block a user