mirror of
https://github.com/rust-vmm/vhost-device.git
synced 2026-01-09 14:19:30 +00:00
gpio: Add tests
Add tests to validate functionality of the GPIO crate. Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
This commit is contained in:
parent
679224af8d
commit
cc6d1e608f
@ -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"] }
|
||||
|
||||
@ -190,3 +190,96 @@ pub(crate) fn gpio_init() -> Result<()> {
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
506
gpio/src/gpio.rs
506
gpio/src/gpio.rs
@ -18,7 +18,7 @@ use vm_memory::{Le16, Le32};
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[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<u16>,
|
||||
irq_type: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct GpioController<D: GpioDevice> {
|
||||
config: VirtioGpioConfig,
|
||||
device: D,
|
||||
@ -487,3 +492,494 @@ impl<D: GpioDevice> GpioController<D> {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -480,3 +480,714 @@ impl<D: 'static + GpioDevice + Sync + Send> VhostUserBackendMut<VringRwLock, ()>
|
||||
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<R: ByteValued>(
|
||||
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::<R>() as u32,
|
||||
VIRTQ_DESC_F_NEXT,
|
||||
index + 1,
|
||||
);
|
||||
|
||||
mem.write_obj::<R>(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::<GuestMemoryMmap>::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::<VirtioGpioRequest>(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::<VirtioGpioIrqRequest>(
|
||||
start_addr,
|
||||
out_hdr,
|
||||
size_of::<VirtioGpioIrqResponse>() as u32,
|
||||
)
|
||||
}
|
||||
|
||||
// Prepares list of dummy descriptors, their content isn't significant.
|
||||
fn prepare_desc_chain_dummy(
|
||||
addr: Option<Vec<u64>>,
|
||||
flags: Vec<u16>,
|
||||
len: Vec<u32>,
|
||||
) -> 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::<GuestMemoryMmap>::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<GpioDescriptorChain>,
|
||||
status: u8,
|
||||
val: Option<Vec<u8>>,
|
||||
) {
|
||||
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::<GpioDescriptorChain>::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<u16> = vec![0];
|
||||
let len: Vec<u32> = 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<u16> = vec![0, 0, 0];
|
||||
let len: Vec<u32> = 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<u16> = vec![VIRTQ_DESC_F_WRITE, VIRTQ_DESC_F_WRITE];
|
||||
let len: Vec<u32> = vec![size_of::<VirtioGpioRequest>() 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<u64> = vec![0x10000, 0];
|
||||
let flags: Vec<u16> = vec![0, VIRTQ_DESC_F_WRITE];
|
||||
let len: Vec<u32> = vec![size_of::<VirtioGpioRequest>() 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<u16> = vec![0, VIRTQ_DESC_F_WRITE];
|
||||
let len: Vec<u32> = 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::<VirtioGpioRequest>(), 100)
|
||||
);
|
||||
|
||||
// Read only in hdr.
|
||||
let flags: Vec<u16> = vec![0, 0];
|
||||
let len: Vec<u32> = vec![size_of::<VirtioGpioRequest>() 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<u64> = vec![0, 0x10000];
|
||||
let flags: Vec<u16> = vec![0, VIRTQ_DESC_F_WRITE];
|
||||
let len: Vec<u32> = vec![size_of::<VirtioGpioRequest>() 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::<GpioDescriptorChain>::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<u16> = vec![0];
|
||||
let len: Vec<u32> = 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<u16> = vec![0, 0, 0];
|
||||
let len: Vec<u32> = 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<u16> = vec![VIRTQ_DESC_F_WRITE, VIRTQ_DESC_F_WRITE];
|
||||
let len: Vec<u32> = vec![
|
||||
size_of::<VirtioGpioIrqRequest>() as u32,
|
||||
size_of::<VirtioGpioIrqResponse>() 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<u64> = vec![0x10000, 0];
|
||||
let flags: Vec<u16> = vec![0, VIRTQ_DESC_F_WRITE];
|
||||
let len: Vec<u32> = vec![
|
||||
size_of::<VirtioGpioIrqRequest>() as u32,
|
||||
size_of::<VirtioGpioIrqResponse>() 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<u16> = vec![0, VIRTQ_DESC_F_WRITE];
|
||||
let len: Vec<u32> = vec![100, size_of::<VirtioGpioIrqResponse>() 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::<VirtioGpioIrqRequest>(), 100)
|
||||
);
|
||||
|
||||
// Read only in hdr
|
||||
let flags: Vec<u16> = vec![0, 0];
|
||||
let len: Vec<u32> = vec![
|
||||
size_of::<VirtioGpioIrqRequest>() as u32,
|
||||
size_of::<VirtioGpioIrqResponse>() 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<u16> = vec![0, VIRTQ_DESC_F_WRITE];
|
||||
let len: Vec<u32> = vec![size_of::<VirtioGpioIrqRequest>() 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::<VirtioGpioIrqResponse>(), 100)
|
||||
);
|
||||
|
||||
// Wait for event without setting irq type first.
|
||||
let flags: Vec<u16> = vec![0, VIRTQ_DESC_F_WRITE];
|
||||
let len: Vec<u32> = vec![
|
||||
size_of::<VirtioGpioIrqRequest>() as u32,
|
||||
size_of::<VirtioGpioIrqResponse>() 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::<VirtioGpioConfig>(),
|
||||
)
|
||||
.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();
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user