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:
Viresh Kumar 2021-06-15 17:12:53 +05:30
parent 6f76558744
commit 5caff0a8ad
7 changed files with 430 additions and 0 deletions

View File

@ -1,6 +1,7 @@
[workspace]
members = [
"gpio",
"i2c",
"rng",
]

24
gpio/Cargo.toml Normal file
View 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
View File

@ -0,0 +1 @@
# vhost-device-gpio - GPIO emulation backend daemon

192
gpio/src/backend.rs Normal file
View 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
View 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
View 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
View 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()
}
}