diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..9242376 --- /dev/null +++ b/Cargo.lock @@ -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", +] diff --git a/coverage_config_x86_64.json b/coverage_config_x86_64.json index 0ed9680..d8b86b0 100644 --- a/coverage_config_x86_64.json +++ b/coverage_config_x86_64.json @@ -1,5 +1,5 @@ { - "coverage_score": 40.6, + "coverage_score": 49.4, "exclude_path": "", "crate_features": "" } diff --git a/src/i2c/Cargo.toml b/src/i2c/Cargo.toml index 399440b..0d18cb6 100644 --- a/src/i2c/Cargo.toml +++ b/src/i2c/Cargo.toml @@ -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" diff --git a/src/i2c/src/i2c.rs b/src/i2c/src/i2c.rs index 103a3da..47baa4c 100644 --- a/src/i2c/src/i2c.rs +++ b/src/i2c/src/i2c.rs @@ -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, } -/// I2C adapter and helpers -pub trait I2cAdapterTrait: Send + Sync + 'static { - fn new(bus: &str) -> Result +/// 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 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 { + 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 { + device: D, + adapter_no: u32, + smbus: bool, +} + +impl I2cAdapter { + // Creates a new adapter corresponding to the specified number. + fn new(adapter_no: u32) -> Result> { + 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 { - let i2cdev = String::from("/dev/i2c-") + bus; - - Ok(I2cAdapter { - bus: bus.parse::().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 { - adapters: Vec, +pub struct I2cMap { + adapters: Vec>, device_map: [u32; MAX_I2C_VDEV], } -impl I2cMap { - pub fn new(list: &str) -> Result +impl I2cMap { + pub(crate) fn new(device_config: &AdapterConfig) -> Result where Self: Sized, { let mut device_map: [u32; MAX_I2C_VDEV] = [I2C_INVALID_ADAPTER; MAX_I2C_VDEV]; - let mut adapters: Vec = Vec::new(); - let busses: Vec<&str> = list.split(',').collect(); + let mut adapters: Vec> = 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::().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 I2cMap { 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 I2cMap { 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 { - Ok(I2cMockAdapter { - bus: bus.parse::().map_err(|_| Error::new(EINVAL))?, - smbus: false, - result: Ok(()), - }) + impl I2cDevice for DummyDevice { + fn open(_device_path: String) -> Result + 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, - reqs: &mut Vec, - 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::::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 = 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 = 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 = I2cMap::new("1:3").unwrap(); + let adapter_config = AdapterConfig::try_from("1:3").unwrap(); + let mut i2c_map: I2cMap = I2cMap::new(&adapter_config).unwrap(); + i2c_map.adapters[0].smbus = false; let mut reqs: Vec = 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 = I2cMap::new("1:3").unwrap(); + let adapter_config = AdapterConfig::try_from("1:3").unwrap(); + let mut i2c_map: I2cMap = I2cMap::new(&adapter_config).unwrap(); + i2c_map.adapters[0].smbus = true; let mut reqs: Vec = 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 = I2cMap::new("1:3").unwrap(); + let adapter_config = AdapterConfig::try_from("1:3").unwrap(); + let mut i2c_map: I2cMap = I2cMap::new(&adapter_config).unwrap(); i2c_map.adapters[0].smbus = true; let mut reqs: Vec = 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()); } } diff --git a/src/i2c/src/main.rs b/src/i2c/src/main.rs index 856aae3..b5d1bf7 100644 --- a/src/i2c/src/main.rs +++ b/src/i2c/src/main.rs @@ -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( - backend: Arc>>, - 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, +} - 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( - cmd_args: ArgMatches, - start_daemon: fn(Arc>>, Listener) -> bool, +#[derive(Debug, PartialEq)] +pub(crate) struct AdapterConfig { + inner: Vec, +} + +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 { + 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::().map_err(|_| "Invalid bus address")?; + let mut adapter = DeviceConfig::new(bus_addr); + + for device_str in list[1..].iter() { + let addr = device_str + .parse::() + .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 for I2cConfiguration { + type Error = String; + + fn try_from(cmd_args: ArgMatches) -> Result { + 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::() + .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( + config: I2cConfiguration, ) -> Result<(), String> { + // The same i2c_map structure instance is shared between all the guests + let i2c_map = Arc::new( + I2cMap::::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::() - .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::::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( )); 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::(cmd_args, start_daemon) + let config = I2cConfiguration::try_from(cmd_args).unwrap(); + start_backend::(config) } #[cfg(test)] mod tests { use super::*; - use i2c::tests::I2cMockAdapter; + + impl DeviceConfig { + pub fn new_with(adapter_no: u32, addr: Vec) -> Self { + DeviceConfig { adapter_no, addr } + } + } + + impl AdapterConfig { + pub fn new_with(devices: Vec) -> 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( - _backend: Arc>>, - _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::(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::(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::(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::(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); } } diff --git a/src/i2c/src/vhu_i2c.rs b/src/i2c/src/vhu_i2c.rs index cac7368..14e8540 100644 --- a/src/i2c/src/vhu_i2c.rs +++ b/src/i2c/src/vhu_i2c.rs @@ -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 { - i2c_map: Arc>, +pub struct VhostUserI2cBackend { + i2c_map: Arc>, event_idx: bool, mem: Option>, pub exit_event: EventFd, } -impl VhostUserI2cBackend { - pub fn new(i2c_map: Arc>) -> Result { +impl VhostUserI2cBackend { + pub fn new(i2c_map: Arc>) -> Result { Ok(VhostUserI2cBackend { i2c_map, event_idx: false, @@ -220,7 +222,9 @@ impl VhostUserI2cBackend { } /// VhostUserBackendMut trait methods -impl VhostUserBackendMut for VhostUserI2cBackend { +impl VhostUserBackendMut + for VhostUserI2cBackend +{ fn num_queues(&self) -> usize { NUM_QUEUES } @@ -303,11 +307,14 @@ impl VhostUserBackendMut 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 = 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 = I2cMap::new(&device_config).unwrap(); let mut backend = VhostUserI2cBackend::new(Arc::new(i2c_map)).unwrap(); assert_eq!(backend.num_queues(), NUM_QUEUES);