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:
Viresh Kumar 2021-11-22 12:49:47 +05:30
parent 679224af8d
commit cc6d1e608f
4 changed files with 1309 additions and 5 deletions

View File

@ -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"] }

View File

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

View File

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

View File

@ -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();
}
}