gpio: Add support for interrupts

Add support for GPIO interrupts. Only edge interrupts are supported by
libgpiod.

Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
This commit is contained in:
Viresh Kumar 2022-01-12 16:47:54 +05:30
parent 8a021c704f
commit 679224af8d
2 changed files with 379 additions and 10 deletions

View File

@ -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<Self>
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<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 {
request: Option<LineRequest>,
// See wait_for_interrupt() for explanation of Arc.
request: Option<Arc<LineRequest>>,
buffer: Option<EdgeEventBuffer>,
}
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<u16>,
irq_type: u16,
}
pub(crate) struct GpioController<D: GpioDevice> {
@ -229,6 +360,7 @@ impl<D: GpioDevice> GpioController<D> {
state.push(RwLock::new(GpioState {
dir: device.get_direction(offset)?,
val: None,
irq_type: VIRTIO_GPIO_IRQ_TYPE_NONE,
}));
}
@ -244,6 +376,10 @@ impl<D: GpioDevice> GpioController<D> {
})
}
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)
}
@ -285,6 +421,47 @@ impl<D: GpioDevice> GpioController<D> {
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<D: GpioDevice> GpioController<D> {
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)),
})
}

View File

@ -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<T> = std::result::Result<T, Error>;
type VhostUserBackendResult<T> = std::result::Result<T, std::io::Error>;
@ -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::<VirtioGpioIrqResponse>(response, addr)
.is_err()
{
error!("Failed to write response");
}
if vring
.add_used(
desc_chain.head_index(),
size_of::<VirtioGpioIrqResponse>() 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<D: GpioDevice> {
controller: GpioController<D>,
controller: Arc<GpioController<D>>,
handles: Arc<RwLock<Vec<Option<JoinHandle<()>>>>>,
event_idx: bool,
pub(crate) exit_event: EventFd,
}
@ -95,8 +154,13 @@ type GpioDescriptorChain = DescriptorChain<GuestMemoryLoadGuard<GuestMemoryMmap<
impl<D: GpioDevice> VhostUserGpioBackend<D> {
pub(crate) fn new(controller: GpioController<D>) -> Result<Self> {
// Can't set a vector to all None easily
let mut handles: Vec<Option<JoinHandle<()>>> = 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<D: GpioDevice> VhostUserGpioBackend<D> {
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<GpioDescriptorChain>,
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::<VirtioGpioIrqRequest>() {
return Err(Error::UnexpectedDescriptorSize(
size_of::<VirtioGpioIrqRequest>(),
desc_request.len(),
));
}
let request = desc_chain
.memory()
.read_obj::<VirtioGpioIrqRequest>(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::<VirtioGpioIrqResponse>() {
return Err(Error::UnexpectedDescriptorSize(
size_of::<VirtioGpioIrqResponse>(),
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<D: 'static + GpioDevice + Sync + Send> VhostUserBackendMut<VringRwLock, ()>
| 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<D: 'static + GpioDevice + Sync + Send> VhostUserBackendMut<VringRwLock, ()>
}
}
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());
}