Merge pull request #63 from vireshk/i2c/testing-wip

I2c/Improved testing
This commit is contained in:
Viresh Kumar 2021-11-29 14:25:21 +05:30 committed by GitHub
commit ae3958743d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 450 additions and 45 deletions

1
Cargo.lock generated
View File

@ -287,6 +287,7 @@ dependencies = [
"vhost",
"vhost-user-backend",
"virtio-bindings",
"virtio-queue",
"vm-memory 0.7.0",
"vmm-sys-util",
]

View File

@ -1,5 +1,5 @@
{
"coverage_score": 58.4,
"coverage_score": 74.1,
"exclude_path": "",
"crate_features": ""
}

View File

@ -19,5 +19,10 @@ thiserror = "1.0"
vhost = { version = "0.2", features = ["vhost-user-slave"] }
vhost-user-backend = { git = "https://github.com/rust-vmm/vhost-user-backend", rev = "4047c697470cc6c37e8e1835025b091d2b59c2f7" }
virtio-bindings = ">=0.1"
virtio-queue = { git = "https://github.com/rust-vmm/vm-virtio", rev = "66cda80" }
vm-memory = "0.7"
vmm-sys-util = "=0.9.0"
[dev-dependencies]
virtio-queue = { git = "https://github.com/rust-vmm/vm-virtio", rev = "66cda80", features = ["test-utils"] }
vm-memory = { version = "0.7.0", features = ["backend-mmap", "backend-atomic"] }

View File

@ -574,6 +574,20 @@ pub mod tests {
use std::convert::TryFrom;
use vmm_sys_util::tempfile::TempFile;
// Update read-buffer of each write-buffer with index + 1 value.
pub fn update_rdwr_buf(buf: &mut Vec<u8>) {
for (i, byte) in buf.iter_mut().enumerate() {
*byte = i as u8 + 1;
}
}
// Verify the write-buffer passed to us
pub fn verify_rdwr_buf(buf: &[u8]) {
for (i, byte) in buf.iter().enumerate() {
assert_eq!(*byte, i as u8 + 1);
}
}
#[derive(Debug)]
pub struct DummyDevice {
funcs_result: Result<u64>,
@ -611,12 +625,15 @@ pub mod tests {
}
fn rdwr(&self, reqs: &mut [I2cReq]) -> Result<()> {
// Update buffer of each write-buffer with index + 1 value.
for req in reqs {
if req.len == 0 {
return Err(Error::I2cTransferInvalid(0));
}
if (req.flags & I2C_M_RD) != 0 {
for (j, byte) in req.buf.iter_mut().enumerate() {
*byte = j as u8 + 1;
}
update_rdwr_buf(&mut req.buf);
} else {
verify_rdwr_buf(&req.buf);
}
}
@ -644,9 +661,7 @@ pub mod tests {
// Match what's done by DummyDevice::rdwr()
for req in reqs {
if (req.flags & I2C_M_RD) != 0 {
for (i, byte) in req.buf.iter().enumerate() {
assert_eq!(*byte, i as u8 + 1);
}
verify_rdwr_buf(&req.buf);
}
}
}
@ -735,6 +750,12 @@ pub mod tests {
},
];
for req in &mut reqs {
if (req.flags & I2C_M_RD) == 0 {
update_rdwr_buf(&mut req.buf);
}
}
i2c_map.transfer(&mut *reqs).unwrap();
verify_rdwr_data(&reqs);
}

View File

@ -17,6 +17,7 @@ use virtio_bindings::bindings::virtio_net::{VIRTIO_F_NOTIFY_ON_EMPTY, VIRTIO_F_V
use virtio_bindings::bindings::virtio_ring::{
VIRTIO_RING_F_EVENT_IDX, VIRTIO_RING_F_INDIRECT_DESC,
};
use virtio_queue::DescriptorChain;
use vm_memory::{ByteValued, Bytes, GuestMemoryAtomic, GuestMemoryMmap, Le16, Le32};
use vmm_sys_util::epoll::EventSet;
use vmm_sys_util::eventfd::{EventFd, EFD_NONBLOCK};
@ -32,21 +33,21 @@ const NUM_QUEUES: usize = 1;
type Result<T> = std::result::Result<T, Error>;
type VhostUserBackendResult<T> = std::result::Result<T, std::io::Error>;
#[derive(Debug, ThisError)]
#[derive(Copy, Clone, Debug, PartialEq, ThisError)]
/// Errors related to vhost-device-i2c daemon.
pub enum Error {
#[error("Failed to handle event, didn't match EPOLLIN")]
HandleEventNotEpollIn,
#[error("Failed to handle unknown event")]
HandleEventUnknown,
#[error("Received unexpected write only descriptor")]
UnexpectedWriteOnlyDescriptor,
#[error("Received unexpected readable descriptor")]
UnexpectedReadableDescriptor,
#[error("Invalid descriptor count")]
UnexpectedDescriptorCount,
#[error("Invalid descriptor size")]
UnexpectedDescriptorSize,
#[error("Received unexpected write only descriptor at index {0}")]
UnexpectedWriteOnlyDescriptor(usize),
#[error("Received unexpected readable descriptor at index {0}")]
UnexpectedReadableDescriptor(usize),
#[error("Invalid descriptor count {0}")]
UnexpectedDescriptorCount(usize),
#[error("Invalid descriptor size, expected: {0}, found: {1}")]
UnexpectedDescriptorSize(usize, u32),
#[error("Descriptor not found")]
DescriptorNotFound,
#[error("Descriptor read failed")]
@ -55,8 +56,8 @@ pub enum Error {
DescriptorWriteFailed,
#[error("Failed to send notification")]
NotificationFailed,
#[error("Failed to create new EventFd: {0:?}")]
EventFdFailed(std::io::Error),
#[error("Failed to create new EventFd")]
EventFdFailed,
}
impl convert::From<Error> for io::Error {
@ -96,26 +97,25 @@ pub struct VhostUserI2cBackend<D: I2cDevice> {
pub exit_event: EventFd,
}
type I2cDescriptorChain = DescriptorChain<GuestMemoryAtomic<GuestMemoryMmap<()>>>;
impl<D: I2cDevice> VhostUserI2cBackend<D> {
pub fn new(i2c_map: Arc<I2cMap<D>>) -> Result<Self> {
Ok(VhostUserI2cBackend {
i2c_map,
event_idx: false,
exit_event: EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdFailed)?,
exit_event: EventFd::new(EFD_NONBLOCK).map_err(|_| Error::EventFdFailed)?,
})
}
/// Process the requests in the vring and dispatch replies
fn process_queue(&self, vring: &VringRwLock) -> Result<bool> {
fn process_requests(
&self,
requests: Vec<I2cDescriptorChain>,
vring: Option<&VringRwLock>,
) -> Result<bool> {
let mut reqs: Vec<I2cReq> = Vec::new();
let requests: Vec<_> = vring
.get_mut()
.get_queue_mut()
.iter()
.map_err(|_| Error::DescriptorNotFound)?
.collect();
if requests.is_empty() {
return Ok(true);
}
@ -125,16 +125,20 @@ impl<D: I2cDevice> VhostUserI2cBackend<D> {
let descriptors: Vec<_> = desc_chain.clone().collect();
if (descriptors.len() != 2) && (descriptors.len() != 3) {
return Err(Error::UnexpectedDescriptorCount);
return Err(Error::UnexpectedDescriptorCount(descriptors.len()));
}
let desc_out_hdr = descriptors[0];
if desc_out_hdr.is_write_only() {
return Err(Error::UnexpectedWriteOnlyDescriptor);
return Err(Error::UnexpectedWriteOnlyDescriptor(0));
}
if desc_out_hdr.len() as usize != size_of::<VirtioI2cOutHdr>() {
return Err(Error::UnexpectedDescriptorSize);
return Err(Error::UnexpectedDescriptorSize(
size_of::<VirtioI2cOutHdr>(),
desc_out_hdr.len(),
));
}
let out_hdr = desc_chain
@ -149,11 +153,14 @@ impl<D: I2cDevice> VhostUserI2cBackend<D> {
let desc_in_hdr = descriptors[descriptors.len() - 1];
if !desc_in_hdr.is_write_only() {
return Err(Error::UnexpectedReadableDescriptor);
return Err(Error::UnexpectedReadableDescriptor(descriptors.len() - 1));
}
if desc_in_hdr.len() as usize != size_of::<u8>() {
return Err(Error::UnexpectedDescriptorSize);
return Err(Error::UnexpectedDescriptorSize(
size_of::<u8>(),
desc_in_hdr.len(),
));
}
let (buf, len) = match descriptors.len() {
@ -163,13 +170,13 @@ impl<D: I2cDevice> VhostUserI2cBackend<D> {
let len = desc_buf.len();
if len == 0 {
return Err(Error::UnexpectedDescriptorSize);
return Err(Error::UnexpectedDescriptorSize(1, len));
}
let mut buf = vec![0; len as usize];
if flags != I2C_M_RD {
if desc_buf.is_write_only() {
return Err(Error::UnexpectedWriteOnlyDescriptor);
return Err(Error::UnexpectedWriteOnlyDescriptor(1));
}
desc_chain
@ -177,7 +184,7 @@ impl<D: I2cDevice> VhostUserI2cBackend<D> {
.read(&mut buf, desc_buf.addr())
.map_err(|_| Error::DescriptorReadFailed)?;
} else if !desc_buf.is_write_only() {
return Err(Error::UnexpectedReadableDescriptor);
return Err(Error::UnexpectedReadableDescriptor(1));
}
(buf, len)
@ -230,15 +237,32 @@ impl<D: I2cDevice> VhostUserI2cBackend<D> {
.write_obj::<VirtioI2cInHdr>(in_hdr, desc_in_hdr.addr())
.map_err(|_| Error::DescriptorWriteFailed)?;
if vring.add_used(desc_chain.head_index(), len).is_err() {
warn!("Couldn't return used descriptors to the ring");
if let Some(vring) = vring {
if vring.add_used(desc_chain.head_index(), len).is_err() {
warn!("Couldn't return used descriptors to the ring");
}
}
}
// Send notification once all the requests are processed
vring
.signal_used_queue()
.map_err(|_| Error::NotificationFailed)?;
Ok(true)
}
/// Process the requests in the vring and dispatch replies
fn process_queue(&self, vring: &VringRwLock) -> Result<bool> {
let requests: Vec<_> = vring
.get_mut()
.get_queue_mut()
.iter()
.map_err(|_| Error::DescriptorNotFound)?
.collect();
if self.process_requests(requests, Some(vring))? {
// Send notification once all the requests are processed
vring
.signal_used_queue()
.map_err(|_| Error::NotificationFailed)?;
}
Ok(true)
}
}
@ -328,11 +352,365 @@ impl<D: 'static + I2cDevice + Sync + Send> VhostUserBackendMut<VringRwLock, ()>
#[cfg(test)]
mod tests {
use super::*;
use crate::i2c::tests::DummyDevice;
use crate::AdapterConfig;
use std::convert::TryFrom;
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::i2c::tests::{update_rdwr_buf, verify_rdwr_buf, DummyDevice};
use crate::AdapterConfig;
// Prepares a single chain of descriptors
fn prepare_desc_chain(
start_addr: GuestAddress,
buf: &mut Vec<u8>,
flag: u32,
client_addr: u16,
) -> I2cDescriptorChain {
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;
// Out header descriptor
let out_hdr = VirtioI2cOutHdr {
addr: From::from(client_addr << 1),
padding: From::from(0x0),
flags: From::from(flag),
};
let desc_out = Descriptor::new(
next_addr,
size_of::<VirtioI2cOutHdr>() as u32,
VIRTQ_DESC_F_NEXT,
index + 1,
);
mem.write_obj::<VirtioI2cOutHdr>(out_hdr, desc_out.addr())
.unwrap();
vq.desc_table().store(index, desc_out);
next_addr += desc_out.len() as u64;
index += 1;
// Buf descriptor: optional
if !buf.is_empty() {
// Set buffer is write-only or not
let flag = if (flag & VIRTIO_I2C_FLAGS_M_RD) == 0 {
update_rdwr_buf(buf);
0
} else {
VIRTQ_DESC_F_WRITE
};
let desc_buf = Descriptor::new(
next_addr,
buf.len() as u32,
flag | VIRTQ_DESC_F_NEXT,
index + 1,
);
mem.write(buf, desc_buf.addr()).unwrap();
vq.desc_table().store(index, desc_buf);
next_addr += desc_buf.len() as u64;
index += 1;
}
// In response descriptor
let desc_in = Descriptor::new(next_addr, size_of::<u8>() as u32, 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()
}
// 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<I2cDescriptorChain>, status: u8) {
for desc_chain in desc_chains {
let descriptors: Vec<_> = desc_chain.clone().collect();
let in_hdr = desc_chain
.memory()
.read_obj::<VirtioI2cInHdr>(descriptors[descriptors.len() - 1].addr())
.unwrap();
// Operation result should match expected status.
assert_eq!(in_hdr.status, status);
let out_hdr = desc_chain
.memory()
.read_obj::<VirtioI2cOutHdr>(descriptors[0].addr())
.unwrap();
if (out_hdr.flags.to_native() & VIRTIO_I2C_FLAGS_M_RD) != 0 && descriptors.len() == 3 {
let mut buf = vec![0; descriptors[1].len() as usize];
desc_chain
.memory()
.read(&mut buf, descriptors[1].addr())
.unwrap();
// Verify the content of the read-buffer
verify_rdwr_buf(&buf);
}
}
}
// 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>,
) -> I2cDescriptorChain {
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()
}
#[test]
fn process_requests_success() {
let device_config = AdapterConfig::try_from("1:4,2:32:21,5:10:23").unwrap();
let i2c_map = I2cMap::<DummyDevice>::new(&device_config).unwrap();
let backend = VhostUserI2cBackend::new(Arc::new(i2c_map)).unwrap();
// Descriptor chain size zero, shouldn't fail
backend
.process_requests(Vec::<I2cDescriptorChain>::new(), None)
.unwrap();
// Valid single read descriptor
let mut buf: Vec<u8> = vec![0; 30];
let desc_chain = prepare_desc_chain(GuestAddress(0), &mut buf, VIRTIO_I2C_FLAGS_M_RD, 4);
let desc_chains = vec![desc_chain];
backend.process_requests(desc_chains.clone(), None).unwrap();
validate_desc_chains(desc_chains, VIRTIO_I2C_MSG_OK);
// Valid single write descriptor
let mut buf: Vec<u8> = vec![0; 30];
let desc_chain = prepare_desc_chain(GuestAddress(0), &mut buf, 0, 4);
let desc_chains = vec![desc_chain];
backend.process_requests(desc_chains.clone(), None).unwrap();
validate_desc_chains(desc_chains, VIRTIO_I2C_MSG_OK);
// Valid mixed read-write descriptors
let mut buf: Vec<Vec<u8>> = vec![vec![0; 30]; 6];
let desc_chains = vec![
// Write
prepare_desc_chain(GuestAddress(0), &mut buf[0], 0, 4),
// Read
prepare_desc_chain(GuestAddress(0), &mut buf[1], VIRTIO_I2C_FLAGS_M_RD, 4),
// Write
prepare_desc_chain(GuestAddress(0), &mut buf[2], 0, 4),
// Read
prepare_desc_chain(GuestAddress(0), &mut buf[3], VIRTIO_I2C_FLAGS_M_RD, 4),
// Write
prepare_desc_chain(GuestAddress(0), &mut buf[4], 0, 4),
// Read
prepare_desc_chain(GuestAddress(0), &mut buf[5], VIRTIO_I2C_FLAGS_M_RD, 4),
];
backend.process_requests(desc_chains.clone(), None).unwrap();
validate_desc_chains(desc_chains, VIRTIO_I2C_MSG_OK);
}
#[test]
fn process_requests_failure() {
let device_config = AdapterConfig::try_from("1:4,2:32:21,5:10:23").unwrap();
let i2c_map = I2cMap::<DummyDevice>::new(&device_config).unwrap();
let backend = VhostUserI2cBackend::new(Arc::new(i2c_map)).unwrap();
// One descriptors
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], None)
.unwrap_err(),
Error::UnexpectedDescriptorCount(1)
);
// Four descriptors
let flags: Vec<u16> = vec![0, 0, 0, 0];
let len: Vec<u32> = vec![0, 0, 0, 0];
let desc_chain = prepare_desc_chain_dummy(None, flags, len);
assert_eq!(
backend
.process_requests(vec![desc_chain], None)
.unwrap_err(),
Error::UnexpectedDescriptorCount(4)
);
// Write only out hdr
let flags: Vec<u16> = vec![VIRTQ_DESC_F_WRITE, 0, VIRTQ_DESC_F_WRITE];
let len: Vec<u32> = vec![
size_of::<VirtioI2cOutHdr>() as u32,
1,
size_of::<u8>() as u32,
];
let desc_chain = prepare_desc_chain_dummy(None, flags, len);
assert_eq!(
backend
.process_requests(vec![desc_chain], None)
.unwrap_err(),
Error::UnexpectedWriteOnlyDescriptor(0)
);
// Invalid out hdr length
let flags: Vec<u16> = vec![0, 0, VIRTQ_DESC_F_WRITE];
let len: Vec<u32> = vec![100, 1, size_of::<u8>() as u32];
let desc_chain = prepare_desc_chain_dummy(None, flags, len);
assert_eq!(
backend
.process_requests(vec![desc_chain], None)
.unwrap_err(),
Error::UnexpectedDescriptorSize(size_of::<VirtioI2cOutHdr>(), 100)
);
// Invalid out hdr address
let addr: Vec<u64> = vec![0x10000, 0, 0];
let flags: Vec<u16> = vec![0, 0, VIRTQ_DESC_F_WRITE];
let len: Vec<u32> = vec![
size_of::<VirtioI2cOutHdr>() as u32,
1,
size_of::<u8>() as u32,
];
let desc_chain = prepare_desc_chain_dummy(Some(addr), flags, len);
assert_eq!(
backend
.process_requests(vec![desc_chain], None)
.unwrap_err(),
Error::DescriptorReadFailed
);
// Read only in hdr
let flags: Vec<u16> = vec![0, 0, 0];
let len: Vec<u32> = vec![
size_of::<VirtioI2cOutHdr>() as u32,
1,
size_of::<u8>() as u32,
];
let desc_chain = prepare_desc_chain_dummy(None, flags, len);
assert_eq!(
backend
.process_requests(vec![desc_chain], None)
.unwrap_err(),
Error::UnexpectedReadableDescriptor(2)
);
// Invalid in hdr length
let flags: Vec<u16> = vec![0, 0, VIRTQ_DESC_F_WRITE];
let len: Vec<u32> = vec![size_of::<VirtioI2cOutHdr>() as u32, 1, 100];
let desc_chain = prepare_desc_chain_dummy(None, flags, len);
assert_eq!(
backend
.process_requests(vec![desc_chain], None)
.unwrap_err(),
Error::UnexpectedDescriptorSize(size_of::<u8>(), 100)
);
// Invalid in hdr address
let addr: Vec<u64> = vec![0, 0, 0x10000];
let flags: Vec<u16> = vec![0, 0, VIRTQ_DESC_F_WRITE];
let len: Vec<u32> = vec![
size_of::<VirtioI2cOutHdr>() as u32,
1,
size_of::<u8>() as u32,
];
let desc_chain = prepare_desc_chain_dummy(Some(addr), flags, len);
assert_eq!(
backend
.process_requests(vec![desc_chain], None)
.unwrap_err(),
Error::DescriptorWriteFailed
);
// Invalid buf length
let flags: Vec<u16> = vec![0, 0, VIRTQ_DESC_F_WRITE];
let len: Vec<u32> = vec![
size_of::<VirtioI2cOutHdr>() as u32,
0,
size_of::<u8>() as u32,
];
let desc_chain = prepare_desc_chain_dummy(None, flags, len);
assert_eq!(
backend
.process_requests(vec![desc_chain], None)
.unwrap_err(),
Error::UnexpectedDescriptorSize(1, 0)
);
// Invalid buf address
let addr: Vec<u64> = vec![0, 0x10000, 0];
let flags: Vec<u16> = vec![0, 0, VIRTQ_DESC_F_WRITE];
let len: Vec<u32> = vec![
size_of::<VirtioI2cOutHdr>() as u32,
1,
size_of::<u8>() as u32,
];
let desc_chain = prepare_desc_chain_dummy(Some(addr), flags, len);
assert_eq!(
backend
.process_requests(vec![desc_chain], None)
.unwrap_err(),
Error::DescriptorReadFailed
);
// Missing buffer for I2C rdwr transfer
let mut buf = Vec::<u8>::new();
let desc_chain = prepare_desc_chain(GuestAddress(0), &mut buf, VIRTIO_I2C_FLAGS_M_RD, 4);
let desc_chains = vec![desc_chain];
backend.process_requests(desc_chains.clone(), None).unwrap();
validate_desc_chains(desc_chains, VIRTIO_I2C_MSG_ERR);
}
#[test]
fn verify_backend() {
let device_config = AdapterConfig::try_from("1:4,2:32:21,5:10:23").unwrap();