diff --git a/Cargo.lock b/Cargo.lock index 6a4e0f99f..c4807a68d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1472,6 +1472,25 @@ dependencies = [ "vmm-sys-util", ] +[[package]] +name = "vhost-device-spi" +version = "0.1.0" +dependencies = [ + "assert_matches", + "bitflags 2.4.1", + "clap", + "env_logger", + "libc", + "log", + "thiserror", + "vhost", + "vhost-user-backend", + "virtio-bindings", + "virtio-queue", + "vm-memory", + "vmm-sys-util", +] + [[package]] name = "vhost-device-template" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index bf2d33ef3..b0d010ddd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "vhost-device-scsi", "vhost-device-scmi", "vhost-device-sound", + "vhost-device-spi", "vhost-device-template", "vhost-device-vsock", ] diff --git a/README.md b/README.md index 245c6659b..6f864e825 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Here is the list of device backends that we support: - [SCMI](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-scmi/README.md) - [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) +- [SPI](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-spi/README.md) - [VSOCK](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-vsock/README.md) The vhost-device workspace also provides a diff --git a/coverage_config_x86_64.json b/coverage_config_x86_64.json index 24d758542..522f5e333 100644 --- a/coverage_config_x86_64.json +++ b/coverage_config_x86_64.json @@ -1,5 +1,5 @@ { - "coverage_score": 77.63, + "coverage_score": 75.76, "exclude_path": "", "crate_features": "" } 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..3c3dec546 --- /dev/null +++ b/vhost-device-spi/Cargo.toml @@ -0,0 +1,35 @@ +[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"] +categories = ["virtualization"] +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.15" +virtio-bindings = "0.2.2" +virtio-queue = "0.12" +vm-memory = "0.14.1" +vmm-sys-util = "0.12" +bitflags = "2.4.0" + +[dev-dependencies] +assert_matches = "1.5" +virtio-queue = { version = "0.12", 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..11074b9a5 --- /dev/null +++ b/vhost-device-spi/README.md @@ -0,0 +1,80 @@ +# 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 + +```shell +vhost-device-spi [OPTIONS] +``` + +## Options +```text + -h, --help + + Print help. + + -s, --socket-path=PATH + + Location of vhost-user Unix domain sockets, this path will be suffixed with + 0,1,2..socket_count-1. + + -c, --socket-count=INT + + Number of guests (sockets) to attach to, default set to 1. + + -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 + +### Dependencies +For testing the device the required dependencies are: +- Linux: + - Integrate *virtio-spi* driver: + - https://lwn.net/Articles/966715/ + - Set `CONFIG_SPI_VIRTIO=y` +- QEMU: + - Integrate vhost-user-spi QEMU device: + - https://lore.kernel.org/all/20240712034246.2553812-1-quic_haixcui@quicinc.com/ + +### Test the device +First start the daemon on the host machine:: + +````suggestion +```console +vhost-device-spi --socket-path=vspi.sock --socket-count=1 --device "/dev/spidev0.0" +``` +```` + +The QEMU invocation needs to create a chardev socket the device spi +use to communicate as well as share the guests memory over a memfd. + +````suggestion +```console +qemu-system-aarch64 -m 1G \ + -chardev socket,path=/home/root/vspi.sock0,id=vspi \ + -device vhost-user-spi-pci,chardev=vspi,id=spi \ + -object memory-backend-file,id=mem,size=1G,mem-path=/dev/shm,share=on \ + -numa node,memdev=mem \ + ... +``` +```` + +## 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/linux_spi.rs b/vhost-device-spi/src/linux_spi.rs new file mode 100644 index 000000000..c08c60444 --- /dev/null +++ b/vhost-device-spi/src/linux_spi.rs @@ -0,0 +1,77 @@ +// Linux SPI bindings +// +// Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. +// Haixu Cui +// +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + +use bitflags::bitflags; +use vmm_sys_util::{ioctl_ioc_nr, ioctl_ior_nr, ioctl_iow_nr}; + +/// Describes a single SPI transfer +#[derive(Debug)] +#[repr(C)] +pub struct SpiIocTransfer { + /// Holds pointer to userspace buffer with transmit data, or null + pub tx_buf: u64, + /// Holds pointer to userspace buffer for receive data, or null. + pub rx_buf: u64, + /// Length of tx and rx buffers, in bytes. + pub len: u32, + /// Temporary override of the device's bitrate. + pub speed_hz: u32, + /// If nonzero, how long to delay after the last bit transfer + /// before optionally deselecting the device before the next transfer. + pub delay_usecs: u16, + /// Temporary override of the device's wordsize. + pub bits_per_word: u8, + /// True to deselect device before starting the next transfer. + pub cs_change: u8, + /// Number of bits used for writing. + pub tx_nbits: u8, + /// Number of bits used for reading. + pub rx_nbits: u8, + /// 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 + pub word_delay_usecs: u8, + pub _padding: u8, +} + +/// 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: u32 = 0x40006b00; + +ioctl_ior_nr!(SPI_IOC_RD_BITS_PER_WORD, 107, 3, u8); +ioctl_iow_nr!(SPI_IOC_WR_BITS_PER_WORD, 107, 3, u8); +ioctl_ior_nr!(SPI_IOC_RD_MAX_SPEED_HZ, 107, 4, u32); +ioctl_iow_nr!(SPI_IOC_WR_MAX_SPEED_HZ, 107, 4, u32); +ioctl_ior_nr!(SPI_IOC_RD_MODE32, 107, 5, u32); +ioctl_iow_nr!(SPI_IOC_WR_MODE32, 107, 5, u32); + +// Corresponds to the SPI_IOC_MESSAGE macro in Linux +pub fn spi_ioc_message(n: u32) -> u64 { + let mut size: u32 = 0; + if n * 32 < (1 << _IOC_SIZEBITS) { + size = n * 32; + } + (SPI_IOC_MESSAGE_BASE | (size << _IOC_SIZESHIFT)) as u64 +} + +bitflags! { + pub struct LnxSpiMode: u32 { + const CPHA = 1 << 0; + const CPOL = 1 << 1; + const CS_HIGH = 1 << 2; + const LSB_FIRST = 1 << 3; + const LOOP = 1 << 5; + const TX_DUAL = 1 << 8; + const TX_QUAD = 1 << 9; + const TX_OCTAL = 1 << 13; + const RX_DUAL = 1 << 10; + const RX_QUAD = 1 << 11; + const RX_OCTAL = 1 << 14; + } +} diff --git a/vhost-device-spi/src/main.rs b/vhost-device-spi/src/main.rs new file mode 100644 index 000000000..ebd510f35 --- /dev/null +++ b/vhost-device-spi/src/main.rs @@ -0,0 +1,226 @@ +// 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 linux_spi; +mod spi; +mod vhu_spi; +mod virtio_spi; + +use std::{ + any::Any, + collections::HashMap, + num::NonZeroUsize, + path::PathBuf, + process::exit, + sync::{Arc, RwLock}, + thread, +}; + +use clap::Parser; +use log::error; +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 +enum Error { + #[error("SPI device file doesn't exists or can't be accessed")] + AccessDeviceFailure(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), + #[error("Thread `{0}` panicked")] + ThreadPanic(String, Box), +} + +#[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: PathBuf, + + /// Number of guests (sockets) to connect to. + #[clap(short = 'c', long, default_value_t = NonZeroUsize::new(1).unwrap())] + socket_count: NonZeroUsize, + + /// SPI device full path + #[clap(short = 'l', long)] + device: PathBuf, +} + +#[derive(PartialEq, Debug)] +struct SpiConfiguration { + socket_path: PathBuf, + socket_count: usize, + device: PathBuf, +} + +impl SpiConfiguration { + fn from(args: SpiArgs) -> Result { + Ok(SpiConfiguration { + socket_path: args.socket_path, + socket_count: args.socket_count.get(), + device: args.device, + }) + } +} + +impl SpiConfiguration { + pub fn generate_socket_paths(&self) -> Vec { + let socket_file_name = self + .socket_path + .file_name() + .expect("socket_path has no filename."); + let socket_file_parent = self + .socket_path + .parent() + .expect("socket_path has no parent directory."); + + let make_socket_path = |i: usize| -> PathBuf { + let mut file_name = socket_file_name.to_os_string(); + file_name.push(std::ffi::OsStr::new(&i.to_string())); + socket_file_parent.join(&file_name) + }; + (0..self.socket_count).map(make_socket_path).collect() + } +} + +pub(crate) fn start_backend_server( + socket: PathBuf, + device: PathBuf, +) -> Result<()> { + loop { + let spi_dev = D::open(&device).map_err(Error::AccessDeviceFailure)?; + let spi_ctrl = + Arc::new(SpiController::::new(spi_dev).map_err(Error::AccessDeviceFailure)?); + + 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)?; + } +} + +fn start_backend(args: SpiArgs) -> Result<()> { + let config = SpiConfiguration::from(args)?; + + let mut handles = HashMap::new(); + + let (senders, receiver) = std::sync::mpsc::channel(); + + for (thread_id, socket) in config.generate_socket_paths().into_iter().enumerate() { + let name = format!("vhu-vsock-spi-{:?}", thread_id); + + let sender = senders.clone(); + + let device_ref = config.device.clone(); + + let handle = thread::Builder::new() + .name(name.clone()) + .spawn(move || { + let result = + std::panic::catch_unwind(move || start_backend_server::(socket, device_ref)); + + sender.send(thread_id).unwrap(); + + result.map_err(|e| Error::ThreadPanic(name, e))? + }) + .unwrap(); + + handles.insert(thread_id, handle); + } + + while !handles.is_empty() { + let thread_id = receiver.recv().unwrap(); + handles + .remove(&thread_id) + .unwrap() + .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 std::path::Path; + + use super::*; + use crate::spi::tests::DummyDevice; + + impl SpiArgs { + fn from_args(path: &str, device: &str, count: usize) -> SpiArgs { + SpiArgs { + socket_path: PathBuf::from(path), + socket_count: NonZeroUsize::new(count) + .expect("Socket count must be a non-zero value"), + device: PathBuf::from(device), + } + } + } + + #[test] + fn test_parse_successful() { + let socket_name = "vspi.sock"; + let device_path = "/dev/spidev0.0"; + + let cmd_args = SpiArgs::from_args(socket_name, device_path, 3); + + let config = SpiConfiguration::from(cmd_args).unwrap(); + + assert_eq!( + config.generate_socket_paths(), + vec![ + Path::new("vspi.sock0").to_path_buf(), + Path::new("vspi.sock1").to_path_buf(), + Path::new("vspi.sock2").to_path_buf(), + ] + ); + } + + #[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..a973e5ae3 --- /dev/null +++ b/vhost-device-spi/src/spi.rs @@ -0,0 +1,1258 @@ +// 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}, + os::unix::io::AsRawFd, + path::Path, + ptr, +}; + +use thiserror::Error as ThisError; +use vmm_sys_util::errno::Error as IoError; +use vmm_sys_util::ioctl::{ioctl_with_mut_ptr, ioctl_with_mut_ref, ioctl_with_ref}; + +use crate::{linux_spi::*, vhu_spi::VirtioSpiConfig, virtio_spi::*}; + +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, +} + +/// 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, +} + +/// 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(path: &Path) -> Result + where + Self: Sized; + + /// Corresponds to the `SPI_IOC_RD_MAX_SPEED_HZ` ioctl call. + fn 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 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 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 [SpiIocTransfer]) -> Result<()>; + + /// Detect the SPI controller supported mode and delay settings + fn detect_supported_features(&self) -> Result; +} + +/// 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(path: &Path) -> Result { + Ok(PhysDevice { + file: OpenOptions::new() + .read(true) + .write(true) + .open(path) + .map_err(|_| Error::DeviceOpenFailed)?, + }) + } + + fn 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_with_mut_ref( + &self.file.as_raw_fd(), + SPI_IOC_RD_MAX_SPEED_HZ(), + &mut max_speed_hz, + ) + }; + + if ret == -1 { + Err(Error::IoctlFailure("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_with_ref( + &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 bits_per_word(&self) -> Result { + let mut bpw: u8 = 0; + + // SAFETY: Safe as the file is a valid SPI controller. + let ret = unsafe { + ioctl_with_mut_ref(&self.file.as_raw_fd(), SPI_IOC_RD_BITS_PER_WORD(), &mut bpw) + }; + + if ret == -1 { + Err(Error::IoctlFailure("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_with_ref(&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 mode(&self) -> Result { + let mut mode: u32 = 0; + + // SAFETY: Safe as the file is a valid SPI controller. + let ret = + unsafe { ioctl_with_mut_ref(&self.file.as_raw_fd(), SPI_IOC_RD_MODE32(), &mut mode) }; + + if ret == -1 { + Err(Error::IoctlFailure("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_with_ref(&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, msgs: &mut [SpiIocTransfer]) -> Result<()> { + let len = msgs.len(); + // SAFETY: Safe as the file is a valid SPI controller. + let ret = unsafe { + ioctl_with_mut_ptr( + &self.file.as_raw_fd(), + spi_ioc_message(len as u32), + msgs.as_mut_ptr(), + ) + }; + + 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.max_speed_hz()?; + + let max_speed_hz: u32 = match self.set_max_speed_hz(0) { + Err(_) => 0, + Ok(()) => self.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.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.mode()?; + + let set_tx_dual: u32 = (origin_mode | LnxSpiMode::TX_DUAL.bits()) + & !LnxSpiMode::TX_QUAD.bits() + & !LnxSpiMode::TX_OCTAL.bits(); + let set_tx_quad: u32 = (origin_mode | LnxSpiMode::TX_QUAD.bits()) + & !LnxSpiMode::TX_DUAL.bits() + & !LnxSpiMode::TX_OCTAL.bits(); + let set_tx_octal: u32 = (origin_mode | LnxSpiMode::TX_OCTAL.bits()) + & !LnxSpiMode::TX_DUAL.bits() + & !LnxSpiMode::TX_QUAD.bits(); + let set_rx_dual: u32 = (origin_mode | LnxSpiMode::RX_DUAL.bits()) + & !LnxSpiMode::RX_QUAD.bits() + & !LnxSpiMode::RX_OCTAL.bits(); + let set_rx_quad: u32 = (origin_mode | LnxSpiMode::RX_QUAD.bits()) + & !LnxSpiMode::RX_DUAL.bits() + & !LnxSpiMode::RX_OCTAL.bits(); + let set_rx_octal: u32 = (origin_mode | LnxSpiMode::RX_OCTAL.bits()) + & !LnxSpiMode::RX_DUAL.bits() + & !LnxSpiMode::RX_QUAD.bits(); + + self.set_mode(set_tx_dual)?; + let get_tx_dual = self.mode()?; + if (get_tx_dual & LnxSpiMode::TX_DUAL.bits()) == LnxSpiMode::TX_DUAL.bits() { + tx_nbits_mask |= ConfigNbits::DUAL.bits(); + } + + self.set_mode(set_tx_quad)?; + let get_tx_quad = self.mode()?; + if (get_tx_quad & LnxSpiMode::TX_QUAD.bits()) == LnxSpiMode::TX_QUAD.bits() { + tx_nbits_mask |= ConfigNbits::QUAD.bits(); + } + + self.set_mode(set_tx_octal)?; + let get_tx_octal = self.mode()?; + if (get_tx_octal & LnxSpiMode::TX_OCTAL.bits()) == LnxSpiMode::TX_OCTAL.bits() { + tx_nbits_mask |= ConfigNbits::OCTAL.bits(); + } + + self.set_mode(set_rx_dual)?; + let get_rx_dual = self.mode()?; + if (get_rx_dual & LnxSpiMode::RX_DUAL.bits()) == LnxSpiMode::RX_DUAL.bits() { + rx_nbits_mask |= ConfigNbits::DUAL.bits(); + } + + self.set_mode(set_rx_quad)?; + let get_rx_quad = self.mode()?; + if (get_rx_quad & LnxSpiMode::RX_QUAD.bits()) == LnxSpiMode::RX_QUAD.bits() { + rx_nbits_mask |= ConfigNbits::QUAD.bits(); + } + + self.set_mode(set_rx_octal)?; + let get_rx_octal = self.mode()?; + if (get_rx_octal & LnxSpiMode::RX_OCTAL.bits()) == LnxSpiMode::RX_OCTAL.bits() { + rx_nbits_mask |= ConfigNbits::OCTAL.bits(); + } + + // 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 & LnxSpiMode::CPHA.bits()) == LnxSpiMode::CPHA.bits() { + mode_function_mask |= ConfigMode::CPHA_1.bits(); + set_cpha_mode &= !LnxSpiMode::CPHA.bits(); + + match self.set_mode(set_cpha_mode) { + Err(_) => mode_function_mask &= !ConfigMode::CPHA_0.bits(), + Ok(()) => { + get_cpha_mode = self.mode()?; + if (get_cpha_mode & LnxSpiMode::CPHA.bits()) == 0 { + mode_function_mask |= ConfigMode::CPHA_0.bits(); + } else { + mode_function_mask &= !ConfigMode::CPHA_0.bits(); + } + } + }; + } else { + mode_function_mask |= ConfigMode::CPHA_0.bits(); + set_cpha_mode |= LnxSpiMode::CPHA.bits(); + + match self.set_mode(set_cpha_mode) { + Err(_) => mode_function_mask &= !ConfigMode::CPHA_1.bits(), + Ok(()) => { + get_cpha_mode = self.mode()?; + if (get_cpha_mode & LnxSpiMode::CPHA.bits()) == LnxSpiMode::CPHA.bits() { + mode_function_mask |= ConfigMode::CPHA_1.bits(); + } else { + mode_function_mask &= !ConfigMode::CPHA_1.bits(); + } + } + }; + } + + // detect supported CPOL setting + let mut set_cpol_mode = origin_mode; + let get_cpol_mode; + + if (origin_mode & LnxSpiMode::CPOL.bits()) == LnxSpiMode::CPOL.bits() { + mode_function_mask |= ConfigMode::CPOL_1.bits(); + + set_cpol_mode &= !LnxSpiMode::CPOL.bits(); + + match self.set_mode(set_cpol_mode) { + Err(_) => mode_function_mask &= !ConfigMode::CPOL_0.bits(), + Ok(()) => { + get_cpol_mode = self.mode()?; + if (get_cpol_mode & LnxSpiMode::CPOL.bits()) == 0 { + mode_function_mask |= ConfigMode::CPOL_0.bits(); + } else { + mode_function_mask &= !ConfigMode::CPOL_0.bits(); + } + } + }; + } else { + mode_function_mask |= ConfigMode::CPOL_0.bits(); + + set_cpol_mode |= LnxSpiMode::CPOL.bits(); + + match self.set_mode(set_cpol_mode) { + Err(_) => mode_function_mask &= !ConfigMode::CPOL_1.bits(), + Ok(()) => { + get_cpol_mode = self.mode()?; + if (get_cpol_mode & LnxSpiMode::CPOL.bits()) == LnxSpiMode::CPOL.bits() { + mode_function_mask |= ConfigMode::CPOL_1.bits(); + } else { + mode_function_mask &= !ConfigMode::CPOL_1.bits(); + } + } + }; + } + + // detect supported CS_HIGH setting + let mut set_cs_high_mode = origin_mode; + let get_cs_high_mode; + + if (origin_mode & LnxSpiMode::CS_HIGH.bits()) == LnxSpiMode::CS_HIGH.bits() { + mode_function_mask |= ConfigMode::CS_HIGH.bits(); + } else { + set_cs_high_mode |= LnxSpiMode::CS_HIGH.bits(); + match self.set_mode(set_cs_high_mode) { + Err(_) => mode_function_mask &= !ConfigMode::CS_HIGH.bits(), + Ok(()) => { + get_cs_high_mode = self.mode()?; + if (get_cs_high_mode & LnxSpiMode::CS_HIGH.bits()) == LnxSpiMode::CS_HIGH.bits() + { + mode_function_mask |= ConfigMode::CS_HIGH.bits(); + } else { + mode_function_mask &= !ConfigMode::CS_HIGH.bits(); + } + } + }; + } + + // detect supported LSB setting + let mut set_lsb_mode = origin_mode; + let get_lsb_mode; + + if (origin_mode & LnxSpiMode::LSB_FIRST.bits()) == LnxSpiMode::LSB_FIRST.bits() { + mode_function_mask |= ConfigMode::LSB.bits(); + } else { + set_lsb_mode |= LnxSpiMode::LSB_FIRST.bits(); + match self.set_mode(set_lsb_mode) { + Err(_) => mode_function_mask &= !ConfigMode::LSB.bits(), + Ok(()) => { + get_lsb_mode = self.mode()?; + if (get_lsb_mode & LnxSpiMode::LSB_FIRST.bits()) == LnxSpiMode::LSB_FIRST.bits() + { + mode_function_mask |= ConfigMode::LSB.bits(); + } else { + mode_function_mask &= !ConfigMode::LSB.bits(); + } + } + }; + } + + // detect supported LOOP setting + let mut set_loop_mode = origin_mode; + let get_loop_mode; + + if (origin_mode & LnxSpiMode::LOOP.bits()) == LnxSpiMode::LOOP.bits() { + mode_function_mask |= ConfigMode::LOOP.bits(); + } else { + set_loop_mode |= LnxSpiMode::LOOP.bits(); + match self.set_mode(set_loop_mode) { + Err(_) => mode_function_mask &= !ConfigMode::LOOP.bits(), + Ok(()) => { + get_loop_mode = self.mode()?; + if (get_loop_mode & LnxSpiMode::LOOP.bits()) == LnxSpiMode::LOOP.bits() { + mode_function_mask |= ConfigMode::LOOP.bits(); + } else { + mode_function_mask &= !ConfigMode::LOOP.bits(); + } + } + }; + } + + 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: bits_per_word_mask.into(), + mode_func_supported: mode_function_mask.into(), + max_freq_hz: max_speed_hz.into(), + max_word_delay_ns: 0.into(), + max_cs_setup_ns: 0.into(), + max_cs_hold_ns: 0.into(), + max_cs_inactive_ns: 0.into(), + }) + } +} + +#[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<()> { + let mut msgs: Vec = Vec::with_capacity(reqs.len()); + let mut tx_buf_ptr: *mut u8; + let mut rx_buf_ptr: *mut u8; + + let saved_mode: u32 = self.device.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, + _padding: 0, + }); + + if (req.mode & ReqMode::CPHA.bits()) == ReqMode::CPHA.bits() { + trans_mode |= LnxSpiMode::CPHA.bits(); + } else { + trans_mode &= !LnxSpiMode::CPHA.bits(); + } + if (req.mode & ReqMode::CPOL.bits()) == ReqMode::CPOL.bits() { + trans_mode |= LnxSpiMode::CPOL.bits(); + } else { + trans_mode &= !LnxSpiMode::CPOL.bits(); + } + if (req.mode & ReqMode::CS_HIGH.bits()) == ReqMode::CS_HIGH.bits() { + trans_mode |= LnxSpiMode::CS_HIGH.bits(); + } else { + trans_mode &= !LnxSpiMode::CS_HIGH.bits(); + } + if (req.mode & ReqMode::LSB_FIRST.bits()) == ReqMode::LSB_FIRST.bits() { + trans_mode |= LnxSpiMode::LSB_FIRST.bits(); + } else { + trans_mode &= !LnxSpiMode::LSB_FIRST.bits(); + } + if (req.mode & ReqMode::LOOP.bits()) == ReqMode::LOOP.bits() { + trans_mode |= LnxSpiMode::LOOP.bits(); + } else { + trans_mode &= !LnxSpiMode::LOOP.bits(); + } + } + + self.device.set_mode(trans_mode)?; + self.device.rdwr(&mut msgs)?; + self.device.set_mode(saved_mode)?; + + Ok(()) + } + + pub(crate) fn check_trans_params( + &self, + reqs: &mut [SpiTransReq], + param_stat: &mut Vec, + ) -> bool { + let mut reqs_valid: bool = true; + let mut trans_params_valid: bool; + + for req in reqs { + trans_params_valid = true; + + if self.config.cs_max_number < req.cs_id { + trans_params_valid = false; + } + + if (self.config.bits_per_word_mask != 0) + && ((1 << (req.bits_per_word - 1)) & self.config.bits_per_word_mask.to_native()) + == 0 + { + println!( + "cuihaixu: self.config.bits_per_word_mask {}", + self.config.bits_per_word_mask.to_native() + ); + trans_params_valid = false; + } + + if ((self.config.tx_nbits_supported & ConfigNbits::DUAL.bits()) == 0 + && req.tx_nbits == 2) + || ((self.config.tx_nbits_supported & ConfigNbits::QUAD.bits()) == 0 + && req.tx_nbits == 4) + || ((self.config.tx_nbits_supported & ConfigNbits::OCTAL.bits()) == 0 + && req.tx_nbits == 8) + || ((req.tx_nbits != 0) + && (req.tx_nbits != 1) + && (req.tx_nbits != 2) + && (req.tx_nbits != 4) + && (req.tx_nbits != 8)) + { + trans_params_valid = false; + } + + if ((self.config.rx_nbits_supported & ConfigNbits::DUAL.bits()) == 0 + && req.rx_nbits == 2) + || ((self.config.rx_nbits_supported & ConfigNbits::QUAD.bits()) == 0 + && req.rx_nbits == 4) + || ((self.config.rx_nbits_supported & ConfigNbits::OCTAL.bits()) == 0 + && req.rx_nbits == 8) + || ((req.rx_nbits != 0) + && (req.rx_nbits != 1) + && (req.rx_nbits != 2) + && (req.rx_nbits != 4) + && (req.rx_nbits != 8)) + { + trans_params_valid = false; + } + + if (req.mode & ReqMode::CPHA.bits() == 0) + && (self.config.mode_func_supported.to_native() & ConfigMode::CPHA_0.bits() == 0) + { + trans_params_valid = false; + } + + if (req.mode & ReqMode::CPHA.bits() == ReqMode::CPHA.bits()) + && (self.config.mode_func_supported.to_native() & ConfigMode::CPHA_1.bits() == 0) + { + trans_params_valid = false; + } + + if (req.mode & ReqMode::CPOL.bits() == 0) + && (self.config.mode_func_supported.to_native() & ConfigMode::CPOL_0.bits() == 0) + { + trans_params_valid = false; + } + + if (req.mode & ReqMode::CPOL.bits() == ReqMode::CPOL.bits()) + && (self.config.mode_func_supported.to_native() & ConfigMode::CPOL_1.bits() == 0) + { + trans_params_valid = false; + } + + if (req.mode & ReqMode::CS_HIGH.bits() == ReqMode::CS_HIGH.bits()) + && (self.config.mode_func_supported.to_native() & ConfigMode::CS_HIGH.bits() == 0) + { + trans_params_valid = false; + } + + if (req.mode & ReqMode::LSB_FIRST.bits() == ReqMode::LSB_FIRST.bits()) + && (self.config.mode_func_supported.to_native() & ConfigMode::LSB.bits() == 0) + { + trans_params_valid = false; + } + + if (req.mode & ReqMode::LOOP.bits() == ReqMode::LOOP.bits()) + && (self.config.mode_func_supported.to_native() & ConfigMode::LOOP.bits() == 0) + { + println!("reach here???"); + println!( + "cuihaixu: self.config.mode_func_supported.to_native {}", + self.config.mode_func_supported.to_native() + ); + println!("cuihaixu {}", ConfigMode::LOOP.bits()); + trans_params_valid = false; + } + + if (self.config.max_freq_hz != 0) + && (req.speed_hz > self.config.max_freq_hz.to_native()) + { + trans_params_valid = false; + } + + param_stat.push(trans_params_valid); + if !trans_params_valid { + reqs_valid = false; + } + } + + reqs_valid + } +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use std::path::PathBuf; + use vmm_sys_util::tempfile::TempFile; + + // Update read-buffer of each write-buffer with index + 1 value. + pub fn update_rdwr_buf(buf: u64, len: u32) { + let buf_ptr = buf as *mut u8; + + // SAFETY: Safe as the buf is from the request tx/rx filed + unsafe { + for i in 0..len { + ptr::write(buf_ptr.add(i as usize), i as u8); + } + } + } + + // Verify the write-buffer passed to us + pub fn verify_rdwr_buf(buf: u64, len: u32) { + let buf_ptr = buf as *mut u8; + + // SAFETY: Safe as the buf is from the request tx/rx filed + unsafe { + for i in 0..len { + assert_eq!(ptr::read(buf_ptr.add(i as usize)), i as u8); + } + } + } + + #[derive(Debug)] + pub struct DummyDevice { + mode_result: Result, + set_mode_result: Result<()>, + 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: 0.into(), + mode_func_supported: 0xf.into(), + max_freq_hz: 10000.into(), + max_word_delay_ns: 0.into(), + max_cs_setup_ns: 0.into(), + max_cs_hold_ns: 0.into(), + max_cs_inactive_ns: 0.into(), + }; + + Self { + mode_result: Ok(0), + set_mode_result: Ok(()), + 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: &Path) -> Result { + Ok(DummyDevice::default()) + } + + fn max_speed_hz(&self) -> Result { + self.max_speed_result + } + + fn set_max_speed_hz(&self, _max_speed_hz: u32) -> Result<()> { + self.set_max_speed_result + } + + fn bits_per_word(&self) -> Result { + self.get_bpw_result + } + + fn set_bits_per_word(&self, _bpw: u8) -> Result<()> { + self.set_bpw_result + } + + fn mode(&self) -> Result { + self.mode_result + } + + fn set_mode(&self, _mode: u32) -> Result<()> { + self.set_mode_result + } + + fn rdwr(&self, reqs: &mut [SpiIocTransfer]) -> Result<()> { + for req in reqs { + if req.tx_buf != 0 { + verify_rdwr_buf(req.tx_buf, req.len); + } + + if req.rx_buf != 0 { + update_rdwr_buf(req.rx_buf, req.len); + } + } + + 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.as_ptr() as u64, req.trans_len); + } + } + } + + impl SpiController { + pub(crate) fn update_bpw_mask_config(&mut self, bpw_mask: u32) { + self.config.bits_per_word_mask = bpw_mask.into(); + } + + pub(crate) fn update_mode_config(&mut self, mode: u32) { + self.config.mode_func_supported = mode.into(); + } + } + + #[test] + fn test_spi_transfer() { + let dummy_device = PathBuf::from("spidev0.0"); + let spi_dummy_ctrl = SpiController::new(DummyDevice::open(&dummy_device).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(req.tx_buf.as_ptr() as u64, req.trans_len); + } + } + + spi_dummy_ctrl.transfer(&mut reqs).unwrap(); + verify_rdwr_data(&reqs); + } + + #[test] + fn test_phys_device_failure() { + // Open failure + + let invalid_spi_dev = PathBuf::from("/dev/spidev-invalid"); + assert_eq!( + PhysDevice::open(&invalid_spi_dev).unwrap_err(), + Error::DeviceOpenFailed + ); + + let file = TempFile::new().unwrap(); + //let dev = PhysDevice::open(file.as_path().to_str().unwrap()).unwrap(); + let dev = PhysDevice::open(file.as_path()).unwrap(); + + assert_eq!( + dev.mode().unwrap_err(), + Error::IoctlFailure("mode", IoError::last()) + ); + + assert_eq!( + dev.set_mode(0).unwrap_err(), + Error::IoctlFailure("set_mode", IoError::last()) + ); + + assert_eq!( + dev.max_speed_hz().unwrap_err(), + Error::IoctlFailure("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.bits_per_word().unwrap_err(), + Error::IoctlFailure("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("max_speed_hz", IoError::last()) + ); + + // rdwr failure + let mut reqs = [SpiIocTransfer { + tx_buf: vec![7, 4].as_ptr() as u64, + rx_buf: vec![7, 4].as_ptr() as u64, + len: 2, + speed_hz: 10000, + delay_usecs: 0, + bits_per_word: 8, + cs_change: 1, + tx_nbits: 1, + rx_nbits: 1, + word_delay_usecs: 0, + _padding: 0, + }]; + assert_eq!( + dev.rdwr(&mut reqs).unwrap_err(), + Error::IoctlFailure("rdwr", IoError::last()) + ); + } + + #[test] + fn test_spi_ioctl_cmd() { + assert_eq!(SPI_IOC_RD_BITS_PER_WORD(), 0x80016b03); + assert_eq!(SPI_IOC_WR_BITS_PER_WORD(), 0x40016b03); + assert_eq!(SPI_IOC_RD_MAX_SPEED_HZ(), 0x80046b04); + assert_eq!(SPI_IOC_WR_MAX_SPEED_HZ(), 0x40046b04); + assert_eq!(SPI_IOC_RD_MODE32(), 0x80046b05); + assert_eq!(SPI_IOC_WR_MODE32(), 0x40046b05); + assert_eq!(spi_ioc_message(1), 0x40206b00); + assert_eq!(spi_ioc_message(2), 0x40406b00); + } + + #[test] + fn test_spi_ctrl_param_check() { + let dummy_device = PathBuf::from("spidev0.0"); + let mut spi_dummy_ctrl = + SpiController::new(DummyDevice::open(&dummy_device).unwrap()).unwrap(); + let mut reqs: Vec = Vec::new(); + let mut param_stats: Vec = Vec::new(); + + // valid transfer request + let trans_header = 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: 1, + rx_nbits: 1, + word_delay_usecs: 0, + cs_id: 0, + }; + + reqs.push(trans_header); + param_stats.push(false); + + assert!(spi_dummy_ctrl.check_trans_params(&mut reqs, &mut param_stats)); + assert!(param_stats.pop().unwrap()); + + reqs.pop(); + + // transfer request with invalid cs_id + let trans_header = 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: 1, + rx_nbits: 1, + word_delay_usecs: 0, + cs_id: 3, + }; + + reqs.push(trans_header); + param_stats.push(true); + + assert!(!spi_dummy_ctrl.check_trans_params(&mut reqs, &mut param_stats)); + assert!(!param_stats.pop().unwrap()); + + reqs.pop(); + + // transfer request with invalid freq + let trans_header = SpiTransReq { + tx_buf: Vec::::new(), + rx_buf: vec![0; 15], + trans_len: 15, + speed_hz: 1000000, + 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, + }; + + reqs.push(trans_header); + param_stats.push(true); + + assert!(!spi_dummy_ctrl.check_trans_params(&mut reqs, &mut param_stats)); + assert!(!param_stats.pop().unwrap()); + + reqs.pop(); + + // transfer request with invalid mode + let trans_header = SpiTransReq { + tx_buf: Vec::::new(), + rx_buf: vec![0; 15], + trans_len: 15, + speed_hz: 10000, + mode: 0x10, + delay_usecs: 0, + bits_per_word: 8, + cs_change: 0, + tx_nbits: 1, + rx_nbits: 1, + word_delay_usecs: 0, + cs_id: 0, + }; + + reqs.push(trans_header); + param_stats.push(true); + + assert!(!spi_dummy_ctrl.check_trans_params(&mut reqs, &mut param_stats)); + assert!(!param_stats.pop().unwrap()); + + reqs.pop(); + + // transfer request with invalid tx_nbits + let trans_header1 = SpiTransReq { + tx_buf: Vec::::new(), + rx_buf: vec![0; 15], + trans_len: 15, + speed_hz: 10000, + mode: 0x10, + delay_usecs: 0, + bits_per_word: 8, + cs_change: 0, + tx_nbits: 2, + rx_nbits: 1, + word_delay_usecs: 0, + cs_id: 0, + }; + + let trans_header2 = 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: 1, + rx_nbits: 1, + word_delay_usecs: 0, + cs_id: 0, + }; + + reqs.push(trans_header1); + param_stats.push(true); + reqs.push(trans_header2); + param_stats.push(false); + + assert!(!spi_dummy_ctrl.check_trans_params(&mut reqs, &mut param_stats)); + assert!(param_stats.pop().unwrap()); + assert!(!param_stats.pop().unwrap()); + + reqs.pop(); + reqs.pop(); + + spi_dummy_ctrl.update_bpw_mask_config(0xf); + let trans_header = 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: 1, + rx_nbits: 1, + word_delay_usecs: 0, + cs_id: 0, + }; + + reqs.push(trans_header); + param_stats.push(false); + + assert!(!spi_dummy_ctrl.check_trans_params(&mut reqs, &mut param_stats)); + assert!(!param_stats.pop().unwrap()); + + reqs.pop(); + + let trans_header = SpiTransReq { + tx_buf: Vec::::new(), + rx_buf: vec![0; 15], + trans_len: 15, + speed_hz: 0, + mode: 0, + delay_usecs: 0, + bits_per_word: 1, + cs_change: 0, + tx_nbits: 1, + rx_nbits: 1, + word_delay_usecs: 0, + cs_id: 0, + }; + + reqs.push(trans_header); + param_stats.push(false); + + assert!(spi_dummy_ctrl.check_trans_params(&mut reqs, &mut param_stats)); + assert!(param_stats.pop().unwrap()); + + reqs.pop(); + + spi_dummy_ctrl.update_mode_config(0x4f); + + let trans_header = SpiTransReq { + tx_buf: Vec::::new(), + rx_buf: vec![0; 15], + trans_len: 15, + speed_hz: 0, + mode: 0x10, + delay_usecs: 0, + bits_per_word: 1, + cs_change: 0, + tx_nbits: 1, + rx_nbits: 1, + word_delay_usecs: 0, + cs_id: 0, + }; + + reqs.push(trans_header); + param_stats.push(true); + + assert!(spi_dummy_ctrl.check_trans_params(&mut reqs, &mut param_stats)); + assert!(param_stats.pop().unwrap()); + + reqs.pop(); + + let trans_header = SpiTransReq { + tx_buf: Vec::::new(), + rx_buf: vec![0; 15], + trans_len: 15, + speed_hz: 0, + mode: 0x14, + delay_usecs: 0, + bits_per_word: 1, + cs_change: 0, + tx_nbits: 1, + rx_nbits: 1, + word_delay_usecs: 0, + cs_id: 0, + }; + + reqs.push(trans_header); + param_stats.push(true); + + assert!(!spi_dummy_ctrl.check_trans_params(&mut reqs, &mut param_stats)); + assert!(!param_stats.pop().unwrap()); + + reqs.pop(); + + spi_dummy_ctrl.update_mode_config(0x5f); + + let trans_header = SpiTransReq { + tx_buf: Vec::::new(), + rx_buf: vec![0; 15], + trans_len: 15, + speed_hz: 0, + mode: 0x14, + delay_usecs: 0, + bits_per_word: 1, + cs_change: 0, + tx_nbits: 1, + rx_nbits: 1, + word_delay_usecs: 0, + cs_id: 0, + }; + + reqs.push(trans_header); + param_stats.push(true); + + assert!(spi_dummy_ctrl.check_trans_params(&mut reqs, &mut param_stats)); + assert!(param_stats.pop().unwrap()); + + reqs.pop(); + + let trans_header = SpiTransReq { + tx_buf: Vec::::new(), + rx_buf: vec![0; 15], + trans_len: 15, + speed_hz: 0, + mode: 0x8, + delay_usecs: 0, + bits_per_word: 1, + cs_change: 0, + tx_nbits: 1, + rx_nbits: 1, + word_delay_usecs: 0, + cs_id: 0, + }; + + reqs.push(trans_header); + param_stats.push(true); + + assert!(!spi_dummy_ctrl.check_trans_params(&mut reqs, &mut param_stats)); + assert!(!param_stats.pop().unwrap()); + + reqs.pop(); + + spi_dummy_ctrl.update_mode_config(0x2f); + + let trans_header = SpiTransReq { + tx_buf: Vec::::new(), + rx_buf: vec![0; 15], + trans_len: 15, + speed_hz: 0, + mode: 0x8, + delay_usecs: 0, + bits_per_word: 1, + cs_change: 0, + tx_nbits: 1, + rx_nbits: 1, + word_delay_usecs: 0, + cs_id: 0, + }; + + reqs.push(trans_header); + param_stats.push(true); + + assert!(spi_dummy_ctrl.check_trans_params(&mut reqs, &mut param_stats)); + assert!(param_stats.pop().unwrap()); + + reqs.pop(); + } +} diff --git a/vhost-device-spi/src/vhu_spi.rs b/vhost-device-spi/src/vhu_spi.rs new file mode 100644 index 000000000..9a10d86a0 --- /dev/null +++ b/vhost-device-spi/src/vhu_spi.rs @@ -0,0 +1,1470 @@ +// 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 std::{ + cmp::Ordering, + convert::From, + io::{self, Read, Result as IoResult, Write}, + mem::size_of, + sync::Arc, +}; + +use log::warn; +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; +use virtio_queue::QueueOwnedT; +use vm_memory::{ + ByteValued, 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("TX length and RX length are both zero")] + TransZeroLength, + #[error("Failed to handle event, didn't match EPOLLIN")] + HandleEventNotEpollIn, + #[error("Failed to handle unknown event {0}")] + HandleEventUnknown(u16), + #[error("Invalid descriptor size, expected: {0}, found: {1}")] + UnexpectedDescriptorSize(usize, u32), + #[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, + #[error("No memory configured")] + NoMemoryConfigured, +} + +impl From for io::Error { + fn from(e: Error) -> Self { + io::Error::new(io::ErrorKind::Other, e) + } +} + +/// The final status written by the device +#[derive(Clone, Copy, Debug)] +#[repr(u8)] +enum ResponseStatus { + #[doc(alias = "VIRTIO_SPI_TRANS_OK")] + TransOk = 0, + #[doc(alias = "VIRTIO_SPI_PARAM_ERR")] + ParamErr = 1, + #[doc(alias = "VIRTIO_SPI_TRANS_ERR")] + TransErr = 2, +} + +#[derive(Copy, Clone, Default, Debug)] +#[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 {} + +/// 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 {} + +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(); + + let Some(ref atomic_mem) = self.mem else { + return Err(Error::NoMemoryConfigured); + }; + + if requests.is_empty() { + return Ok(true); + } + + // Iterate over each SPI request. + for desc_chain in requests.clone() { + let mem = atomic_mem.memory(); + let mut tx_buf: Vec; + let mut trans_len: u32 = 0; + + let mut reader = desc_chain + .clone() + .reader(&mem) + .map_err(|_| Error::DescriptorReadFailed)?; + + let writter = desc_chain + .clone() + .writer(&mem) + .map_err(|_| Error::DescriptorReadFailed)?; + + match reader + .available_bytes() + .cmp(&size_of::()) + { + Ordering::Less => { + return Err(Error::UnexpectedDescriptorSize( + size_of::(), + reader.available_bytes() as u32, + )); + } + Ordering::Equal => { + tx_buf = Vec::new(); + } + Ordering::Greater => { + trans_len = + (reader.available_bytes() - size_of::()) as u32; + let mut reader_content = reader + .split_at(size_of::()) + .map_err(|_| Error::DescriptorReadFailed)?; + + tx_buf = vec![0; trans_len as usize]; + + trans_len = reader_content + .read(&mut tx_buf) + .map_err(|_| Error::DescriptorReadFailed)? + as u32; + } + } + + let rx_buf: Vec = match writter.available_bytes().cmp(&size_of::()) { + Ordering::Less => { + return Err(Error::UnexpectedDescriptorSize( + size_of::(), + writter.available_bytes() as u32, + )); + } + Ordering::Equal => Vec::new(), + Ordering::Greater => { + if trans_len != 0 + && trans_len != (writter.available_bytes() - size_of::()) as u32 + { + return Err(Error::TxRxTrnasLenNotEqual( + trans_len, + (writter.available_bytes() - size_of::()) as u32, + )); + } else if trans_len == 0 { + trans_len = (writter.available_bytes() - size_of::()) as u32; + } + vec![0; trans_len as usize] + } + }; + + if trans_len == 0 { + return Err(Error::TransZeroLength); + } + + let out_hdr = reader + .read_obj::() + .map_err(|_| Error::DescriptorReadFailed)?; + + 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 mut req_param_stat = Vec::with_capacity(requests.len()); + + if !self + .spi_ctrl + .check_trans_params(&mut reqs, &mut req_param_stat) + { + for (i, desc_chain) in requests.iter().enumerate() { + let mut len = size_of::() as u32; + let mem = atomic_mem.memory(); + let req_param_valid = match req_param_stat[i] { + true => VirtioSpiTransferResult { + status: ResponseStatus::TransOk as u8, + }, + _ => VirtioSpiTransferResult { + status: ResponseStatus::ParamErr as u8, + }, + }; + + let mut writter = desc_chain + .clone() + .writer(&mem) + .map_err(|_| Error::DescriptorReadFailed)?; + + if writter.available_bytes() > size_of::() { + let rx_len = (writter.available_bytes() - size_of::()) as u32; + let mut writter_status = writter + .split_at(rx_len as usize) + .map_err(|_| Error::DescriptorReadFailed)?; + + writter_status + .write_obj(req_param_valid) + .map_err(|_| Error::DescriptorWriteFailed)?; + + len += rx_len; + } else { + writter + .write_obj(req_param_valid) + .map_err(|_| Error::DescriptorWriteFailed)?; + } + + // Write the transfer status + if vring.add_used(desc_chain.head_index(), len).is_err() { + warn!("Couldn't return used descriptors to the ring"); + } + } + } else { + match self.spi_ctrl.transfer(&mut reqs) { + Ok(()) => { + let in_hdr = VirtioSpiTransferResult { + status: ResponseStatus::TransOk as u8, + }; + for (desc_chain, req) in requests.iter().zip(reqs.iter()) { + let mut len = size_of::() as u32; + let mem = atomic_mem.memory(); + + let mut writter = desc_chain + .clone() + .writer(&mem) + .map_err(|_| Error::DescriptorReadFailed)?; + + if writter.available_bytes() > size_of::() { + let rx_len = (writter.available_bytes() - size_of::()) as u32; + let mut writter_status = writter + .split_at(rx_len as usize) + .map_err(|_| Error::DescriptorReadFailed)?; + + writter_status + .write_obj(in_hdr) + .map_err(|_| Error::DescriptorWriteFailed)?; + + writter + .write(&req.rx_buf) + .map_err(|_| Error::DescriptorWriteFailed)?; + + len += rx_len; + } else { + writter + .write_obj(in_hdr) + .map_err(|_| Error::DescriptorWriteFailed)?; + } + + // Write the transfer status + if vring.add_used(desc_chain.head_index(), len).is_err() { + warn!("Couldn't return used descriptors to the ring"); + } + } + } + Err(_) => { + let in_hdr = VirtioSpiTransferResult { + status: ResponseStatus::TransErr as u8, + }; + + for desc_chain in requests.clone() { + let len = size_of::() as u32; + let mem = atomic_mem.memory(); + + let mut writter = desc_chain + .clone() + .writer(&mem) + .map_err(|_| Error::DescriptorReadFailed)?; + + writter + .write_obj(in_hdr) + .map_err(|_| Error::DescriptorWriteFailed)?; + + // Write the transfer status + 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 Some(ref atomic_mem) = self.mem else { + return Err(Error::NoMemoryConfigured); + }; + let requests: Vec<_> = vring + .get_mut() + .get_queue_mut() + .iter(atomic_mem.memory()) + .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) { + self.event_idx = enabled; + } + + fn update_memory(&mut self, mem: GuestMemoryAtomic) -> IoResult<()> { + self.mem = Some(mem); + Ok(()) + } + + fn get_config(&self, offset: u32, size: u32) -> Vec { + self.spi_ctrl.config().as_slice()[(offset as usize)..][..(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)?; + } + } + + _ => { + return Err(Error::HandleEventUnknown(device_event).into()); + } + } + Ok(()) + } + + fn exit_event(&self, _thread_index: usize) -> Option { + self.exit_event.try_clone().ok() + } +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + use std::slice::from_raw_parts; + use virtio_bindings::bindings::virtio_ring::{VRING_DESC_F_NEXT, VRING_DESC_F_WRITE}; + use virtio_queue::{mock::MockSplitQueue, Descriptor}; + use vm_memory::{Bytes, GuestAddress, GuestMemoryAtomic, GuestMemoryMmap}; + + use super::Error; + use super::*; + use crate::spi::tests::{verify_rdwr_buf, DummyDevice}; + + // Prepares descriptor chains + fn setup_descs(descs: &[Descriptor]) -> (VringRwLock, GuestMemoryAtomic) { + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000_0000)]).unwrap(), + ); + let mem_handle = mem.memory(); + + let queue = MockSplitQueue::new(&*mem_handle, 16); + + let mut modified_descs: Vec = Vec::with_capacity(descs.len()); + + // Use this tag to indicate the start of request + let mut request_head: bool = true; + + for (idx, desc) in descs.iter().enumerate() { + let next = if desc.flags() & VRING_DESC_F_NEXT as u16 == 0 { + // This is the last descriptor of the request. Next descriptor is the head + // of the next request, so set tag as true. + request_head = true; + 0 + } else { + // If not the request header and readable, the descriptor indicates the tx_buf. + // Then update the tx_buf to test the transfer process. + if !request_head && desc.flags() & VRING_DESC_F_WRITE as u16 == 0 { + let tx_data: Vec = (0..desc.len() as u8).collect(); + + mem.memory() + .write(&tx_data, desc.addr()) + .expect("writing to succeed"); + } + + request_head = false; + idx as u16 + 1 + }; + + modified_descs.push(Descriptor::new( + desc.addr().0, + desc.len(), + desc.flags(), + next, + )); + } + + queue + .build_multiple_desc_chains(&modified_descs[..]) + .unwrap(); + + let vring = VringRwLock::new(mem.clone(), 16).unwrap(); + + vring.set_queue_size(16); + vring + .set_queue_info( + queue.desc_table_addr().0, + queue.avail_addr().0, + queue.used_addr().0, + ) + .unwrap(); + vring.set_queue_ready(true); + + (vring, mem) + } + + // Validate descriptor chains after processing them, checks pass/failure of + // operation and the value of the buffers updated by the `DummyDevice`. + fn validate_rx_data(mem: GuestMemoryAtomic, rx_addr: u64, rx_len: u32) { + let mut rx_buf: Vec = vec![0; rx_len as usize]; + + mem.memory() + .read(&mut rx_buf, GuestAddress(rx_addr)) + .expect("reading to succeed"); + + verify_rdwr_buf(rx_buf.as_ptr() as u64, rx_len); + } + + fn validate_trans_result( + mem: GuestMemoryAtomic, + result_addr: u64, + status: u8, + ) { + let in_hdr = mem + .memory() + .read_obj::(GuestAddress(result_addr)) + .expect("reading to succeed"); + + assert_eq!(in_hdr.status, status); + } + + #[test] + fn process_requests_success() { + let spi_dummy_ctrl = + SpiController::new(DummyDevice::open(&PathBuf::from("spidev0.0")).unwrap()).unwrap(); + let mut backend = VhostUserSpiBackend::new(Arc::new(spi_dummy_ctrl)).unwrap(); + + // Parameters to create two requests + let trans_header_addr1: u64 = 0x10_0000; + let tx_buf_addr1: u64 = 0x20_0000; + let rx_buf_addr1: u64 = 0x30_0000; + let trans_result_addr1: u64 = 0x40_0000; + + let trans_header_addr2: u64 = 0x50_0000; + let tx_buf_addr2: u64 = 0x60_0000; + let rx_buf_addr2: u64 = 0x70_0000; + let trans_result_addr2: u64 = 0x80_0000; + + // Valid single write request + let to_descs = [ + Descriptor::new( + trans_header_addr1, + size_of::() as u32, + VRING_DESC_F_NEXT as u16, + 0, + ), + Descriptor::new(tx_buf_addr1, 30, VRING_DESC_F_NEXT as u16, 0), + Descriptor::new( + trans_result_addr1, + size_of::() as u32, + VRING_DESC_F_WRITE as u16, + 0, + ), + ]; + + let (vring, mem) = setup_descs(&to_descs); + + let out_hdr = VirtioSpiTransferHead { + chip_select_id: 0, + bits_per_word: 8, + cs_change: 0, + tx_nbits: 1, + rx_nbits: 1, + reserved1: 0, + reserved2: 0, + reserved3: 0, + mode: From::from(0), + freq: From::from(10000), + 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), + }; + + mem.memory() + .write_obj(out_hdr, GuestAddress(trans_header_addr1)) + .expect("writing to succeed"); + + backend.update_memory(mem.clone()).unwrap(); + backend.process_queue(&vring).unwrap(); + + validate_trans_result( + mem.clone(), + trans_result_addr1, + ResponseStatus::TransOk as u8, + ); + + // Valid single read request + let ro_descs = [ + Descriptor::new( + trans_header_addr1, + size_of::() as u32, + VRING_DESC_F_NEXT as u16, + 0, + ), + Descriptor::new( + rx_buf_addr1, + 30, + (VRING_DESC_F_NEXT | VRING_DESC_F_WRITE) as u16, + 0, + ), + Descriptor::new( + trans_result_addr1, + size_of::() as u32, + VRING_DESC_F_WRITE as u16, + 0, + ), + ]; + + let (vring, mem) = setup_descs(&ro_descs); + + let out_hdr = VirtioSpiTransferHead { + chip_select_id: 0, + bits_per_word: 8, + cs_change: 0, + tx_nbits: 1, + rx_nbits: 1, + reserved1: 0, + reserved2: 0, + reserved3: 0, + mode: From::from(0), + freq: From::from(10000), + 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), + }; + + mem.memory() + .write_obj(out_hdr, GuestAddress(trans_header_addr1)) + .expect("writing to succeed"); + + backend.update_memory(mem.clone()).unwrap(); + backend.process_queue(&vring).unwrap(); + + validate_trans_result( + mem.clone(), + trans_result_addr1, + ResponseStatus::TransOk as u8, + ); + + validate_rx_data(mem.clone(), rx_buf_addr1, 30); + + // Valid mixed read-write request + let tx_rx_descs = [ + Descriptor::new( + trans_header_addr1, + size_of::() as u32, + VRING_DESC_F_NEXT as u16, + 0, + ), + Descriptor::new(tx_buf_addr1, 30, VRING_DESC_F_NEXT as u16, 0), + Descriptor::new( + rx_buf_addr1, + 30, + (VRING_DESC_F_NEXT | VRING_DESC_F_WRITE) as u16, + 0, + ), + Descriptor::new( + trans_result_addr1, + size_of::() as u32, + VRING_DESC_F_WRITE as u16, + 0, + ), + ]; + + let (vring, mem) = setup_descs(&tx_rx_descs); + + let out_hdr = VirtioSpiTransferHead { + chip_select_id: 0, + bits_per_word: 8, + cs_change: 0, + tx_nbits: 1, + rx_nbits: 1, + reserved1: 0, + reserved2: 0, + reserved3: 0, + mode: From::from(0), + freq: From::from(10000), + 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), + }; + + mem.memory() + .write_obj(out_hdr, GuestAddress(trans_header_addr1)) + .expect("writing to succeed"); + + backend.update_memory(mem.clone()).unwrap(); + backend.process_queue(&vring).unwrap(); + + validate_trans_result( + mem.clone(), + trans_result_addr1, + ResponseStatus::TransOk as u8, + ); + + validate_rx_data(mem.clone(), rx_buf_addr1, 30); + + // Valid multiple requests + let multi_reqs_descs = [ + Descriptor::new( + trans_header_addr1, + size_of::() as u32, + VRING_DESC_F_NEXT as u16, + 0, + ), + Descriptor::new(tx_buf_addr1, 30, VRING_DESC_F_NEXT as u16, 0), + Descriptor::new( + rx_buf_addr1, + 30, + (VRING_DESC_F_NEXT | VRING_DESC_F_WRITE) as u16, + 0, + ), + Descriptor::new( + trans_result_addr1, + size_of::() as u32, + VRING_DESC_F_WRITE as u16, + 0, + ), + Descriptor::new( + trans_header_addr2, + size_of::() as u32, + VRING_DESC_F_NEXT as u16, + 0, + ), + Descriptor::new(tx_buf_addr2, 16, VRING_DESC_F_NEXT as u16, 0), + Descriptor::new( + rx_buf_addr2, + 16, + (VRING_DESC_F_NEXT | VRING_DESC_F_WRITE) as u16, + 0, + ), + Descriptor::new( + trans_result_addr2, + size_of::() as u32, + VRING_DESC_F_WRITE as u16, + 0, + ), + ]; + + let (vring, mem) = setup_descs(&multi_reqs_descs); + + let out_hdr = VirtioSpiTransferHead { + chip_select_id: 0, + bits_per_word: 8, + cs_change: 0, + tx_nbits: 1, + rx_nbits: 1, + reserved1: 0, + reserved2: 0, + reserved3: 0, + mode: From::from(0), + freq: From::from(10000), + 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), + }; + + mem.memory() + .write_obj(out_hdr, GuestAddress(trans_header_addr1)) + .expect("writing to succeed"); + + mem.memory() + .write_obj(out_hdr, GuestAddress(trans_header_addr2)) + .expect("writing to succeed"); + + backend.update_memory(mem.clone()).unwrap(); + backend.process_queue(&vring).unwrap(); + + validate_trans_result( + mem.clone(), + trans_result_addr1, + ResponseStatus::TransOk as u8, + ); + + validate_rx_data(mem.clone(), rx_buf_addr1, 30); + + validate_trans_result( + mem.clone(), + trans_result_addr2, + ResponseStatus::TransOk as u8, + ); + + validate_rx_data(mem.clone(), rx_buf_addr2, 16); + + // unsupported LOOP mode, should filter by parameter check + let mode_invalid_descs = [ + Descriptor::new( + trans_header_addr1, + size_of::() as u32, + VRING_DESC_F_NEXT as u16, + 0, + ), + Descriptor::new(tx_buf_addr1, 30, VRING_DESC_F_NEXT as u16, 0), + Descriptor::new( + trans_result_addr1, + size_of::() as u32, + VRING_DESC_F_WRITE as u16, + 0, + ), + ]; + + let (vring, mem) = setup_descs(&mode_invalid_descs); + + let out_hdr = VirtioSpiTransferHead { + chip_select_id: 0, + bits_per_word: 8, + cs_change: 0, + tx_nbits: 1, + rx_nbits: 1, + reserved1: 0, + reserved2: 0, + reserved3: 0, + mode: From::from(0x10), + freq: From::from(10000), + 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), + }; + + mem.memory() + .write_obj(out_hdr, GuestAddress(trans_header_addr1)) + .expect("writing to succeed"); + + backend.update_memory(mem.clone()).unwrap(); + backend.process_queue(&vring).unwrap(); + + validate_trans_result( + mem.clone(), + trans_result_addr1, + ResponseStatus::ParamErr as u8, + ); + + // unsupported tx_nbits, should filter by parameter check + let mode_invalid_descs = [ + Descriptor::new( + trans_header_addr1, + size_of::() as u32, + VRING_DESC_F_NEXT as u16, + 0, + ), + Descriptor::new(tx_buf_addr1, 30, VRING_DESC_F_NEXT as u16, 0), + Descriptor::new( + trans_result_addr1, + size_of::() as u32, + VRING_DESC_F_WRITE as u16, + 0, + ), + ]; + + let (vring, mem) = setup_descs(&mode_invalid_descs); + + let out_hdr = VirtioSpiTransferHead { + chip_select_id: 0, + bits_per_word: 8, + cs_change: 0, + tx_nbits: 2, + rx_nbits: 1, + reserved1: 0, + reserved2: 0, + reserved3: 0, + mode: From::from(0), + freq: From::from(10000), + 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), + }; + + mem.memory() + .write_obj(out_hdr, GuestAddress(trans_header_addr1)) + .expect("writing to succeed"); + + backend.update_memory(mem.clone()).unwrap(); + backend.process_queue(&vring).unwrap(); + + validate_trans_result( + mem.clone(), + trans_result_addr1, + ResponseStatus::ParamErr as u8, + ); + + // Valid multiple requests which contains invalid request header + let multi_reqs_descs = [ + Descriptor::new( + trans_header_addr1, + size_of::() as u32, + VRING_DESC_F_NEXT as u16, + 0, + ), + Descriptor::new(tx_buf_addr1, 30, VRING_DESC_F_NEXT as u16, 0), + Descriptor::new( + rx_buf_addr1, + 30, + (VRING_DESC_F_NEXT | VRING_DESC_F_WRITE) as u16, + 0, + ), + Descriptor::new( + trans_result_addr1, + size_of::() as u32, + VRING_DESC_F_WRITE as u16, + 0, + ), + Descriptor::new( + trans_header_addr2, + size_of::() as u32, + VRING_DESC_F_NEXT as u16, + 0, + ), + Descriptor::new(tx_buf_addr2, 16, VRING_DESC_F_NEXT as u16, 0), + Descriptor::new( + rx_buf_addr2, + 16, + (VRING_DESC_F_NEXT | VRING_DESC_F_WRITE) as u16, + 0, + ), + Descriptor::new( + trans_result_addr2, + size_of::() as u32, + VRING_DESC_F_WRITE as u16, + 0, + ), + ]; + + let (vring, mem) = setup_descs(&multi_reqs_descs); + + let out_hdr1 = VirtioSpiTransferHead { + chip_select_id: 0, + bits_per_word: 8, + cs_change: 0, + tx_nbits: 1, + rx_nbits: 1, + reserved1: 0, + reserved2: 0, + reserved3: 0, + mode: From::from(0), + freq: From::from(10000), + 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 out_hdr2 = VirtioSpiTransferHead { + chip_select_id: 0, + bits_per_word: 8, + cs_change: 0, + tx_nbits: 1, + rx_nbits: 4, + reserved1: 0, + reserved2: 0, + reserved3: 0, + mode: From::from(0), + freq: From::from(10000), + 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), + }; + + mem.memory() + .write_obj(out_hdr1, GuestAddress(trans_header_addr1)) + .expect("writing to succeed"); + + mem.memory() + .write_obj(out_hdr2, GuestAddress(trans_header_addr2)) + .expect("writing to succeed"); + + backend.update_memory(mem.clone()).unwrap(); + backend.process_queue(&vring).unwrap(); + + validate_trans_result( + mem.clone(), + trans_result_addr1, + ResponseStatus::TransOk as u8, + ); + + validate_trans_result( + mem.clone(), + trans_result_addr2, + ResponseStatus::ParamErr as u8, + ); + } + + #[test] + fn process_requests_failure() { + let spi_dummy_ctrl = + SpiController::new(DummyDevice::open(&PathBuf::from("spidev0.0")).unwrap()).unwrap(); + let mut backend = VhostUserSpiBackend::new(Arc::new(spi_dummy_ctrl)).unwrap(); + + // Parameters to create two requests + let trans_header_addr1: u64 = 0x10_0000; + let tx_buf_addr1: u64 = 0x20_0000; + let rx_buf_addr1: u64 = 0x30_0000; + let trans_result_addr1: u64 = 0x40_0000; + + // Backend mem must be set properly before transmit. + let writable_head_descs = [ + Descriptor::new( + trans_header_addr1, + size_of::() as u32, + (VRING_DESC_F_WRITE | VRING_DESC_F_NEXT) as u16, + 0, + ), + Descriptor::new(tx_buf_addr1, 30, VRING_DESC_F_NEXT as u16, 0), + Descriptor::new( + trans_result_addr1, + size_of::() as u32, + VRING_DESC_F_WRITE as u16, + 0, + ), + ]; + + let (vring, mem) = setup_descs(&writable_head_descs); + + let out_hdr = VirtioSpiTransferHead { + chip_select_id: 0, + bits_per_word: 8, + cs_change: 0, + tx_nbits: 1, + rx_nbits: 1, + reserved1: 0, + reserved2: 0, + reserved3: 0, + mode: From::from(0), + freq: From::from(10000), + 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), + }; + + mem.memory() + .write_obj(out_hdr, GuestAddress(trans_header_addr1)) + .expect("writing to succeed"); + + assert_eq!( + backend.process_queue(&vring).unwrap_err(), + Error::NoMemoryConfigured + ); + + // Set request head descriptor as writable, which is invalid. + let writable_head_descs = [ + Descriptor::new( + trans_header_addr1, + size_of::() as u32, + (VRING_DESC_F_WRITE | VRING_DESC_F_NEXT) as u16, + 0, + ), + Descriptor::new(tx_buf_addr1, 30, VRING_DESC_F_NEXT as u16, 0), + Descriptor::new( + trans_result_addr1, + size_of::() as u32, + VRING_DESC_F_WRITE as u16, + 0, + ), + ]; + + let (vring, mem) = setup_descs(&writable_head_descs); + + let out_hdr = VirtioSpiTransferHead { + chip_select_id: 0, + bits_per_word: 8, + cs_change: 0, + tx_nbits: 1, + rx_nbits: 1, + reserved1: 0, + reserved2: 0, + reserved3: 0, + mode: From::from(0), + freq: From::from(10000), + 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), + }; + + mem.memory() + .write_obj(out_hdr, GuestAddress(trans_header_addr1)) + .expect("writing to succeed"); + + backend.update_memory(mem.clone()).unwrap(); + assert_eq!( + backend.process_queue(&vring).unwrap_err(), + Error::UnexpectedDescriptorSize(32, 30) + ); + + // Set request result descriptor as readable, which is invalid. + let readable_result_descs = [ + Descriptor::new( + trans_header_addr1, + size_of::() as u32, + VRING_DESC_F_NEXT as u16, + 0, + ), + Descriptor::new(tx_buf_addr1, 30, VRING_DESC_F_NEXT as u16, 0), + Descriptor::new( + trans_result_addr1, + size_of::() as u32, + 0, + 0, + ), + ]; + + let (vring, mem) = setup_descs(&readable_result_descs); + + let out_hdr = VirtioSpiTransferHead { + chip_select_id: 0, + bits_per_word: 8, + cs_change: 0, + tx_nbits: 1, + rx_nbits: 1, + reserved1: 0, + reserved2: 0, + reserved3: 0, + mode: From::from(0), + freq: From::from(10000), + 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), + }; + + mem.memory() + .write_obj(out_hdr, GuestAddress(trans_header_addr1)) + .expect("writing to succeed"); + + backend.update_memory(mem.clone()).unwrap(); + assert_eq!( + backend.process_queue(&vring).unwrap_err(), + Error::UnexpectedDescriptorSize(1, 0) + ); + + // Set tx_buf len and rx_buf len different, which is invalid. + let tx_rx_len_diff_descs = [ + Descriptor::new( + trans_header_addr1, + size_of::() as u32, + VRING_DESC_F_NEXT as u16, + 0, + ), + Descriptor::new(tx_buf_addr1, 30, VRING_DESC_F_NEXT as u16, 0), + Descriptor::new( + rx_buf_addr1, + 20, + (VRING_DESC_F_WRITE | VRING_DESC_F_NEXT) as u16, + 0, + ), + Descriptor::new( + trans_result_addr1, + size_of::() as u32, + VRING_DESC_F_WRITE as u16, + 0, + ), + ]; + + let (vring, mem) = setup_descs(&tx_rx_len_diff_descs); + + let out_hdr = VirtioSpiTransferHead { + chip_select_id: 0, + bits_per_word: 8, + cs_change: 0, + tx_nbits: 1, + rx_nbits: 1, + reserved1: 0, + reserved2: 0, + reserved3: 0, + mode: From::from(0), + freq: From::from(10000), + 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), + }; + + mem.memory() + .write_obj(out_hdr, GuestAddress(trans_header_addr1)) + .expect("writing to succeed"); + + backend.update_memory(mem.clone()).unwrap(); + assert_eq!( + backend.process_queue(&vring).unwrap_err(), + Error::TxRxTrnasLenNotEqual(30, 20) + ); + + // At lease one buf needed, either tx_buf or rx_buf. + let no_buf_descs = [ + Descriptor::new( + trans_header_addr1, + size_of::() as u32, + VRING_DESC_F_NEXT as u16, + 0, + ), + Descriptor::new( + trans_result_addr1, + size_of::() as u32, + VRING_DESC_F_WRITE as u16, + 0, + ), + ]; + + let (vring, mem) = setup_descs(&no_buf_descs); + + let out_hdr = VirtioSpiTransferHead { + chip_select_id: 0, + bits_per_word: 8, + cs_change: 0, + tx_nbits: 1, + rx_nbits: 1, + reserved1: 0, + reserved2: 0, + reserved3: 0, + mode: From::from(0), + freq: From::from(10000), + 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), + }; + + mem.memory() + .write_obj(out_hdr, GuestAddress(trans_header_addr1)) + .expect("writing to succeed"); + + backend.update_memory(mem.clone()).unwrap(); + assert_eq!( + backend.process_queue(&vring).unwrap_err(), + Error::TransZeroLength + ); + + // The address range is from 0 to 0x1000_1000, set head address out of range. + let head_addr_invalid_descs = [ + Descriptor::new( + 0x2000_0000, + size_of::() as u32, + VRING_DESC_F_NEXT as u16, + 0, + ), + Descriptor::new(tx_buf_addr1, 30, VRING_DESC_F_NEXT as u16, 0), + Descriptor::new( + rx_buf_addr1, + 30, + (VRING_DESC_F_WRITE | VRING_DESC_F_NEXT) as u16, + 0, + ), + Descriptor::new( + trans_result_addr1, + size_of::() as u32, + VRING_DESC_F_WRITE as u16, + 0, + ), + ]; + + let (vring, mem) = setup_descs(&head_addr_invalid_descs); + + backend.update_memory(mem.clone()).unwrap(); + assert_eq!( + backend.process_queue(&vring).unwrap_err(), + Error::DescriptorReadFailed + ); + + // The address range is from 0 to 0x1000_1000, set result address out of range. + let result_addr_invalid_descs = [ + Descriptor::new( + trans_header_addr1, + size_of::() as u32, + VRING_DESC_F_NEXT as u16, + 0, + ), + Descriptor::new(tx_buf_addr1, 30, VRING_DESC_F_NEXT as u16, 0), + Descriptor::new( + rx_buf_addr1, + 30, + (VRING_DESC_F_WRITE | VRING_DESC_F_NEXT) as u16, + 0, + ), + Descriptor::new( + 0x2000_0000, + size_of::() as u32, + VRING_DESC_F_WRITE as u16, + 0, + ), + ]; + + let (vring, mem) = setup_descs(&result_addr_invalid_descs); + + let out_hdr = VirtioSpiTransferHead { + chip_select_id: 0, + bits_per_word: 8, + cs_change: 0, + tx_nbits: 1, + rx_nbits: 1, + reserved1: 0, + reserved2: 0, + reserved3: 0, + mode: From::from(0), + freq: From::from(10000), + 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), + }; + + mem.memory() + .write_obj(out_hdr, GuestAddress(trans_header_addr1)) + .expect("writing to succeed"); + + backend.update_memory(mem.clone()).unwrap(); + assert_eq!( + backend.process_queue(&vring).unwrap_err(), + Error::DescriptorReadFailed + ); + } + + #[test] + fn verify_backend() { + let spi_dummy_ctrl = + SpiController::new(DummyDevice::open(&PathBuf::from("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]); + + let dummy_config = VirtioSpiConfig { + cs_max_number: 1, + cs_change_supported: 1, + tx_nbits_supported: 0, + rx_nbits_supported: 0, + bits_per_word_mask: 0.into(), + mode_func_supported: 0xf.into(), + max_freq_hz: 10000.into(), + max_word_delay_ns: 0.into(), + max_cs_setup_ns: 0.into(), + max_cs_hold_ns: 0.into(), + max_cs_inactive_ns: 0.into(), + }; + + assert_eq!( + backend.get_config(0, size_of::() as u32), + // SAFETY: The layout of the structure is fixed and can be initialized by + // reading its content from byte array. + unsafe { + from_raw_parts( + &dummy_config as *const _ as *const _, + size_of::(), + ) + .to_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(); + } +} diff --git a/vhost-device-spi/src/virtio_spi.rs b/vhost-device-spi/src/virtio_spi.rs new file mode 100644 index 000000000..188b609dd --- /dev/null +++ b/vhost-device-spi/src/virtio_spi.rs @@ -0,0 +1,34 @@ +// Virtio 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 bitflags::bitflags; + +bitflags! { + pub struct ConfigNbits: u8 { + const DUAL = 0x1; + const QUAD = 0x2; + const OCTAL = 0x4; + } + + pub struct ConfigMode: u32 { + const CPHA_0 = 0x1; + const CPHA_1 = 0x2; + const CPOL_0 = 0x4; + const CPOL_1 = 0x8; + const CS_HIGH = 0x10; + const LSB = 0x20; + const LOOP = 0x40; + } + + pub struct ReqMode: u32 { + const CPHA = 1 << 0; + const CPOL = 1 << 1; + const CS_HIGH = 1 << 2; + const LSB_FIRST = 1 << 3; + const LOOP = 1 << 4; + } +}