Merge pull request #76 from vireshk/gpio/backend

Gpio/backend
This commit is contained in:
Viresh Kumar 2022-04-20 23:16:29 +05:30 committed by GitHub
commit 4a7766f8fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 2630 additions and 1 deletions

42
Cargo.lock generated
View File

@ -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"
@ -186,6 +192,24 @@ version = "0.2.124"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50"
[[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"
@ -410,6 +434,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"

View File

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

View File

@ -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)

View File

@ -1,5 +1,5 @@
{
"coverage_score": 85.2,
"coverage_score": 77.0,
"exclude_path": "",
"crate_features": ""
}

32
gpio/Cargo.toml Normal file
View File

@ -0,0 +1,32 @@
[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"
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]
virtio-queue = { version = "0.2", features = ["test-utils"] }
vm-memory = { version = "0.7.0", features = ["backend-mmap", "backend-atomic"] }

65
gpio/README.md Normal file
View File

@ -0,0 +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:
<device1>[:<device2>]
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 "<socket-path>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 \
...

285
gpio/src/backend.rs Normal file
View File

@ -0,0 +1,285 @@
// 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())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::gpio::tests::DummyDevice;
impl DeviceConfig {
pub fn new_with(devices: Vec<u32>) -> 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::<u32>().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::<DummyDevice>(cmd_args).unwrap_err(),
Error::FailedJoiningThreads
);
}
}

985
gpio/src/gpio.rs Normal file
View File

@ -0,0 +1,985 @@
// GPIO backend device
//
// Copyright 2022 Linaro Ltd. All Rights Reserved.
// Viresh Kumar <viresh.kumar@linaro.org>
//
// SPDX-License-Identifier: Apache-2.0
use log::error;
use std::sync::{Arc, RwLock};
use std::time::Duration;
use libgpiod::{
Chip, Direction, Edge, EdgeEventBuffer, Error as LibGpiodError, LineConfig, LineRequest,
RequestConfig,
};
use thiserror::Error as ThisError;
use vm_memory::{Le16, Le32};
type Result<T> = std::result::Result<T, Error>;
#[derive(Copy, 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),
#[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,
#[cfg(test)]
#[error("Gpio test Operation failed {0}")]
GpioOperationFailed(&'static str),
}
/// 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;
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, Debug, PartialEq)]
#[repr(C)]
pub(crate) struct VirtioGpioConfig {
pub(crate) ngpio: Le16,
pub(crate) padding: Le16,
pub(crate) gpio_names_size: Le32,
}
/// 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: Send + Sync + 'static {
fn open(device: u32) -> Result<Self>
where
Self: Sized;
fn get_num_gpios(&self) -> Result<u16>;
fn get_gpio_name(&self, gpio: u16) -> Result<String>;
fn get_direction(&self, gpio: u16) -> Result<u8>;
fn set_direction(&self, gpio: u16, dir: u8, value: u32) -> Result<()>;
fn get_value(&self, gpio: u16) -> Result<u8>;
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 {
// See wait_for_interrupt() for explanation of Arc.
request: Option<Arc<LineRequest>>,
buffer: Option<EdgeEventBuffer>,
}
pub(crate) struct PhysDevice {
chip: Chip,
ngpio: u16,
state: Vec<RwLock<PhysLineState>>,
}
unsafe impl Send for PhysDevice {}
unsafe impl Sync for PhysDevice {}
impl GpioDevice for PhysDevice {
fn open(device: u32) -> Result<Self>
where
Self: Sized,
{
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<RwLock<PhysLineState>> = Vec::new();
state.resize_with(ngpio as usize, || {
RwLock::new(PhysLineState {
request: None,
buffer: None,
})
});
Ok(PhysDevice { chip, ngpio, state })
}
fn get_num_gpios(&self) -> Result<u16> {
Ok(self.ngpio)
}
fn get_gpio_name(&self, gpio: u16) -> Result<String> {
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<u8> {
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(Arc::new(
self.chip
.request_lines(&rconfig, &config)
.map_err(Error::GpiodFailed)?,
));
}
Ok(())
}
fn get_value(&self, gpio: u16) -> Result<u8> {
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(())
}
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)
}
}
}
#[derive(Debug, Copy, Clone)]
struct GpioState {
dir: u8,
val: Option<u16>,
irq_type: u16,
}
#[derive(Debug)]
pub(crate) struct GpioController<D: GpioDevice> {
config: VirtioGpioConfig,
device: D,
state: Vec<RwLock<GpioState>>,
gpio_names: String,
}
impl<D: GpioDevice> GpioController<D> {
// Creates a new controller corresponding to `device`.
pub(crate) fn new(device: D) -> Result<GpioController<D>> {
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,
irq_type: VIRTIO_GPIO_IRQ_TYPE_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,
})
}
pub(crate) fn get_num_gpios(&self) -> u16 {
self.device.get_num_gpios().unwrap()
}
fn get_direction(&self, gpio: u16) -> Result<u8> {
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<u8> {
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_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
}
pub(crate) fn operation(&self, rtype: u16, gpio: u16, value: u32) -> Result<Vec<u8>> {
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]
}
VIRTIO_GPIO_MSG_IRQ_TYPE => {
self.set_irq_type(gpio, value)?;
vec![0]
}
msg => return Err(Error::GpioMessageInvalid(msg)),
})
}
}
#[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<String>,
state: RwLock<Vec<GpioState>>,
get_num_gpios_result: Result<u16>,
get_gpio_name_result: Result<String>,
get_direction_result: Result<u8>,
set_direction_result: Result<()>,
get_value_result: Result<u8>,
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<Self>
where
Self: Sized,
{
Ok(DummyDevice::new(8))
}
fn get_num_gpios(&self) -> Result<u16> {
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<String> {
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<u8> {
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<u8> {
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)
);
}
}
}

25
gpio/src/main.rs Normal file
View File

@ -0,0 +1,25 @@
// 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
#[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() {}

1193
gpio/src/vhu_gpio.rs Normal file

File diff suppressed because it is too large Load Diff