Merge pull request #28 from rust-vmm/refactor_for_testing

[i2c] Refactoring for improved testing & separation of concerns
This commit is contained in:
Viresh Kumar 2021-10-07 15:28:01 +05:30 committed by GitHub
commit a2e55a91e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 822 additions and 323 deletions

371
Cargo.lock generated Normal file
View File

@ -0,0 +1,371 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "arc-swap"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6df5aef5c5830360ce5218cecb8f018af3438af5686ae945094affc86fdec63"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "3.0.0-beta.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142"
dependencies = [
"atty",
"bitflags",
"clap_derive",
"indexmap",
"lazy_static",
"os_str_bytes",
"strsim",
"termcolor",
"textwrap",
"unicode-width",
"vec_map",
"yaml-rust",
]
[[package]]
name = "clap_derive"
version = "3.0.0-beta.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b5bb0d655624a0b8770d1c178fb8ffcb1f91cc722cb08f451e3dc72465421ac"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "epoll"
version = "4.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20df693c700404f7e19d4d6fae6b15215d2913c27955d2b9d6f2c0f537511cd0"
dependencies = [
"bitflags",
"libc",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "indexmap"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103"
[[package]]
name = "linked-hash-map"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "os_str_bytes"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "termcolor"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789"
dependencies = [
"unicode-width",
]
[[package]]
name = "unicode-segmentation"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
[[package]]
name = "unicode-width"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "vhost"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a6b90237e10f1a61b35fba73885c3567e1a5a8c40d44daae335f7710210a7dc"
dependencies = [
"bitflags",
"libc",
"vmm-sys-util",
]
[[package]]
name = "vhost-device-i2c"
version = "0.1.0"
dependencies = [
"clap",
"epoll",
"libc",
"log",
"vhost",
"vhost-user-backend",
"virtio-bindings",
"vm-memory",
"vmm-sys-util",
]
[[package]]
name = "vhost-user-backend"
version = "0.1.0"
source = "git+https://github.com/rust-vmm/vhost-user-backend?rev=70f668a699d865f13ba40498897ad181a409bd41#70f668a699d865f13ba40498897ad181a409bd41"
dependencies = [
"epoll",
"libc",
"log",
"vhost",
"virtio-bindings",
"virtio-queue",
"vm-memory",
"vmm-sys-util",
]
[[package]]
name = "virtio-bindings"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ff512178285488516ed85f15b5d0113a7cdb89e9e8a760b269ae4f02b84bd6b"
[[package]]
name = "virtio-queue"
version = "0.1.0"
source = "git+https://github.com/rust-vmm/vm-virtio?rev=6013dd9#6013dd91b2e6eb77ea10c6bdeda8f5eb18de6dda"
dependencies = [
"log",
"vm-memory",
"vmm-sys-util",
]
[[package]]
name = "vm-memory"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a8ebcb86ca457f9d6e14cf97009f679952eba42f0113de5db596e514cd0e43b"
dependencies = [
"arc-swap",
"libc",
"winapi",
]
[[package]]
name = "vmm-sys-util"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01cf11afbc4ebc0d5c7a7748a77d19e2042677fc15faa2f4ccccb27c18a60605"
dependencies = [
"bitflags",
"libc",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]

View File

@ -1,5 +1,5 @@
{
"coverage_score": 40.6,
"coverage_score": 49.4,
"exclude_path": "",
"crate_features": ""
}

View File

@ -20,4 +20,4 @@ vhost = { version = "0.1", features = ["vhost-user-slave"] }
vhost-user-backend = { git = "https://github.com/rust-vmm/vhost-user-backend", rev = "70f668a699d865f13ba40498897ad181a409bd41" }
virtio-bindings = ">=0.1"
vm-memory = "0.6"
vmm-sys-util = ">=0.8.0"
vmm-sys-util = "=0.8.0"

View File

@ -5,11 +5,14 @@
//
// SPDX-License-Identifier: Apache-2.0
use libc::{c_ulong, ioctl, EADDRINUSE, EADDRNOTAVAIL, EINVAL};
use std::fs::{File, OpenOptions};
use std::os::unix::io::AsRawFd;
use libc::{c_ulong, ioctl, EINVAL};
use vmm_sys_util::errno::{errno_result, Error, Result};
use super::AdapterConfig;
// The type of the `req` parameter is different for the `musl` library. This will enable
// successful build for other non-musl libraries.
#[cfg(target_env = "musl")]
@ -230,25 +233,99 @@ pub struct I2cReq {
pub buf: Vec<u8>,
}
/// I2C adapter and helpers
pub trait I2cAdapterTrait: Send + Sync + 'static {
fn new(bus: &str) -> Result<Self>
/// Trait that represents an I2C Device.
///
/// This trait is introduced for development purposes only, and should not
/// be used outside of this crate. The purpose of this trait is to provide a
/// mock implementation for the I2C driver so that we can test the I2C
/// functionality without the need of a physical device.
pub trait I2cDevice {
// Open the device specified by path.
fn open(device_path: String) -> Result<Self>
where
Self: Sized;
fn bus(&self) -> u32;
fn is_smbus(&self) -> bool;
// Corresponds to the I2C_FUNCS ioctl call.
fn funcs(&mut self, func: u64) -> i32;
/// Sets device's address for an I2C adapter.
fn set_device_addr(&self, addr: usize) -> Result<()>;
// Corresponds to the I2C_RDWR ioctl call.
fn rdwr(&self, data: &I2cRdwrIoctlData) -> i32;
/// Gets adapter's functionality
fn get_func(&mut self) -> Result<()>;
// Corresponds to the I2C_SMBUS ioctl call.
fn smbus(&self, data: &I2cSmbusIoctlData) -> i32;
/// Transfer data
fn do_i2c_transfer(&self, data: &I2cRdwrIoctlData, addr: u16) -> Result<()>;
// Corresponds to the I2C_SLAVE ioctl call.
fn slave(&self, addr: u64) -> i32;
}
fn do_smbus_transfer(&self, data: &I2cSmbusIoctlData, addr: u16) -> Result<()>;
/// A physical I2C device. This structure can only be initialized on hosts
/// where `/dev/i2c-XX` is available.
pub struct PhysDevice {
file: File,
}
impl I2cDevice for PhysDevice {
fn open(device_path: String) -> Result<Self> {
Ok(PhysDevice {
file: OpenOptions::new()
.read(true)
.write(true)
.open(device_path)?,
})
}
fn funcs(&mut self, func: u64) -> i32 {
unsafe { ioctl(self.file.as_raw_fd(), I2C_FUNCS, &func) }
}
fn rdwr(&self, data: &I2cRdwrIoctlData) -> i32 {
unsafe { ioctl(self.file.as_raw_fd(), I2C_RDWR, data) }
}
fn smbus(&self, data: &I2cSmbusIoctlData) -> i32 {
unsafe { ioctl(self.file.as_raw_fd(), I2C_SMBUS, data) }
}
fn slave(&self, addr: u64) -> i32 {
unsafe { ioctl(self.file.as_raw_fd(), I2C_SLAVE, addr as c_ulong) }
}
}
pub struct I2cAdapter<D: I2cDevice> {
device: D,
adapter_no: u32,
smbus: bool,
}
impl<D: I2cDevice> I2cAdapter<D> {
// Creates a new adapter corresponding to the specified number.
fn new(adapter_no: u32) -> Result<I2cAdapter<D>> {
let i2cdev = format!("/dev/i2c-{}", adapter_no);
let func: u64 = I2C_FUNC_SMBUS_ALL;
let mut device = D::open(i2cdev)?;
let smbus;
let ret = device.funcs(func);
if ret == -1 {
println!("Failed to get I2C function");
return errno_result();
}
if (func & I2C_FUNC_I2C) != 0 {
smbus = false;
} else if (func & I2C_FUNC_SMBUS_ALL) != 0 {
smbus = true;
} else {
println!("Invalid functionality {:x}", func);
return Err(Error::new(EINVAL));
}
Ok(I2cAdapter {
device,
adapter_no,
smbus,
})
}
/// Perform I2C_RDWR transfer
fn i2c_transfer(&self, reqs: &mut [I2cReq]) -> Result<()> {
@ -270,14 +347,27 @@ pub trait I2cAdapterTrait: Send + Sync + 'static {
nmsgs: len as u32,
};
self.do_i2c_transfer(&data, addr)
let ret = self.device.rdwr(&data);
if ret == -1 {
println!("Failed to transfer i2c data to device addr to {:x}", addr);
errno_result()
} else {
Ok(())
}
}
/// Perform I2C_SMBUS transfer
fn smbus_transfer(&self, reqs: &mut [I2cReq]) -> Result<()> {
let smbus_data = I2cSmbusIoctlData::new(reqs)?;
self.do_smbus_transfer(&smbus_data, reqs[0].addr)?;
let ret = self.device.smbus(&smbus_data);
if ret == -1 {
println!(
"Failed to transfer smbus data to device addr to {:x}",
reqs[0].addr
);
return errno_result();
}
if smbus_data.read_write == I2C_SMBUS_READ {
unsafe {
@ -298,27 +388,9 @@ pub trait I2cAdapterTrait: Send + Sync + 'static {
}
Ok(())
}
}
pub struct I2cAdapter {
fd: File,
bus: u32,
smbus: bool,
}
impl I2cAdapterTrait for I2cAdapter {
fn new(bus: &str) -> Result<I2cAdapter> {
let i2cdev = String::from("/dev/i2c-") + bus;
Ok(I2cAdapter {
bus: bus.parse::<u32>().map_err(|_| Error::new(EINVAL))?,
smbus: false,
fd: OpenOptions::new().read(true).write(true).open(i2cdev)?,
})
}
fn bus(&self) -> u32 {
self.bus
fn adapter_no(&self) -> u32 {
self.adapter_no
}
fn is_smbus(&self) -> bool {
@ -327,7 +399,7 @@ impl I2cAdapterTrait for I2cAdapter {
/// Sets device's address for an I2C adapter.
fn set_device_addr(&self, addr: usize) -> Result<()> {
let ret = unsafe { ioctl(self.fd.as_raw_fd(), I2C_SLAVE, addr as c_ulong) };
let ret = self.device.slave(addr as u64);
if ret == -1 {
println!("Failed to set device addr to {:x}", addr);
@ -337,103 +409,44 @@ impl I2cAdapterTrait for I2cAdapter {
}
}
/// Gets adapter's functionality
fn get_func(&mut self) -> Result<()> {
let func: u64 = I2C_FUNC_SMBUS_ALL;
let ret = unsafe { ioctl(self.fd.as_raw_fd(), I2C_FUNCS, &func) };
if ret == -1 {
println!("Failed to get I2C function");
return errno_result();
}
if (func & I2C_FUNC_I2C) != 0 {
self.smbus = false;
} else if (func & I2C_FUNC_SMBUS_ALL) != 0 {
self.smbus = true;
fn transfer(&self, reqs: &mut [I2cReq]) -> Result<()> {
if self.is_smbus() {
self.smbus_transfer(reqs)
} else {
println!("Invalid functionality {:x}", func);
return Err(Error::new(EINVAL));
self.i2c_transfer(reqs)
}
Ok(())
}
/// Transfer data
fn do_i2c_transfer(&self, data: &I2cRdwrIoctlData, addr: u16) -> Result<()> {
let ret = unsafe { ioctl(self.fd.as_raw_fd(), I2C_RDWR, data) };
if ret == -1 {
println!("Failed to transfer i2c data to device addr to {:x}", addr);
errno_result()
} else {
Ok(())
}
}
fn do_smbus_transfer(&self, data: &I2cSmbusIoctlData, addr: u16) -> Result<()> {
let ret = unsafe { ioctl(self.fd.as_raw_fd(), I2C_SMBUS, data) };
if ret == -1 {
println!("Failed to transfer smbus data to device addr to {:x}", addr);
return errno_result();
}
Ok(())
}
}
/// I2C map and helpers
const MAX_I2C_VDEV: usize = 1 << 7;
pub(crate) const MAX_I2C_VDEV: usize = 1 << 7;
const I2C_INVALID_ADAPTER: u32 = 0xFFFFFFFF;
pub struct I2cMap<A: I2cAdapterTrait> {
adapters: Vec<A>,
pub struct I2cMap<D: I2cDevice> {
adapters: Vec<I2cAdapter<D>>,
device_map: [u32; MAX_I2C_VDEV],
}
impl<A: I2cAdapterTrait> I2cMap<A> {
pub fn new(list: &str) -> Result<Self>
impl<D: I2cDevice> I2cMap<D> {
pub(crate) fn new(device_config: &AdapterConfig) -> Result<Self>
where
Self: Sized,
{
let mut device_map: [u32; MAX_I2C_VDEV] = [I2C_INVALID_ADAPTER; MAX_I2C_VDEV];
let mut adapters: Vec<A> = Vec::new();
let busses: Vec<&str> = list.split(',').collect();
let mut adapters: Vec<I2cAdapter<D>> = Vec::new();
for (i, businfo) in busses.iter().enumerate() {
let list: Vec<&str> = businfo.split(':').collect();
let mut adapter = A::new(list[0])?;
let devices = &list[1..];
for (i, device_cfg) in device_config.inner.iter().enumerate() {
let adapter = I2cAdapter::new(device_cfg.adapter_no)?;
adapter.get_func()?;
for device in devices {
let device = device.parse::<usize>().map_err(|_| Error::new(EINVAL))?;
if device > MAX_I2C_VDEV {
println!("Invalid device address {}", device);
return Err(Error::new(EADDRNOTAVAIL));
}
if device_map[device] != I2C_INVALID_ADAPTER {
println!(
"Client address {} is already used by {}",
device,
adapters[device_map[device] as usize].bus()
);
return Err(Error::new(EADDRINUSE));
}
adapter.set_device_addr(device)?;
device_map[device] = i as u32;
// Check that all addresses corresponding to the adapter are valid.
for addr in &device_cfg.addr {
adapter.set_device_addr(*addr as usize)?;
device_map[*addr as usize] = i as u32;
}
println!(
"Added I2C master with bus id: {:x} for devices: {:?}",
adapter.bus(),
devices
"Added I2C master with bus id: {:x} for devices",
adapter.adapter_no(),
);
adapters.push(adapter);
@ -447,6 +460,8 @@ impl<A: I2cAdapterTrait> I2cMap<A> {
pub fn transfer(&self, reqs: &mut [I2cReq]) -> Result<()> {
let device = reqs[0].addr as usize;
// identify the device in the device_map
let index = self.device_map[device];
// This can happen a lot while scanning the bus, don't print any errors.
@ -454,92 +469,72 @@ impl<A: I2cAdapterTrait> I2cMap<A> {
return Err(Error::new(EINVAL));
}
// get the corresponding adapter based on the device config.
let adapter = &self.adapters[index as usize];
// Set device's address
adapter.set_device_addr(device)?;
if adapter.is_smbus() {
adapter.smbus_transfer(reqs)
} else {
adapter.i2c_transfer(reqs)
}
adapter.transfer(reqs)
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use std::convert::TryFrom;
pub struct I2cMockAdapter {
bus: u32,
smbus: bool,
result: Result<()>,
#[derive(Debug, Default)]
pub struct DummyDevice {
funcs_result: i32,
rdwr_result: i32,
smbus_result: i32,
slave_result: i32,
}
impl I2cAdapterTrait for I2cMockAdapter {
fn new(bus: &str) -> Result<I2cMockAdapter> {
Ok(I2cMockAdapter {
bus: bus.parse::<u32>().map_err(|_| Error::new(EINVAL))?,
smbus: false,
result: Ok(()),
})
impl I2cDevice for DummyDevice {
fn open(_device_path: String) -> Result<Self>
where
Self: Sized,
{
Ok(DummyDevice::default())
}
fn bus(&self) -> u32 {
self.bus
fn funcs(&mut self, _func: u64) -> i32 {
self.funcs_result
}
fn is_smbus(&self) -> bool {
self.smbus
fn rdwr(&self, _data: &I2cRdwrIoctlData) -> i32 {
self.rdwr_result
}
fn set_device_addr(&self, _addr: usize) -> Result<()> {
Ok(())
fn smbus(&self, _data: &I2cSmbusIoctlData) -> i32 {
self.smbus_result
}
fn get_func(&mut self) -> Result<()> {
Ok(())
fn slave(&self, _addr: u64) -> i32 {
self.slave_result
}
fn do_i2c_transfer(&self, _data: &I2cRdwrIoctlData, _addr: u16) -> Result<()> {
println!("In i2c-transfer");
self.result
}
fn do_smbus_transfer(&self, _data: &I2cSmbusIoctlData, _addr: u16) -> Result<()> {
println!("In smbus-transfer");
self.result
}
}
fn assert_results(
i2c_map: &mut I2cMap<I2cMockAdapter>,
reqs: &mut Vec<I2cReq>,
before: bool,
after: bool,
) {
i2c_map.adapters[0].result = Ok(());
assert_eq!(i2c_map.transfer(reqs).is_err(), before);
i2c_map.adapters[0].result = Err(Error::new(EINVAL));
assert_eq!(i2c_map.transfer(reqs).is_err(), after);
reqs.clear();
}
#[test]
fn test_i2c_map_duplicate_device4() {
assert!(I2cMap::<I2cMockAdapter>::new("1:4,2:32:21,5:4:23").is_err());
assert!(AdapterConfig::try_from("1:4,2:32:21,5:4:23").is_err());
}
#[test]
fn test_duplicated_adapter_no() {
assert!(AdapterConfig::try_from("1:4,1:32:21,5:10:23").is_err());
}
#[test]
fn test_i2c_map() {
let i2c_map: I2cMap<I2cMockAdapter> = I2cMap::new("1:4,2:32:21,5:10:23").unwrap();
let adapter_config = AdapterConfig::try_from("1:4,2:32:21,5:10:23").unwrap();
let i2c_map: I2cMap<DummyDevice> = I2cMap::new(&adapter_config).unwrap();
assert_eq!(i2c_map.adapters.len(), 3);
assert_eq!(i2c_map.adapters[0].bus, 1);
assert_eq!(i2c_map.adapters[1].bus, 2);
assert_eq!(i2c_map.adapters[2].bus, 5);
assert_eq!(i2c_map.adapters[0].adapter_no, 1);
assert_eq!(i2c_map.adapters[1].adapter_no, 2);
assert_eq!(i2c_map.adapters[2].adapter_no, 5);
assert_eq!(i2c_map.device_map[4], 0);
assert_eq!(i2c_map.device_map[32], 1);
@ -550,22 +545,26 @@ pub mod tests {
#[test]
fn test_i2c_transfer() {
let mut i2c_map: I2cMap<I2cMockAdapter> = I2cMap::new("1:3").unwrap();
let adapter_config = AdapterConfig::try_from("1:3").unwrap();
let mut i2c_map: I2cMap<DummyDevice> = I2cMap::new(&adapter_config).unwrap();
i2c_map.adapters[0].smbus = false;
let mut reqs: Vec<I2cReq> = vec![I2cReq {
addr: 0x3,
flags: 0,
len: 2,
buf: [7, 4].to_vec(),
buf: vec![7, 4],
}];
assert_results(&mut i2c_map, &mut reqs, false, true);
i2c_map.transfer(&mut *reqs).unwrap();
}
#[test]
fn test_smbus_transfer() {
let mut i2c_map: I2cMap<I2cMockAdapter> = I2cMap::new("1:3").unwrap();
let adapter_config = AdapterConfig::try_from("1:3").unwrap();
let mut i2c_map: I2cMap<DummyDevice> = I2cMap::new(&adapter_config).unwrap();
i2c_map.adapters[0].smbus = true;
let mut reqs: Vec<I2cReq> = vec![I2cReq {
@ -576,28 +575,30 @@ pub mod tests {
}];
// I2C_SMBUS_WRITE (I2C_SMBUS_BYTE_DATA) operation
assert_results(&mut i2c_map, &mut reqs, false, true);
i2c_map.transfer(&mut reqs).unwrap();
// I2C_SMBUS_READ (I2C_SMBUS_WORD_DATA) operation
reqs.push(I2cReq {
addr: 0x3,
flags: 0,
len: 1,
buf: [34].to_vec(),
});
reqs.push(I2cReq {
addr: 0x3,
flags: 1,
len: 2,
buf: [3, 4].to_vec(),
});
assert_results(&mut i2c_map, &mut reqs, false, true);
let mut reqs = vec![
I2cReq {
addr: 0x3,
flags: 0,
len: 1,
buf: [34].to_vec(),
},
I2cReq {
addr: 0x3,
flags: 1,
len: 2,
buf: [3, 4].to_vec(),
},
];
i2c_map.transfer(&mut reqs).unwrap();
}
#[test]
fn test_smbus_transfer_failure() {
let mut i2c_map: I2cMap<I2cMockAdapter> = I2cMap::new("1:3").unwrap();
let adapter_config = AdapterConfig::try_from("1:3").unwrap();
let mut i2c_map: I2cMap<DummyDevice> = I2cMap::new(&adapter_config).unwrap();
i2c_map.adapters[0].smbus = true;
let mut reqs: Vec<I2cReq> = vec![
@ -617,57 +618,63 @@ pub mod tests {
];
// I2C_SMBUS_READ (I2C_SMBUS_WORD_DATA) failure operation
assert_results(&mut i2c_map, &mut reqs, true, true);
// TODO: check the actual error once we have an error type defined.
// TODO-continued: otherwise this test is unreliable because it might
// fail for another reason than the expected one.
assert!(i2c_map.transfer(&mut reqs).is_err());
// I2C_SMBUS_READ (I2C_SMBUS_WORD_DATA) failure operation
reqs.push(I2cReq {
addr: 0x3,
flags: 0,
len: 1,
buf: [34].to_vec(),
});
reqs.push(I2cReq {
addr: 0x3,
// Will cause failure
flags: 0,
len: 2,
buf: [3, 4].to_vec(),
});
assert_results(&mut i2c_map, &mut reqs, true, true);
let mut reqs = vec![
I2cReq {
addr: 0x3,
flags: 0,
len: 1,
buf: [34].to_vec(),
},
I2cReq {
addr: 0x3,
// Will cause failure
flags: 0,
len: 2,
buf: [3, 4].to_vec(),
},
];
assert!(i2c_map.transfer(&mut reqs).is_err());
// I2C_SMBUS_READ (I2C_SMBUS_WORD_DATA) failure operation
reqs.push(I2cReq {
addr: 0x3,
flags: 0,
// Will cause failure
len: 2,
buf: [3, 4].to_vec(),
});
reqs.push(I2cReq {
addr: 0x3,
flags: 1,
len: 2,
buf: [3, 4].to_vec(),
});
assert_results(&mut i2c_map, &mut reqs, true, true);
let mut reqs = vec![
I2cReq {
addr: 0x3,
flags: 0,
// Will cause failure
len: 2,
buf: [3, 4].to_vec(),
},
I2cReq {
addr: 0x3,
flags: 1,
len: 2,
buf: [3, 4].to_vec(),
},
];
assert!(i2c_map.transfer(&mut reqs).is_err());
// I2C_SMBUS_READ (I2C_SMBUS_WORD_DATA) failure operation
reqs.push(I2cReq {
addr: 0x3,
flags: 0,
len: 1,
buf: [34].to_vec(),
});
reqs.push(I2cReq {
addr: 0x3,
flags: 1,
// Will cause failure
len: 3,
buf: [3, 4, 5].to_vec(),
});
assert_results(&mut i2c_map, &mut reqs, true, true);
let mut reqs = vec![
I2cReq {
addr: 0x3,
flags: 0,
len: 1,
buf: [34].to_vec(),
},
I2cReq {
addr: 0x3,
flags: 1,
// Will cause failure
len: 3,
buf: [3, 4, 5].to_vec(),
},
];
assert!(i2c_map.transfer(&mut reqs).is_err());
}
}

View File

@ -8,75 +8,150 @@
mod i2c;
mod vhu_i2c;
use clap::{load_yaml, App, ArgMatches};
use i2c::{I2cAdapter, I2cAdapterTrait, I2cMap};
use std::convert::TryFrom;
use std::sync::{Arc, RwLock};
use std::thread::spawn;
use clap::{load_yaml, App, ArgMatches};
use vhost::{vhost_user, vhost_user::Listener};
use vhost_user_backend::VhostUserDaemon;
use vhu_i2c::VhostUserI2cBackend;
use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap};
fn start_daemon<T: I2cAdapterTrait>(
backend: Arc<RwLock<VhostUserI2cBackend<T>>>,
listener: Listener,
) -> bool {
let mut daemon = VhostUserDaemon::new(
String::from("vhost-device-i2c-backend"),
backend.clone(),
GuestMemoryAtomic::new(GuestMemoryMmap::new()),
)
.unwrap();
use i2c::{I2cDevice, I2cMap, PhysDevice, MAX_I2C_VDEV};
use vhu_i2c::VhostUserI2cBackend;
daemon.start(listener).unwrap();
#[derive(Debug, PartialEq)]
struct DeviceConfig {
adapter_no: u32,
addr: Vec<u16>,
}
match daemon.wait() {
Ok(()) => {
println!("Stopping cleanly.");
}
Err(vhost_user_backend::Error::HandleRequest(vhost_user::Error::PartialMessage)) => {
println!("vhost-user connection closed with partial message. If the VM is shutting down, this is expected behavior; otherwise, it might be a bug.");
}
Err(e) => {
println!("Error running daemon: {:?}", e);
impl DeviceConfig {
fn new(adapter_no: u32) -> Self {
DeviceConfig {
adapter_no,
addr: Vec::new(),
}
}
// No matter the result, we need to shut down the worker thread.
backend
.read()
.unwrap()
.exit_event
.write(1)
.expect("Shutting down worker thread");
fn push(&mut self, addr: u16) -> std::result::Result<(), String> {
if addr as usize > MAX_I2C_VDEV {
return Err(format!("Invalid address: {} (> maximum allowed)", addr));
}
false
if self.addr.contains(&addr) {
return Err(format!("Address already in use: {}", addr));
}
self.addr.push(addr);
Ok(())
}
}
fn start_backend<T: I2cAdapterTrait>(
cmd_args: ArgMatches,
start_daemon: fn(Arc<RwLock<VhostUserI2cBackend<T>>>, Listener) -> bool,
#[derive(Debug, PartialEq)]
pub(crate) struct AdapterConfig {
inner: Vec<DeviceConfig>,
}
impl AdapterConfig {
fn new() -> Self {
Self { inner: Vec::new() }
}
fn contains_adapter_no(&self, adapter_no: u32) -> bool {
self.inner.iter().any(|elem| elem.adapter_no == adapter_no)
}
fn contains_addr(&self, addr: u16) -> bool {
self.inner.iter().any(|elem| elem.addr.contains(&addr))
}
fn push(&mut self, device: DeviceConfig) -> std::result::Result<(), String> {
if self.contains_adapter_no(device.adapter_no) {
return Err("Duplicated adapter number".to_string());
}
for addr in device.addr.iter() {
if self.contains_addr(*addr) {
return Err(format!("Address already in use: {}", addr));
}
}
self.inner.push(device);
Ok(())
}
}
impl TryFrom<&str> for AdapterConfig {
type Error = String;
fn try_from(list: &str) -> Result<Self, Self::Error> {
let busses: Vec<&str> = list.split(',').collect();
let mut devices = AdapterConfig::new();
for businfo in busses.iter() {
let list: Vec<&str> = businfo.split(':').collect();
let bus_addr = list[0].parse::<u32>().map_err(|_| "Invalid bus address")?;
let mut adapter = DeviceConfig::new(bus_addr);
for device_str in list[1..].iter() {
let addr = device_str
.parse::<u16>()
.map_err(|_| "Invalid device addr: {}")?;
adapter.push(addr)?;
}
devices.push(adapter)?;
}
Ok(devices)
}
}
#[derive(PartialEq, Debug)]
struct I2cConfiguration {
socket_path: String,
socket_count: usize,
devices: AdapterConfig,
}
impl TryFrom<ArgMatches> for I2cConfiguration {
type Error = String;
fn try_from(cmd_args: ArgMatches) -> Result<Self, Self::Error> {
let socket_path = cmd_args
.value_of("socket_path")
.ok_or("Invalid socket path")?
.to_string();
let socket_count = cmd_args
.value_of("socket_count")
.unwrap_or("1")
.parse::<usize>()
.map_err(|_| "Invalid socket_count")?;
let list = cmd_args.value_of("devices").ok_or("Invalid devices list")?;
let devices = AdapterConfig::try_from(list)?;
Ok(I2cConfiguration {
socket_path,
socket_count,
devices,
})
}
}
fn start_backend<D: 'static + I2cDevice + Send + Sync>(
config: I2cConfiguration,
) -> Result<(), String> {
// The same i2c_map structure instance is shared between all the guests
let i2c_map = Arc::new(
I2cMap::<D>::new(&config.devices)
.map_err(|e| format!("Failed to create i2c_map ({})", e))?,
);
let mut handles = Vec::new();
let path = cmd_args
.value_of("socket_path")
.ok_or("Invalid socket path")?;
let count = cmd_args
.value_of("socket_count")
.unwrap_or("1")
.parse::<u32>()
.map_err(|_| "Invalid socket_count")?;
let list = cmd_args.value_of("devices").ok_or("Invalid devices list")?;
// The same i2c_map structure instance is shared between all the guests
let i2c_map =
Arc::new(I2cMap::<T>::new(list).map_err(|e| format!("Failed to create i2c_map ({})", e))?);
for i in 0..count {
let socket = path.to_owned() + &i.to_string();
for i in 0..config.socket_count {
let socket = config.socket_path.to_owned() + &i.to_string();
let i2c_map = i2c_map.clone();
let handle = spawn(move || loop {
@ -85,9 +160,36 @@ fn start_backend<T: I2cAdapterTrait>(
));
let listener = Listener::new(socket.clone(), true).unwrap();
if start_daemon(backend, listener) {
break;
let mut daemon = VhostUserDaemon::new(
String::from("vhost-device-i2c-backend"),
backend.clone(),
GuestMemoryAtomic::new(GuestMemoryMmap::new()),
)
.unwrap();
daemon.start(listener).unwrap();
match daemon.wait() {
Ok(()) => {
println!("Stopping cleanly.");
}
Err(vhost_user_backend::Error::HandleRequest(
vhost_user::Error::PartialMessage,
)) => {
println!("vhost-user connection closed with partial message. If the VM is shutting down, this is expected behavior; otherwise, it might be a bug.");
}
Err(e) => {
println!("Error running daemon: {:?}", e);
}
}
// No matter the result, we need to shut down the worker thread.
backend
.read()
.unwrap()
.exit_event
.write(1)
.expect("Shutting down worker thread");
});
handles.push(handle);
@ -104,13 +206,25 @@ fn main() -> Result<(), String> {
let yaml = load_yaml!("cli.yaml");
let cmd_args = App::from(yaml).get_matches();
start_backend::<I2cAdapter>(cmd_args, start_daemon)
let config = I2cConfiguration::try_from(cmd_args).unwrap();
start_backend::<PhysDevice>(config)
}
#[cfg(test)]
mod tests {
use super::*;
use i2c::tests::I2cMockAdapter;
impl DeviceConfig {
pub fn new_with(adapter_no: u32, addr: Vec<u16>) -> Self {
DeviceConfig { adapter_no, addr }
}
}
impl AdapterConfig {
pub fn new_with(devices: Vec<DeviceConfig>) -> Self {
AdapterConfig { inner: devices }
}
}
fn get_cmd_args(name: &str, devices: &str, count: u32) -> ArgMatches {
let yaml = load_yaml!("cli.yaml");
@ -133,34 +247,34 @@ mod tests {
}
}
fn mock_start_daemon<T: I2cAdapterTrait>(
_backend: Arc<RwLock<VhostUserI2cBackend<T>>>,
_listener: Listener,
) -> bool {
true
}
#[test]
fn test_backend_single() {
let cmd_args = get_cmd_args("vi2c.sock_single", "1:4,2:32:21,5:5:23", 0);
assert!(start_backend::<I2cMockAdapter>(cmd_args, mock_start_daemon).is_ok());
}
#[test]
fn test_backend_multiple() {
let cmd_args = get_cmd_args("vi2c.sock", "1:4,2:32:21,5:5:23", 5);
assert!(start_backend::<I2cMockAdapter>(cmd_args, mock_start_daemon).is_ok());
}
#[test]
fn test_backend_failure() {
fn test_parse_failure() {
let cmd_args = get_cmd_args("vi2c.sock_failure", "1:4d", 5);
assert!(start_backend::<I2cMockAdapter>(cmd_args, mock_start_daemon).is_err());
// TODO: Check against the actual error instead of `is_err`.
assert!(I2cConfiguration::try_from(cmd_args).is_err());
let cmd_args = get_cmd_args("vi2c.sock_duplicate", "1:4,2:32:21,5:4:23", 5);
// TODO: Check against the actual error instead of `is_err`.
assert!(I2cConfiguration::try_from(cmd_args).is_err());
}
#[test]
fn test_backend_failure_duplicate_device4() {
let cmd_args = get_cmd_args("vi2c.sock_duplicate", "1:4,2:32:21,5:4:23", 5);
assert!(start_backend::<I2cMockAdapter>(cmd_args, mock_start_daemon).is_err());
fn test_parse_successful() {
let cmd_args = get_cmd_args("vi2c.sock_single", "1:4,2:32:21,5:5:23", 5);
let config = I2cConfiguration::try_from(cmd_args).unwrap();
let expected_devices = AdapterConfig::new_with(vec![
DeviceConfig::new_with(1, vec![4]),
DeviceConfig::new_with(2, vec![32, 21]),
DeviceConfig::new_with(5, vec![5, 23]),
]);
let expected_config = I2cConfiguration {
socket_count: 5,
socket_path: String::from("vi2c.sock_single"),
devices: expected_devices,
};
assert_eq!(config, expected_config);
}
}

View File

@ -5,10 +5,10 @@
//
// SPDX-License-Identifier: Apache-2.0
use crate::i2c::*;
use std::mem::size_of;
use std::sync::Arc;
use std::{convert, error, fmt, io};
use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
use vhost_user_backend::{VhostUserBackendMut, VringRwLock, VringT};
use virtio_bindings::bindings::virtio_net::{VIRTIO_F_NOTIFY_ON_EMPTY, VIRTIO_F_VERSION_1};
@ -18,6 +18,8 @@ use virtio_bindings::bindings::virtio_ring::{
use vm_memory::{ByteValued, Bytes, GuestMemoryAtomic, GuestMemoryMmap, Le16, Le32};
use vmm_sys_util::eventfd::{EventFd, EFD_NONBLOCK};
use crate::i2c::*;
const QUEUE_SIZE: usize = 1024;
const NUM_QUEUES: usize = 1;
@ -84,15 +86,15 @@ struct VirtioI2cInHdr {
}
unsafe impl ByteValued for VirtioI2cInHdr {}
pub struct VhostUserI2cBackend<A: I2cAdapterTrait> {
i2c_map: Arc<I2cMap<A>>,
pub struct VhostUserI2cBackend<D: I2cDevice> {
i2c_map: Arc<I2cMap<D>>,
event_idx: bool,
mem: Option<GuestMemoryAtomic<GuestMemoryMmap>>,
pub exit_event: EventFd,
}
impl<A: I2cAdapterTrait> VhostUserI2cBackend<A> {
pub fn new(i2c_map: Arc<I2cMap<A>>) -> Result<Self> {
impl<D: I2cDevice> VhostUserI2cBackend<D> {
pub fn new(i2c_map: Arc<I2cMap<D>>) -> Result<Self> {
Ok(VhostUserI2cBackend {
i2c_map,
event_idx: false,
@ -220,7 +222,9 @@ impl<A: I2cAdapterTrait> VhostUserI2cBackend<A> {
}
/// VhostUserBackendMut trait methods
impl<A: I2cAdapterTrait> VhostUserBackendMut<VringRwLock, ()> for VhostUserI2cBackend<A> {
impl<D: 'static + I2cDevice + Sync + Send> VhostUserBackendMut<VringRwLock, ()>
for VhostUserI2cBackend<D>
{
fn num_queues(&self) -> usize {
NUM_QUEUES
}
@ -303,11 +307,14 @@ impl<A: I2cAdapterTrait> VhostUserBackendMut<VringRwLock, ()> for VhostUserI2cBa
#[cfg(test)]
mod tests {
use super::*;
use crate::i2c::tests::I2cMockAdapter;
use crate::i2c::tests::DummyDevice;
use crate::AdapterConfig;
use std::convert::TryFrom;
#[test]
fn verify_backend() {
let i2c_map: I2cMap<I2cMockAdapter> = I2cMap::new("1:4,2:32:21,5:10:23").unwrap();
let device_config = AdapterConfig::try_from("1:4,2:32:21,5:10:23").unwrap();
let i2c_map: I2cMap<DummyDevice> = I2cMap::new(&device_config).unwrap();
let mut backend = VhostUserI2cBackend::new(Arc::new(i2c_map)).unwrap();
assert_eq!(backend.num_queues(), NUM_QUEUES);