From 5caff0a8ad42e7e17b911ea5010c8b6191e9249b Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Tue, 15 Jun 2021 17:12:53 +0530 Subject: [PATCH 1/8] 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 --- Cargo.toml | 1 + gpio/Cargo.toml | 24 ++++++ gpio/README.md | 1 + gpio/src/backend.rs | 192 +++++++++++++++++++++++++++++++++++++++++++ gpio/src/gpio.rs | 51 ++++++++++++ gpio/src/main.rs | 14 ++++ gpio/src/vhu_gpio.rs | 147 +++++++++++++++++++++++++++++++++ 7 files changed, 430 insertions(+) create mode 100644 gpio/Cargo.toml create mode 100644 gpio/README.md create mode 100644 gpio/src/backend.rs create mode 100644 gpio/src/gpio.rs create mode 100644 gpio/src/main.rs create mode 100644 gpio/src/vhu_gpio.rs diff --git a/Cargo.toml b/Cargo.toml index 92b4fd0..faee2f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ + "gpio", "i2c", "rng", ] diff --git a/gpio/Cargo.toml b/gpio/Cargo.toml new file mode 100644 index 0000000..1a5c214 --- /dev/null +++ b/gpio/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "vhost-device-gpio" +version = "0.1.0" +authors = ["Viresh Kumar "] +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" diff --git a/gpio/README.md b/gpio/README.md new file mode 100644 index 0000000..5c6067a --- /dev/null +++ b/gpio/README.md @@ -0,0 +1 @@ +# vhost-device-gpio - GPIO emulation backend daemon diff --git a/gpio/src/backend.rs b/gpio/src/backend.rs new file mode 100644 index 0000000..debcc2d --- /dev/null +++ b/gpio/src/backend.rs @@ -0,0 +1,192 @@ +// VIRTIO GPIO Emulation via vhost-user +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar +// +// 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 = std::result::Result; + +#[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 [:]. 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, +} + +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 { + let list: Vec<&str> = list.split(':').collect(); + let mut devices = DeviceConfig::new(); + + for info in list.iter() { + let number = info.parse::().map_err(Error::ParseFailure)?; + devices.push(number)?; + } + Ok(devices) + } +} + +#[derive(PartialEq, Debug)] +struct GpioConfiguration { + socket_path: String, + socket_count: usize, + devices: DeviceConfig, +} + +impl TryFrom for GpioConfiguration { + type Error = Error; + + fn try_from(args: GpioArgs) -> Result { + 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(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::::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::(GpioArgs::parse()) +} diff --git a/gpio/src/gpio.rs b/gpio/src/gpio.rs new file mode 100644 index 0000000..a2738e2 --- /dev/null +++ b/gpio/src/gpio.rs @@ -0,0 +1,51 @@ +// GPIO backend device +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar +// +// SPDX-License-Identifier: Apache-2.0 + +use thiserror::Error as ThisError; + +type Result = std::result::Result; + +#[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 + where + Self: Sized; +} + +pub(crate) struct PhysDevice {} + +impl GpioDevice for PhysDevice { + fn open(_device: u32) -> Result + where + Self: Sized, + { + Ok(Self {}) + } +} + +pub(crate) struct GpioController { + device: D, +} + +impl GpioController { + // Creates a new controller corresponding to `device`. + pub(crate) fn new(device: D) -> Result> { + Ok(GpioController { + device, + }) + } +} diff --git a/gpio/src/main.rs b/gpio/src/main.rs new file mode 100644 index 0000000..3531af3 --- /dev/null +++ b/gpio/src/main.rs @@ -0,0 +1,14 @@ +// VIRTIO GPIO Emulation via vhost-user +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar +// +// SPDX-License-Identifier: Apache-2.0 + +mod backend; +mod gpio; +mod vhu_gpio; + +fn main() -> backend::Result<()> { + backend::gpio_init() +} diff --git a/gpio/src/vhu_gpio.rs b/gpio/src/vhu_gpio.rs new file mode 100644 index 0000000..f6708b9 --- /dev/null +++ b/gpio/src/vhu_gpio.rs @@ -0,0 +1,147 @@ +// vhost device gpio +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar +// +// 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 = std::result::Result; +type VhostUserBackendResult = std::result::Result; + +#[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 for io::Error { + fn from(e: Error) -> Self { + io::Error::new(io::ErrorKind::Other, e) + } +} + +pub(crate) struct VhostUserGpioBackend { + controller: GpioController, + event_idx: bool, + pub(crate) exit_event: EventFd, +} + +impl VhostUserGpioBackend { + pub(crate) fn new(controller: GpioController) -> Result { + 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 VhostUserBackendMut + for VhostUserGpioBackend +{ + 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, + ) -> VhostUserBackendResult<()> { + Ok(()) + } + + fn handle_event( + &mut self, + device_event: u16, + evset: EventSet, + vrings: &[VringRwLock], + _thread_id: usize, + ) -> VhostUserBackendResult { + 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 { + self.exit_event.try_clone().ok() + } +} From 472870d44db74a26d35913fc4d25eb23274c33a9 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Tue, 22 Jun 2021 13:18:23 +0530 Subject: [PATCH 2/8] gpio: Implement low level GPIO helpers This patch implements the low level GPIO helpers responsible for pin level functioning. Signed-off-by: Viresh Kumar --- gpio/Cargo.toml | 2 + gpio/src/gpio.rs | 263 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 262 insertions(+), 3 deletions(-) diff --git a/gpio/Cargo.toml b/gpio/Cargo.toml index 1a5c214..968a260 100644 --- a/gpio/Cargo.toml +++ b/gpio/Cargo.toml @@ -22,3 +22,5 @@ vhost-user-backend = "0.3" virtio-bindings = ">=0.1" vm-memory = "0.7" vmm-sys-util = "=0.9.0" + +libgpiod = { git = "https://github.com/vireshk/libgpiod" } diff --git a/gpio/src/gpio.rs b/gpio/src/gpio.rs index a2738e2..94b163d 100644 --- a/gpio/src/gpio.rs +++ b/gpio/src/gpio.rs @@ -5,13 +5,50 @@ // // SPDX-License-Identifier: Apache-2.0 +use log::error; +use std::sync::RwLock; + +use libgpiod::{Chip, Direction, Error as LibGpiodError, LineConfig, LineRequest, RequestConfig}; use thiserror::Error as ThisError; +use vm_memory::{Le16, Le32}; type Result = std::result::Result; #[derive(Clone, Debug, PartialEq, ThisError)] /// Errors related to low level gpio helpers pub(crate) enum Error { + #[error("Invalid gpio direction: {0}")] + GpioDirectionInvalid(u32), + #[error("Invalid current gpio value")] + GpioCurrentValueInvalid, + #[error("Invalid gpio value: {0}")] + GpioValueInvalid(u32), + #[error("Invalid gpio message type: {0}")] + GpioMessageInvalid(u16), + #[error("Gpiod operation failed {0:?}")] + GpiodFailed(LibGpiodError), +} + +/// Virtio specification definitions +/// Virtio GPIO request types +pub(crate) const VIRTIO_GPIO_MSG_GET_LINE_NAMES: u16 = 0x0001; +pub(crate) const VIRTIO_GPIO_MSG_GET_DIRECTION: u16 = 0x0002; +pub(crate) const VIRTIO_GPIO_MSG_SET_DIRECTION: u16 = 0x0003; +pub(crate) const VIRTIO_GPIO_MSG_GET_VALUE: u16 = 0x0004; +pub(crate) const VIRTIO_GPIO_MSG_SET_VALUE: u16 = 0x0005; + +/// Direction types +pub(crate) const VIRTIO_GPIO_DIRECTION_NONE: u8 = 0x00; +pub(crate) const VIRTIO_GPIO_DIRECTION_OUT: u8 = 0x01; +pub(crate) const VIRTIO_GPIO_DIRECTION_IN: u8 = 0x02; + +/// Virtio GPIO Configuration +#[derive(Clone)] +#[repr(C)] +pub(crate) struct VirtioGpioConfig { + ngpio: Le16, + padding: Le16, + gpio_names_size: Le32, } /// Trait that represents an GPIO Device. @@ -24,28 +61,248 @@ pub(crate) trait GpioDevice { fn open(device: u32) -> Result where Self: Sized; + + fn get_num_gpios(&self) -> Result; + fn get_gpio_name(&self, gpio: u16) -> Result; + fn get_direction(&self, gpio: u16) -> Result; + fn set_direction(&self, gpio: u16, dir: u8, value: u32) -> Result<()>; + fn get_value(&self, gpio: u16) -> Result; + fn set_value(&self, gpio: u16, value: u32) -> Result<()>; } -pub(crate) struct PhysDevice {} +pub(crate) struct PhysLineState { + request: Option, +} + +pub(crate) struct PhysDevice { + chip: Chip, + ngpio: u16, + state: Vec>, +} + +unsafe impl Send for PhysDevice {} +unsafe impl Sync for PhysDevice {} impl GpioDevice for PhysDevice { - fn open(_device: u32) -> Result + fn open(device: u32) -> Result where Self: Sized, { - Ok(Self {}) + let path = format!("/dev/gpiochip{}", device); + let chip = Chip::open(&path).map_err(Error::GpiodFailed)?; + let ngpio = chip.get_num_lines() as u16; + + // Can't set a vector to all None easily + let mut state: Vec> = Vec::new(); + state.resize_with(ngpio as usize, || { + RwLock::new(PhysLineState { + request: None, + }) + }); + + Ok(PhysDevice { chip, ngpio, state }) + } + + fn get_num_gpios(&self) -> Result { + Ok(self.ngpio) + } + + fn get_gpio_name(&self, gpio: u16) -> Result { + let line_info = self + .chip + .line_info(gpio.into()) + .map_err(Error::GpiodFailed)?; + + Ok(line_info.get_name().unwrap_or("").to_string()) + } + + fn get_direction(&self, gpio: u16) -> Result { + let line_info = self + .chip + .line_info(gpio.into()) + .map_err(Error::GpiodFailed)?; + + Ok( + match line_info.get_direction().map_err(Error::GpiodFailed)? { + Direction::AsIs => VIRTIO_GPIO_DIRECTION_NONE, + Direction::Input => VIRTIO_GPIO_DIRECTION_IN, + Direction::Output => VIRTIO_GPIO_DIRECTION_OUT, + }, + ) + } + + fn set_direction(&self, gpio: u16, dir: u8, value: u32) -> Result<()> { + let mut config = LineConfig::new().map_err(Error::GpiodFailed)?; + let state = &mut self.state[gpio as usize].write().unwrap(); + + match dir { + VIRTIO_GPIO_DIRECTION_NONE => { + state.request = None; + return Ok(()); + } + + VIRTIO_GPIO_DIRECTION_IN => config.set_direction_offset(Direction::Input, gpio as u32), + VIRTIO_GPIO_DIRECTION_OUT => { + config.set_direction(Direction::Output); + config.set_output_value(gpio as u32, value); + } + + _ => return Err(Error::GpioDirectionInvalid(value)), + }; + + if let Some(request) = &state.request { + request + .reconfigure_lines(&config) + .map_err(Error::GpiodFailed)?; + } else { + let rconfig = RequestConfig::new().map_err(Error::GpiodFailed)?; + + rconfig.set_consumer("vhu-gpio"); + rconfig.set_offsets(&[gpio as u32]); + + state.request = Some( + self.chip + .request_lines(&rconfig, &config) + .map_err(Error::GpiodFailed)?, + ); + } + + Ok(()) + } + + fn get_value(&self, gpio: u16) -> Result { + let state = &self.state[gpio as usize].read().unwrap(); + + if let Some(request) = &state.request { + Ok(request.get_value(gpio as u32).map_err(Error::GpiodFailed)? as u8) + } else { + Err(Error::GpioDirectionInvalid( + VIRTIO_GPIO_DIRECTION_NONE as u32, + )) + } + } + + fn set_value(&self, gpio: u16, value: u32) -> Result<()> { + let state = &self.state[gpio as usize].read().unwrap(); + + // Direction change can follow value change, don't fail here for invalid + // direction. + if let Some(request) = &state.request { + request + .set_value(gpio as u32, value as i32) + .map_err(Error::GpiodFailed)?; + } + + Ok(()) } } +struct GpioState { + dir: u8, + val: Option, +} + pub(crate) struct GpioController { + config: VirtioGpioConfig, device: D, + state: Vec>, + gpio_names: String, } impl GpioController { // Creates a new controller corresponding to `device`. pub(crate) fn new(device: D) -> Result> { + let ngpio = device.get_num_gpios()?; + + // The gpio's name can be of any length, we are just trying to allocate something + // reasonable to start with, we can always extend it later. + let mut gpio_names = String::with_capacity((ngpio * 10).into()); + let mut state = Vec::with_capacity(ngpio as usize); + + for offset in 0..ngpio { + let name = device.get_gpio_name(offset)?; + + // Create line names + gpio_names.push_str(&name); + gpio_names.push('\0'); + + state.push(RwLock::new(GpioState { + dir: device.get_direction(offset)?, + val: None, + })); + } + Ok(GpioController { + config: VirtioGpioConfig { + ngpio: From::from(ngpio), + padding: From::from(0), + gpio_names_size: From::from(gpio_names.len() as u32), + }, device, + state, + gpio_names, + }) + } + + fn get_direction(&self, gpio: u16) -> Result { + self.device.get_direction(gpio) + } + + fn set_direction(&self, gpio: u16, dir: u32) -> Result<()> { + let state = &mut self.state[gpio as usize].write().unwrap(); + + let value = match dir as u8 { + VIRTIO_GPIO_DIRECTION_NONE => { + state.val = None; + 0 + } + + VIRTIO_GPIO_DIRECTION_IN => 0, + VIRTIO_GPIO_DIRECTION_OUT => match state.val { + Some(val) => val, + None => return Err(Error::GpioCurrentValueInvalid), + }, + + _ => return Err(Error::GpioDirectionInvalid(dir)), + }; + + self.device.set_direction(gpio, dir as u8, value as u32)?; + state.dir = dir as u8; + Ok(()) + } + + fn get_value(&self, gpio: u16) -> Result { + self.device.get_value(gpio) + } + + fn set_value(&self, gpio: u16, value: u32) -> Result<()> { + if value > 1 { + return Err(Error::GpioValueInvalid(value)); + } + + self.device.set_value(gpio, value)?; + self.state[gpio as usize].write().unwrap().val = Some(value as u16); + Ok(()) + } + + pub(crate) fn get_config(&self) -> &VirtioGpioConfig { + &self.config + } + + pub(crate) fn operation(&self, rtype: u16, gpio: u16, value: u32) -> Result> { + Ok(match rtype { + VIRTIO_GPIO_MSG_GET_LINE_NAMES => self.gpio_names.as_bytes().to_vec(), + VIRTIO_GPIO_MSG_GET_DIRECTION => vec![self.get_direction(gpio)?], + VIRTIO_GPIO_MSG_SET_DIRECTION => { + self.set_direction(gpio, value)?; + vec![0] + } + VIRTIO_GPIO_MSG_GET_VALUE => vec![self.get_value(gpio)?], + VIRTIO_GPIO_MSG_SET_VALUE => { + self.set_value(gpio, value)?; + vec![0] + } + msg => return Err(Error::GpioMessageInvalid(msg)), }) } } From 8a021c704f662c1c67464d3ce00e9c054be59647 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Fri, 23 Apr 2021 16:11:12 +0530 Subject: [PATCH 3/8] gpio: Process virtio requests This patch implements process_queue() to process incoming requests. Signed-off-by: Viresh Kumar --- gpio/Cargo.toml | 1 + gpio/src/vhu_gpio.rs | 159 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 154 insertions(+), 6 deletions(-) diff --git a/gpio/Cargo.toml b/gpio/Cargo.toml index 968a260..6111c08 100644 --- a/gpio/Cargo.toml +++ b/gpio/Cargo.toml @@ -20,6 +20,7 @@ thiserror = "1.0" vhost = { version = "0.4", features = ["vhost-user-slave"] } vhost-user-backend = "0.3" virtio-bindings = ">=0.1" +virtio-queue = "0.2" vm-memory = "0.7" vmm-sys-util = "=0.9.0" diff --git a/gpio/src/vhu_gpio.rs b/gpio/src/vhu_gpio.rs index f6708b9..4a4dcad 100644 --- a/gpio/src/vhu_gpio.rs +++ b/gpio/src/vhu_gpio.rs @@ -5,6 +5,9 @@ // // SPDX-License-Identifier: Apache-2.0 +use log::error; +use std::mem::size_of; +use std::slice::from_raw_parts; use std::{convert, io}; use thiserror::Error as ThisError; @@ -14,11 +17,18 @@ use virtio_bindings::bindings::virtio_net::{VIRTIO_F_NOTIFY_ON_EMPTY, VIRTIO_F_V use virtio_bindings::bindings::virtio_ring::{ VIRTIO_RING_F_EVENT_IDX, VIRTIO_RING_F_INDIRECT_DESC, }; -use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; +use virtio_queue::DescriptorChain; +use vm_memory::{ + ByteValued, Bytes, GuestMemoryAtomic, GuestMemoryLoadGuard, GuestMemoryMmap, Le16, Le32, +}; use vmm_sys_util::epoll::EventSet; use vmm_sys_util::eventfd::{EventFd, EFD_NONBLOCK}; -use crate::gpio::{GpioController, GpioDevice}; +use crate::gpio::{GpioController, GpioDevice, VirtioGpioConfig}; + +/// Possible values of the status field +const VIRTIO_GPIO_STATUS_OK: u8 = 0x0; +const VIRTIO_GPIO_STATUS_ERR: u8 = 0x1; const QUEUE_SIZE: usize = 256; const NUM_QUEUES: usize = 2; @@ -29,13 +39,31 @@ const REQUEST_QUEUE: u16 = 0; type Result = std::result::Result; type VhostUserBackendResult = std::result::Result; -#[derive(Debug, ThisError)] +#[derive(Copy, Clone, Debug, PartialEq, 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, + #[error("Received unexpected write only descriptor at index {0}")] + UnexpectedWriteOnlyDescriptor(usize), + #[error("Received unexpected readable descriptor at index {0}")] + UnexpectedReadableDescriptor(usize), + #[error("Invalid descriptor count {0}")] + UnexpectedDescriptorCount(usize), + #[error("Invalid descriptor size, expected: {0}, found: {1}")] + UnexpectedDescriptorSize(usize, u32), + #[error("Descriptor not found")] + DescriptorNotFound, + #[error("Descriptor read failed")] + DescriptorReadFailed, + #[error("Descriptor write failed")] + DescriptorWriteFailed, + #[error("Failed to send notification")] + NotificationFailed, + #[error("Failed to create new EventFd")] + EventFdFailed, } impl convert::From for io::Error { @@ -44,23 +72,132 @@ impl convert::From for io::Error { } } +/// Virtio GPIO 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)] +struct VirtioGpioRequest { + rtype: Le16, + gpio: Le16, + value: Le32, +} +unsafe impl ByteValued for VirtioGpioRequest {} + pub(crate) struct VhostUserGpioBackend { controller: GpioController, event_idx: bool, pub(crate) exit_event: EventFd, } +type GpioDescriptorChain = DescriptorChain>>; + impl VhostUserGpioBackend { pub(crate) fn new(controller: GpioController) -> Result { Ok(VhostUserGpioBackend { controller, event_idx: false, - exit_event: EventFd::new(EFD_NONBLOCK).expect("Creating exit eventfd"), + exit_event: EventFd::new(EFD_NONBLOCK).map_err(|_| Error::EventFdFailed)?, }) } + /// Process the requests in the request queue + fn process_requests( + &self, + requests: Vec, + vring: &VringRwLock, + ) -> Result { + if requests.is_empty() { + return Ok(true); + } + + for desc_chain in requests { + let descriptors: Vec<_> = desc_chain.clone().collect(); + if descriptors.len() != 2 { + return Err(Error::UnexpectedDescriptorCount(descriptors.len())); + } + + let desc_request = descriptors[0]; + if desc_request.is_write_only() { + return Err(Error::UnexpectedWriteOnlyDescriptor(0)); + } + + if desc_request.len() as usize != size_of::() { + return Err(Error::UnexpectedDescriptorSize( + size_of::(), + desc_request.len(), + )); + } + + let request = desc_chain + .memory() + .read_obj::(desc_request.addr()) + .map_err(|_| Error::DescriptorReadFailed)?; + + let desc_response = descriptors[1]; + if !desc_response.is_write_only() { + return Err(Error::UnexpectedReadableDescriptor(1)); + } + + let response = match self.controller.operation( + request.rtype.to_native(), + request.gpio.to_native(), + request.value.to_native(), + ) { + Ok(mut data) => { + if data.len() != (desc_response.len() - 1) as usize { + error!( + "Invalid response size, expected {}, received {}", + desc_response.len(), + data.len() + ); + vec![VIRTIO_GPIO_STATUS_ERR, 0] + } else { + let mut buf = vec![VIRTIO_GPIO_STATUS_OK]; + buf.append(&mut data); + buf + } + } + + Err(x) => { + error!("{:?}", x); + vec![VIRTIO_GPIO_STATUS_ERR, 0] + } + }; + + desc_chain + .memory() + .write_slice(response.as_slice(), desc_response.addr()) + .map_err(|_| Error::DescriptorWriteFailed)?; + + if vring + .add_used(desc_chain.head_index(), desc_response.len()) + .is_err() + { + error!("Couldn't return used descriptors to the ring"); + } + } + + Ok(true) + } + /// Process the messages in the vring and dispatch replies - fn process_request_queue(&self, _vring: &VringRwLock) -> Result<()> { + fn process_request_queue(&self, vring: &VringRwLock) -> Result<()> { + let requests: Vec<_> = vring + .get_mut() + .get_queue_mut() + .iter() + .map_err(|_| Error::DescriptorNotFound)? + .collect(); + + if self.process_requests(requests, vring)? { + // Send notification once all the requests are processed + vring + .signal_used_queue() + .map_err(|_| Error::NotificationFailed)?; + } + Ok(()) } } @@ -87,7 +224,17 @@ impl VhostUserBackendMut } fn protocol_features(&self) -> VhostUserProtocolFeatures { - VhostUserProtocolFeatures::MQ + VhostUserProtocolFeatures::MQ | VhostUserProtocolFeatures::CONFIG + } + + fn get_config(&self, _offset: u32, _size: u32) -> Vec { + unsafe { + from_raw_parts( + self.controller.get_config() as *const _ as *const _, + size_of::(), + ) + .to_vec() + } } fn set_event_idx(&mut self, enabled: bool) { From 679224af8d95d293d1fada6322f306998c806810 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Wed, 12 Jan 2022 16:47:54 +0530 Subject: [PATCH 4/8] gpio: Add support for interrupts Add support for GPIO interrupts. Only edge interrupts are supported by libgpiod. Signed-off-by: Viresh Kumar --- gpio/src/gpio.rs | 193 ++++++++++++++++++++++++++++++++++++++++-- gpio/src/vhu_gpio.rs | 196 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 379 insertions(+), 10 deletions(-) diff --git a/gpio/src/gpio.rs b/gpio/src/gpio.rs index 94b163d..4bbc373 100644 --- a/gpio/src/gpio.rs +++ b/gpio/src/gpio.rs @@ -6,9 +6,13 @@ // SPDX-License-Identifier: Apache-2.0 use log::error; -use std::sync::RwLock; +use std::sync::{Arc, RwLock}; +use std::time::Duration; -use libgpiod::{Chip, Direction, Error as LibGpiodError, LineConfig, LineRequest, RequestConfig}; +use libgpiod::{ + Chip, Direction, Edge, EdgeEventBuffer, Error as LibGpiodError, LineConfig, LineRequest, + RequestConfig, +}; use thiserror::Error as ThisError; use vm_memory::{Le16, Le32}; @@ -27,6 +31,22 @@ pub(crate) enum Error { GpioMessageInvalid(u16), #[error("Gpiod operation failed {0:?}")] GpiodFailed(LibGpiodError), + #[error("Gpio irq operation timed out")] + GpioIrqOpTimedOut, + #[error("Gpio irq type invalid {0}")] + GpioIrqTypeInvalid(u16), + #[error("Gpio irq type not supported {0}")] + GpioIrqTypeNotSupported(u16), + #[error("Gpio irq type same as current {0}")] + GpioIrqTypeNoChange(u16), + #[error("Gpio irq not enabled yet")] + GpioIrqNotEnabled, + #[error( + "Current Gpio irq type is valid, must configure to VIRTIO_GPIO_IRQ_TYPE_NONE first {0}" + )] + GpioOldIrqTypeValid(u16), + #[error("Gpio line-request not configured")] + GpioLineRequestNotConfigured, } /// Virtio specification definitions @@ -36,12 +56,25 @@ pub(crate) const VIRTIO_GPIO_MSG_GET_DIRECTION: u16 = 0x0002; pub(crate) const VIRTIO_GPIO_MSG_SET_DIRECTION: u16 = 0x0003; pub(crate) const VIRTIO_GPIO_MSG_GET_VALUE: u16 = 0x0004; pub(crate) const VIRTIO_GPIO_MSG_SET_VALUE: u16 = 0x0005; +pub(crate) const VIRTIO_GPIO_MSG_IRQ_TYPE: u16 = 0x0006; /// Direction types pub(crate) const VIRTIO_GPIO_DIRECTION_NONE: u8 = 0x00; pub(crate) const VIRTIO_GPIO_DIRECTION_OUT: u8 = 0x01; pub(crate) const VIRTIO_GPIO_DIRECTION_IN: u8 = 0x02; +/// Virtio GPIO IRQ types +pub(crate) const VIRTIO_GPIO_IRQ_TYPE_NONE: u16 = 0x00; +pub(crate) const VIRTIO_GPIO_IRQ_TYPE_EDGE_RISING: u16 = 0x01; +pub(crate) const VIRTIO_GPIO_IRQ_TYPE_EDGE_FALLING: u16 = 0x02; +pub(crate) const VIRTIO_GPIO_IRQ_TYPE_EDGE_BOTH: u16 = + VIRTIO_GPIO_IRQ_TYPE_EDGE_RISING | VIRTIO_GPIO_IRQ_TYPE_EDGE_FALLING; +pub(crate) const VIRTIO_GPIO_IRQ_TYPE_LEVEL_HIGH: u16 = 0x04; +pub(crate) const VIRTIO_GPIO_IRQ_TYPE_LEVEL_LOW: u16 = 0x08; +const VIRTIO_GPIO_IRQ_TYPE_ALL: u16 = VIRTIO_GPIO_IRQ_TYPE_EDGE_BOTH + | VIRTIO_GPIO_IRQ_TYPE_LEVEL_HIGH + | VIRTIO_GPIO_IRQ_TYPE_LEVEL_LOW; + /// Virtio GPIO Configuration #[derive(Clone)] #[repr(C)] @@ -57,7 +90,7 @@ pub(crate) struct VirtioGpioConfig { /// 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 { +pub(crate) trait GpioDevice: Send + Sync + 'static { fn open(device: u32) -> Result where Self: Sized; @@ -68,10 +101,15 @@ pub(crate) trait GpioDevice { fn set_direction(&self, gpio: u16, dir: u8, value: u32) -> Result<()>; fn get_value(&self, gpio: u16) -> Result; fn set_value(&self, gpio: u16, value: u32) -> Result<()>; + + fn set_irq_type(&self, gpio: u16, value: u16) -> Result<()>; + fn wait_for_interrupt(&self, gpio: u16) -> Result<()>; } pub(crate) struct PhysLineState { - request: Option, + // See wait_for_interrupt() for explanation of Arc. + request: Option>, + buffer: Option, } pub(crate) struct PhysDevice { @@ -97,6 +135,7 @@ impl GpioDevice for PhysDevice { state.resize_with(ngpio as usize, || { RwLock::new(PhysLineState { request: None, + buffer: None, }) }); @@ -160,11 +199,11 @@ impl GpioDevice for PhysDevice { rconfig.set_consumer("vhu-gpio"); rconfig.set_offsets(&[gpio as u32]); - state.request = Some( + state.request = Some(Arc::new( self.chip .request_lines(&rconfig, &config) .map_err(Error::GpiodFailed)?, - ); + )); } Ok(()) @@ -195,11 +234,103 @@ impl GpioDevice for PhysDevice { Ok(()) } + + fn set_irq_type(&self, gpio: u16, value: u16) -> Result<()> { + let state = &mut self.state[gpio as usize].write().unwrap(); + let mut config = LineConfig::new().map_err(Error::GpiodFailed)?; + + match value as u16 { + VIRTIO_GPIO_IRQ_TYPE_EDGE_RISING => config.set_edge_detection(Edge::Rising), + VIRTIO_GPIO_IRQ_TYPE_EDGE_FALLING => config.set_edge_detection(Edge::Falling), + VIRTIO_GPIO_IRQ_TYPE_EDGE_BOTH => config.set_edge_detection(Edge::Both), + + // Drop the buffer. + VIRTIO_GPIO_IRQ_TYPE_NONE => { + state.buffer = None; + return Ok(()); + } + + // Only edge IRQs are supported for now. + _ => return Err(Error::GpioIrqTypeNotSupported(value)), + }; + + // Allocate the buffer and configure the line for interrupt. + if state.request.is_none() { + Err(Error::GpioLineRequestNotConfigured) + } else { + // The GPIO Virtio specification allows a single interrupt event for each + // `wait_for_interrupt()` message. And for that we need a single `EdgeEventBuffer`. + state.buffer = Some(EdgeEventBuffer::new(1).map_err(Error::GpiodFailed)?); + + state + .request + .as_ref() + .unwrap() + .reconfigure_lines(&config) + .map_err(Error::GpiodFailed) + } + } + + fn wait_for_interrupt(&self, gpio: u16) -> Result<()> { + // While waiting here for the interrupt to occur, it is possible that we receive another + // request from the guest to disable the interrupt instead, via a call to `set_irq_type()`. + // + // The interrupt design here should allow that call to return as soon as possible, after + // disabling the interrupt and at the same time we need to make sure that we don't end up + // freeing resources currently used by `wait_for_interrupt()`. + // + // To allow that, the line state management is done via two resources: `request` and + // `buffer`. + // + // The `request` is required by `wait_for_interrupt()` to query libgpiod and must not get + // freed while we are waiting for an interrupt. This can happen, for example, if another + // thread disables the interrupt, via `set_irq_type(VIRTIO_GPIO_IRQ_TYPE_NONE)`, followed + // by `set_direction(VIRTIO_GPIO_DIRECTION_NONE)`, where we drop the `request`. For this + // reason, the `request` is implemented as an Arc instance. + // + // The `buffer` on the other hand is required only after we have sensed an interrupt and + // need to read it. The design here takes advantage of that and allows `set_irq_type()` to + // go and free the `buffer`, while this routine is waiting for the interrupt. Once the + // waiting period is over or an interrupt is sensed, `wait_for_interrupt() will find the + // buffer being dropped and return an error which will be handled by + // `Controller::wait_for_interrupt()`. + // + // This design also allows `wait_for_interrupt()` to not take a lock for the entire + // duration, which can potentially also starve the other thread trying to disable the + // interrupt. + let request = { + let state = &self.state[gpio as usize].write().unwrap(); + + match &state.request { + Some(x) => x.clone(), + None => return Err(Error::GpioIrqNotEnabled), + } + }; + + // Wait for the interrupt for a second. + match request.edge_event_wait(Duration::new(1, 0)) { + Err(LibGpiodError::OperationTimedOut) => return Err(Error::GpioIrqOpTimedOut), + x => x.map_err(Error::GpiodFailed)?, + } + + // The interrupt has already occurred, we can lock now just fine. + let state = &self.state[gpio as usize].write().unwrap(); + if let Some(buffer) = &state.buffer { + request + .edge_event_read(buffer, 1) + .map_err(Error::GpiodFailed)?; + + Ok(()) + } else { + Err(Error::GpioLineRequestNotConfigured) + } + } } struct GpioState { dir: u8, val: Option, + irq_type: u16, } pub(crate) struct GpioController { @@ -229,6 +360,7 @@ impl GpioController { state.push(RwLock::new(GpioState { dir: device.get_direction(offset)?, val: None, + irq_type: VIRTIO_GPIO_IRQ_TYPE_NONE, })); } @@ -244,6 +376,10 @@ impl GpioController { }) } + pub(crate) fn get_num_gpios(&self) -> u16 { + self.device.get_num_gpios().unwrap() + } + fn get_direction(&self, gpio: u16) -> Result { self.device.get_direction(gpio) } @@ -285,6 +421,47 @@ impl GpioController { Ok(()) } + pub(crate) fn get_irq_type(&self, gpio: u16) -> u16 { + self.state[gpio as usize].read().unwrap().irq_type + } + + fn set_irq_type(&self, gpio: u16, value: u32) -> Result<()> { + let irq_type = value as u16; + + // Invalid irq type + if (irq_type & !VIRTIO_GPIO_IRQ_TYPE_ALL) != 0 { + return Err(Error::GpioIrqTypeInvalid(irq_type)); + } + + // Begin critical section + let state = &mut self.state[gpio as usize].write().unwrap(); + let prev_irq_type = state.irq_type; + + // New irq type same as current one. + if irq_type == prev_irq_type { + return Err(Error::GpioIrqTypeNoChange(irq_type)); + } + + // Must configure to VIRTIO_GPIO_IRQ_TYPE_NONE first before changing irq type. + if prev_irq_type != VIRTIO_GPIO_IRQ_TYPE_NONE && irq_type != VIRTIO_GPIO_IRQ_TYPE_NONE { + return Err(Error::GpioOldIrqTypeValid(prev_irq_type)); + } + + self.device.set_irq_type(gpio, irq_type)?; + state.irq_type = irq_type; + Ok(()) + } + + pub(crate) fn wait_for_interrupt(&self, gpio: u16) -> Result<()> { + loop { + match self.device.wait_for_interrupt(gpio) { + Err(Error::GpioIrqOpTimedOut) => continue, + Ok(_) => return Ok(()), + x => x?, + } + } + } + pub(crate) fn get_config(&self) -> &VirtioGpioConfig { &self.config } @@ -302,6 +479,10 @@ impl GpioController { self.set_value(gpio, value)?; vec![0] } + VIRTIO_GPIO_MSG_IRQ_TYPE => { + self.set_irq_type(gpio, value)?; + vec![0] + } msg => return Err(Error::GpioMessageInvalid(msg)), }) } diff --git a/gpio/src/vhu_gpio.rs b/gpio/src/vhu_gpio.rs index 4a4dcad..923257f 100644 --- a/gpio/src/vhu_gpio.rs +++ b/gpio/src/vhu_gpio.rs @@ -8,6 +8,8 @@ use log::error; use std::mem::size_of; use std::slice::from_raw_parts; +use std::sync::{Arc, RwLock}; +use std::thread::{spawn, JoinHandle}; use std::{convert, io}; use thiserror::Error as ThisError; @@ -19,22 +21,27 @@ use virtio_bindings::bindings::virtio_ring::{ }; use virtio_queue::DescriptorChain; use vm_memory::{ - ByteValued, Bytes, GuestMemoryAtomic, GuestMemoryLoadGuard, GuestMemoryMmap, Le16, Le32, + ByteValued, Bytes, GuestAddress, GuestMemoryAtomic, GuestMemoryLoadGuard, GuestMemoryMmap, + Le16, Le32, }; use vmm_sys_util::epoll::EventSet; use vmm_sys_util::eventfd::{EventFd, EFD_NONBLOCK}; -use crate::gpio::{GpioController, GpioDevice, VirtioGpioConfig}; +use crate::gpio::{GpioController, GpioDevice, VirtioGpioConfig, VIRTIO_GPIO_IRQ_TYPE_NONE}; /// Possible values of the status field const VIRTIO_GPIO_STATUS_OK: u8 = 0x0; const VIRTIO_GPIO_STATUS_ERR: u8 = 0x1; +/// Virtio GPIO Feature bits +const VIRTIO_GPIO_F_IRQ: u16 = 0; + const QUEUE_SIZE: usize = 256; const NUM_QUEUES: usize = 2; /// Queues const REQUEST_QUEUE: u16 = 0; +const EVENT_QUEUE: u16 = 1; type Result = std::result::Result; type VhostUserBackendResult = std::result::Result; @@ -85,8 +92,60 @@ struct VirtioGpioRequest { } unsafe impl ByteValued for VirtioGpioRequest {} +/// Virtio GPIO IRQ Request / Response +#[derive(Copy, Clone, Default)] +struct VirtioGpioIrqRequest { + gpio: Le16, +} +unsafe impl ByteValued for VirtioGpioIrqRequest {} + +#[derive(Copy, Clone, Default)] +struct VirtioGpioIrqResponse { + #[allow(dead_code)] + status: u8, +} +unsafe impl ByteValued for VirtioGpioIrqResponse {} + +/// Possible values of the interrupt status field +const VIRTIO_GPIO_IRQ_STATUS_INVALID: u8 = 0x0; +const VIRTIO_GPIO_IRQ_STATUS_VALID: u8 = 0x1; + +/// Send response over the eventq virtqueue. +fn send_event_response( + vring: &VringRwLock, + desc_chain: GpioDescriptorChain, + addr: GuestAddress, + status: u8, +) { + let response = VirtioGpioIrqResponse { status }; + + if desc_chain + .memory() + .write_obj::(response, addr) + .is_err() + { + error!("Failed to write response"); + } + + if vring + .add_used( + desc_chain.head_index(), + size_of::() as u32, + ) + .is_err() + { + error!("Couldn't return used descriptors to the ring"); + } + + // Send notification once all the requests are processed + if vring.signal_used_queue().is_err() { + error!("Couldn't signal used queue"); + } +} + pub(crate) struct VhostUserGpioBackend { - controller: GpioController, + controller: Arc>, + handles: Arc>>>>, event_idx: bool, pub(crate) exit_event: EventFd, } @@ -95,8 +154,13 @@ type GpioDescriptorChain = DescriptorChain VhostUserGpioBackend { pub(crate) fn new(controller: GpioController) -> Result { + // Can't set a vector to all None easily + let mut handles: Vec>> = Vec::new(); + handles.resize_with(controller.get_num_gpios() as usize, || None); + Ok(VhostUserGpioBackend { - controller, + controller: Arc::new(controller), + handles: Arc::new(RwLock::new(handles)), event_idx: false, exit_event: EventFd::new(EFD_NONBLOCK).map_err(|_| Error::EventFdFailed)?, }) @@ -200,6 +264,108 @@ impl VhostUserGpioBackend { Ok(()) } + + /// Process the request in the event queue + fn process_event( + &mut self, + vring: &VringRwLock, + desc_chain: GpioDescriptorChain, + gpio: u16, + addr: GuestAddress, + ) { + // Take lock here to avoid race with thread. + let handle = &mut self.handles.write().unwrap()[gpio as usize]; + let controller = self.controller.clone(); + + // Interrupt should be enabled before sending buffer and no other buffer + // should have been received earlier for this GPIO pin. + if controller.get_irq_type(gpio) == VIRTIO_GPIO_IRQ_TYPE_NONE || handle.is_some() { + send_event_response(vring, desc_chain, addr, VIRTIO_GPIO_IRQ_STATUS_INVALID); + return; + } + + // Queue a thread to wait for and process the interrupt. + let handles = self.handles.clone(); + let vring = vring.clone(); + *handle = Some(spawn(move || { + let status = match controller.wait_for_interrupt(gpio) { + Ok(_) => VIRTIO_GPIO_IRQ_STATUS_VALID, + _ => VIRTIO_GPIO_IRQ_STATUS_INVALID, + }; + + send_event_response(&vring, desc_chain, addr, status); + handles.write().unwrap()[gpio as usize] = None; + })); + } + + /// Process the requests in the event queue + fn process_events( + &mut self, + requests: Vec, + vring: &VringRwLock, + ) -> Result<()> { + if requests.is_empty() { + return Ok(()); + } + + for desc_chain in requests { + let descriptors: Vec<_> = desc_chain.clone().collect(); + if descriptors.len() != 2 { + return Err(Error::UnexpectedDescriptorCount(descriptors.len())); + } + + let desc_request = descriptors[0]; + if desc_request.is_write_only() { + return Err(Error::UnexpectedWriteOnlyDescriptor(0)); + } + + if desc_request.len() as usize != size_of::() { + return Err(Error::UnexpectedDescriptorSize( + size_of::(), + desc_request.len(), + )); + } + + let request = desc_chain + .memory() + .read_obj::(desc_request.addr()) + .map_err(|_| Error::DescriptorReadFailed)?; + + let desc_response = descriptors[1]; + if !desc_response.is_write_only() { + return Err(Error::UnexpectedReadableDescriptor(1)); + } + + if desc_response.len() as usize != size_of::() { + return Err(Error::UnexpectedDescriptorSize( + size_of::(), + desc_response.len(), + )); + } + + self.process_event( + vring, + desc_chain, + request.gpio.to_native(), + desc_response.addr(), + ); + } + + Ok(()) + } + + /// Process the messages in the vring and dispatch replies + fn process_event_queue(&mut self, vring: &VringRwLock) -> Result<()> { + let requests: Vec<_> = vring + .get_mut() + .get_queue_mut() + .iter() + .map_err(|_| Error::DescriptorNotFound)? + .collect(); + + self.process_events(requests, vring)?; + Ok(()) + } } /// VhostUserBackendMut trait methods @@ -220,6 +386,7 @@ impl VhostUserBackendMut | 1 << VIRTIO_F_NOTIFY_ON_EMPTY | 1 << VIRTIO_RING_F_INDIRECT_DESC | 1 << VIRTIO_RING_F_EVENT_IDX + | 1 << VIRTIO_GPIO_F_IRQ | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() } @@ -281,6 +448,27 @@ impl VhostUserBackendMut } } + EVENT_QUEUE => { + let vring = &vrings[1]; + + 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_event_queue() until it stops finding + // new requests on the queue. + loop { + vring.disable_notification().unwrap(); + self.process_event_queue(vring)?; + if !vring.enable_notification().unwrap() { + break; + } + } + } else { + // Without EVENT_IDX, a single call is enough. + self.process_event_queue(vring)?; + } + } + _ => { return Err(Error::HandleEventUnknown.into()); } From cc6d1e608f299bf002c78d927c709f4613c817c4 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Mon, 22 Nov 2021 12:49:47 +0530 Subject: [PATCH 5/8] gpio: Add tests Add tests to validate functionality of the GPIO crate. Signed-off-by: Viresh Kumar --- gpio/Cargo.toml | 4 + gpio/src/backend.rs | 93 ++++++ gpio/src/gpio.rs | 506 +++++++++++++++++++++++++++++- gpio/src/vhu_gpio.rs | 711 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1309 insertions(+), 5 deletions(-) diff --git a/gpio/Cargo.toml b/gpio/Cargo.toml index 6111c08..28ae5c2 100644 --- a/gpio/Cargo.toml +++ b/gpio/Cargo.toml @@ -25,3 +25,7 @@ vm-memory = "0.7" vmm-sys-util = "=0.9.0" libgpiod = { git = "https://github.com/vireshk/libgpiod" } + +[dev-dependencies] +virtio-queue = { version = "0.2", features = ["test-utils"] } +vm-memory = { version = "0.7.0", features = ["backend-mmap", "backend-atomic"] } diff --git a/gpio/src/backend.rs b/gpio/src/backend.rs index debcc2d..5bfd709 100644 --- a/gpio/src/backend.rs +++ b/gpio/src/backend.rs @@ -190,3 +190,96 @@ pub(crate) fn gpio_init() -> Result<()> { start_backend::(GpioArgs::parse()) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::gpio::tests::DummyDevice; + + impl DeviceConfig { + pub fn new_with(devices: Vec) -> Self { + DeviceConfig { inner: devices } + } + } + + fn get_cmd_args(path: &str, devices: &str, count: usize) -> GpioArgs { + GpioArgs { + socket_path: path.to_string(), + socket_count: count, + device_list: devices.to_string(), + } + } + + #[test] + fn test_gpio_device_config() { + let mut config = DeviceConfig::new(); + + config.push(5).unwrap(); + config.push(6).unwrap(); + + assert_eq!(config.push(5).unwrap_err(), Error::DeviceDuplicate(5)); + } + + #[test] + fn test_gpio_parse_failure() { + let socket_name = "vgpio.sock"; + + // Invalid device number + let cmd_args = get_cmd_args(socket_name, "1:4d:5", 3); + assert_eq!( + GpioConfiguration::try_from(cmd_args).unwrap_err(), + Error::ParseFailure("4d".parse::().unwrap_err()) + ); + + // Zero socket count + let cmd_args = get_cmd_args(socket_name, "1:4", 0); + assert_eq!( + GpioConfiguration::try_from(cmd_args).unwrap_err(), + Error::SocketCountInvalid(0) + ); + + // Duplicate client address: 4 + let cmd_args = get_cmd_args(socket_name, "1:4:5:6:4", 5); + assert_eq!( + GpioConfiguration::try_from(cmd_args).unwrap_err(), + Error::DeviceDuplicate(4) + ); + + // Device count mismatch + let cmd_args = get_cmd_args(socket_name, "1:4:5:6", 5); + assert_eq!( + GpioConfiguration::try_from(cmd_args).unwrap_err(), + Error::DeviceCountMismatch(5, 4) + ); + } + + #[test] + fn test_gpio_parse_successful() { + let socket_name = "vgpio.sock"; + + // Match expected and actual configuration + let cmd_args = get_cmd_args(socket_name, "1:4:32:21:5", 5); + let config = GpioConfiguration::try_from(cmd_args).unwrap(); + + let expected_devices = DeviceConfig::new_with(vec![1, 4, 32, 21, 5]); + let expected_config = GpioConfiguration { + socket_count: 5, + socket_path: String::from(socket_name), + devices: expected_devices, + }; + + assert_eq!(config, expected_config); + } + + #[test] + fn test_gpio_fail_listener() { + // This will fail the listeners and thread will panic. + let socket_name = "~/path/not/present/gpio"; + let cmd_args = get_cmd_args(socket_name, "1:4:3:5", 4); + + assert_eq!( + start_backend::(cmd_args).unwrap_err(), + Error::FailedJoiningThreads + ); + } +} diff --git a/gpio/src/gpio.rs b/gpio/src/gpio.rs index 4bbc373..0a6f3ca 100644 --- a/gpio/src/gpio.rs +++ b/gpio/src/gpio.rs @@ -18,7 +18,7 @@ use vm_memory::{Le16, Le32}; type Result = std::result::Result; -#[derive(Clone, Debug, PartialEq, ThisError)] +#[derive(Copy, Clone, Debug, PartialEq, ThisError)] /// Errors related to low level gpio helpers pub(crate) enum Error { #[error("Invalid gpio direction: {0}")] @@ -47,6 +47,9 @@ pub(crate) enum Error { GpioOldIrqTypeValid(u16), #[error("Gpio line-request not configured")] GpioLineRequestNotConfigured, + #[cfg(test)] + #[error("Gpio test Operation failed {0}")] + GpioOperationFailed(&'static str), } /// Virtio specification definitions @@ -76,12 +79,12 @@ const VIRTIO_GPIO_IRQ_TYPE_ALL: u16 = VIRTIO_GPIO_IRQ_TYPE_EDGE_BOTH | VIRTIO_GPIO_IRQ_TYPE_LEVEL_LOW; /// Virtio GPIO Configuration -#[derive(Clone)] +#[derive(Clone, Debug, PartialEq)] #[repr(C)] pub(crate) struct VirtioGpioConfig { - ngpio: Le16, - padding: Le16, - gpio_names_size: Le32, + pub(crate) ngpio: Le16, + pub(crate) padding: Le16, + pub(crate) gpio_names_size: Le32, } /// Trait that represents an GPIO Device. @@ -327,12 +330,14 @@ impl GpioDevice for PhysDevice { } } +#[derive(Debug, Copy, Clone)] struct GpioState { dir: u8, val: Option, irq_type: u16, } +#[derive(Debug)] pub(crate) struct GpioController { config: VirtioGpioConfig, device: D, @@ -487,3 +492,494 @@ impl GpioController { }) } } + +#[cfg(test)] +pub(crate) mod tests { + use std::mem::size_of_val; + + use super::Error; + use super::*; + + #[derive(Debug)] + pub(crate) struct DummyDevice { + ngpio: u16, + pub(crate) gpio_names: Vec, + state: RwLock>, + get_num_gpios_result: Result, + get_gpio_name_result: Result, + get_direction_result: Result, + set_direction_result: Result<()>, + get_value_result: Result, + set_value_result: Result<()>, + set_irq_type_result: Result<()>, + pub(crate) wait_for_irq_result: Result<()>, + } + + impl DummyDevice { + pub(crate) fn new(ngpio: u16) -> Self { + Self { + ngpio, + gpio_names: vec!['\0'.to_string(); ngpio.into()], + state: RwLock::new(vec![ + GpioState { + dir: VIRTIO_GPIO_DIRECTION_NONE, + val: None, + irq_type: VIRTIO_GPIO_IRQ_TYPE_NONE, + }; + ngpio.into() + ]), + get_num_gpios_result: Ok(0), + get_gpio_name_result: Ok("".to_string()), + get_direction_result: Ok(0), + set_direction_result: Ok(()), + get_value_result: Ok(0), + set_value_result: Ok(()), + set_irq_type_result: Ok(()), + wait_for_irq_result: Ok(()), + } + } + } + + impl GpioDevice for DummyDevice { + fn open(_device: u32) -> Result + where + Self: Sized, + { + Ok(DummyDevice::new(8)) + } + + fn get_num_gpios(&self) -> Result { + if self.get_num_gpios_result.is_err() { + return self.get_num_gpios_result; + } + + Ok(self.ngpio) + } + + fn get_gpio_name(&self, gpio: u16) -> Result { + assert!((gpio as usize) < self.gpio_names.len()); + + if self.get_gpio_name_result.is_err() { + return self.get_gpio_name_result.clone(); + } + + Ok(self.gpio_names[gpio as usize].clone()) + } + + fn get_direction(&self, gpio: u16) -> Result { + if self.get_direction_result.is_err() { + return self.get_direction_result; + } + + Ok(self.state.read().unwrap()[gpio as usize].dir) + } + + fn set_direction(&self, gpio: u16, dir: u8, value: u32) -> Result<()> { + if self.set_direction_result.is_err() { + return self.set_direction_result; + } + + self.state.write().unwrap()[gpio as usize].dir = dir; + self.state.write().unwrap()[gpio as usize].val = match dir as u8 { + VIRTIO_GPIO_DIRECTION_NONE => None, + VIRTIO_GPIO_DIRECTION_IN => self.state.read().unwrap()[gpio as usize].val, + VIRTIO_GPIO_DIRECTION_OUT => Some(value as u16), + + _ => return Err(Error::GpioDirectionInvalid(dir as u32)), + }; + + Ok(()) + } + + fn get_value(&self, gpio: u16) -> Result { + if self.get_value_result.is_err() { + return self.get_value_result; + } + + if let Some(val) = self.state.read().unwrap()[gpio as usize].val { + Ok(val as u8) + } else { + Err(Error::GpioCurrentValueInvalid) + } + } + + fn set_value(&self, gpio: u16, value: u32) -> Result<()> { + if self.set_value_result.is_err() { + return self.set_value_result; + } + + self.state.write().unwrap()[gpio as usize].val = Some(value as u16); + Ok(()) + } + + fn set_irq_type(&self, _gpio: u16, _value: u16) -> Result<()> { + if self.set_irq_type_result.is_err() { + return self.set_irq_type_result; + } + + Ok(()) + } + + fn wait_for_interrupt(&self, _gpio: u16) -> Result<()> { + if self.wait_for_irq_result.is_err() { + return self.wait_for_irq_result; + } + + Ok(()) + } + } + + #[test] + fn test_verify_gpio_controller() { + const NGPIO: u16 = 8; + let gpio_names = vec![ + "gpio0".to_string(), + '\0'.to_string(), + "gpio2".to_string(), + '\0'.to_string(), + "gpio4".to_string(), + '\0'.to_string(), + "gpio6".to_string(), + '\0'.to_string(), + ]; + + // Controller adds '\0' for each line. + let names_size = size_of_val(&gpio_names) + gpio_names.len(); + + let mut device = DummyDevice::new(NGPIO); + device.gpio_names.clear(); + device.gpio_names.append(&mut gpio_names.clone()); + let controller = GpioController::new(device).unwrap(); + + assert_eq!( + *controller.get_config(), + VirtioGpioConfig { + ngpio: From::from(NGPIO), + padding: From::from(0), + gpio_names_size: From::from(names_size as u32), + } + ); + + let mut name = String::with_capacity(gpio_names.len()); + for i in gpio_names { + name.push_str(&i); + name.push('\0'); + } + + assert_eq!(controller.get_num_gpios(), NGPIO); + + assert_eq!( + controller + .operation(VIRTIO_GPIO_MSG_GET_LINE_NAMES, 0, 0) + .unwrap(), + name.as_bytes().to_vec() + ); + + for gpio in 0..NGPIO { + // No initial value + assert_eq!( + controller + .operation(VIRTIO_GPIO_MSG_GET_VALUE, gpio, 0) + .unwrap_err(), + Error::GpioCurrentValueInvalid + ); + + // No initial direction + assert_eq!( + controller + .operation(VIRTIO_GPIO_MSG_GET_DIRECTION, gpio, 0) + .unwrap(), + vec![VIRTIO_GPIO_DIRECTION_NONE] + ); + + // No initial irq type + assert_eq!(controller.get_irq_type(gpio), VIRTIO_GPIO_IRQ_TYPE_NONE); + } + } + + #[test] + fn test_verify_gpio_operation() { + const NGPIO: u16 = 256; + let device = DummyDevice::new(NGPIO); + let controller = GpioController::new(device).unwrap(); + + for gpio in 0..NGPIO { + // Set value first followed by direction + controller + .operation(VIRTIO_GPIO_MSG_SET_VALUE, gpio, 1) + .unwrap(); + + // Set direction OUT + controller + .operation( + VIRTIO_GPIO_MSG_SET_DIRECTION, + gpio, + VIRTIO_GPIO_DIRECTION_OUT as u32, + ) + .unwrap(); + + // Valid value + assert_eq!( + controller + .operation(VIRTIO_GPIO_MSG_GET_VALUE, gpio, 0) + .unwrap(), + vec![1] + ); + + // Valid direction + assert_eq!( + controller + .operation(VIRTIO_GPIO_MSG_GET_DIRECTION, gpio, 0) + .unwrap(), + vec![VIRTIO_GPIO_DIRECTION_OUT] + ); + + // Set direction IN + controller + .operation( + VIRTIO_GPIO_MSG_SET_DIRECTION, + gpio, + VIRTIO_GPIO_DIRECTION_IN as u32, + ) + .unwrap(); + + // Valid value retained here + assert_eq!( + controller + .operation(VIRTIO_GPIO_MSG_GET_VALUE, gpio, 0) + .unwrap(), + vec![1] + ); + + // Valid direction + assert_eq!( + controller + .operation(VIRTIO_GPIO_MSG_GET_DIRECTION, gpio, 0) + .unwrap(), + vec![VIRTIO_GPIO_DIRECTION_IN] + ); + + // Set irq type rising + controller + .operation( + VIRTIO_GPIO_MSG_IRQ_TYPE, + gpio, + VIRTIO_GPIO_IRQ_TYPE_EDGE_RISING as u32, + ) + .unwrap(); + + // Verify interrupt type + assert_eq!( + controller.get_irq_type(gpio), + VIRTIO_GPIO_IRQ_TYPE_EDGE_RISING, + ); + + // Set irq type none + controller + .operation( + VIRTIO_GPIO_MSG_IRQ_TYPE, + gpio, + VIRTIO_GPIO_IRQ_TYPE_NONE as u32, + ) + .unwrap(); + + // Verify interrupt type + assert_eq!(controller.get_irq_type(gpio), VIRTIO_GPIO_IRQ_TYPE_NONE); + + // Set irq type falling + controller + .operation( + VIRTIO_GPIO_MSG_IRQ_TYPE, + gpio, + VIRTIO_GPIO_IRQ_TYPE_EDGE_FALLING as u32, + ) + .unwrap(); + + // Verify interrupt type + assert_eq!( + controller.get_irq_type(gpio), + VIRTIO_GPIO_IRQ_TYPE_EDGE_FALLING, + ); + + // Set irq type none + controller + .operation( + VIRTIO_GPIO_MSG_IRQ_TYPE, + gpio, + VIRTIO_GPIO_IRQ_TYPE_NONE as u32, + ) + .unwrap(); + + // Verify interrupt type + assert_eq!(controller.get_irq_type(gpio), VIRTIO_GPIO_IRQ_TYPE_NONE); + + // Set irq type both + controller + .operation( + VIRTIO_GPIO_MSG_IRQ_TYPE, + gpio, + VIRTIO_GPIO_IRQ_TYPE_EDGE_BOTH as u32, + ) + .unwrap(); + + // Verify interrupt type + assert_eq!( + controller.get_irq_type(gpio), + VIRTIO_GPIO_IRQ_TYPE_EDGE_BOTH, + ); + } + } + + #[test] + fn test_gpio_controller_new_failure() { + const NGPIO: u16 = 256; + // Get num lines failure + let error = Error::GpioOperationFailed("get-num-lines"); + let mut device = DummyDevice::new(NGPIO); + device.get_num_gpios_result = Err(error); + assert_eq!(GpioController::new(device).unwrap_err(), error); + + // Get line name failure + let error = Error::GpioOperationFailed("get-line-name"); + let mut device = DummyDevice::new(NGPIO); + device.get_gpio_name_result = Err(error); + assert_eq!(GpioController::new(device).unwrap_err(), error); + + // Get direction failure + let error = Error::GpioOperationFailed("get-direction"); + let mut device = DummyDevice::new(NGPIO); + device.get_direction_result = Err(error); + assert_eq!(GpioController::new(device).unwrap_err(), error); + } + + #[test] + fn test_gpio_set_direction_failure() { + const NGPIO: u16 = 256; + let device = DummyDevice::new(NGPIO); + let controller = GpioController::new(device).unwrap(); + + for gpio in 0..NGPIO { + // Set direction out without setting value first + assert_eq!( + controller + .operation( + VIRTIO_GPIO_MSG_SET_DIRECTION, + gpio, + VIRTIO_GPIO_DIRECTION_OUT as u32, + ) + .unwrap_err(), + Error::GpioCurrentValueInvalid + ); + + // Set invalid direction + let dir = 10; + assert_eq!( + controller + .operation(VIRTIO_GPIO_MSG_SET_DIRECTION, gpio, dir) + .unwrap_err(), + Error::GpioDirectionInvalid(dir) + ); + } + } + + #[test] + fn test_gpio_set_value_failure() { + const NGPIO: u16 = 256; + let device = DummyDevice::new(NGPIO); + let controller = GpioController::new(device).unwrap(); + + for gpio in 0..NGPIO { + // Set invalid value + let val = 10; + assert_eq!( + controller + .operation(VIRTIO_GPIO_MSG_SET_VALUE, gpio, val) + .unwrap_err(), + Error::GpioValueInvalid(val) + ); + } + } + + #[test] + fn test_gpio_set_irq_type_failure() { + const NGPIO: u16 = 256; + let device = DummyDevice::new(NGPIO); + let controller = GpioController::new(device).unwrap(); + + for gpio in 0..NGPIO { + // Set invalid irq type + assert_eq!( + controller + .operation( + VIRTIO_GPIO_MSG_IRQ_TYPE, + gpio, + (VIRTIO_GPIO_IRQ_TYPE_ALL + 1) as u32, + ) + .unwrap_err(), + Error::GpioIrqTypeInvalid(VIRTIO_GPIO_IRQ_TYPE_ALL + 1) + ); + + // Set irq type level none -> none + assert_eq!( + controller + .operation( + VIRTIO_GPIO_MSG_IRQ_TYPE, + gpio, + VIRTIO_GPIO_IRQ_TYPE_NONE as u32, + ) + .unwrap_err(), + Error::GpioIrqTypeNoChange(VIRTIO_GPIO_IRQ_TYPE_NONE) + ); + + // Set irq type level rising -> falling, without intermediate none + controller + .operation( + VIRTIO_GPIO_MSG_IRQ_TYPE, + gpio, + VIRTIO_GPIO_IRQ_TYPE_EDGE_RISING as u32, + ) + .unwrap(); + + assert_eq!( + controller + .operation( + VIRTIO_GPIO_MSG_IRQ_TYPE, + gpio, + VIRTIO_GPIO_IRQ_TYPE_EDGE_FALLING as u32, + ) + .unwrap_err(), + Error::GpioOldIrqTypeValid(VIRTIO_GPIO_IRQ_TYPE_EDGE_RISING) + ); + } + } + + #[test] + fn test_gpio_wait_for_interrupt_failure() { + const NGPIO: u16 = 256; + let err = Error::GpioIrqTypeInvalid(0); + let mut device = DummyDevice::new(NGPIO); + + device.wait_for_irq_result = Err(err); + + let controller = GpioController::new(device).unwrap(); + + for gpio in 0..NGPIO { + assert_eq!(controller.wait_for_interrupt(gpio).unwrap_err(), err); + } + } + + #[test] + fn test_gpio_operation_failure() { + const NGPIO: u16 = 256; + let device = DummyDevice::new(NGPIO); + let controller = GpioController::new(device).unwrap(); + + for gpio in 0..NGPIO { + // Invalid operation + assert_eq!( + controller.operation(100, gpio, 0).unwrap_err(), + Error::GpioMessageInvalid(100) + ); + } + } +} diff --git a/gpio/src/vhu_gpio.rs b/gpio/src/vhu_gpio.rs index 923257f..5f82def 100644 --- a/gpio/src/vhu_gpio.rs +++ b/gpio/src/vhu_gpio.rs @@ -480,3 +480,714 @@ impl VhostUserBackendMut self.exit_event.try_clone().ok() } } + +#[cfg(test)] +mod tests { + use virtio_queue::defs::{VIRTQ_DESC_F_NEXT, VIRTQ_DESC_F_WRITE}; + use virtio_queue::{mock::MockSplitQueue, Descriptor}; + use vm_memory::{Address, GuestAddress, GuestMemoryAtomic, GuestMemoryMmap}; + + use super::Error; + use super::*; + use crate::gpio::tests::DummyDevice; + use crate::gpio::Error as GpioError; + use crate::gpio::*; + + // Prepares a single chain of descriptors for request queue + fn prepare_desc_chain( + start_addr: GuestAddress, + out_hdr: R, + response_len: u32, + ) -> GpioDescriptorChain { + let mem = GuestMemoryMmap::<()>::from_ranges(&[(start_addr, 0x1000)]).unwrap(); + let vq = MockSplitQueue::new(&mem, 16); + let mut next_addr = vq.desc_table().total_size() + 0x100; + let mut index = 0; + + let desc_out = Descriptor::new( + next_addr, + size_of::() as u32, + VIRTQ_DESC_F_NEXT, + index + 1, + ); + + mem.write_obj::(out_hdr, desc_out.addr()).unwrap(); + vq.desc_table().store(index, desc_out); + next_addr += desc_out.len() as u64; + index += 1; + + // In response descriptor + let desc_in = Descriptor::new(next_addr, response_len, VIRTQ_DESC_F_WRITE, 0); + vq.desc_table().store(index, desc_in); + + // Put the descriptor index 0 in the first available ring position. + mem.write_obj(0u16, vq.avail_addr().unchecked_add(4)) + .unwrap(); + + // Set `avail_idx` to 1. + mem.write_obj(1u16, vq.avail_addr().unchecked_add(2)) + .unwrap(); + + // Create descriptor chain from pre-filled memory + vq.create_queue(GuestMemoryAtomic::::new(mem.clone())) + .iter() + .unwrap() + .next() + .unwrap() + } + + // Prepares a single chain of descriptors for request queue + fn prepare_request_desc_chain( + start_addr: GuestAddress, + rtype: u16, + gpio: u16, + value: u32, + len: u32, + ) -> GpioDescriptorChain { + // Out request descriptor + let out_hdr = VirtioGpioRequest { + rtype: From::from(rtype), + gpio: From::from(gpio), + value: From::from(value), + }; + + prepare_desc_chain::(start_addr, out_hdr, len + 1) + } + + // Prepares a single chain of descriptors for event queue + fn prepare_event_desc_chain(start_addr: GuestAddress, gpio: u16) -> GpioDescriptorChain { + // Out event descriptor + let out_hdr = VirtioGpioIrqRequest { + gpio: From::from(gpio), + }; + + prepare_desc_chain::( + start_addr, + out_hdr, + size_of::() as u32, + ) + } + + // Prepares list of dummy descriptors, their content isn't significant. + fn prepare_desc_chain_dummy( + addr: Option>, + flags: Vec, + len: Vec, + ) -> GpioDescriptorChain { + let mem = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(); + let vq = MockSplitQueue::new(&mem, 16); + + for (i, flag) in flags.iter().enumerate() { + let mut f = if i == flags.len() - 1 { + 0 + } else { + VIRTQ_DESC_F_NEXT + }; + f |= flag; + + let offset = match addr { + Some(ref addr) => addr[i], + _ => 0x100, + }; + + let desc = Descriptor::new(offset, len[i], f, (i + 1) as u16); + vq.desc_table().store(i as u16, desc); + } + + // Put the descriptor index 0 in the first available ring position. + mem.write_obj(0u16, vq.avail_addr().unchecked_add(4)) + .unwrap(); + + // Set `avail_idx` to 1. + mem.write_obj(1u16, vq.avail_addr().unchecked_add(2)) + .unwrap(); + + // Create descriptor chain from pre-filled memory + vq.create_queue(GuestMemoryAtomic::::new(mem.clone())) + .iter() + .unwrap() + .next() + .unwrap() + } + + // Validate descriptor chains after processing them, checks pass/failure of + // operation and the value of the buffers updated by the `DummyDevice`. + fn validate_desc_chains( + desc_chains: Vec, + status: u8, + val: Option>, + ) { + for (i, desc_chain) in desc_chains.iter().enumerate() { + let descriptors: Vec<_> = desc_chain.clone().collect(); + let mut response = vec![0; descriptors[1].len() as usize]; + + desc_chain + .memory() + .read(&mut response, descriptors[1].addr()) + .unwrap(); + + // Operation result should match expected status. + assert_eq!(response[0], status); + if let Some(val) = &val { + assert_eq!(response[1], val[i]); + } + } + } + + #[test] + fn test_gpio_process_requests_success() { + const NGPIO: u16 = 256; + const GPIO: u16 = 5; + let device = DummyDevice::new(NGPIO); + let controller = GpioController::new(device).unwrap(); + let backend = VhostUserGpioBackend::new(controller).unwrap(); + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(), + ); + let vring = VringRwLock::new(mem, 0x1000); + + // Descriptor chain size zero, shouldn't fail + backend + .process_requests(Vec::::new(), &vring) + .unwrap(); + + // Valid single GPIO operation + let desc_chain = + prepare_request_desc_chain(GuestAddress(0), VIRTIO_GPIO_MSG_SET_VALUE, GPIO, 1, 1); + let desc_chains = vec![desc_chain]; + + backend + .process_requests(desc_chains.clone(), &vring) + .unwrap(); + validate_desc_chains(desc_chains, VIRTIO_GPIO_STATUS_OK, Some(vec![0])); + + // Valid multi GPIO operation + let desc_chains = vec![ + prepare_request_desc_chain(GuestAddress(0), VIRTIO_GPIO_MSG_SET_VALUE, GPIO, 1, 1), + prepare_request_desc_chain( + GuestAddress(0), + VIRTIO_GPIO_MSG_SET_DIRECTION, + GPIO, + VIRTIO_GPIO_DIRECTION_OUT as u32, + 1, + ), + prepare_request_desc_chain(GuestAddress(0), VIRTIO_GPIO_MSG_GET_VALUE, GPIO, 0, 1), + prepare_request_desc_chain(GuestAddress(0), VIRTIO_GPIO_MSG_GET_DIRECTION, GPIO, 0, 1), + ]; + + backend + .process_requests(desc_chains.clone(), &vring) + .unwrap(); + validate_desc_chains( + desc_chains, + VIRTIO_GPIO_STATUS_OK, + Some(vec![0, 0, 1, VIRTIO_GPIO_DIRECTION_OUT]), + ); + } + + #[test] + fn test_gpio_process_requests_failure() { + const NGPIO: u16 = 256; + const GPIO: u16 = 5; + let device = DummyDevice::new(NGPIO); + let controller = GpioController::new(device).unwrap(); + let backend = VhostUserGpioBackend::new(controller).unwrap(); + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(), + ); + let vring = VringRwLock::new(mem, 0x1000); + + // Have only one descriptor, expected two. + let flags: Vec = vec![0]; + let len: Vec = vec![0]; + let desc_chain = prepare_desc_chain_dummy(None, flags, len); + assert_eq!( + backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err(), + Error::UnexpectedDescriptorCount(1) + ); + + // Have three descriptors, expected two. + let flags: Vec = vec![0, 0, 0]; + let len: Vec = vec![0, 0, 0]; + let desc_chain = prepare_desc_chain_dummy(None, flags, len); + assert_eq!( + backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err(), + Error::UnexpectedDescriptorCount(3) + ); + + // Write only out hdr. + let flags: Vec = vec![VIRTQ_DESC_F_WRITE, VIRTQ_DESC_F_WRITE]; + let len: Vec = vec![size_of::() as u32, 2]; + let desc_chain = prepare_desc_chain_dummy(None, flags, len); + assert_eq!( + backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err(), + Error::UnexpectedWriteOnlyDescriptor(0) + ); + + // Invalid out hdr address. + let addr: Vec = vec![0x10000, 0]; + let flags: Vec = vec![0, VIRTQ_DESC_F_WRITE]; + let len: Vec = vec![size_of::() as u32, 2]; + let desc_chain = prepare_desc_chain_dummy(Some(addr), flags, len); + assert_eq!( + backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err(), + Error::DescriptorReadFailed + ); + + // Invalid out hdr length. + let flags: Vec = vec![0, VIRTQ_DESC_F_WRITE]; + let len: Vec = vec![100, 2]; + let desc_chain = prepare_desc_chain_dummy(None, flags, len); + assert_eq!( + backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err(), + Error::UnexpectedDescriptorSize(size_of::(), 100) + ); + + // Read only in hdr. + let flags: Vec = vec![0, 0]; + let len: Vec = vec![size_of::() as u32, 2]; + let desc_chain = prepare_desc_chain_dummy(None, flags, len); + assert_eq!( + backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err(), + Error::UnexpectedReadableDescriptor(1) + ); + + // Invalid in hdr address. + let addr: Vec = vec![0, 0x10000]; + let flags: Vec = vec![0, VIRTQ_DESC_F_WRITE]; + let len: Vec = vec![size_of::() as u32, 2]; + let desc_chain = prepare_desc_chain_dummy(Some(addr), flags, len); + assert_eq!( + backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err(), + Error::DescriptorWriteFailed + ); + + // Invalid in hdr length. + let desc_chain = + prepare_request_desc_chain(GuestAddress(0), VIRTIO_GPIO_MSG_SET_VALUE, GPIO, 1, 3); + let desc_chains = vec![desc_chain]; + backend + .process_requests(desc_chains.clone(), &vring) + .unwrap(); + validate_desc_chains(desc_chains, VIRTIO_GPIO_STATUS_ERR, Some(vec![0])); + } + + #[test] + fn test_gpio_process_events_success() { + const NGPIO: u16 = 256; + const GPIO: u16 = 5; + let device = DummyDevice::new(NGPIO); + let controller = GpioController::new(device).unwrap(); + let mut backend = VhostUserGpioBackend::new(controller).unwrap(); + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(), + ); + let vring = VringRwLock::new(mem, 0x1000); + + // Descriptor chain size zero, shouldn't fail. + backend + .process_events(Vec::::new(), &vring) + .unwrap(); + + // Set direction should pass. + let desc_chain = prepare_request_desc_chain( + GuestAddress(0), + VIRTIO_GPIO_MSG_SET_DIRECTION, + GPIO, + VIRTIO_GPIO_DIRECTION_IN as u32, + 1, + ); + let desc_chains = vec![desc_chain]; + backend + .process_requests(desc_chains.clone(), &vring) + .unwrap(); + validate_desc_chains(desc_chains, VIRTIO_GPIO_STATUS_OK, Some(vec![0])); + + // Set irq type should pass. + let desc_chain = prepare_request_desc_chain( + GuestAddress(0), + VIRTIO_GPIO_MSG_IRQ_TYPE, + GPIO, + VIRTIO_GPIO_IRQ_TYPE_EDGE_BOTH as u32, + 1, + ); + let desc_chains = vec![desc_chain]; + backend + .process_requests(desc_chains.clone(), &vring) + .unwrap(); + validate_desc_chains(desc_chains, VIRTIO_GPIO_STATUS_OK, Some(vec![0])); + + // Wait for interrupt should pass + let desc_chain = prepare_event_desc_chain(GuestAddress(0), GPIO); + let desc_chains = vec![desc_chain]; + backend.process_events(desc_chains.clone(), &vring).unwrap(); + + while backend.handles.read().unwrap()[GPIO as usize].is_some() {} + validate_desc_chains(desc_chains, VIRTIO_GPIO_IRQ_STATUS_VALID, None); + } + + #[test] + fn test_gpio_process_events_multi_success() { + const NGPIO: u16 = 256; + const GPIO: u16 = 5; + let device = DummyDevice::new(NGPIO); + let controller = GpioController::new(device).unwrap(); + let mut backend = VhostUserGpioBackend::new(controller).unwrap(); + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(), + ); + let vring = VringRwLock::new(mem, 0x1000); + + let desc_chains = vec![ + // Prepare line: GPIO + prepare_request_desc_chain( + GuestAddress(0), + VIRTIO_GPIO_MSG_SET_DIRECTION, + GPIO, + VIRTIO_GPIO_DIRECTION_IN as u32, + 1, + ), + prepare_request_desc_chain( + GuestAddress(0), + VIRTIO_GPIO_MSG_IRQ_TYPE, + GPIO, + VIRTIO_GPIO_IRQ_TYPE_EDGE_BOTH as u32, + 1, + ), + // Prepare line: GPIO + 1 + prepare_request_desc_chain( + GuestAddress(0), + VIRTIO_GPIO_MSG_SET_DIRECTION, + GPIO + 1, + VIRTIO_GPIO_DIRECTION_IN as u32, + 1, + ), + prepare_request_desc_chain( + GuestAddress(0), + VIRTIO_GPIO_MSG_IRQ_TYPE, + GPIO + 1, + VIRTIO_GPIO_IRQ_TYPE_EDGE_BOTH as u32, + 1, + ), + // Prepare line: GPIO + 2 + prepare_request_desc_chain( + GuestAddress(0), + VIRTIO_GPIO_MSG_SET_DIRECTION, + GPIO + 2, + VIRTIO_GPIO_DIRECTION_IN as u32, + 1, + ), + prepare_request_desc_chain( + GuestAddress(0), + VIRTIO_GPIO_MSG_IRQ_TYPE, + GPIO + 2, + VIRTIO_GPIO_IRQ_TYPE_EDGE_BOTH as u32, + 1, + ), + ]; + + backend + .process_requests(desc_chains.clone(), &vring) + .unwrap(); + validate_desc_chains( + desc_chains, + VIRTIO_GPIO_STATUS_OK, + Some(vec![0, 0, 0, 0, 0, 0]), + ); + + // Wait for interrupt should pass. + let desc_chains = vec![ + prepare_event_desc_chain(GuestAddress(0), GPIO), + prepare_event_desc_chain(GuestAddress(0), GPIO + 1), + prepare_event_desc_chain(GuestAddress(0), GPIO + 2), + ]; + + backend.process_events(desc_chains.clone(), &vring).unwrap(); + + while backend.handles.read().unwrap()[GPIO as usize].is_some() + || backend.handles.read().unwrap()[(GPIO + 1) as usize].is_some() + || backend.handles.read().unwrap()[(GPIO + 2) as usize].is_some() + {} + + validate_desc_chains(desc_chains, VIRTIO_GPIO_IRQ_STATUS_VALID, None); + } + + #[test] + fn test_gpio_process_events_failure() { + const NGPIO: u16 = 256; + let err = GpioError::GpioIrqTypeInvalid(0); + let mut device = DummyDevice::new(NGPIO); + + // This will make process-request fail later with + // VIRTIO_GPIO_IRQ_STATUS_INVALID error. + device.wait_for_irq_result = Err(err); + + let controller = GpioController::new(device).unwrap(); + let mut backend = VhostUserGpioBackend::new(controller).unwrap(); + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(), + ); + let vring = VringRwLock::new(mem, 0x1000); + + // Only one descriptor, expected two. + let flags: Vec = vec![0]; + let len: Vec = vec![0]; + let desc_chain = prepare_desc_chain_dummy(None, flags, len); + assert_eq!( + backend + .process_events(vec![desc_chain], &vring) + .unwrap_err(), + Error::UnexpectedDescriptorCount(1) + ); + + // Three descriptors, expected two. + let flags: Vec = vec![0, 0, 0]; + let len: Vec = vec![0, 0, 0]; + let desc_chain = prepare_desc_chain_dummy(None, flags, len); + assert_eq!( + backend + .process_events(vec![desc_chain], &vring) + .unwrap_err(), + Error::UnexpectedDescriptorCount(3) + ); + + // Write only out hdr + let flags: Vec = vec![VIRTQ_DESC_F_WRITE, VIRTQ_DESC_F_WRITE]; + let len: Vec = vec![ + size_of::() as u32, + size_of::() as u32, + ]; + let desc_chain = prepare_desc_chain_dummy(None, flags, len); + assert_eq!( + backend + .process_events(vec![desc_chain], &vring) + .unwrap_err(), + Error::UnexpectedWriteOnlyDescriptor(0) + ); + + // Invalid out hdr address + let addr: Vec = vec![0x10000, 0]; + let flags: Vec = vec![0, VIRTQ_DESC_F_WRITE]; + let len: Vec = vec![ + size_of::() as u32, + size_of::() as u32, + ]; + let desc_chain = prepare_desc_chain_dummy(Some(addr), flags, len); + assert_eq!( + backend + .process_events(vec![desc_chain], &vring) + .unwrap_err(), + Error::DescriptorReadFailed + ); + + // Invalid out hdr length + let flags: Vec = vec![0, VIRTQ_DESC_F_WRITE]; + let len: Vec = vec![100, size_of::() as u32]; + let desc_chain = prepare_desc_chain_dummy(None, flags, len); + assert_eq!( + backend + .process_events(vec![desc_chain], &vring) + .unwrap_err(), + Error::UnexpectedDescriptorSize(size_of::(), 100) + ); + + // Read only in hdr + let flags: Vec = vec![0, 0]; + let len: Vec = vec![ + size_of::() as u32, + size_of::() as u32, + ]; + let desc_chain = prepare_desc_chain_dummy(None, flags, len); + assert_eq!( + backend + .process_events(vec![desc_chain], &vring) + .unwrap_err(), + Error::UnexpectedReadableDescriptor(1) + ); + + // Invalid in hdr length + let flags: Vec = vec![0, VIRTQ_DESC_F_WRITE]; + let len: Vec = vec![size_of::() as u32, 100]; + let desc_chain = prepare_desc_chain_dummy(None, flags, len); + assert_eq!( + backend + .process_events(vec![desc_chain], &vring) + .unwrap_err(), + Error::UnexpectedDescriptorSize(size_of::(), 100) + ); + + // Wait for event without setting irq type first. + let flags: Vec = vec![0, VIRTQ_DESC_F_WRITE]; + let len: Vec = vec![ + size_of::() as u32, + size_of::() as u32, + ]; + let desc_chain = prepare_desc_chain_dummy(None, flags, len); + let desc_chains = vec![desc_chain]; + backend.process_events(desc_chains.clone(), &vring).unwrap(); + validate_desc_chains(desc_chains, VIRTIO_GPIO_IRQ_STATUS_INVALID, None); + + // Wait for interrupt failure with VIRTIO_GPIO_IRQ_STATUS_INVALID status, as was set at the + // top of this function. + const GPIO: u16 = 5; + // Set irq type + let desc_chain = prepare_request_desc_chain( + GuestAddress(0), + VIRTIO_GPIO_MSG_IRQ_TYPE, + GPIO, + VIRTIO_GPIO_IRQ_TYPE_EDGE_BOTH as u32, + 1, + ); + let desc_chains = vec![desc_chain]; + backend + .process_requests(desc_chains.clone(), &vring) + .unwrap(); + validate_desc_chains(desc_chains, VIRTIO_GPIO_STATUS_OK, Some(vec![0])); + + // Wait for interrupt + let desc_chain = prepare_event_desc_chain(GuestAddress(0), GPIO); + let desc_chains = vec![desc_chain]; + backend.process_events(desc_chains.clone(), &vring).unwrap(); + + while backend.handles.read().unwrap()[GPIO as usize].is_some() {} + validate_desc_chains(desc_chains, VIRTIO_GPIO_IRQ_STATUS_INVALID, None); + } + + #[test] + fn test_gpio_verify_backend() { + const NGPIO: u16 = 8; + let mut gpio_names = vec![ + "gpio0".to_string(), + '\0'.to_string(), + "gpio2".to_string(), + '\0'.to_string(), + "gpio4".to_string(), + '\0'.to_string(), + "gpio6".to_string(), + '\0'.to_string(), + ]; + // Controller adds '\0' for each line. + let names_size = std::mem::size_of_val(&gpio_names) + gpio_names.len(); + + let mut device = DummyDevice::new(NGPIO); + device.gpio_names.clear(); + device.gpio_names.append(&mut gpio_names); + let controller = GpioController::new(device).unwrap(); + let mut backend = VhostUserGpioBackend::new(controller).unwrap(); + + assert_eq!(backend.num_queues(), NUM_QUEUES); + assert_eq!(backend.max_queue_size(), QUEUE_SIZE); + assert_eq!(backend.features(), 0x171000001); + assert_eq!( + backend.protocol_features(), + VhostUserProtocolFeatures::MQ | VhostUserProtocolFeatures::CONFIG + ); + + assert_eq!(backend.queues_per_thread(), vec![0xffff_ffff]); + + backend.set_event_idx(true); + assert!(backend.event_idx); + + assert!(backend.exit_event(0).is_some()); + + let config = VirtioGpioConfig { + ngpio: From::from(NGPIO), + padding: From::from(0), + gpio_names_size: From::from(names_size as u32), + }; + + assert_eq!(backend.get_config(0, 0), unsafe { + from_raw_parts( + &config as *const _ as *const _, + size_of::(), + ) + .to_vec() + }); + + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(), + ); + backend.update_memory(mem.clone()).unwrap(); + + let vring_request = VringRwLock::new(mem.clone(), 0x1000); + let vring_event = VringRwLock::new(mem, 0x1000); + assert_eq!( + backend + .handle_event( + 0, + EventSet::OUT, + &[vring_request.clone(), vring_event.clone()], + 0, + ) + .unwrap_err() + .kind(), + io::ErrorKind::Other + ); + + assert_eq!( + backend + .handle_event( + 2, + EventSet::IN, + &[vring_request.clone(), vring_event.clone()], + 0, + ) + .unwrap_err() + .kind(), + io::ErrorKind::Other + ); + + // Hit the loop part + backend.set_event_idx(true); + backend + .handle_event( + 0, + EventSet::IN, + &[vring_request.clone(), vring_event.clone()], + 0, + ) + .unwrap(); + + // Hit the non-loop part + backend.set_event_idx(false); + backend + .handle_event( + 0, + EventSet::IN, + &[vring_request.clone(), vring_event.clone()], + 0, + ) + .unwrap(); + + // Hit the loop part + backend.set_event_idx(true); + backend + .handle_event( + 1, + EventSet::IN, + &[vring_request.clone(), vring_event.clone()], + 0, + ) + .unwrap(); + + // Hit the non-loop part + backend.set_event_idx(false); + backend + .handle_event(1, EventSet::IN, &[vring_request, vring_event], 0) + .unwrap(); + } +} From c38dacd880794027307e9e78bb2f70d439ab8ec8 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Wed, 26 May 2021 13:49:37 +0530 Subject: [PATCH 6/8] gpio: Update README.md and add one for gpio This updates the main README and adds a specific one for GPIO crate. Signed-off-by: Viresh Kumar --- README.md | 1 + gpio/README.md | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/README.md b/README.md index c4c6330..9138f1e 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ crates. Here is the list of device backends that we support: +- [GPIO](https://github.com/rust-vmm/vhost-device/blob/main/gpio/README.md) - [I2C](https://github.com/rust-vmm/vhost-device/blob/main/i2c/README.md) - [RNG](https://github.com/rust-vmm/vhost-device/blob/main/rng/README.md) diff --git a/gpio/README.md b/gpio/README.md index 5c6067a..a71ca57 100644 --- a/gpio/README.md +++ b/gpio/README.md @@ -1 +1,65 @@ # vhost-device-gpio - GPIO emulation backend daemon + +## Description +This program is a vhost-user backend that emulates a VirtIO GPIO device. This +program takes a list of gpio devices on the host OS and then talks to them via +the /dev/gpiochip{X} interface when a request comes from the guest OS for an +GPIO device. + +This program is tested with QEMU's `-device vhost-user-gpio-pci` but should +work with any virtual machine monitor (VMM) that supports vhost-user. See the +Examples section below. + +## Synopsis + +**vhost-device-gpio** [*OPTIONS*] + +## Options + +.. program:: vhost-device-gpio + +.. 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-list=GPIO-DEVICES + + GPIO device list at the host OS in the format: + [:] + + Example: --device-list "2:4:7" + + Here, each GPIO devices correspond to a separate guest instance, i.e. the + number of devices in the device-list must match the number of sockets in the + --socket-count. For example, the GPIO device 0 will be allocated to the guest + with "0" path. + +## Examples + +The daemon should be started first: + +:: + + host# vhost-device-gpio --socket-path=gpio.sock --socket-count=1 --device-list 0:3 + +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. + +:: + + host# qemu-system \ + -chardev socket,path=vgpio.sock,id=vgpio \ + -device vhost-user-gpio-pci,chardev=vgpio,id=gpio \ + -m 4096 \ + -object memory-backend-file,id=mem,size=4G,mem-path=/dev/shm,share=on \ + -numa node,memdev=mem \ + ... From a9678adfb9d726e9d255607ddb560554a948235f Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Fri, 26 Nov 2021 11:10:19 +0530 Subject: [PATCH 7/8] gpio: Update Cargo.lock and coverage score Update Cargo.lock and coverage score. Signed-off-by: Viresh Kumar --- Cargo.lock | 42 +++++++++++++++++++++++++++++++++++++ coverage_config_x86_64.json | 2 +- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index fe5a8e4..ee32681 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,6 +40,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "cc" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" + [[package]] name = "cfg-if" version = "1.0.0" @@ -177,6 +183,24 @@ version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" +[[package]] +name = "libgpiod" +version = "0.1.0" +source = "git+https://github.com/vireshk/libgpiod#b91233f1c74ca98cc9161afaa37b17668696b0f6" +dependencies = [ + "libgpiod-sys", + "thiserror", + "vmm-sys-util", +] + +[[package]] +name = "libgpiod-sys" +version = "0.1.0" +source = "git+https://github.com/vireshk/libgpiod#b91233f1c74ca98cc9161afaa37b17668696b0f6" +dependencies = [ + "cc", +] + [[package]] name = "log" version = "0.4.16" @@ -404,6 +428,24 @@ dependencies = [ "vmm-sys-util", ] +[[package]] +name = "vhost-device-gpio" +version = "0.1.0" +dependencies = [ + "clap", + "env_logger", + "libc", + "libgpiod", + "log", + "thiserror", + "vhost", + "vhost-user-backend", + "virtio-bindings", + "virtio-queue", + "vm-memory", + "vmm-sys-util", +] + [[package]] name = "vhost-device-i2c" version = "0.1.0" diff --git a/coverage_config_x86_64.json b/coverage_config_x86_64.json index c19c966..47e6d43 100644 --- a/coverage_config_x86_64.json +++ b/coverage_config_x86_64.json @@ -1,5 +1,5 @@ { - "coverage_score": 85.2, + "coverage_score": 77.0, "exclude_path": "", "crate_features": "" } From 131464787d32ab9406dbac87db419a2a80a3db83 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Thu, 23 Dec 2021 13:15:11 +0530 Subject: [PATCH 8/8] gpio: Skip musl builds Disable builds for musl, where libgpiod build fails on rust-vmm-container due to missing tools and headers. Signed-off-by: Viresh Kumar --- gpio/Cargo.toml | 1 + gpio/src/main.rs | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/gpio/Cargo.toml b/gpio/Cargo.toml index 28ae5c2..07cc8b2 100644 --- a/gpio/Cargo.toml +++ b/gpio/Cargo.toml @@ -24,6 +24,7 @@ virtio-queue = "0.2" vm-memory = "0.7" vmm-sys-util = "=0.9.0" +[target.'cfg(target_env = "gnu")'.dependencies] libgpiod = { git = "https://github.com/vireshk/libgpiod" } [dev-dependencies] diff --git a/gpio/src/main.rs b/gpio/src/main.rs index 3531af3..045cea3 100644 --- a/gpio/src/main.rs +++ b/gpio/src/main.rs @@ -5,10 +5,21 @@ // // SPDX-License-Identifier: Apache-2.0 +#[cfg(target_env = "gnu")] mod backend; +#[cfg(target_env = "gnu")] mod gpio; +#[cfg(target_env = "gnu")] mod vhu_gpio; +#[cfg(target_env = "gnu")] fn main() -> backend::Result<()> { backend::gpio_init() } + +// Rust vmm container (https://github.com/rust-vmm/rust-vmm-container) doesn't +// have tools to do a musl build at the moment, and adding that support is +// tricky as well to the container. Skip musl builds until the time pre-built +// libgpiod library is available for musl. +#[cfg(target_env = "musl")] +fn main() {}