mirror of
https://github.com/rust-vmm/vhost-device.git
synced 2026-01-09 14:19:30 +00:00
input: Initial support for input emulation
This patch is to add support for vhost input device emulation. The code skeleton heavily reuses vhost-device-i2c and vhost-device-rng, including sharing the same license, and how to create the multi-thread mode and daemon for backend virtual device. It gives detailed usage for enabling virtual input device for guest in the README.md. Signed-off-by: Leo Yan <leo.yan@linaro.org>
This commit is contained in:
parent
ddc25ecf34
commit
01e57d07c6
@ -4,6 +4,7 @@ resolver = "2"
|
||||
members = [
|
||||
"vhost-device-gpio",
|
||||
"vhost-device-i2c",
|
||||
"vhost-device-input",
|
||||
"vhost-device-rng",
|
||||
"vhost-device-scsi",
|
||||
"vhost-device-scmi",
|
||||
|
||||
@ -16,6 +16,7 @@ Here is the list of device backends that we support:
|
||||
|
||||
- [GPIO](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-gpio/README.md)
|
||||
- [I2C](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-i2c/README.md)
|
||||
- [Input](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-input/README.md)
|
||||
- [RNG](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-rng/README.md)
|
||||
- [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)
|
||||
|
||||
3
vhost-device-input/CHANGELOG.md
Normal file
3
vhost-device-input/CHANGELOG.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Upcoming Release
|
||||
|
||||
- First initial vhost-device-input daemon implementation.
|
||||
37
vhost-device-input/Cargo.toml
Normal file
37
vhost-device-input/Cargo.toml
Normal file
@ -0,0 +1,37 @@
|
||||
[package]
|
||||
name = "vhost-device-input"
|
||||
version = "0.1.0"
|
||||
authors = ["Leo Yan <leo.yan@linaro.org>"]
|
||||
description = "vhost input backend device"
|
||||
repository = "https://github.com/rust-vmm/vhost-device"
|
||||
readme = "README.md"
|
||||
keywords = ["virtio-input", "vhost-user", "vhost", "virtio", "rust-vmm"]
|
||||
categories = ["virtualization"]
|
||||
license = "Apache-2.0 OR BSD-3-Clause"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
xen = ["vm-memory/xen", "vhost/xen", "vhost-user-backend/xen"]
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
env_logger = "0.10"
|
||||
epoll = "4.3"
|
||||
libc = "0.2"
|
||||
log = "0.4"
|
||||
rand = "0.8.5"
|
||||
tempfile = "3.5"
|
||||
thiserror = "1.0"
|
||||
vhost = { version = "0.9", features = ["vhost-user-backend"] }
|
||||
vhost-user-backend = "0.11"
|
||||
virtio-bindings = "0.2.2"
|
||||
virtio-queue = "0.10"
|
||||
vm-memory = "0.13.1"
|
||||
vmm-sys-util = "0.11"
|
||||
evdev = "0.12"
|
||||
nix = "0.26"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_matches = "1.5"
|
||||
virtio-queue = { version = "0.10", features = ["test-utils"] }
|
||||
vm-memory = { version = "0.13", features = ["backend-mmap", "backend-atomic"] }
|
||||
1
vhost-device-input/LICENSE-APACHE
Symbolic link
1
vhost-device-input/LICENSE-APACHE
Symbolic link
@ -0,0 +1 @@
|
||||
../LICENSE-APACHE
|
||||
1
vhost-device-input/LICENSE-BSD-3-Clause
Symbolic link
1
vhost-device-input/LICENSE-BSD-3-Clause
Symbolic link
@ -0,0 +1 @@
|
||||
../LICENSE-BSD-3-Clause
|
||||
63
vhost-device-input/README.md
Normal file
63
vhost-device-input/README.md
Normal file
@ -0,0 +1,63 @@
|
||||
# vhost-device-input
|
||||
|
||||
## Synopsis
|
||||
vhost-device-input --socket-path <SOCKET_PATH> --event-list <EVENT_LIST>
|
||||
|
||||
## Description
|
||||
|
||||
This program is a vhost-user backend that emulates a VirtIO input event.
|
||||
It polls on a host's input event device (/dev/input/eventX) and passes the
|
||||
input event data to guests.
|
||||
|
||||
This program is tested with QEMU's `vhost-user-input-pci`. The
|
||||
implemenation is based on the vhost-user protocol and as such should be
|
||||
interoperable with other virtual machine managers. Please see below for
|
||||
working examples.
|
||||
|
||||
## Options
|
||||
|
||||
```text
|
||||
-h, --help
|
||||
Print help.
|
||||
|
||||
-s, --socket-path <SOCKET_PATH>
|
||||
Location of vhost-user Unix domain sockets, this path will be suffixed with
|
||||
0,1,2..event_count-1.
|
||||
|
||||
-e, --event-list <EVENT_LIST>
|
||||
Input event device list in the format: event_device1,event_device2,...
|
||||
Example: --event-list /dev/input/event14,/dev/input/event15
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
The daemon should be started first:
|
||||
|
||||
```shell
|
||||
host# vhost-device-input --socket-path /some/path/input.sock \
|
||||
--event-list /dev/input/event14,/dev/input/event15
|
||||
```
|
||||
|
||||
Note that from the above command the socket path "/some/path/input.sock0" and
|
||||
"/some/path/input.sock1" will be created for input events "event14" and
|
||||
"event15" respectively. This in turn needs to be communicated as chardev
|
||||
sockets to QEMU in order for the backend daemon and access the Virtio queues
|
||||
with the guest over the shared memory.
|
||||
|
||||
```shell
|
||||
host# qemu-system -M virt \
|
||||
-object memory-backend-file,id=mem,size=4G,mem-path=/dev/shm,share=on \
|
||||
-chardev socket,path=/some/path/input.sock0,id=kbd0 \
|
||||
-device vhost-user-input-pci,chardev=kdb0 \
|
||||
-chardev socket,path=/some/path/input.sock1,id=mouse0 \
|
||||
-device vhost-user-input-pci,chardev=mouse0 \
|
||||
-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)
|
||||
56
vhost-device-input/src/input.rs
Normal file
56
vhost-device-input/src/input.rs
Normal file
@ -0,0 +1,56 @@
|
||||
// Low level input device definitions
|
||||
//
|
||||
// Copyright 2023 Linaro Ltd. All Rights Reserved.
|
||||
// Leo Yan <leo.yan@linaro.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
|
||||
use evdev::{Device, FetchEventsSynced, InputId};
|
||||
use nix::ioctl_read_buf;
|
||||
use std::io;
|
||||
use std::os::fd::{AsRawFd, RawFd};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Trait that operates on input event device. This main purpose for this
|
||||
/// trait is to encapsulate a "Device" structure for accessing hardware
|
||||
/// device or mock device for test.
|
||||
pub trait InputDevice {
|
||||
/// Open the input device specified by the path.
|
||||
fn open(path: PathBuf) -> io::Result<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Fetch input events.
|
||||
fn fetch_events(&mut self) -> io::Result<FetchEventsSynced<'_>>;
|
||||
|
||||
/// Return the raw file descriptor.
|
||||
fn get_raw_fd(&self) -> RawFd;
|
||||
|
||||
/// Return the Input ID.
|
||||
fn input_id(&self) -> InputId;
|
||||
}
|
||||
|
||||
impl InputDevice for Device {
|
||||
fn open(path: PathBuf) -> io::Result<Self> {
|
||||
Device::open(path)
|
||||
}
|
||||
|
||||
fn fetch_events(&mut self) -> io::Result<FetchEventsSynced<'_>> {
|
||||
Device::fetch_events(self)
|
||||
}
|
||||
|
||||
fn get_raw_fd(&self) -> RawFd {
|
||||
Device::as_raw_fd(self)
|
||||
}
|
||||
|
||||
fn input_id(&self) -> InputId {
|
||||
Device::input_id(self)
|
||||
}
|
||||
}
|
||||
|
||||
ioctl_read_buf!(eviocgname, b'E', 0x06, u8);
|
||||
ioctl_read_buf!(eviocgbit_key, b'E', 0x21, u8);
|
||||
ioctl_read_buf!(eviocgbit_relative, b'E', 0x22, u8);
|
||||
ioctl_read_buf!(eviocgbit_absolute, b'E', 0x23, u8);
|
||||
ioctl_read_buf!(eviocgbit_misc, b'E', 0x24, u8);
|
||||
ioctl_read_buf!(eviocgbit_switch, b'E', 0x25, u8);
|
||||
171
vhost-device-input/src/main.rs
Normal file
171
vhost-device-input/src/main.rs
Normal file
@ -0,0 +1,171 @@
|
||||
//
|
||||
// Copyright 2023 Linaro Ltd. All Rights Reserved.
|
||||
// Leo Yan <leo.yan@linaro.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
|
||||
mod input;
|
||||
mod vhu_input;
|
||||
|
||||
use clap::Parser;
|
||||
use evdev::Device;
|
||||
use log::error;
|
||||
use std::{
|
||||
any::Any,
|
||||
collections::HashMap,
|
||||
path::PathBuf,
|
||||
process::exit,
|
||||
sync::{Arc, RwLock},
|
||||
thread,
|
||||
};
|
||||
|
||||
use thiserror::Error as ThisError;
|
||||
use vhost_user_backend::VhostUserDaemon;
|
||||
use vhu_input::VuInputBackend;
|
||||
use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap};
|
||||
use vmm_sys_util::epoll::EventSet;
|
||||
|
||||
use crate::input::*;
|
||||
|
||||
#[derive(Debug, ThisError)]
|
||||
/// Errors related to vhost-device-input daemon.
|
||||
pub(crate) enum Error {
|
||||
#[error("Event device file doesn't exists or can't be accessed")]
|
||||
AccessEventDeviceFile,
|
||||
#[error("Could not create backend: {0}")]
|
||||
CouldNotCreateBackend(std::io::Error),
|
||||
#[error("Could not create daemon: {0}")]
|
||||
CouldNotCreateDaemon(vhost_user_backend::Error),
|
||||
#[error("Could not register input event into vring epoll")]
|
||||
CouldNotRegisterInputEvent,
|
||||
#[error("Fatal error: {0}")]
|
||||
ServeFailed(vhost_user_backend::Error),
|
||||
#[error("Thread `{0}` panicked")]
|
||||
ThreadPanic(String, Box<dyn Any + Send>),
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Clone, Parser, Debug, PartialEq)]
|
||||
#[clap(author, version, about, long_about = None)]
|
||||
struct InputArgs {
|
||||
// Location of vhost-user Unix domain socket.
|
||||
#[clap(short, long, value_name = "SOCKET")]
|
||||
socket_path: PathBuf,
|
||||
|
||||
// Path for reading input events.
|
||||
#[clap(
|
||||
short = 'e',
|
||||
long,
|
||||
use_value_delimiter = true,
|
||||
value_delimiter = ',',
|
||||
required = true
|
||||
)]
|
||||
event_list: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl InputArgs {
|
||||
pub fn generate_socket_paths(&self) -> Vec<PathBuf> {
|
||||
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.event_list.len()).map(make_socket_path).collect()
|
||||
}
|
||||
}
|
||||
|
||||
// This is the public API through which an external program starts the
|
||||
/// vhost-device-input backend server.
|
||||
pub(crate) fn start_backend_server<D: 'static + InputDevice + Send + Sync>(
|
||||
socket: PathBuf,
|
||||
event: PathBuf,
|
||||
) -> Result<()> {
|
||||
loop {
|
||||
let ev_dev = D::open(event.clone()).map_err(|_| Error::AccessEventDeviceFile)?;
|
||||
let raw_fd = ev_dev.get_raw_fd();
|
||||
|
||||
// If creating the VuInputBackend isn't successful there isn't much else to do than
|
||||
// killing the thread, which .unwrap() does. When that happens an error code is
|
||||
// generated and displayed by the runtime mechanic. Killing a thread doesn't affect
|
||||
// the other threads spun-off by the daemon.
|
||||
let vu_input_backend = Arc::new(RwLock::new(
|
||||
VuInputBackend::new(ev_dev).map_err(Error::CouldNotCreateBackend)?,
|
||||
));
|
||||
|
||||
let mut daemon = VhostUserDaemon::new(
|
||||
String::from("vhost-device-input-backend"),
|
||||
Arc::clone(&vu_input_backend),
|
||||
GuestMemoryAtomic::new(GuestMemoryMmap::new()),
|
||||
)
|
||||
.map_err(Error::CouldNotCreateDaemon)?;
|
||||
|
||||
let handlers = daemon.get_epoll_handlers();
|
||||
handlers[0]
|
||||
.register_listener(raw_fd, EventSet::IN, vhu_input::EVENT_ID_IN_VRING_EPOLL)
|
||||
.map_err(|_| Error::CouldNotRegisterInputEvent)?;
|
||||
|
||||
daemon.serve(&socket).map_err(Error::ServeFailed)?;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start_backend<D: 'static + InputDevice + Send + Sync>(
|
||||
config: InputArgs,
|
||||
) -> Result<()> {
|
||||
let mut handles = HashMap::new();
|
||||
let (senders, receiver) = std::sync::mpsc::channel();
|
||||
|
||||
for (thread_id, (socket, event)) in config
|
||||
.generate_socket_paths()
|
||||
.into_iter()
|
||||
.zip(config.event_list.iter().cloned())
|
||||
.enumerate()
|
||||
{
|
||||
let name = format!("vhu-vsock-input-{:?}", event);
|
||||
let sender = senders.clone();
|
||||
let handle = thread::Builder::new()
|
||||
.name(name.clone())
|
||||
.spawn(move || {
|
||||
let result =
|
||||
std::panic::catch_unwind(move || start_backend_server::<D>(socket, event));
|
||||
|
||||
// Notify the main thread that we are done.
|
||||
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::<Device>(InputArgs::parse()) {
|
||||
error!("{e}");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
424
vhost-device-input/src/vhu_input.rs
Normal file
424
vhost-device-input/src/vhu_input.rs
Normal file
@ -0,0 +1,424 @@
|
||||
// VIRTIO Input Emulation via vhost-user
|
||||
//
|
||||
// Copyright 2023 Linaro Ltd. All Rights Reserved.
|
||||
// Leo Yan <leo.yan@linaro.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
|
||||
use log::error;
|
||||
use nix::libc;
|
||||
use std::collections::VecDeque;
|
||||
use std::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_VERSION_1;
|
||||
use virtio_bindings::bindings::virtio_ring::{
|
||||
VIRTIO_RING_F_EVENT_IDX, VIRTIO_RING_F_INDIRECT_DESC,
|
||||
};
|
||||
use virtio_queue::QueueT;
|
||||
use vm_memory::{ByteValued, Bytes, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
|
||||
use vmm_sys_util::epoll::EventSet;
|
||||
use vmm_sys_util::eventfd::{EventFd, EFD_NONBLOCK};
|
||||
|
||||
use crate::input::*;
|
||||
|
||||
pub const EVENT_ID_IN_VRING_EPOLL: u64 = 3;
|
||||
|
||||
const QUEUE_SIZE: usize = 1024;
|
||||
const NUM_QUEUES: usize = 2;
|
||||
|
||||
const VIRTIO_INPUT_CFG_ID_NAME: u8 = 0x01;
|
||||
const VIRTIO_INPUT_CFG_ID_DEVIDS: u8 = 0x03;
|
||||
const VIRTIO_INPUT_CFG_EV_BITS: u8 = 0x11;
|
||||
const VIRTIO_INPUT_CFG_SIZE: usize = 128;
|
||||
|
||||
const EV_SYN: u8 = 0x00;
|
||||
const EV_KEY: u8 = 0x01;
|
||||
const EV_REL: u8 = 0x02;
|
||||
const EV_ABS: u8 = 0x03;
|
||||
const EV_MSC: u8 = 0x04;
|
||||
const EV_SW: u8 = 0x05;
|
||||
|
||||
const SYN_REPORT: u8 = 0x00;
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) struct VuInputConfig {
|
||||
select: u8,
|
||||
subsel: u8,
|
||||
size: u8,
|
||||
reserved: [u8; 5],
|
||||
val: [u8; VIRTIO_INPUT_CFG_SIZE],
|
||||
}
|
||||
|
||||
// If deriving the 'Default' trait, an array is limited with a maximum size of 32 bytes,
|
||||
// thus it cannot meet the length VIRTIO_INPUT_CFG_SIZE (128) for the 'val' array.
|
||||
// Implement Default trait to accommodate array 'val'.
|
||||
impl Default for VuInputConfig {
|
||||
fn default() -> VuInputConfig {
|
||||
VuInputConfig {
|
||||
select: 0,
|
||||
subsel: 0,
|
||||
size: 0,
|
||||
reserved: [0; 5],
|
||||
val: [0; VIRTIO_INPUT_CFG_SIZE],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: The layout of the structure is fixed and can be initialized by
|
||||
// reading its content from byte array.
|
||||
unsafe impl ByteValued for VuInputConfig {}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub(crate) struct VuInputEvent {
|
||||
ev_type: u16,
|
||||
code: u16,
|
||||
value: u32,
|
||||
}
|
||||
|
||||
// SAFETY: The layout of the structure is fixed and can be initialized by
|
||||
// reading its content from byte array.
|
||||
unsafe impl ByteValued for VuInputEvent {}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, ThisError)]
|
||||
/// Errors related to vhost-device-input daemon.
|
||||
pub(crate) enum VuInputError {
|
||||
#[error("Notification send failed")]
|
||||
SendNotificationFailed,
|
||||
#[error("Can't create eventFd")]
|
||||
EventFdError,
|
||||
#[error("Failed to handle event")]
|
||||
HandleEventNotEpollIn,
|
||||
#[error("Unknown device event")]
|
||||
HandleEventUnknownEvent,
|
||||
#[error("Unknown config request: {0}")]
|
||||
UnexpectedConfig(u8),
|
||||
#[error("Failed to fetch event")]
|
||||
UnexpectedFetchEventError,
|
||||
#[error("Too many descriptors: {0}")]
|
||||
UnexpectedDescriptorCount(usize),
|
||||
#[error("Failed to read from the input device")]
|
||||
UnexpectedInputDeviceError,
|
||||
#[error("Failed to write descriptor to vring")]
|
||||
UnexpectedWriteDescriptorError,
|
||||
#[error("Failed to write event to vring")]
|
||||
UnexpectedWriteVringError,
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, VuInputError>;
|
||||
|
||||
impl From<VuInputError> for io::Error {
|
||||
fn from(e: VuInputError) -> Self {
|
||||
Self::new(io::ErrorKind::Other, e)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct VuInputBackend<T: InputDevice> {
|
||||
event_idx: bool,
|
||||
ev_dev: T,
|
||||
pub exit_event: EventFd,
|
||||
select: u8,
|
||||
subsel: u8,
|
||||
mem: Option<GuestMemoryAtomic<GuestMemoryMmap>>,
|
||||
ev_list: VecDeque<VuInputEvent>,
|
||||
}
|
||||
|
||||
impl<T: InputDevice> VuInputBackend<T> {
|
||||
pub fn new(ev_dev: T) -> std::result::Result<Self, std::io::Error> {
|
||||
Ok(VuInputBackend {
|
||||
event_idx: false,
|
||||
ev_dev,
|
||||
exit_event: EventFd::new(EFD_NONBLOCK).map_err(|_| VuInputError::EventFdError)?,
|
||||
select: 0,
|
||||
subsel: 0,
|
||||
mem: None,
|
||||
ev_list: VecDeque::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn process_event(&mut self, vring: &VringRwLock) -> Result<bool> {
|
||||
let last_sync_index = self
|
||||
.ev_list
|
||||
.iter()
|
||||
.rposition(|event| event.ev_type == EV_SYN as u16 && event.code == SYN_REPORT as u16)
|
||||
.unwrap_or(0);
|
||||
|
||||
if last_sync_index == 0 {
|
||||
log::warn!("No available events on the list!");
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let mut index = 0;
|
||||
while index <= last_sync_index {
|
||||
let event = self.ev_list.get(index).unwrap();
|
||||
index += 1;
|
||||
|
||||
let mem = self.mem.as_ref().unwrap().memory();
|
||||
|
||||
let desc = vring
|
||||
.get_mut()
|
||||
.get_queue_mut()
|
||||
.pop_descriptor_chain(mem.clone());
|
||||
|
||||
if let Some(desc_chain) = desc {
|
||||
let descriptors: Vec<_> = desc_chain.clone().collect();
|
||||
if descriptors.len() != 1 {
|
||||
return Err(VuInputError::UnexpectedDescriptorCount(descriptors.len()));
|
||||
}
|
||||
let descriptor = descriptors[0];
|
||||
|
||||
desc_chain
|
||||
.memory()
|
||||
.write_obj(*event, descriptor.addr())
|
||||
.map_err(|_| VuInputError::UnexpectedWriteDescriptorError)?;
|
||||
|
||||
if vring
|
||||
.add_used(desc_chain.head_index(), event.as_slice().len() as u32)
|
||||
.is_err()
|
||||
{
|
||||
log::error!("Couldn't write event data to the ring");
|
||||
return Err(VuInputError::UnexpectedWriteVringError);
|
||||
}
|
||||
} else {
|
||||
// Now cannot get available descriptor, which means the host cannot process
|
||||
// event data in time and overrun happens in the backend. In this case,
|
||||
// we simply drop the incomping input event and notify guest for handling
|
||||
// events. At the end, it returns Ok(false) so can avoid exiting the thread loop.
|
||||
self.ev_list.clear();
|
||||
|
||||
vring
|
||||
.signal_used_queue()
|
||||
.map_err(|_| VuInputError::SendNotificationFailed)?;
|
||||
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Sent the events [0..last_sync_index] to vring and remove them from the list.
|
||||
// The range end parameter is an exclusive value, so use 'last_sync_index + 1'.
|
||||
self.ev_list.drain(0..last_sync_index + 1);
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Process the requests in the vring and dispatch replies
|
||||
fn process_queue(&mut self, vring: &VringRwLock) -> Result<bool> {
|
||||
let events = self
|
||||
.ev_dev
|
||||
.fetch_events()
|
||||
.map_err(|_| VuInputError::UnexpectedFetchEventError)?;
|
||||
|
||||
for event in events {
|
||||
let ev_raw_data = VuInputEvent {
|
||||
ev_type: event.event_type().0,
|
||||
code: event.code(),
|
||||
value: event.value() as u32,
|
||||
};
|
||||
self.ev_list.push_back(ev_raw_data);
|
||||
}
|
||||
|
||||
self.process_event(vring)?;
|
||||
|
||||
vring
|
||||
.signal_used_queue()
|
||||
.map_err(|_| VuInputError::SendNotificationFailed)?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn read_event_config(&self) -> Result<VuInputConfig> {
|
||||
let mut cfg: [u8; VIRTIO_INPUT_CFG_SIZE] = [0; VIRTIO_INPUT_CFG_SIZE];
|
||||
|
||||
let func: unsafe fn(nix::libc::c_int, &mut [u8]) -> nix::Result<libc::c_int> =
|
||||
match self.subsel {
|
||||
EV_KEY => eviocgbit_key,
|
||||
EV_ABS => eviocgbit_absolute,
|
||||
EV_REL => eviocgbit_relative,
|
||||
EV_MSC => eviocgbit_misc,
|
||||
EV_SW => eviocgbit_switch,
|
||||
_ => {
|
||||
return Err(VuInputError::HandleEventUnknownEvent);
|
||||
}
|
||||
};
|
||||
|
||||
// SAFETY: Safe as the file is a valid event device, the kernel will only
|
||||
// update the correct amount of memory in func.
|
||||
if unsafe { func(self.ev_dev.get_raw_fd(), &mut cfg) }.is_err() {
|
||||
return Err(VuInputError::UnexpectedInputDeviceError);
|
||||
}
|
||||
|
||||
let mut size: u8 = 0;
|
||||
for (index, val) in cfg.iter().enumerate() {
|
||||
if *val != 0 {
|
||||
size = (index + 1) as u8;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(VuInputConfig {
|
||||
select: self.select,
|
||||
subsel: self.subsel,
|
||||
size,
|
||||
reserved: [0; 5],
|
||||
val: cfg,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read_name_config(&self) -> Result<VuInputConfig> {
|
||||
let mut name: [u8; VIRTIO_INPUT_CFG_SIZE] = [0; VIRTIO_INPUT_CFG_SIZE];
|
||||
|
||||
// SAFETY: Safe as the file is a valid event device, the kernel will only
|
||||
// update the correct amount of memory in func.
|
||||
match unsafe { eviocgname(self.ev_dev.get_raw_fd(), name.as_mut_slice()) } {
|
||||
Ok(len) if len as usize > name.len() => {
|
||||
return Err(VuInputError::UnexpectedInputDeviceError);
|
||||
}
|
||||
Ok(len) if len <= 1 => {
|
||||
return Err(VuInputError::UnexpectedInputDeviceError);
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(VuInputError::UnexpectedInputDeviceError);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let size = String::from_utf8(name.to_vec()).unwrap().len();
|
||||
|
||||
Ok(VuInputConfig {
|
||||
select: self.select,
|
||||
subsel: 0,
|
||||
size: size as u8,
|
||||
reserved: [0; 5],
|
||||
val: name,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read_id_config(&self) -> Result<VuInputConfig> {
|
||||
let input_id = self.ev_dev.input_id();
|
||||
|
||||
let mut dev_id = [
|
||||
input_id.bus_type().0.as_slice(),
|
||||
input_id.vendor().as_slice(),
|
||||
input_id.product().as_slice(),
|
||||
input_id.version().as_slice(),
|
||||
]
|
||||
.concat();
|
||||
|
||||
dev_id.resize(VIRTIO_INPUT_CFG_SIZE, 0);
|
||||
|
||||
Ok(VuInputConfig {
|
||||
select: VIRTIO_INPUT_CFG_ID_DEVIDS,
|
||||
subsel: 0,
|
||||
size: VIRTIO_INPUT_CFG_SIZE as u8,
|
||||
reserved: [0; 5],
|
||||
val: dev_id.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// VhostUserBackend trait methods
|
||||
impl<T: 'static + InputDevice + Sync + Send> VhostUserBackendMut for VuInputBackend<T> {
|
||||
type Bitmap = ();
|
||||
type Vring = VringRwLock;
|
||||
|
||||
fn num_queues(&self) -> usize {
|
||||
NUM_QUEUES
|
||||
}
|
||||
|
||||
fn max_queue_size(&self) -> usize {
|
||||
QUEUE_SIZE
|
||||
}
|
||||
|
||||
fn features(&self) -> u64 {
|
||||
1 << VIRTIO_F_VERSION_1
|
||||
| 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
|
||||
}
|
||||
|
||||
fn get_config(&self, offset: u32, size: u32) -> Vec<u8> {
|
||||
let cfg = match self.select {
|
||||
VIRTIO_INPUT_CFG_ID_NAME => self.read_name_config(),
|
||||
VIRTIO_INPUT_CFG_ID_DEVIDS => self.read_id_config(),
|
||||
VIRTIO_INPUT_CFG_EV_BITS => self.read_event_config(),
|
||||
_ => Err(VuInputError::UnexpectedConfig(self.select)),
|
||||
};
|
||||
|
||||
let val = match cfg {
|
||||
Ok(v) => v.as_slice().to_vec(),
|
||||
_ => vec![0; size as usize],
|
||||
};
|
||||
|
||||
let mut result: Vec<_> = val
|
||||
.as_slice()
|
||||
.iter()
|
||||
.skip(offset as usize)
|
||||
.take(size as usize)
|
||||
.copied()
|
||||
.collect();
|
||||
// pad with 0s up to `size`
|
||||
result.resize(size as usize, 0);
|
||||
result
|
||||
}
|
||||
|
||||
// In virtio spec https://docs.oasis-open.org/virtio/virtio/v1.2/cs01/virtio-v1.2-cs01.pdf,
|
||||
// section "5.8.5.1 Driver Requirements: Device Initialization", it doesn't mention to
|
||||
// use 'offset' argument, so set it as unused.
|
||||
fn set_config(&mut self, _offset: u32, buf: &[u8]) -> io::Result<()> {
|
||||
self.select = buf[0];
|
||||
self.subsel = buf[1];
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_event_idx(&mut self, enabled: bool) {
|
||||
self.event_idx = enabled;
|
||||
}
|
||||
|
||||
fn update_memory(&mut self, mem: GuestMemoryAtomic<GuestMemoryMmap>) -> IoResult<()> {
|
||||
self.mem = Some(mem);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_event(
|
||||
&mut self,
|
||||
device_event: u16,
|
||||
evset: EventSet,
|
||||
vrings: &[VringRwLock],
|
||||
_thread_id: usize,
|
||||
) -> IoResult<()> {
|
||||
if !self.event_idx {
|
||||
self.ev_dev.fetch_events()?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if evset != EventSet::IN {
|
||||
return Err(VuInputError::HandleEventNotEpollIn.into());
|
||||
}
|
||||
|
||||
if device_event == EVENT_ID_IN_VRING_EPOLL as u16 {
|
||||
let vring = &vrings[0];
|
||||
|
||||
if self.event_idx {
|
||||
vring.disable_notification().unwrap();
|
||||
self.process_queue(vring)?;
|
||||
vring.enable_notification().unwrap();
|
||||
} else {
|
||||
// Without EVENT_IDX, a single call is enough.
|
||||
self.process_queue(vring)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exit_event(&self, _thread_index: usize) -> Option<EventFd> {
|
||||
self.exit_event.try_clone().ok()
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user