From 5d11c40dce7117584ca247e8adfb7302442177be Mon Sep 17 00:00:00 2001 From: Haixu Cui Date: Wed, 27 Mar 2024 15:22:15 +0800 Subject: [PATCH] vhost-device-spi: Add initial implementation This program is a vhost-user backend that emulates a VirtIO SPI bus. This program takes the layout of the spi bus and its devices on the host OS and then talks to them via the /dev/spidevX.Y interface when a request comes from the guest OS for a SPI device. The implementation corresponds with the specification: https://github.com/oasis-tcs/virtio-spec/tree/virtio-1.4/device-types/spi Signed-off-by: Haixu Cui --- Cargo.toml | 1 + README.md | 1 + vhost-device-spi/CHANGELOG.md | 15 + vhost-device-spi/Cargo.toml | 33 + vhost-device-spi/LICENSE-APACHE | 1 + vhost-device-spi/LICENSE-BSD-3-Clause | 1 + vhost-device-spi/README.md | 52 ++ vhost-device-spi/src/main.rs | 173 ++++ vhost-device-spi/src/spi.rs | 1006 +++++++++++++++++++++++ vhost-device-spi/src/vhu_spi.rs | 1053 +++++++++++++++++++++++++ 10 files changed, 2336 insertions(+) create mode 100644 vhost-device-spi/CHANGELOG.md create mode 100644 vhost-device-spi/Cargo.toml create mode 120000 vhost-device-spi/LICENSE-APACHE create mode 120000 vhost-device-spi/LICENSE-BSD-3-Clause create mode 100644 vhost-device-spi/README.md create mode 100644 vhost-device-spi/src/main.rs create mode 100644 vhost-device-spi/src/spi.rs create mode 100644 vhost-device-spi/src/vhu_spi.rs diff --git a/Cargo.toml b/Cargo.toml index bf2d33ef3..5a466388a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,5 @@ members = [ "vhost-device-sound", "vhost-device-template", "vhost-device-vsock", + "vhost-device-spi", ] diff --git a/README.md b/README.md index 245c6659b..46e23a723 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Here is the list of device backends that we support: - [SCSI](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-scsi/README.md) - [Sound](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-sound/README.md) - [VSOCK](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-vsock/README.md) +- [SPI](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-spi/README.md) The vhost-device workspace also provides a [template](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-template/README.md) diff --git a/vhost-device-spi/CHANGELOG.md b/vhost-device-spi/CHANGELOG.md new file mode 100644 index 000000000..51d3f040d --- /dev/null +++ b/vhost-device-spi/CHANGELOG.md @@ -0,0 +1,15 @@ +# Changelog +## [Unreleased] + +### Added + +### Changed + +### Fixed + +### Deprecated + +## [0.1.0] + +First release + diff --git a/vhost-device-spi/Cargo.toml b/vhost-device-spi/Cargo.toml new file mode 100644 index 000000000..fcd7506d2 --- /dev/null +++ b/vhost-device-spi/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "vhost-device-spi" +version = "0.1.0" +authors = ["Haixu Cui "] +description = "vhost spi backend device" +repository = "https://github.com/rust-vmm/vhost-device" +readme = "README.md" +keywords = ["spi", "vhost", "virt", "backend"] +license = "Apache-2.0 OR BSD-3-Clause" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +xen = ["vm-memory/xen", "vhost/xen", "vhost-user-backend/xen"] + +[dependencies] +clap = { version = "4.5", features = ["derive"] } +env_logger = "0.11" +libc = "0.2" +log = "0.4" +thiserror = "1.0" +vhost = { version = "0.11", features = ["vhost-user-backend"] } +vhost-user-backend = "0.14" +virtio-bindings = "0.2.2" +virtio-queue = "0.11" +vm-memory = "0.14.1" +vmm-sys-util = "0.12" + +[dev-dependencies] +assert_matches = "1.5" +virtio-queue = { version = "0.11", features = ["test-utils"] } +vm-memory = { version = "0.14.1", features = ["backend-mmap", "backend-atomic"] } diff --git a/vhost-device-spi/LICENSE-APACHE b/vhost-device-spi/LICENSE-APACHE new file mode 120000 index 000000000..965b606f3 --- /dev/null +++ b/vhost-device-spi/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/vhost-device-spi/LICENSE-BSD-3-Clause b/vhost-device-spi/LICENSE-BSD-3-Clause new file mode 120000 index 000000000..f2b079135 --- /dev/null +++ b/vhost-device-spi/LICENSE-BSD-3-Clause @@ -0,0 +1 @@ +../LICENSE-BSD-3-Clause \ No newline at end of file diff --git a/vhost-device-spi/README.md b/vhost-device-spi/README.md new file mode 100644 index 000000000..2e93fa384 --- /dev/null +++ b/vhost-device-spi/README.md @@ -0,0 +1,52 @@ +# vhost-device-spi - SPI emulation backend daemon + +## Description +This program is a vhost-user backend that emulates a VirtIO SPI bus. +This program takes the layout of the spi bus and its devices on the host +OS and then talks to them via the /dev/spidevX.Y interface when a request +comes from the guest OS for a SPI device. + +## Synopsis + +**vhost-device-spi** [*OPTIONS*] + +## Options + +.. program:: vhost-device-spi + +.. option:: -h, --help + + Print help. + +.. option:: -s, --socket-path=PATH + + Location of vhost-user Unix domain sockets, this path will be suffixed with + 0,1,2..socket_count-1. + +.. option:: -c, --socket-count=INT + + Number of guests (sockets) to attach to, default set to 1. + +.. option:: -l, --device=SPI-DEVICES + + Spi device full path at the host OS in the format: + /dev/spidevX.Y + + Here, + X: is spi controller's bus number. + Y: is chip select index. + +## Examples + +The daemon should be started first: + +:: + + host# vhost-device-spi --socket-path=vspi.sock --socket-count=1 --device "/dev/spidev0.0" + +## License + +This project is licensed under either of + +- [Apache License](http://www.apache.org/licenses/LICENSE-2.0), Version 2.0 +- [BSD-3-Clause License](https://opensource.org/licenses/BSD-3-Clause) diff --git a/vhost-device-spi/src/main.rs b/vhost-device-spi/src/main.rs new file mode 100644 index 000000000..e2ed3f9b6 --- /dev/null +++ b/vhost-device-spi/src/main.rs @@ -0,0 +1,173 @@ +// VIRTIO SPI Emulation via vhost-user +// +// Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. +// Haixu Cui +// +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + +mod spi; +mod vhu_spi; + +use log::error; +use std::process::exit; +use std::sync::{Arc, RwLock}; +use std::thread::{spawn, JoinHandle}; + +use clap::Parser; +use thiserror::Error as ThisError; +use vhost_user_backend::VhostUserDaemon; +use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; + +use spi::{PhysDevice, SpiController, SpiDevice}; +use vhu_spi::VhostUserSpiBackend; + +type Result = std::result::Result; + +#[derive(Debug, ThisError)] +/// Errors related to low level spi helpers +pub(crate) enum Error { + #[error("Invalid socket count: {0}")] + SocketCountInvalid(usize), + #[error("Low level Spi failure: {0:?}")] + SpiFailure(spi::Error), + #[error("Could not create backend: {0}")] + CouldNotCreateBackend(vhu_spi::Error), + #[error("Could not create daemon: {0}")] + CouldNotCreateDaemon(vhost_user_backend::Error), + #[error("Fatal error: {0}")] + ServeFailed(vhost_user_backend::Error), +} + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct SpiArgs { + /// Location of vhost-user Unix domain socket. This is suffixed by 0,1,2..socket_count-1. + #[clap(short, long)] + socket_path: String, + + /// Number of guests (sockets) to connect to. + #[clap(short = 'c', long, default_value_t = 1)] + socket_count: usize, + + /// SPI device full path + #[clap(short = 'l', long)] + device: String, +} + +#[derive(PartialEq, Debug)] +struct SpiConfiguration { + socket_path: String, + socket_count: usize, + device: String, +} + +impl SpiConfiguration { + fn try_from(args: SpiArgs) -> Result { + if args.socket_count == 0 { + return Err(Error::SocketCountInvalid(0)); + } + + Ok(SpiConfiguration { + socket_path: args.socket_path.trim().to_string(), + socket_count: args.socket_count, + device: args.device.trim().to_string(), + }) + } +} + +fn start_backend(args: SpiArgs) -> Result<()> { + let config = SpiConfiguration::try_from(args).unwrap(); + + let spi_dev = D::open(&config.device).map_err(Error::SpiFailure)?; + + let spi_ctrl = Arc::new(SpiController::::new(spi_dev).map_err(Error::SpiFailure)?); + + let mut handles = Vec::new(); + + for i in 0..config.socket_count { + let socket = config.socket_path.to_owned() + &i.to_string(); + let spi_ctrl = spi_ctrl.clone(); + + let handle: JoinHandle> = spawn(move || loop { + // A separate thread is spawned for each socket and can connect to a separate guest. + // These are run in an infinite loop to not require the daemon to be restarted once a + // guest exits. + // + // There isn't much value in complicating code here to return an error from the + // threads, and so the code uses unwrap() instead. The panic on a thread won't cause + // trouble to other threads/guests or the main() function and should be safe for the + // daemon. + let backend = Arc::new(RwLock::new( + VhostUserSpiBackend::new(spi_ctrl.clone()).map_err(Error::CouldNotCreateBackend)?, + )); + + let mut daemon = VhostUserDaemon::new( + String::from("vhost-device-spi-backend"), + backend.clone(), + GuestMemoryAtomic::new(GuestMemoryMmap::new()), + ) + .map_err(Error::CouldNotCreateDaemon)?; + + daemon.serve(&socket).map_err(Error::ServeFailed)?; + }); + + handles.push(handle); + } + + for handle in handles { + handle.join().map_err(std::panic::resume_unwind).unwrap()?; + } + + Ok(()) +} + +fn main() { + env_logger::init(); + + if let Err(e) = start_backend::(SpiArgs::parse()) { + error!("{e}"); + exit(1); + } +} + +#[cfg(test)] +pub(crate) mod tests { + use assert_matches::assert_matches; + + use super::*; + use crate::spi::tests::DummyDevice; + + impl SpiArgs { + fn from_args(path: &str, device: &str, count: usize) -> SpiArgs { + SpiArgs { + socket_path: path.to_string(), + socket_count: count, + device: device.to_string(), + } + } + } + + #[test] + fn test_parse_failure() { + let socket_name = "vspi.sock"; + + // Zero socket count + let cmd_args = SpiArgs::from_args(socket_name, "spidev0.0", 0); + assert_matches!( + SpiConfiguration::try_from(cmd_args).unwrap_err(), + Error::SocketCountInvalid(0) + ); + } + + #[test] + fn test_fail_listener() { + // This will fail the listeners and thread will panic. + let socket_name = "~/path/not/present/spi"; + let cmd_args = SpiArgs::from_args(socket_name, "spidev0.0", 1); + + assert_matches!( + start_backend::(cmd_args).unwrap_err(), + Error::ServeFailed(_) + ); + } +} diff --git a/vhost-device-spi/src/spi.rs b/vhost-device-spi/src/spi.rs new file mode 100644 index 000000000..76df5cdf4 --- /dev/null +++ b/vhost-device-spi/src/spi.rs @@ -0,0 +1,1006 @@ +// Low level SPI definitions +// +// Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. +// Haixu Cui +// +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + +use std::fs::{File, OpenOptions}; +use std::os::unix::io::AsRawFd; + +use libc::{c_ulong, ioctl}; +use thiserror::Error as ThisError; +use vmm_sys_util::errno::Error as IoError; + +use std::convert::From; +use std::ptr; +use vm_memory::{ByteValued, Le32}; + +type Result = std::result::Result; + +#[derive(Copy, Clone, Debug, PartialEq, ThisError)] +/// Errors related to low level spi helpers +pub(crate) enum Error { + #[error("Ioctl command failed for {0} operation: {1}")] + IoctlFailure(&'static str, IoError), + #[error("Failed to open spi controller")] + DeviceOpenFailed, +} + +/// Linux SPI definitions +/// IOCTL commands, refer Linux's Documentation/spi/spidev.rst for further details. +const _IOC_SIZEBITS: u32 = 14; +const _IOC_SIZESHIFT: u32 = 16; +const SPI_IOC_MESSAGE_BASE: c_ulong = 0x40006b00; +const SPI_IOC_RD_MODE32: c_ulong = 0x80046b05; +const SPI_IOC_WR_MODE32: c_ulong = 0x40046b05; +const SPI_IOC_RD_MAX_SPEED_HZ: c_ulong = 0x80046b04; +const SPI_IOC_WR_MAX_SPEED_HZ: c_ulong = 0x40046b04; +const SPI_IOC_RD_BITS_PER_WORD: c_ulong = 0x80016b03; +const SPI_IOC_WR_BITS_PER_WORD: c_ulong = 0x40016b03; + +// Corresponds to the SPI_IOC_MESSAGE macro in Linux +fn spi_ioc_message(n: u32) -> c_ulong { + let mut size: u32 = 0; + if n * 32 < (1 << _IOC_SIZEBITS) { + size = n * 32; + } + (SPI_IOC_MESSAGE_BASE | ((size as c_ulong) << _IOC_SIZESHIFT)) as c_ulong +} + +/// Linux SPI mode, prefix with "LNX_" +/// refer to Linux include/uapi/linux/spi/spi.h +const LNX_SPI_CPHA: u32 = 1 << 0; +const LNX_SPI_CPOL: u32 = 1 << 1; +const LNX_SPI_CS_HIGH: u32 = 1 << 2; +const LNX_SPI_LSB_FIRST: u32 = 1 << 3; +const LNX_SPI_LOOP: u32 = 1 << 5; +const LNX_SPI_TX_DUAL: u32 = 1 << 8; +const LNX_SPI_TX_QUAD: u32 = 1 << 9; +const LNX_SPI_TX_OCTAL: u32 = 1 << 13; +const LNX_SPI_RX_DUAL: u32 = 1 << 10; +const LNX_SPI_RX_QUAD: u32 = 1 << 11; +const LNX_SPI_RX_OCTAL: u32 = 1 << 14; + +/// Config space supported mode mask +const CONFIG_SPACE_TRANS_DUAL: u8 = 0x1; +const CONFIG_SPACE_TRANS_QUAD: u8 = 0x2; +const CONFIG_SPACE_TRANS_OCTAL: u8 = 0x4; +const CONFIG_SPACE_CPHA_0: u32 = 0x1; +const CONFIG_SPACE_CPHA_1: u32 = 0x2; +const CONFIG_SPACE_CPOL_0: u32 = 0x4; +const CONFIG_SPACE_CPOL_1: u32 = 0x8; +const CONFIG_SPACE_CS_HIGH: u32 = 0x10; +const CONFIG_SPACE_LSB: u32 = 0x20; +const CONFIG_SPACE_LOOP: u32 = 0x40; + +/// Mode setting in Requests +pub const SPI_CPHA: u32 = 1 << 0; +pub const SPI_CPOL: u32 = 1 << 1; +pub const SPI_CS_HIGH: u32 = 1 << 2; +pub const SPI_LSB_FIRST: u32 = 1 << 3; +pub const SPI_LOOP: u32 = 1 << 4; + +/// Copied (partially) from Linux's include/uapi/linux/spi/spidev.h +/// +/// SpiIocTransfer - describes a single SPI transfer +/// +/// @tx_buf: Holds pointer to userspace buffer with transmit data, or null. +/// +/// @rx_buf: Holds pointer to userspace buffer for receive data, or null. +/// +/// @len: Length of tx and rx buffers, in bytes. +/// +/// @speed_hz: Temporary override of the device's bitrate. +/// +/// @bits_per_word: Temporary override of the device's wordsize. +/// +/// @delay_usecs: If nonzero, how long to delay after the last bit transfer +/// before optionally deselecting the device before the next transfer. +/// +/// @cs_change: True to deselect device before starting the next transfer. +/// +/// @word_delay_usecs: If nonzero, how long to wait between words within one +/// transfer. This property needs explicit support in the SPI controller, +/// otherwise it is silently ignored. +/// +/// This structure is mapped directly to the kernel spi_transfer structure; +/// the fields have the same meanings, except of course that the pointers +/// are in a different address space (and may be of different sizes in some +/// cases, such as 32-bit i386 userspace over a 64-bit x86_64 kernel). +/// Zero-initialize the structure, including currently unused fields, to +/// accommodate potential future updates. + +#[derive(Debug)] +#[repr(C)] +pub(crate) struct SpiIocTransfer { + tx_buf: u64, + rx_buf: u64, + len: u32, + speed_hz: u32, + delay_usecs: u16, + bits_per_word: u8, + cs_change: u8, + tx_nbits: u8, + rx_nbits: u8, + word_delay_usecs: u8, + pad: u8, +} + +/// SPI definitions +pub(crate) struct SpiTransReq { + pub tx_buf: Vec, + pub rx_buf: Vec, + pub trans_len: u32, + pub speed_hz: u32, + pub mode: u32, + pub delay_usecs: u16, + pub bits_per_word: u8, + pub cs_change: u8, + pub tx_nbits: u8, + pub rx_nbits: u8, + pub word_delay_usecs: u8, + pub cs_id: u8, +} + +/// Virtio SPI Configuration +#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[repr(C)] +pub(crate) struct VirtioSpiConfig { + pub(crate) cs_max_number: u8, + pub(crate) cs_change_supported: u8, + pub(crate) tx_nbits_supported: u8, + pub(crate) rx_nbits_supported: u8, + pub(crate) bits_per_word_mask: Le32, + pub(crate) mode_func_supported: Le32, + pub(crate) max_freq_hz: Le32, + pub(crate) max_word_delay_ns: Le32, + pub(crate) max_cs_setup_ns: Le32, + pub(crate) max_cs_hold_ns: Le32, + pub(crate) max_cs_inactive_ns: Le32, +} + +// SAFETY: The layout of the structure is fixed and can be initialized by +// reading its content from byte array. +unsafe impl ByteValued for VirtioSpiConfig {} + +/// Trait that represents a SPI 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 SPI driver so that we can test the SPI +/// functionality without the need of a physical device. +pub(crate) trait SpiDevice { + // Open the device specified by the controller path. + fn open(spidev_path: &str) -> Result + where + Self: Sized; + + // Corresponds to the SPI_IOC_RD_MAX_SPEED_HZ ioctl call. + fn get_max_speed_hz(&self) -> Result; + + // Corresponds to the SPI_IOC_WR_MAX_SPEED_HZ ioctl call. + fn set_max_speed_hz(&self, max_speed_hz: u32) -> Result<()>; + + // Corresponds to the SPI_IOC_RD_BITS_PER_WORD ioctl call. + fn get_bits_per_word(&self) -> Result; + + // Corresponds to the SPI_IOC_WR_BITS_PER_WORD ioctl call. + fn set_bits_per_word(&self, bpw: u8) -> Result<()>; + + // Corresponds to the SPI_IOC_RD_MODE/SPI_IOC_RD_MODE32 ioctl call. + fn get_mode(&self) -> Result; + + // Corresponds to the SPI_IOC_WR_MODE/SPI_IOC_WR_MODE32 ioctl call. + fn set_mode(&self, mode: u32) -> Result<()>; + + // Corresponds to the default ioctl call. + fn rdwr(&self, reqs: &mut [SpiTransReq]) -> Result<()>; + + // Detect the spi controller supported mode and delay settings + fn detect_supported_features(&self) -> Result { + Ok(VirtioSpiConfig { + cs_max_number: 1, + cs_change_supported: 1, + tx_nbits_supported: 0, + rx_nbits_supported: 0, + bits_per_word_mask: From::from(0), + mode_func_supported: From::from(0xf), + max_freq_hz: From::from(0), + max_word_delay_ns: From::from(0), + max_cs_setup_ns: From::from(0), + max_cs_hold_ns: From::from(0), + max_cs_inactive_ns: From::from(0), + }) + } +} + +/// A physical SPI device. This structure can only be initialized on hosts +/// where `/dev/spidevX.Y` is available. +#[derive(Debug)] +pub(crate) struct PhysDevice { + file: File, +} + +impl SpiDevice for PhysDevice { + fn open(spidev_path: &str) -> Result { + Ok(PhysDevice { + file: OpenOptions::new() + .read(true) + .write(true) + .open(spidev_path) + .map_err(|_| Error::DeviceOpenFailed)?, + }) + } + + fn get_max_speed_hz(&self) -> Result { + let mut max_speed_hz: u32 = 0; + + // SAFETY: Safe as the file is a valid SPI controller. + let ret = unsafe { + ioctl( + self.file.as_raw_fd(), + SPI_IOC_RD_MAX_SPEED_HZ, + &mut max_speed_hz, + ) + }; + + if ret == -1 { + Err(Error::IoctlFailure("get_max_speed_hz", IoError::last())) + } else { + Ok(max_speed_hz) + } + } + + fn set_max_speed_hz(&self, max_speed_hz: u32) -> Result<()> { + // SAFETY: Safe as the file is a valid SPI controller. + let ret = unsafe { + ioctl( + self.file.as_raw_fd(), + SPI_IOC_WR_MAX_SPEED_HZ, + &max_speed_hz, + ) + }; + + if ret == -1 { + Err(Error::IoctlFailure("set_max_speed_hz", IoError::last())) + } else { + Ok(()) + } + } + + fn get_bits_per_word(&self) -> Result { + let mut bpw: u8 = 0; + + // SAFETY: Safe as the file is a valid SPI controller. + let ret = unsafe { ioctl(self.file.as_raw_fd(), SPI_IOC_RD_BITS_PER_WORD, &mut bpw) }; + + if ret == -1 { + Err(Error::IoctlFailure("get_bits_per_word", IoError::last())) + } else { + Ok(bpw) + } + } + + fn set_bits_per_word(&self, bpw: u8) -> Result<()> { + // SAFETY: Safe as the file is a valid SPI controller. + let ret = unsafe { ioctl(self.file.as_raw_fd(), SPI_IOC_WR_BITS_PER_WORD, &bpw) }; + + if ret == -1 { + Err(Error::IoctlFailure("set_bits_per_word", IoError::last())) + } else { + Ok(()) + } + } + + fn get_mode(&self) -> Result { + let mut mode: u32 = 0; + + // SAFETY: Safe as the file is a valid SPI controller. + let ret = unsafe { ioctl(self.file.as_raw_fd(), SPI_IOC_RD_MODE32, &mut mode) }; + + if ret == -1 { + Err(Error::IoctlFailure("get_mode", IoError::last())) + } else { + Ok(mode) + } + } + + fn set_mode(&self, mode: u32) -> Result<()> { + // SAFETY: Safe as the file is a valid SPI controller. + let ret = unsafe { ioctl(self.file.as_raw_fd(), SPI_IOC_WR_MODE32, &mode) }; + + if ret == -1 { + Err(Error::IoctlFailure("set_mode", IoError::last())) + } else { + Ok(()) + } + } + + fn rdwr(&self, reqs: &mut [SpiTransReq]) -> Result<()> { + let mut msgs: Vec = Vec::with_capacity(reqs.len()); + let len = reqs.len(); + let mut tx_buf_ptr: *mut u8; + let mut rx_buf_ptr: *mut u8; + + let saved_mode: u32 = self.get_mode()?; + let mut trans_mode: u32 = saved_mode; + + for req in reqs { + if req.tx_buf.is_empty() { + tx_buf_ptr = ptr::null_mut(); + } else { + tx_buf_ptr = req.tx_buf.as_mut_ptr(); + } + + if req.rx_buf.is_empty() { + rx_buf_ptr = ptr::null_mut(); + } else { + rx_buf_ptr = req.rx_buf.as_mut_ptr(); + } + + msgs.push(SpiIocTransfer { + tx_buf: tx_buf_ptr as u64, + rx_buf: rx_buf_ptr as u64, + len: req.trans_len, + speed_hz: req.speed_hz, + delay_usecs: req.delay_usecs, + bits_per_word: req.bits_per_word, + cs_change: req.cs_change, + tx_nbits: req.tx_nbits, + rx_nbits: req.rx_nbits, + word_delay_usecs: req.word_delay_usecs, + pad: 0, + }); + + if (req.mode & SPI_CPHA) == SPI_CPHA { + trans_mode |= LNX_SPI_CPHA; + } else { + trans_mode &= !LNX_SPI_CPHA; + } + if (req.mode & SPI_CPOL) == SPI_CPOL { + trans_mode |= LNX_SPI_CPOL; + } else { + trans_mode &= !LNX_SPI_CPOL; + } + if (req.mode & SPI_CS_HIGH) == SPI_CS_HIGH { + trans_mode |= LNX_SPI_CS_HIGH; + } else { + trans_mode &= !LNX_SPI_CS_HIGH; + } + if (req.mode & SPI_LSB_FIRST) == SPI_LSB_FIRST { + trans_mode |= LNX_SPI_LSB_FIRST; + } else { + trans_mode &= !LNX_SPI_LSB_FIRST; + } + if (req.mode & SPI_LOOP) == SPI_LOOP { + trans_mode |= LNX_SPI_LOOP; + } else { + trans_mode &= !LNX_SPI_LOOP; + } + } + + self.set_mode(trans_mode)?; + + // SAFETY: Safe as the file is a valid SPI controller. + let ret = unsafe { + ioctl( + self.file.as_raw_fd(), + spi_ioc_message(len as u32), + msgs.as_mut_ptr(), + ) + }; + + self.set_mode(saved_mode)?; + + if ret == -1 { + Err(Error::IoctlFailure("rdwr", IoError::last())) + } else { + Ok(()) + } + } + + fn detect_supported_features(&self) -> Result { + // supported cs_max_number 1 + // can't set cs timing from userland in Linux, reserve cs timing as 0 + // cs_change_supported always enabled, cause Linux can handle this in software + // max_word_delay_ns reserved as 0, also can't set from userland + + //detect max_speed_hz + let origin_speed: u32 = self.get_max_speed_hz()?; + + let max_speed_hz: u32 = match self.set_max_speed_hz(0) { + Err(_) => 0, + Ok(()) => self.get_max_speed_hz().unwrap_or(0), + }; + + self.set_max_speed_hz(origin_speed)?; + + //detect supported bpw + let mut bits_per_word_mask: u32 = 0; + + let origin_bpw: u8 = self.get_bits_per_word()?; + + match self.set_bits_per_word(64) { + Ok(()) => { + bits_per_word_mask = 0; + } + Err(_) => { + for bpw in 1..33 { + match self.set_bits_per_word(bpw) { + Ok(()) => { + bits_per_word_mask |= 1 << (bpw - 1); + } + Err(_) => { + bits_per_word_mask &= !(1 << (bpw - 1)); + } + }; + } + } + } + + self.set_bits_per_word(origin_bpw)?; + + //detect supported tx_nbit and rx_nbits + let mut tx_nbits_mask: u8 = 0; + let mut rx_nbits_mask: u8 = 0; + + let origin_mode = self.get_mode()?; + + let set_tx_dual: u32 = + (origin_mode | LNX_SPI_TX_DUAL) & !LNX_SPI_TX_QUAD & !LNX_SPI_TX_OCTAL; + let set_tx_quad: u32 = + (origin_mode | LNX_SPI_TX_QUAD) & !LNX_SPI_TX_DUAL & !LNX_SPI_TX_OCTAL; + let set_tx_octal: u32 = + (origin_mode | LNX_SPI_TX_OCTAL) & !LNX_SPI_TX_DUAL & !LNX_SPI_TX_QUAD; + let set_rx_dual: u32 = + (origin_mode | LNX_SPI_RX_DUAL) & !LNX_SPI_RX_QUAD & !LNX_SPI_RX_OCTAL; + let set_rx_quad: u32 = + (origin_mode | LNX_SPI_RX_QUAD) & !LNX_SPI_RX_DUAL & !LNX_SPI_RX_OCTAL; + let set_rx_octal: u32 = + (origin_mode | LNX_SPI_RX_OCTAL) & !LNX_SPI_RX_DUAL & !LNX_SPI_RX_QUAD; + + self.set_mode(set_tx_dual)?; + let get_tx_dual = self.get_mode()?; + if (get_tx_dual & LNX_SPI_TX_DUAL) == LNX_SPI_TX_DUAL { + tx_nbits_mask |= CONFIG_SPACE_TRANS_DUAL; + } + + self.set_mode(set_tx_quad)?; + let get_tx_quad = self.get_mode()?; + if (get_tx_quad & LNX_SPI_TX_QUAD) == LNX_SPI_TX_QUAD { + tx_nbits_mask |= CONFIG_SPACE_TRANS_QUAD; + } + + self.set_mode(set_tx_octal)?; + let get_tx_octal = self.get_mode()?; + if (get_tx_octal & LNX_SPI_TX_OCTAL) == LNX_SPI_TX_OCTAL { + tx_nbits_mask |= CONFIG_SPACE_TRANS_OCTAL; + } + + self.set_mode(set_rx_dual)?; + let get_rx_dual = self.get_mode()?; + if (get_rx_dual & LNX_SPI_RX_DUAL) == LNX_SPI_RX_DUAL { + rx_nbits_mask |= CONFIG_SPACE_TRANS_DUAL; + } + + self.set_mode(set_rx_quad)?; + let get_rx_quad = self.get_mode()?; + if (get_rx_quad & LNX_SPI_RX_QUAD) == LNX_SPI_RX_QUAD { + rx_nbits_mask |= CONFIG_SPACE_TRANS_QUAD; + } + + self.set_mode(set_rx_octal)?; + let get_rx_octal = self.get_mode()?; + if (get_rx_octal & LNX_SPI_RX_OCTAL) == LNX_SPI_RX_OCTAL { + rx_nbits_mask |= CONFIG_SPACE_TRANS_OCTAL; + } + + //detect supported CPHA setting + let mut mode_function_mask: u32 = 0; + + let mut set_cpha_mode = origin_mode; + let get_cpha_mode; + + if (origin_mode & LNX_SPI_CPHA) == LNX_SPI_CPHA { + mode_function_mask |= CONFIG_SPACE_CPHA_1; + set_cpha_mode &= !LNX_SPI_CPHA; + + match self.set_mode(set_cpha_mode) { + Err(_) => mode_function_mask &= !CONFIG_SPACE_CPHA_0, + Ok(()) => { + get_cpha_mode = self.get_mode()?; + if (get_cpha_mode & LNX_SPI_CPHA) == 0 { + mode_function_mask |= CONFIG_SPACE_CPHA_0; + } else { + mode_function_mask &= !CONFIG_SPACE_CPHA_0; + } + } + }; + } else { + mode_function_mask |= CONFIG_SPACE_CPHA_0; + set_cpha_mode |= LNX_SPI_CPHA; + + match self.set_mode(set_cpha_mode) { + Err(_) => mode_function_mask &= !CONFIG_SPACE_CPHA_1, + Ok(()) => { + get_cpha_mode = self.get_mode()?; + if (get_cpha_mode & LNX_SPI_CPHA) == LNX_SPI_CPHA { + mode_function_mask |= CONFIG_SPACE_CPHA_1; + } else { + mode_function_mask &= !CONFIG_SPACE_CPHA_1; + } + } + }; + } + + //detect supported CPOL setting + let mut set_cpol_mode = origin_mode; + let get_cpol_mode; + + if (origin_mode & LNX_SPI_CPOL) == LNX_SPI_CPOL { + mode_function_mask |= CONFIG_SPACE_CPOL_1; + + set_cpol_mode &= !LNX_SPI_CPOL; + + match self.set_mode(set_cpol_mode) { + Err(_) => mode_function_mask &= !CONFIG_SPACE_CPOL_0, + Ok(()) => { + get_cpol_mode = self.get_mode()?; + if (get_cpol_mode & LNX_SPI_CPOL) == 0 { + mode_function_mask |= CONFIG_SPACE_CPOL_0; + } else { + mode_function_mask &= !CONFIG_SPACE_CPOL_0; + } + } + }; + } else { + mode_function_mask |= CONFIG_SPACE_CPOL_0; + + set_cpol_mode |= LNX_SPI_CPOL; + + match self.set_mode(set_cpol_mode) { + Err(_) => mode_function_mask &= !CONFIG_SPACE_CPOL_1, + Ok(()) => { + get_cpol_mode = self.get_mode()?; + if (get_cpol_mode & LNX_SPI_CPOL) == LNX_SPI_CPOL { + mode_function_mask |= CONFIG_SPACE_CPOL_1; + } else { + mode_function_mask &= !CONFIG_SPACE_CPOL_1; + } + } + }; + } + + //detect supported CS_HIGH setting + let mut set_cs_high_mode = origin_mode; + let get_cs_high_mode; + + if (origin_mode & LNX_SPI_CS_HIGH) == LNX_SPI_CS_HIGH { + mode_function_mask |= CONFIG_SPACE_CS_HIGH; + } else { + set_cs_high_mode |= LNX_SPI_CS_HIGH; + match self.set_mode(set_cs_high_mode) { + Err(_) => mode_function_mask &= !CONFIG_SPACE_CS_HIGH, + Ok(()) => { + get_cs_high_mode = self.get_mode()?; + if (get_cs_high_mode & LNX_SPI_CS_HIGH) == LNX_SPI_CS_HIGH { + mode_function_mask |= CONFIG_SPACE_CS_HIGH; + } else { + mode_function_mask &= !CONFIG_SPACE_CS_HIGH; + } + } + }; + } + + //detect supported LSB setting + let mut set_lsb_mode = origin_mode; + let get_lsb_mode; + + if (origin_mode & LNX_SPI_LSB_FIRST) == LNX_SPI_LSB_FIRST { + mode_function_mask |= CONFIG_SPACE_LSB; + } else { + set_lsb_mode |= LNX_SPI_LSB_FIRST; + match self.set_mode(set_lsb_mode) { + Err(_) => mode_function_mask &= !CONFIG_SPACE_LSB, + Ok(()) => { + get_lsb_mode = self.get_mode()?; + if (get_lsb_mode & LNX_SPI_LSB_FIRST) == LNX_SPI_LSB_FIRST { + mode_function_mask |= CONFIG_SPACE_LSB; + } else { + mode_function_mask &= !CONFIG_SPACE_LSB; + } + } + }; + } + + //detect supported LOOP setting + let mut set_loop_mode = origin_mode; + let get_loop_mode; + + if (origin_mode & LNX_SPI_LOOP) == LNX_SPI_LOOP { + mode_function_mask |= CONFIG_SPACE_LOOP; + } else { + set_loop_mode |= LNX_SPI_LOOP; + match self.set_mode(set_loop_mode) { + Err(_) => mode_function_mask &= !CONFIG_SPACE_LOOP, + Ok(()) => { + get_loop_mode = self.get_mode()?; + if (get_loop_mode & LNX_SPI_LOOP) == LNX_SPI_LOOP { + mode_function_mask |= CONFIG_SPACE_LOOP; + } else { + mode_function_mask &= !CONFIG_SPACE_LOOP; + } + } + }; + } + + self.set_mode(origin_mode)?; + + Ok(VirtioSpiConfig { + cs_max_number: 1, + cs_change_supported: 1, + tx_nbits_supported: tx_nbits_mask, + rx_nbits_supported: rx_nbits_mask, + bits_per_word_mask: From::from(bits_per_word_mask), + mode_func_supported: From::from(mode_function_mask), + max_freq_hz: From::from(max_speed_hz), + max_word_delay_ns: From::from(0), + max_cs_setup_ns: From::from(0), + max_cs_hold_ns: From::from(0), + max_cs_inactive_ns: From::from(0), + }) + } +} + +#[derive(Debug)] +pub(crate) struct SpiController { + device: D, + config: VirtioSpiConfig, +} + +impl SpiController { + // Creates a new controller corresponding to `device`. + pub(crate) fn new(device: D) -> Result> { + let config: VirtioSpiConfig = device.detect_supported_features()?; + + Ok(SpiController { device, config }) + } + + pub(crate) fn config(&self) -> &VirtioSpiConfig { + &self.config + } + + pub(crate) fn transfer(&self, reqs: &mut [SpiTransReq]) -> Result<()> { + self.device.rdwr(reqs) + } + + pub(crate) fn check_trans_params(&self, trans_header: &mut SpiTransReq) -> bool { + let mut trans_params_valid: bool = true; + + if self.config.cs_max_number < trans_header.cs_id { + trans_params_valid = false; + } + + if (self.config.bits_per_word_mask != 0) + && ((1 << (trans_header.bits_per_word - 1)) + & self.config.bits_per_word_mask.to_native()) + == 0 + { + trans_params_valid = false; + } + + if ((self.config.tx_nbits_supported & CONFIG_SPACE_TRANS_DUAL) == 0 + && trans_header.tx_nbits == 2) + || ((self.config.tx_nbits_supported & CONFIG_SPACE_TRANS_QUAD) == 0 + && trans_header.tx_nbits == 4) + || ((self.config.tx_nbits_supported & CONFIG_SPACE_TRANS_OCTAL) == 0 + && trans_header.tx_nbits == 8) + || ((trans_header.tx_nbits != 0) + && (trans_header.tx_nbits != 1) + && (trans_header.tx_nbits != 2) + && (trans_header.tx_nbits != 4) + && (trans_header.tx_nbits != 8)) + { + trans_params_valid = false; + } + + if ((self.config.rx_nbits_supported & CONFIG_SPACE_TRANS_DUAL) == 0 + && trans_header.rx_nbits == 2) + || ((self.config.rx_nbits_supported & CONFIG_SPACE_TRANS_QUAD) == 0 + && trans_header.rx_nbits == 4) + || ((self.config.rx_nbits_supported & CONFIG_SPACE_TRANS_OCTAL) == 0 + && trans_header.rx_nbits == 8) + || ((trans_header.rx_nbits != 0) + && (trans_header.rx_nbits != 1) + && (trans_header.rx_nbits != 2) + && (trans_header.rx_nbits != 4) + && (trans_header.rx_nbits != 8)) + { + trans_params_valid = false; + } + + if (trans_header.mode & SPI_CPHA == 0) + && (self.config.mode_func_supported.to_native() & CONFIG_SPACE_CPHA_0 == 0) + { + trans_params_valid = false; + } + + if (trans_header.mode & SPI_CPHA == SPI_CPHA) + && (self.config.mode_func_supported.to_native() & CONFIG_SPACE_CPHA_1 == 0) + { + trans_params_valid = false; + } + + if (trans_header.mode & SPI_CPOL == 0) + && (self.config.mode_func_supported.to_native() & CONFIG_SPACE_CPOL_0 == 0) + { + trans_params_valid = false; + } + + if (trans_header.mode & SPI_CPOL == SPI_CPOL) + && (self.config.mode_func_supported.to_native() & CONFIG_SPACE_CPOL_1 == 0) + { + trans_params_valid = false; + } + + if (trans_header.mode & SPI_CS_HIGH == SPI_CS_HIGH) + && (self.config.mode_func_supported.to_native() & CONFIG_SPACE_CS_HIGH == 0) + { + trans_params_valid = false; + } + + if (trans_header.mode & SPI_LSB_FIRST == SPI_LSB_FIRST) + && (self.config.mode_func_supported.to_native() & CONFIG_SPACE_LSB == 0) + { + trans_params_valid = false; + } + + if (trans_header.mode & SPI_LOOP == SPI_LOOP) + && (self.config.mode_func_supported.to_native() & CONFIG_SPACE_LOOP == 0) + { + trans_params_valid = false; + } + + if (self.config.max_freq_hz != 0) + && (trans_header.speed_hz > self.config.max_freq_hz.to_native()) + { + trans_params_valid = false; + } + + trans_params_valid + } +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use vmm_sys_util::tempfile::TempFile; + + // Update read-buffer of each write-buffer with index + 1 value. + pub fn update_rdwr_buf(buf: &mut [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 { + get_mode_result: Result, + set_mode_result: Result<()>, + get_max_speed_result: Result, + set_max_speed_result: Result<()>, + get_bpw_result: Result, + set_bpw_result: Result<()>, + rdwr_result: Result<()>, + detect_supported_features_result: Result, + } + + impl Default for DummyDevice { + fn default() -> Self { + let default_config = VirtioSpiConfig { + cs_max_number: 1, + cs_change_supported: 1, + tx_nbits_supported: 0, + rx_nbits_supported: 0, + bits_per_word_mask: From::from(0), + mode_func_supported: From::from(0xf), + max_freq_hz: From::from(0), + max_word_delay_ns: From::from(0), + max_cs_setup_ns: From::from(0), + max_cs_hold_ns: From::from(0), + max_cs_inactive_ns: From::from(0), + }; + + Self { + get_mode_result: Ok(0), + set_mode_result: Ok(()), + get_max_speed_result: Ok(0), + set_max_speed_result: Ok(()), + get_bpw_result: Ok(0), + set_bpw_result: Ok(()), + rdwr_result: Ok(()), + detect_supported_features_result: Ok(default_config), + } + } + } + + impl SpiDevice for DummyDevice { + fn open(_spidev_name: &str) -> Result { + Ok(DummyDevice { + ..Default::default() + }) + } + + fn get_max_speed_hz(&self) -> Result { + self.get_max_speed_result + } + + fn set_max_speed_hz(&self, _max_speed_hz: u32) -> Result<()> { + self.set_max_speed_result + } + + fn get_bits_per_word(&self) -> Result { + self.get_bpw_result + } + + fn set_bits_per_word(&self, _bpw: u8) -> Result<()> { + self.set_bpw_result + } + + fn get_mode(&self) -> Result { + self.get_mode_result + } + + fn set_mode(&self, _mode: u32) -> Result<()> { + self.set_mode_result + } + + fn rdwr(&self, reqs: &mut [SpiTransReq]) -> Result<()> { + for req in reqs { + if !req.tx_buf.is_empty() { + verify_rdwr_buf(&req.tx_buf); + } + + if !req.rx_buf.is_empty() { + update_rdwr_buf(&mut req.rx_buf); + } + } + + self.rdwr_result + } + + fn detect_supported_features(&self) -> Result { + self.detect_supported_features_result + } + } + + fn verify_rdwr_data(reqs: &[SpiTransReq]) { + // Match what's done by DummyDevice::rdwr() + for req in reqs { + if !req.rx_buf.is_empty() { + verify_rdwr_buf(&req.rx_buf); + } + } + } + + #[test] + fn test_spi_transfer() { + let spi_dummy_ctrl = SpiController::new(DummyDevice::open("spidev0.0").unwrap()).unwrap(); + + // Read-Write-Read-Write-Read block + let mut reqs: Vec = vec![ + SpiTransReq { + tx_buf: vec![0; 10], + rx_buf: vec![0; 10], + trans_len: 10, + speed_hz: 0, + mode: 0, + delay_usecs: 0, + bits_per_word: 8, + cs_change: 0, + tx_nbits: 1, + rx_nbits: 1, + word_delay_usecs: 0, + cs_id: 0, + }, + SpiTransReq { + tx_buf: Vec::::new(), + rx_buf: vec![0; 15], + trans_len: 15, + speed_hz: 0, + mode: 0, + delay_usecs: 0, + bits_per_word: 8, + cs_change: 0, + tx_nbits: 0, + rx_nbits: 1, + word_delay_usecs: 0, + cs_id: 0, + }, + ]; + + for req in &mut reqs { + if !req.tx_buf.is_empty() { + update_rdwr_buf(&mut req.tx_buf); + } + } + + spi_dummy_ctrl.transfer(&mut reqs).unwrap(); + verify_rdwr_data(&reqs); + } + + #[test] + fn test_phys_device_failure() { + // Open failure + assert_eq!( + PhysDevice::open("/dev/spidev-invalid").unwrap_err(), + Error::DeviceOpenFailed + ); + + let file = TempFile::new().unwrap(); + let dev = PhysDevice::open(file.as_path().to_str().unwrap()).unwrap(); + + assert_eq!( + dev.get_mode().unwrap_err(), + Error::IoctlFailure("get_mode", IoError::last()) + ); + + assert_eq!( + dev.set_mode(0).unwrap_err(), + Error::IoctlFailure("set_mode", IoError::last()) + ); + + assert_eq!( + dev.get_max_speed_hz().unwrap_err(), + Error::IoctlFailure("get_max_speed_hz", IoError::last()) + ); + + assert_eq!( + dev.set_max_speed_hz(0).unwrap_err(), + Error::IoctlFailure("set_max_speed_hz", IoError::last()) + ); + + assert_eq!( + dev.get_bits_per_word().unwrap_err(), + Error::IoctlFailure("get_bits_per_word", IoError::last()) + ); + + assert_eq!( + dev.set_bits_per_word(0).unwrap_err(), + Error::IoctlFailure("set_bits_per_word", IoError::last()) + ); + + assert_eq!( + dev.detect_supported_features().unwrap_err(), + Error::IoctlFailure("get_max_speed_hz", IoError::last()) + ); + + // rdwr failure + let mut reqs = [SpiTransReq { + tx_buf: vec![7, 4], + rx_buf: vec![7, 4], + trans_len: 2, + speed_hz: 10000, + mode: 0, + delay_usecs: 0, + bits_per_word: 8, + cs_change: 1, + tx_nbits: 1, + rx_nbits: 1, + word_delay_usecs: 0, + cs_id: 0, + }]; + assert_eq!( + dev.rdwr(&mut reqs).unwrap_err(), + Error::IoctlFailure("get_mode", IoError::last()) + ); + } +} diff --git a/vhost-device-spi/src/vhu_spi.rs b/vhost-device-spi/src/vhu_spi.rs new file mode 100644 index 000000000..7c3a6284d --- /dev/null +++ b/vhost-device-spi/src/vhu_spi.rs @@ -0,0 +1,1053 @@ +// vhost device spi +// +// Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. +// Haixu Cui +// +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + +use log::warn; +use std::mem::size_of; +use std::slice::from_raw_parts; +use std::sync::Arc; +use std::{ + convert, + io::{self, Result as IoResult}, +}; + +use thiserror::Error as ThisError; +use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; +use vhost_user_backend::{VhostUserBackendMut, VringRwLock, VringT}; +use virtio_bindings::bindings::virtio_config::{VIRTIO_F_NOTIFY_ON_EMPTY, VIRTIO_F_VERSION_1}; +use virtio_bindings::bindings::virtio_ring::{ + VIRTIO_RING_F_EVENT_IDX, VIRTIO_RING_F_INDIRECT_DESC, +}; +use virtio_queue::{DescriptorChain, QueueOwnedT}; +use vm_memory::{ + ByteValued, Bytes, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryLoadGuard, GuestMemoryMmap, + Le32, +}; +use vmm_sys_util::epoll::EventSet; +use vmm_sys_util::eventfd::{EventFd, EFD_NONBLOCK}; + +use crate::spi::*; + +const QUEUE_SIZE: usize = 1024; +const NUM_QUEUES: usize = 1; + +type Result = std::result::Result; + +#[derive(Copy, Clone, Debug, Eq, PartialEq, ThisError)] +/// Errors related to vhost-device-spi daemon. +pub(crate) enum Error { + #[error("TX length {0} and RX length {1} don't match")] + TxRxTrnasLenNotEqual(u32, u32), + #[error("Failed to handle event, didn't match EPOLLIN")] + HandleEventNotEpollIn, + #[error("Failed to handle unknown event")] + HandleEventUnknown, + #[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("Tx buf and Rx buf mismatch in descriptor")] + TxRxBufOrderMismatch, + #[error("Descriptor not found")] + DescriptorNotFound, + #[error("Descriptor read failed")] + DescriptorReadFailed, + #[error("Descriptor write failed")] + DescriptorWriteFailed, + #[error("Failed to send notification")] + NotificationFailed, + #[error("Failed to create new EventFd")] + EventFdFailed, +} + +impl convert::From for io::Error { + fn from(e: Error) -> Self { + io::Error::new(io::ErrorKind::Other, e) + } +} + +/// The final status written by the device +const VIRTIO_SPI_TRANS_OK: u8 = 0; +const VIRTIO_SPI_PARAM_ERR: u8 = 1; +const VIRTIO_SPI_TRANS_ERR: u8 = 2; + +#[derive(Copy, Clone, Default)] +#[repr(C)] +struct VirtioSpiTransferHead { + chip_select_id: u8, + bits_per_word: u8, + cs_change: u8, + tx_nbits: u8, + rx_nbits: u8, + reserved1: u8, + reserved2: u8, + reserved3: u8, + mode: Le32, + freq: Le32, + word_delay_ns: Le32, + cs_setup_ns: Le32, + cs_delay_hold_ns: Le32, + cs_change_delay_inactive_ns: Le32, +} +// SAFETY: The layout of the structure is fixed and can be initialized by +// reading its content from byte array. +unsafe impl ByteValued for VirtioSpiTransferHead {} + +#[derive(Copy, Clone, Default)] +#[repr(C)] +struct VirtioSpiTransferResult { + status: u8, +} +// SAFETY: The layout of the structure is fixed and can be initialized by +// reading its content from byte array. +unsafe impl ByteValued for VirtioSpiTransferResult {} + +pub(crate) struct VhostUserSpiBackend { + spi_ctrl: Arc>, + event_idx: bool, + pub exit_event: EventFd, + mem: Option>, +} + +type SpiDescriptorChain = DescriptorChain>>; + +impl VhostUserSpiBackend { + pub fn new(spi_ctrl: Arc>) -> Result { + Ok(VhostUserSpiBackend { + spi_ctrl, + event_idx: false, + exit_event: EventFd::new(EFD_NONBLOCK).map_err(|_| Error::EventFdFailed)?, + mem: None, + }) + } + + /// Process the requests in the vring and dispatch replies + fn process_requests( + &self, + requests: Vec, + vring: &VringRwLock, + ) -> Result { + let mut reqs: Vec = Vec::new(); + + if requests.is_empty() { + return Ok(true); + } + + // Iterate over each SPI request and push it to "reqs" vector. + for desc_chain in requests.clone() { + let descriptors: Vec<_> = desc_chain.clone().collect(); + + if (descriptors.len() != 3) && (descriptors.len() != 4) { + return Err(Error::UnexpectedDescriptorCount(descriptors.len())); + } + + if descriptors.len() == 4 + && (descriptors[1].is_write_only() || !descriptors[2].is_write_only()) + { + return Err(Error::TxRxBufOrderMismatch); + } + + let desc_out_hdr = descriptors[0]; + + if desc_out_hdr.is_write_only() { + return Err(Error::UnexpectedWriteOnlyDescriptor(0)); + } + + if desc_out_hdr.len() as usize != size_of::() { + return Err(Error::UnexpectedDescriptorSize( + size_of::(), + desc_out_hdr.len(), + )); + } + + let out_hdr = desc_chain + .memory() + .read_obj::(desc_out_hdr.addr()) + .map_err(|_| Error::DescriptorReadFailed)?; + + let desc_in_hdr = descriptors[descriptors.len() - 1]; + if !desc_in_hdr.is_write_only() { + return Err(Error::UnexpectedReadableDescriptor(descriptors.len() - 1)); + } + + if desc_in_hdr.len() as usize != size_of::() { + return Err(Error::UnexpectedDescriptorSize( + size_of::(), + desc_in_hdr.len(), + )); + } + + let mut tx_buf: Vec; + let mut rx_buf: Vec; + let trans_len: u32; + + (tx_buf, rx_buf, trans_len) = match descriptors.len() { + 3 => { + // half-duplex transfer + let desc_buf = descriptors[1]; + let len = desc_buf.len(); + + if len == 0 { + return Err(Error::UnexpectedDescriptorSize(1, len)); + } + + if desc_buf.is_write_only() { + rx_buf = vec![0; len as usize]; + tx_buf = Vec::::new(); + } else { + tx_buf = vec![0; len as usize]; + + desc_chain + .memory() + .read(&mut tx_buf, desc_buf.addr()) + .map_err(|_| Error::DescriptorReadFailed)?; + + rx_buf = Vec::::new(); + } + + (tx_buf, rx_buf, len) + } + + 4 => { + // full-duplex transfer + let tx_desc_buf = descriptors[1]; + let rx_desc_buf = descriptors[2]; + + let tx_len = tx_desc_buf.len(); + let rx_len = rx_desc_buf.len(); + + if tx_len == 0 { + return Err(Error::UnexpectedDescriptorSize(1, tx_len)); + } + + if rx_len == 0 { + return Err(Error::UnexpectedDescriptorSize(1, rx_len)); + } + + if tx_len != rx_len { + return Err(Error::TxRxTrnasLenNotEqual(tx_len, rx_len)); + } + + rx_buf = vec![0; rx_len as usize]; + tx_buf = vec![0; tx_len as usize]; + + desc_chain + .memory() + .read(&mut tx_buf, tx_desc_buf.addr()) + .map_err(|_| Error::DescriptorReadFailed)?; + + (tx_buf, rx_buf, tx_len) + } + + _ => { + return Err(Error::UnexpectedDescriptorCount(descriptors.len())); + } + }; + + reqs.push(SpiTransReq { + tx_buf, + rx_buf, + trans_len, + speed_hz: out_hdr.freq.to_native(), + delay_usecs: (out_hdr.cs_delay_hold_ns.to_native() / 1000) as u16, + bits_per_word: out_hdr.bits_per_word, + cs_change: out_hdr.cs_change, + tx_nbits: out_hdr.tx_nbits, + rx_nbits: out_hdr.rx_nbits, + word_delay_usecs: (out_hdr.word_delay_ns.to_native() / 1000) as u8, + mode: out_hdr.mode.to_native(), + cs_id: out_hdr.chip_select_id, + }); + } + + let in_hdr = match self.spi_ctrl.check_trans_params(&mut reqs[0]) { + true => VirtioSpiTransferResult { + status: match self.spi_ctrl.transfer(&mut reqs) { + Ok(()) => VIRTIO_SPI_TRANS_OK, + Err(_) => VIRTIO_SPI_TRANS_ERR, + }, + }, + _ => VirtioSpiTransferResult { + status: VIRTIO_SPI_PARAM_ERR, + }, + }; + + for (i, desc_chain) in requests.iter().enumerate() { + let descriptors: Vec<_> = desc_chain.clone().collect(); + let desc_in_hdr = descriptors[descriptors.len() - 1]; + let mut len = size_of::() as u32; + + if descriptors.len() == 3 { + let desc_buf = descriptors[1]; + + if desc_buf.is_write_only() { + desc_chain + .memory() + .write(&reqs[i].rx_buf, desc_buf.addr()) + .map_err(|_| Error::DescriptorWriteFailed)?; + + if in_hdr.status == VIRTIO_SPI_TRANS_OK { + len += desc_buf.len(); + } + } + } + + if descriptors.len() == 4 { + let desc_buf = descriptors[2]; + + desc_chain + .memory() + .write(&reqs[i].rx_buf, desc_buf.addr()) + .map_err(|_| Error::DescriptorWriteFailed)?; + + if in_hdr.status == VIRTIO_SPI_TRANS_OK { + len += desc_buf.len(); + } + } + + // Write the transfer status + desc_chain + .memory() + .write_obj::(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"); + } + } + + Ok(true) + } + + /// Process the requests in the vring and dispatch replies + fn process_queue(&self, vring: &VringRwLock) -> Result<()> { + let requests: Vec<_> = vring + .get_mut() + .get_queue_mut() + .iter(self.mem.as_ref().unwrap().clone()) + .map_err(|_| Error::DescriptorNotFound)? + .collect(); + + if self.process_requests(requests, vring)? { + // Send notification once all the requests are processed + vring + .signal_used_queue() + .map_err(|_| Error::NotificationFailed)?; + } + + Ok(()) + } +} + +/// VhostUserBackendMut trait methods +impl VhostUserBackendMut for VhostUserSpiBackend { + type Vring = VringRwLock; + type Bitmap = (); + + fn num_queues(&self) -> usize { + NUM_QUEUES + } + + fn max_queue_size(&self) -> usize { + QUEUE_SIZE + } + + fn features(&self) -> u64 { + // this matches the current libvhost defaults except VHOST_F_LOG_ALL + 1 << VIRTIO_F_VERSION_1 + | 1 << VIRTIO_F_NOTIFY_ON_EMPTY + | 1 << VIRTIO_RING_F_INDIRECT_DESC + | 1 << VIRTIO_RING_F_EVENT_IDX + | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() + } + + fn protocol_features(&self) -> VhostUserProtocolFeatures { + VhostUserProtocolFeatures::MQ + | VhostUserProtocolFeatures::CONFIG + | VhostUserProtocolFeatures::REPLY_ACK + } + + fn set_event_idx(&mut self, enabled: bool) { + dbg!(self.event_idx = enabled); + } + + fn update_memory(&mut self, mem: GuestMemoryAtomic) -> IoResult<()> { + self.mem = Some(mem.memory()); + Ok(()) + } + + fn get_config(&self, offset: u32, size: u32) -> Vec { + // SAFETY: The layout of the structure is fixed and can be initialized by + // reading its content from byte array. + unsafe { + from_raw_parts( + self.spi_ctrl + .config() + .as_slice() + .as_ptr() + .offset(offset as isize) as *const _ as *const _, + size as usize, + ) + .to_vec() + } + } + + fn handle_event( + &mut self, + device_event: u16, + evset: EventSet, + vrings: &[VringRwLock], + _thread_id: usize, + ) -> IoResult<()> { + if evset != EventSet::IN { + return Err(Error::HandleEventNotEpollIn.into()); + } + + match device_event { + 0 => { + let vring = &vrings[0]; + + if self.event_idx { + // vm-virtio's Queue implementation only checks avail_index + // once, so to properly support EVENT_IDX we need to keep + // calling process_queue() until it stops finding new + // requests on the queue. + loop { + vring.disable_notification().unwrap(); + self.process_queue(vring)?; + if !vring.enable_notification().unwrap() { + break; + } + } + } else { + // Without EVENT_IDX, a single call is enough. + self.process_queue(vring)?; + } + } + + _ => { + warn!("unhandled device_event: {}", device_event); + return Err(Error::HandleEventUnknown.into()); + } + } + Ok(()) + } + + fn exit_event(&self, _thread_index: usize) -> Option { + self.exit_event.try_clone().ok() + } +} + +#[cfg(test)] +mod tests { + use virtio_bindings::bindings::virtio_ring::{VRING_DESC_F_NEXT, VRING_DESC_F_WRITE}; + use virtio_queue::{mock::MockSplitQueue, Descriptor, Queue}; + use vm_memory::{Address, GuestAddress, GuestMemoryAtomic, GuestMemoryMmap}; + + use super::Error; + use super::*; + use crate::spi::tests::{update_rdwr_buf, verify_rdwr_buf, DummyDevice}; + + // Prepares a single chain of descriptors + fn prepare_desc_chain( + start_addr: GuestAddress, + tx_buf: &mut Vec, + rx_buf: &mut Vec, + chip_select_id: u8, + bits_per_word: u8, + cs_change: u8, + tx_nbits: u8, + rx_nbits: u8, + mode: u32, + freq: u32, + ) -> SpiDescriptorChain { + 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 = VirtioSpiTransferHead { + chip_select_id, + bits_per_word, + cs_change, + tx_nbits, + rx_nbits, + reserved1: 0, + reserved2: 0, + reserved3: 0, + mode: From::from(mode), + freq: From::from(freq), + word_delay_ns: From::from(0), + cs_setup_ns: From::from(0), + cs_delay_hold_ns: From::from(0), + cs_change_delay_inactive_ns: From::from(0), + }; + + let desc_out = Descriptor::new( + next_addr, + size_of::() as u32, + VRING_DESC_F_NEXT as u16, + index + 1, + ); + + mem.write_obj::(out_hdr, desc_out.addr()) + .unwrap(); + vq.desc_table().store(index, desc_out).unwrap(); + next_addr += desc_out.len() as u64; + index += 1; + + // TX buf descriptor: optional + if !tx_buf.is_empty() { + update_rdwr_buf(tx_buf); + + let desc_tx_buf = Descriptor::new( + next_addr, + tx_buf.len() as u32, + (VRING_DESC_F_NEXT) as u16, + index + 1, + ); + mem.write(tx_buf, desc_tx_buf.addr()).unwrap(); + vq.desc_table().store(index, desc_tx_buf).unwrap(); + next_addr += desc_tx_buf.len() as u64; + index += 1; + } + + // RX buf descriptor: optional + if !rx_buf.is_empty() { + let desc_rx_buf = Descriptor::new( + next_addr, + rx_buf.len() as u32, + (VRING_DESC_F_WRITE | VRING_DESC_F_NEXT) as u16, + index + 1, + ); + mem.write(rx_buf, desc_rx_buf.addr()).unwrap(); + vq.desc_table().store(index, desc_rx_buf).unwrap(); + next_addr += desc_rx_buf.len() as u64; + index += 1; + } + + // In response descriptor + let desc_in = Descriptor::new( + next_addr, + size_of::() as u32, + VRING_DESC_F_WRITE as u16, + 0, + ); + vq.desc_table().store(index, desc_in).unwrap(); + + // 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::() + .unwrap() + .iter(GuestMemoryAtomic::new(mem.clone()).memory()) + .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, status: u8) { + for desc_chain in desc_chains { + let descriptors: Vec<_> = desc_chain.clone().collect(); + + let in_hdr = desc_chain + .memory() + .read_obj::(descriptors[descriptors.len() - 1].addr()) + .unwrap(); + + // Operation result should match expected status. + assert_eq!(in_hdr.status, status); + + if descriptors.len() == 3 && descriptors[1].is_write_only() { + 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); + } + + if descriptors.len() == 4 { + let mut buf = vec![0; descriptors[2].len() as usize]; + desc_chain + .memory() + .read(&mut buf, descriptors[2].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>, + flags: Vec, + len: Vec, + ) -> SpiDescriptorChain { + 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: u16 = if i == flags.len() - 1 { + 0 + } else { + VRING_DESC_F_NEXT as u16 + }; + 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).unwrap(); + } + + // 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::() + .unwrap() + .iter(GuestMemoryAtomic::new(mem.clone()).memory()) + .unwrap() + .next() + .unwrap() + } + + #[test] + fn process_requests_success() { + let spi_dummy_ctrl = SpiController::new(DummyDevice::open("spidev0.0").unwrap()).unwrap(); + let backend = VhostUserSpiBackend::new(Arc::new(spi_dummy_ctrl)).unwrap(); + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(), + ); + let vring = VringRwLock::new(mem, 0x1000).unwrap(); + + // Descriptor chain size zero, shouldn't fail + backend + .process_requests(Vec::::new(), &vring) + .unwrap(); + + // Valid single read descriptor + let mut tx_buf: Vec = Vec::::new(); + let mut rx_buf: Vec = vec![0; 30]; + let desc_chain = prepare_desc_chain( + GuestAddress(0), + &mut tx_buf, + &mut rx_buf, + 0, + 8, + 0, + 1, + 1, + 0, + 10000, + ); + let desc_chains = vec![desc_chain]; + + backend + .process_requests(desc_chains.clone(), &vring) + .unwrap(); + validate_desc_chains(desc_chains, VIRTIO_SPI_TRANS_OK); + + // Valid single write descriptor + let mut tx_buf: Vec = vec![0; 30]; + let mut rx_buf: Vec = Vec::::new(); + let desc_chain = prepare_desc_chain( + GuestAddress(0), + &mut tx_buf, + &mut rx_buf, + 0, + 8, + 0, + 1, + 1, + 0, + 10000, + ); + let desc_chains = vec![desc_chain]; + + backend + .process_requests(desc_chains.clone(), &vring) + .unwrap(); + validate_desc_chains(desc_chains, VIRTIO_SPI_TRANS_OK); + + // Valid mixed read-write descriptors + let mut tx_buf: Vec = vec![0; 30]; + let mut rx_buf: Vec = vec![0; 30]; + let mut null_buf: Vec = Vec::::new(); + + let desc_chains = vec![ + // Write + prepare_desc_chain( + GuestAddress(0), + &mut tx_buf, + &mut null_buf, + 0, + 8, + 0, + 1, + 1, + 0, + 10000, + ), + // Read + prepare_desc_chain( + GuestAddress(0), + &mut null_buf, + &mut rx_buf, + 0, + 8, + 0, + 1, + 1, + 0, + 10000, + ), + // Write+Read + prepare_desc_chain( + GuestAddress(0), + &mut tx_buf, + &mut rx_buf, + 0, + 8, + 0, + 1, + 1, + 0, + 10000, + ), + // Write + prepare_desc_chain( + GuestAddress(0), + &mut tx_buf, + &mut null_buf, + 0, + 8, + 0, + 1, + 1, + 0, + 10000, + ), + // Read + prepare_desc_chain( + GuestAddress(0), + &mut null_buf, + &mut rx_buf, + 0, + 8, + 0, + 1, + 1, + 0, + 10000, + ), + // Write+Read + prepare_desc_chain( + GuestAddress(0), + &mut tx_buf, + &mut rx_buf, + 0, + 8, + 0, + 1, + 1, + 0, + 10000, + ), + ]; + + backend + .process_requests(desc_chains.clone(), &vring) + .unwrap(); + validate_desc_chains(desc_chains, VIRTIO_SPI_TRANS_OK); + + // unsupported LOOP mode, should filter by parameter check + let mut tx_buf: Vec = vec![0; 30]; + let mut rx_buf: Vec = Vec::::new(); + let desc_chain = prepare_desc_chain( + GuestAddress(0), + &mut tx_buf, + &mut rx_buf, + 0, + 8, + 0, + 1, + 1, + 0x10, + 10000, + ); + let desc_chains = vec![desc_chain]; + + backend + .process_requests(desc_chains.clone(), &vring) + .unwrap(); + validate_desc_chains(desc_chains, VIRTIO_SPI_PARAM_ERR); + } + + #[test] + fn process_requests_failure() { + let spi_dummy_ctrl = SpiController::new(DummyDevice::open("spidev0.0").unwrap()).unwrap(); + let backend = VhostUserSpiBackend::new(Arc::new(spi_dummy_ctrl)).unwrap(); + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(), + ); + let vring = VringRwLock::new(mem, 0x1000).unwrap(); + + // One descriptors + let flags: Vec = vec![0]; + let len: Vec = 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) + ); + + // Five descriptors + let flags: Vec = vec![0, 0, 0, 0, 0]; + let len: Vec = vec![0, 0, 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(5) + ); + + // Write only out hdr + let flags: Vec = vec![VRING_DESC_F_WRITE as u16, 0, VRING_DESC_F_WRITE as u16]; + let len: Vec = vec![ + size_of::() as u32, + 1, + size_of::() as u32, + ]; + 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 length + let flags: Vec = vec![0, 0, VRING_DESC_F_WRITE as u16]; + let len: Vec = vec![100, 1, size_of::() as u32]; + 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::(), 100) + ); + + // Invalid out hdr address + let addr: Vec = vec![0x10000, 0, 0]; + let flags: Vec = vec![0, 0, VRING_DESC_F_WRITE as u16]; + let len: Vec = vec![ + size_of::() as u32, + 1, + size_of::() as u32, + ]; + let desc_chain = prepare_desc_chain_dummy(Some(addr), flags, len); + assert_eq!( + backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err(), + Error::DescriptorReadFailed + ); + + // Read only in hdr + let flags: Vec = vec![0, 0, 0]; + let len: Vec = vec![ + size_of::() as u32, + 1, + size_of::() as u32, + ]; + let desc_chain = prepare_desc_chain_dummy(None, flags, len); + assert_eq!( + backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err(), + Error::UnexpectedReadableDescriptor(2) + ); + + // Invalid in hdr length + let flags: Vec = vec![0, 0, VRING_DESC_F_WRITE as u16]; + let len: Vec = vec![size_of::() as u32, 1, 100]; + 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::(), 100) + ); + + // Invalid in hdr address + let addr: Vec = vec![0, 0, 0x10000]; + let flags: Vec = vec![0, VRING_DESC_F_WRITE as u16, VRING_DESC_F_WRITE as u16]; + let len: Vec = vec![ + size_of::() as u32, + 1, + size_of::() as u32, + ]; + 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 buf length + let flags: Vec = vec![0, 0, VRING_DESC_F_WRITE as u16]; + let len: Vec = vec![ + size_of::() as u32, + 0, + size_of::() as u32, + ]; + let desc_chain = prepare_desc_chain_dummy(None, flags, len); + assert_eq!( + backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err(), + Error::UnexpectedDescriptorSize(1, 0) + ); + + // Different tx length and rx length + let flags: Vec = vec![0, 0, VRING_DESC_F_WRITE as u16, VRING_DESC_F_WRITE as u16]; + let len: Vec = vec![ + size_of::() as u32, + 1, + 2, + size_of::() as u32, + ]; + let desc_chain = prepare_desc_chain_dummy(None, flags, len); + assert_eq!( + backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err(), + Error::TxRxTrnasLenNotEqual(1, 2) + ); + + // Invalid buf address + let addr: Vec = vec![0, 0x10000, 0]; + let flags: Vec = vec![0, 0, VRING_DESC_F_WRITE as u16]; + let len: Vec = vec![ + size_of::() as u32, + 1, + size_of::() as u32, + ]; + let desc_chain = prepare_desc_chain_dummy(Some(addr), flags, len); + assert_eq!( + backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err(), + Error::DescriptorReadFailed + ); + + // tx buffer and rx buffer order mismatch + let flags: Vec = vec![0, VRING_DESC_F_WRITE as u16, 0, VRING_DESC_F_WRITE as u16]; + let len: Vec = vec![ + size_of::() as u32, + 10, + 10, + size_of::() as u32, + ]; + let desc_chain = prepare_desc_chain_dummy(None, flags, len); + assert_eq!( + backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err(), + Error::TxRxBufOrderMismatch + ); + } + + #[test] + fn verify_backend() { + let spi_dummy_ctrl = SpiController::new(DummyDevice::open("spidev0.0").unwrap()).unwrap(); + let mut backend = VhostUserSpiBackend::new(Arc::new(spi_dummy_ctrl)).unwrap(); + + assert_eq!(backend.num_queues(), NUM_QUEUES); + assert_eq!(backend.max_queue_size(), QUEUE_SIZE); + assert_eq!(backend.features(), 0x171000000); + assert_eq!( + backend.protocol_features(), + (VhostUserProtocolFeatures::MQ + | VhostUserProtocolFeatures::CONFIG + | VhostUserProtocolFeatures::REPLY_ACK) + ); + + assert_eq!(backend.queues_per_thread(), vec![0xffff_ffff]); + assert_eq!(backend.get_config(0, 0), vec![]); + + backend.set_event_idx(true); + assert!(backend.event_idx); + + assert!(backend.exit_event(0).is_some()); + + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(), + ); + backend.update_memory(mem.clone()).unwrap(); + + let vring = VringRwLock::new(mem, 0x1000).unwrap(); + vring.set_queue_info(0x100, 0x200, 0x300).unwrap(); + vring.set_queue_ready(true); + + assert_eq!( + backend + .handle_event(0, EventSet::OUT, &[vring.clone()], 0) + .unwrap_err() + .kind(), + io::ErrorKind::Other + ); + + assert_eq!( + backend + .handle_event(1, EventSet::IN, &[vring.clone()], 0) + .unwrap_err() + .kind(), + io::ErrorKind::Other + ); + + // Hit the loop part + backend.set_event_idx(true); + backend + .handle_event(0, EventSet::IN, &[vring.clone()], 0) + .unwrap(); + + // Hit the non-loop part + backend.set_event_idx(false); + backend.handle_event(0, EventSet::IN, &[vring], 0).unwrap(); + } +}