generated from rust-vmm/crate-template
-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
vhost-device-console: Add initial implementation
This program is a vhost-user backend that emulates a VirtIO SPI bus. This program takes the layout of the spi bus and its devices on the host OS and then talks to them via the /dev/spidevX.Y interface when a request comes from the guest OS for a SPI device. The implementation corresponds with the specification: https://github.com/oasis-tcs/virtio-spec/tree/virtio-1.4/device-types/spi Signed-off-by: Haixu Cui <[email protected]>
- Loading branch information
Showing
10 changed files
with
2,336 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,4 +11,5 @@ members = [ | |
"vhost-device-sound", | ||
"vhost-device-template", | ||
"vhost-device-vsock", | ||
"vhost-device-spi", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# Changelog | ||
## [Unreleased] | ||
|
||
### Added | ||
|
||
### Changed | ||
|
||
### Fixed | ||
|
||
### Deprecated | ||
|
||
## [0.1.0] | ||
|
||
First release | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
[package] | ||
name = "vhost-device-spi" | ||
version = "0.1.0" | ||
authors = ["Haixu Cui <[email protected]>"] | ||
description = "vhost spi backend device" | ||
repository = "https://github.com/rust-vmm/vhost-device" | ||
readme = "README.md" | ||
keywords = ["spi", "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.5", features = ["derive"] } | ||
env_logger = "0.11" | ||
libc = "0.2" | ||
log = "0.4" | ||
thiserror = "1.0" | ||
vhost = { version = "0.11", features = ["vhost-user-backend"] } | ||
vhost-user-backend = "0.14" | ||
virtio-bindings = "0.2.2" | ||
virtio-queue = "0.11" | ||
vm-memory = "0.14.1" | ||
vmm-sys-util = "0.12" | ||
|
||
[dev-dependencies] | ||
assert_matches = "1.5" | ||
virtio-queue = { version = "0.11", features = ["test-utils"] } | ||
vm-memory = { version = "0.14.1", features = ["backend-mmap", "backend-atomic"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../LICENSE-APACHE |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../LICENSE-BSD-3-Clause |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# vhost-device-spi - SPI emulation backend daemon | ||
|
||
## Description | ||
This program is a vhost-user backend that emulates a VirtIO SPI bus. | ||
This program takes the layout of the spi bus and its devices on the host | ||
OS and then talks to them via the /dev/spidevX.Y interface when a request | ||
comes from the guest OS for a SPI device. | ||
|
||
## Synopsis | ||
|
||
**vhost-device-spi** [*OPTIONS*] | ||
|
||
## Options | ||
|
||
.. program:: vhost-device-spi | ||
|
||
.. 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:: -l, --device=SPI-DEVICES | ||
|
||
Spi device full path at the host OS in the format: | ||
/dev/spidevX.Y | ||
|
||
Here, | ||
X: is spi controller's bus number. | ||
Y: is chip select index. | ||
|
||
## Examples | ||
|
||
The daemon should be started first: | ||
|
||
:: | ||
|
||
host# vhost-device-spi --socket-path=vspi.sock --socket-count=1 --device "/dev/spidev0.0" | ||
|
||
## 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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
// VIRTIO SPI Emulation via vhost-user | ||
// | ||
// Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. | ||
// Haixu Cui <[email protected]> | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause | ||
|
||
mod spi; | ||
mod vhu_spi; | ||
|
||
use log::error; | ||
use std::process::exit; | ||
use std::sync::{Arc, RwLock}; | ||
use std::thread::{spawn, JoinHandle}; | ||
|
||
use clap::Parser; | ||
use thiserror::Error as ThisError; | ||
use vhost_user_backend::VhostUserDaemon; | ||
use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; | ||
|
||
use spi::{PhysDevice, SpiController, SpiDevice}; | ||
use vhu_spi::VhostUserSpiBackend; | ||
|
||
type Result<T> = std::result::Result<T, Error>; | ||
|
||
#[derive(Debug, ThisError)] | ||
/// Errors related to low level spi helpers | ||
pub(crate) enum Error { | ||
#[error("Invalid socket count: {0}")] | ||
SocketCountInvalid(usize), | ||
#[error("Low level Spi failure: {0:?}")] | ||
SpiFailure(spi::Error), | ||
#[error("Could not create backend: {0}")] | ||
CouldNotCreateBackend(vhu_spi::Error), | ||
#[error("Could not create daemon: {0}")] | ||
CouldNotCreateDaemon(vhost_user_backend::Error), | ||
#[error("Fatal error: {0}")] | ||
ServeFailed(vhost_user_backend::Error), | ||
} | ||
|
||
#[derive(Parser, Debug)] | ||
#[clap(author, version, about, long_about = None)] | ||
struct SpiArgs { | ||
/// Location of vhost-user Unix domain socket. This is suffixed by 0,1,2..socket_count-1. | ||
#[clap(short, long)] | ||
socket_path: String, | ||
|
||
/// Number of guests (sockets) to connect to. | ||
#[clap(short = 'c', long, default_value_t = 1)] | ||
socket_count: usize, | ||
|
||
/// SPI device full path | ||
#[clap(short = 'l', long)] | ||
device: String, | ||
} | ||
|
||
#[derive(PartialEq, Debug)] | ||
struct SpiConfiguration { | ||
socket_path: String, | ||
socket_count: usize, | ||
device: String, | ||
} | ||
|
||
impl SpiConfiguration { | ||
fn try_from(args: SpiArgs) -> Result<Self> { | ||
if args.socket_count == 0 { | ||
return Err(Error::SocketCountInvalid(0)); | ||
} | ||
|
||
Ok(SpiConfiguration { | ||
socket_path: args.socket_path.trim().to_string(), | ||
socket_count: args.socket_count, | ||
device: args.device.trim().to_string(), | ||
}) | ||
} | ||
} | ||
|
||
fn start_backend<D: 'static + SpiDevice + Send + Sync>(args: SpiArgs) -> Result<()> { | ||
let config = SpiConfiguration::try_from(args).unwrap(); | ||
|
||
let spi_dev = D::open(&config.device).map_err(Error::SpiFailure)?; | ||
|
||
let spi_ctrl = Arc::new(SpiController::<D>::new(spi_dev).map_err(Error::SpiFailure)?); | ||
|
||
let mut handles = Vec::new(); | ||
|
||
for i in 0..config.socket_count { | ||
let socket = config.socket_path.to_owned() + &i.to_string(); | ||
let spi_ctrl = spi_ctrl.clone(); | ||
|
||
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 backend = Arc::new(RwLock::new( | ||
VhostUserSpiBackend::new(spi_ctrl.clone()).map_err(Error::CouldNotCreateBackend)?, | ||
)); | ||
|
||
let mut daemon = VhostUserDaemon::new( | ||
String::from("vhost-device-spi-backend"), | ||
backend.clone(), | ||
GuestMemoryAtomic::new(GuestMemoryMmap::new()), | ||
) | ||
.map_err(Error::CouldNotCreateDaemon)?; | ||
|
||
daemon.serve(&socket).map_err(Error::ServeFailed)?; | ||
}); | ||
|
||
handles.push(handle); | ||
} | ||
|
||
for handle in handles { | ||
handle.join().map_err(std::panic::resume_unwind).unwrap()?; | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
fn main() { | ||
env_logger::init(); | ||
|
||
if let Err(e) = start_backend::<PhysDevice>(SpiArgs::parse()) { | ||
error!("{e}"); | ||
exit(1); | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
pub(crate) mod tests { | ||
use assert_matches::assert_matches; | ||
|
||
use super::*; | ||
use crate::spi::tests::DummyDevice; | ||
|
||
impl SpiArgs { | ||
fn from_args(path: &str, device: &str, count: usize) -> SpiArgs { | ||
SpiArgs { | ||
socket_path: path.to_string(), | ||
socket_count: count, | ||
device: device.to_string(), | ||
} | ||
} | ||
} | ||
|
||
#[test] | ||
fn test_parse_failure() { | ||
let socket_name = "vspi.sock"; | ||
|
||
// Zero socket count | ||
let cmd_args = SpiArgs::from_args(socket_name, "spidev0.0", 0); | ||
assert_matches!( | ||
SpiConfiguration::try_from(cmd_args).unwrap_err(), | ||
Error::SocketCountInvalid(0) | ||
); | ||
} | ||
|
||
#[test] | ||
fn test_fail_listener() { | ||
// This will fail the listeners and thread will panic. | ||
let socket_name = "~/path/not/present/spi"; | ||
let cmd_args = SpiArgs::from_args(socket_name, "spidev0.0", 1); | ||
|
||
assert_matches!( | ||
start_backend::<DummyDevice>(cmd_args).unwrap_err(), | ||
Error::ServeFailed(_) | ||
); | ||
} | ||
} |
Oops, something went wrong.