mirror of
https://github.com/rust-vmm/vhost-device.git
synced 2025-12-26 14:41:23 +00:00
gpio: Add boilerplate code
This adds the boilerplate code and its dependencies to get the basic structure for the gpio crate. Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
This commit is contained in:
parent
6f76558744
commit
5caff0a8ad
@ -1,6 +1,7 @@
|
||||
[workspace]
|
||||
|
||||
members = [
|
||||
"gpio",
|
||||
"i2c",
|
||||
"rng",
|
||||
]
|
||||
|
||||
24
gpio/Cargo.toml
Normal file
24
gpio/Cargo.toml
Normal file
@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "vhost-device-gpio"
|
||||
version = "0.1.0"
|
||||
authors = ["Viresh Kumar <viresh.kumar@linaro.org>"]
|
||||
description = "vhost gpio backend device"
|
||||
repository = "https://github.com/rust-vmm/vhost-device"
|
||||
readme = "README.md"
|
||||
keywords = ["gpio", "vhost", "virt", "backend"]
|
||||
license = "Apache-2.0 OR BSD-3-Clause"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap = { version = ">=3.0", features = ["derive"] }
|
||||
env_logger = ">=0.9"
|
||||
libc = ">=0.2.95"
|
||||
log = ">=0.4.6"
|
||||
thiserror = "1.0"
|
||||
vhost = { version = "0.4", features = ["vhost-user-slave"] }
|
||||
vhost-user-backend = "0.3"
|
||||
virtio-bindings = ">=0.1"
|
||||
vm-memory = "0.7"
|
||||
vmm-sys-util = "=0.9.0"
|
||||
1
gpio/README.md
Normal file
1
gpio/README.md
Normal file
@ -0,0 +1 @@
|
||||
# vhost-device-gpio - GPIO emulation backend daemon
|
||||
192
gpio/src/backend.rs
Normal file
192
gpio/src/backend.rs
Normal file
@ -0,0 +1,192 @@
|
||||
// VIRTIO GPIO Emulation via vhost-user
|
||||
//
|
||||
// Copyright 2022 Linaro Ltd. All Rights Reserved.
|
||||
// Viresh Kumar <viresh.kumar@linaro.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use log::{info, warn};
|
||||
use std::convert::TryFrom;
|
||||
use std::num::ParseIntError;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread::spawn;
|
||||
|
||||
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::gpio::{GpioController, GpioDevice, PhysDevice};
|
||||
use crate::vhu_gpio::VhostUserGpioBackend;
|
||||
|
||||
pub(crate) type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug, PartialEq, ThisError)]
|
||||
/// Errors related to low level GPIO helpers
|
||||
pub enum Error {
|
||||
#[error("Invalid socket count: {0}")]
|
||||
SocketCountInvalid(usize),
|
||||
#[error("Socket count ({0}) doesn't match device count {1}")]
|
||||
DeviceCountMismatch(usize, usize),
|
||||
#[error("Duplicate device detected: {0}")]
|
||||
DeviceDuplicate(u32),
|
||||
#[error("Failed while parsing to integer: {0:?}")]
|
||||
ParseFailure(ParseIntError),
|
||||
#[error("Failed to join threads")]
|
||||
FailedJoiningThreads,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(author, version, about, long_about = None)]
|
||||
struct GpioArgs {
|
||||
/// 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,
|
||||
|
||||
/// List of GPIO devices, one for each guest, in the format <N1>[:<N2>]. The first entry is for
|
||||
/// Guest that connects to socket 0, next one for socket 1, and so on. Each device number here
|
||||
/// will be used to access the corresponding /dev/gpiochipX. Example, "-c 4 -l 3:4:6:1"
|
||||
#[clap(short = 'l', long)]
|
||||
device_list: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) struct DeviceConfig {
|
||||
inner: Vec<u32>,
|
||||
}
|
||||
|
||||
impl DeviceConfig {
|
||||
fn new() -> Self {
|
||||
Self { inner: Vec::new() }
|
||||
}
|
||||
|
||||
fn contains_device(&self, number: u32) -> bool {
|
||||
self.inner.iter().any(|elem| *elem == number)
|
||||
}
|
||||
|
||||
fn push(&mut self, device: u32) -> Result<()> {
|
||||
if self.contains_device(device) {
|
||||
return Err(Error::DeviceDuplicate(device));
|
||||
}
|
||||
|
||||
self.inner.push(device);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for DeviceConfig {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(list: &str) -> Result<Self> {
|
||||
let list: Vec<&str> = list.split(':').collect();
|
||||
let mut devices = DeviceConfig::new();
|
||||
|
||||
for info in list.iter() {
|
||||
let number = info.parse::<u32>().map_err(Error::ParseFailure)?;
|
||||
devices.push(number)?;
|
||||
}
|
||||
Ok(devices)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
struct GpioConfiguration {
|
||||
socket_path: String,
|
||||
socket_count: usize,
|
||||
devices: DeviceConfig,
|
||||
}
|
||||
|
||||
impl TryFrom<GpioArgs> for GpioConfiguration {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(args: GpioArgs) -> Result<Self> {
|
||||
if args.socket_count == 0 {
|
||||
return Err(Error::SocketCountInvalid(0));
|
||||
}
|
||||
|
||||
let devices = DeviceConfig::try_from(args.device_list.as_str())?;
|
||||
|
||||
if devices.inner.len() != args.socket_count as usize {
|
||||
return Err(Error::DeviceCountMismatch(
|
||||
args.socket_count,
|
||||
devices.inner.len(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(GpioConfiguration {
|
||||
socket_path: args.socket_path,
|
||||
socket_count: args.socket_count,
|
||||
devices,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn start_backend<D: 'static + GpioDevice + Send + Sync>(args: GpioArgs) -> Result<()> {
|
||||
let config = GpioConfiguration::try_from(args).unwrap();
|
||||
let mut handles = Vec::new();
|
||||
|
||||
for i in 0..config.socket_count {
|
||||
let socket = config.socket_path.to_owned() + &i.to_string();
|
||||
let device_num = config.devices.inner[i];
|
||||
|
||||
let handle = 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 device = D::open(device_num).unwrap();
|
||||
let controller = GpioController::<D>::new(device).unwrap();
|
||||
let backend = Arc::new(RwLock::new(VhostUserGpioBackend::new(controller).unwrap()));
|
||||
let listener = Listener::new(socket.clone(), true).unwrap();
|
||||
|
||||
let mut daemon = VhostUserDaemon::new(
|
||||
String::from("vhost-device-gpio-backend"),
|
||||
backend.clone(),
|
||||
GuestMemoryAtomic::new(GuestMemoryMmap::new()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
daemon.start(listener).unwrap();
|
||||
|
||||
match daemon.wait() {
|
||||
Ok(()) => {
|
||||
info!("Stopping cleanly.");
|
||||
}
|
||||
Err(vhost_user_backend::Error::HandleRequest(
|
||||
vhost_user::Error::PartialMessage,
|
||||
)) => {
|
||||
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.
|
||||
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 gpio_init() -> Result<()> {
|
||||
env_logger::init();
|
||||
|
||||
start_backend::<PhysDevice>(GpioArgs::parse())
|
||||
}
|
||||
51
gpio/src/gpio.rs
Normal file
51
gpio/src/gpio.rs
Normal file
@ -0,0 +1,51 @@
|
||||
// GPIO backend device
|
||||
//
|
||||
// Copyright 2022 Linaro Ltd. All Rights Reserved.
|
||||
// Viresh Kumar <viresh.kumar@linaro.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use thiserror::Error as ThisError;
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, ThisError)]
|
||||
/// Errors related to low level gpio helpers
|
||||
pub(crate) enum Error {
|
||||
}
|
||||
|
||||
/// Trait that represents an GPIO Device.
|
||||
///
|
||||
/// This trait is introduced for development purposes only, and should not
|
||||
/// be used outside of this crate. The purpose of this trait is to provide a
|
||||
/// mock implementation for the GPIO driver so that we can test the GPIO
|
||||
/// functionality without the need of a physical device.
|
||||
pub(crate) trait GpioDevice {
|
||||
fn open(device: u32) -> Result<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
pub(crate) struct PhysDevice {}
|
||||
|
||||
impl GpioDevice for PhysDevice {
|
||||
fn open(_device: u32) -> Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Ok(Self {})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct GpioController<D: GpioDevice> {
|
||||
device: D,
|
||||
}
|
||||
|
||||
impl<D: GpioDevice> GpioController<D> {
|
||||
// Creates a new controller corresponding to `device`.
|
||||
pub(crate) fn new(device: D) -> Result<GpioController<D>> {
|
||||
Ok(GpioController {
|
||||
device,
|
||||
})
|
||||
}
|
||||
}
|
||||
14
gpio/src/main.rs
Normal file
14
gpio/src/main.rs
Normal file
@ -0,0 +1,14 @@
|
||||
// VIRTIO GPIO Emulation via vhost-user
|
||||
//
|
||||
// Copyright 2022 Linaro Ltd. All Rights Reserved.
|
||||
// Viresh Kumar <viresh.kumar@linaro.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
mod backend;
|
||||
mod gpio;
|
||||
mod vhu_gpio;
|
||||
|
||||
fn main() -> backend::Result<()> {
|
||||
backend::gpio_init()
|
||||
}
|
||||
147
gpio/src/vhu_gpio.rs
Normal file
147
gpio/src/vhu_gpio.rs
Normal file
@ -0,0 +1,147 @@
|
||||
// vhost device gpio
|
||||
//
|
||||
// Copyright 2022 Linaro Ltd. All Rights Reserved.
|
||||
// Viresh Kumar <viresh.kumar@linaro.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::{convert, io};
|
||||
|
||||
use thiserror::Error as ThisError;
|
||||
use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
|
||||
use vhost_user_backend::{VhostUserBackendMut, VringRwLock, VringT};
|
||||
use virtio_bindings::bindings::virtio_net::{VIRTIO_F_NOTIFY_ON_EMPTY, VIRTIO_F_VERSION_1};
|
||||
use virtio_bindings::bindings::virtio_ring::{
|
||||
VIRTIO_RING_F_EVENT_IDX, VIRTIO_RING_F_INDIRECT_DESC,
|
||||
};
|
||||
use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap};
|
||||
use vmm_sys_util::epoll::EventSet;
|
||||
use vmm_sys_util::eventfd::{EventFd, EFD_NONBLOCK};
|
||||
|
||||
use crate::gpio::{GpioController, GpioDevice};
|
||||
|
||||
const QUEUE_SIZE: usize = 256;
|
||||
const NUM_QUEUES: usize = 2;
|
||||
|
||||
/// Queues
|
||||
const REQUEST_QUEUE: u16 = 0;
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
type VhostUserBackendResult<T> = std::result::Result<T, std::io::Error>;
|
||||
|
||||
#[derive(Debug, ThisError)]
|
||||
/// Errors related to vhost-device-gpio-daemon.
|
||||
pub(crate) enum Error {
|
||||
#[error("Failed to handle event, didn't match EPOLLIN")]
|
||||
HandleEventNotEpollIn,
|
||||
#[error("Failed to handle unknown event")]
|
||||
HandleEventUnknown,
|
||||
}
|
||||
|
||||
impl convert::From<Error> for io::Error {
|
||||
fn from(e: Error) -> Self {
|
||||
io::Error::new(io::ErrorKind::Other, e)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct VhostUserGpioBackend<D: GpioDevice> {
|
||||
controller: GpioController<D>,
|
||||
event_idx: bool,
|
||||
pub(crate) exit_event: EventFd,
|
||||
}
|
||||
|
||||
impl<D: GpioDevice> VhostUserGpioBackend<D> {
|
||||
pub(crate) fn new(controller: GpioController<D>) -> Result<Self> {
|
||||
Ok(VhostUserGpioBackend {
|
||||
controller,
|
||||
event_idx: false,
|
||||
exit_event: EventFd::new(EFD_NONBLOCK).expect("Creating exit eventfd"),
|
||||
})
|
||||
}
|
||||
|
||||
/// Process the messages in the vring and dispatch replies
|
||||
fn process_request_queue(&self, _vring: &VringRwLock) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// VhostUserBackendMut trait methods
|
||||
impl<D: 'static + GpioDevice + Sync + Send> VhostUserBackendMut<VringRwLock, ()>
|
||||
for VhostUserGpioBackend<D>
|
||||
{
|
||||
fn num_queues(&self) -> usize {
|
||||
NUM_QUEUES
|
||||
}
|
||||
|
||||
fn max_queue_size(&self) -> usize {
|
||||
QUEUE_SIZE
|
||||
}
|
||||
|
||||
fn features(&self) -> u64 {
|
||||
// this matches the current libvhost defaults except VHOST_F_LOG_ALL
|
||||
1 << VIRTIO_F_VERSION_1
|
||||
| 1 << VIRTIO_F_NOTIFY_ON_EMPTY
|
||||
| 1 << VIRTIO_RING_F_INDIRECT_DESC
|
||||
| 1 << VIRTIO_RING_F_EVENT_IDX
|
||||
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
|
||||
}
|
||||
|
||||
fn protocol_features(&self) -> VhostUserProtocolFeatures {
|
||||
VhostUserProtocolFeatures::MQ
|
||||
}
|
||||
|
||||
fn set_event_idx(&mut self, enabled: bool) {
|
||||
dbg!(self.event_idx = enabled);
|
||||
}
|
||||
|
||||
fn update_memory(
|
||||
&mut self,
|
||||
_mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
||||
) -> VhostUserBackendResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_event(
|
||||
&mut self,
|
||||
device_event: u16,
|
||||
evset: EventSet,
|
||||
vrings: &[VringRwLock],
|
||||
_thread_id: usize,
|
||||
) -> VhostUserBackendResult<bool> {
|
||||
if evset != EventSet::IN {
|
||||
return Err(Error::HandleEventNotEpollIn.into());
|
||||
}
|
||||
|
||||
match device_event {
|
||||
REQUEST_QUEUE => {
|
||||
let vring = &vrings[0];
|
||||
|
||||
if self.event_idx {
|
||||
// vm-virtio's Queue implementation only checks avail_index
|
||||
// once, so to properly support EVENT_IDX we need to keep
|
||||
// calling process_request_queue() until it stops finding
|
||||
// new requests on the queue.
|
||||
loop {
|
||||
vring.disable_notification().unwrap();
|
||||
self.process_request_queue(vring)?;
|
||||
if !vring.enable_notification().unwrap() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Without EVENT_IDX, a single call is enough.
|
||||
self.process_request_queue(vring)?;
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
return Err(Error::HandleEventUnknown.into());
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn exit_event(&self, _thread_index: usize) -> Option<EventFd> {
|
||||
self.exit_event.try_clone().ok()
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user