Skip to content

Commit 88d7ffb

Browse files
committed
devices, libkrun: Introduce krun_add_input_device_fd
Introduce krun_add_input_device_fd, allowing the user to pass a file descriptor to a /dev/input/event* device, which will be become available in the guest. Signed-off-by: Matej Hrica <[email protected]>
1 parent 2316316 commit 88d7ffb

File tree

4 files changed

+264
-0
lines changed

4 files changed

+264
-0
lines changed

include/libkrun.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,19 @@ int32_t krun_set_display_backend(uint32_t ctx_id, const void *display_backend, s
631631
int krun_add_input_device(uint32_t ctx_id, const void *config_backend, size_t config_backend_size,
632632
const void *events_backend, size_t events_backend_size);
633633

634+
/**
635+
* Creates a passthrough input device from a host /dev/input/* file descriptor.
636+
* The device configuration will be automatically queried from the host device using ioctls.
637+
*
638+
* Arguments:
639+
* "ctx_id" - The krun context
640+
* "input_fd" - File descriptor to a /dev/input/* device on the host
641+
*
642+
* Returns:
643+
* Zero on success or a negative error code otherwise.
644+
*/
645+
int krun_add_input_device_fd(uint32_t ctx_id, int input_fd);
646+
634647
/**
635648
* Enables or disables a virtio-snd device.
636649
*

src/devices/src/virtio/input/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod device;
2+
pub mod passthrough;
23
mod worker;
34

45
pub use self::defs::uapi::VIRTIO_ID_INPUT as TYPE_INPUT;
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
use krun_input::{
2+
InputAbsInfo, InputBackendError, InputDeviceIds, InputEvent, InputEventsImpl, InputQueryConfig,
3+
ObjectNew,
4+
};
5+
use nix::fcntl::{fcntl, OFlag, F_GETFL, F_SETFL};
6+
use nix::{errno::Errno, ioctl_read, ioctl_read_buf, unistd};
7+
use std::mem;
8+
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, RawFd};
9+
10+
/// Internal passthrough input backend that forwards host /dev/input/* devices
11+
pub struct PassthroughInputBackend {
12+
fd: BorrowedFd<'static>,
13+
}
14+
15+
impl InputQueryConfig for PassthroughInputBackend {
16+
fn query_serial_name(&self, serial_buf: &mut [u8]) -> Result<u8, InputBackendError> {
17+
match unsafe { eviocguniq(self.fd.as_raw_fd(), serial_buf) } {
18+
Ok(len) => Ok(len as u8),
19+
Err(e) => {
20+
error!("Failed to get device serial (eviocguniq): {e}");
21+
Err(InputBackendError::InternalError)
22+
}
23+
}
24+
}
25+
26+
fn query_device_name(&self, name_buf: &mut [u8]) -> Result<u8, InputBackendError> {
27+
match unsafe { eviocgname(self.fd.as_raw_fd(), name_buf) } {
28+
Ok(len) => Ok(len as u8),
29+
Err(e) => {
30+
error!("Failed to get device name (eviocgname): {e}");
31+
Err(InputBackendError::InternalError)
32+
}
33+
}
34+
}
35+
36+
fn query_device_ids(&self, ids: &mut InputDeviceIds) -> Result<(), InputBackendError> {
37+
match unsafe { eviocgid(self.fd.as_raw_fd(), ids) } {
38+
Ok(_) => Ok(()),
39+
Err(e) => {
40+
error!("Failed to get device information ids (eviocgid): {e}");
41+
Err(InputBackendError::InternalError)
42+
}
43+
}
44+
}
45+
46+
fn query_event_capabilities(
47+
&self,
48+
event_type: u8,
49+
bitmap_buf: &mut [u8],
50+
) -> Result<u8, InputBackendError> {
51+
match unsafe { eviocgbit(self.fd.as_raw_fd(), event_type, bitmap_buf) } {
52+
Ok(n) => {
53+
let len = find_length(&bitmap_buf[..n as usize]) as u8;
54+
debug!(
55+
"eviocgbit: {event_type}, got {n} bytes (from n): {:#?}",
56+
&bitmap_buf[..n as usize]
57+
);
58+
Ok(len)
59+
}
60+
Err(e) => {
61+
error!("Failed to get device event capabilities (eviocgbit): {e}");
62+
Err(InputBackendError::InternalError)
63+
}
64+
}
65+
}
66+
67+
fn query_abs_info(
68+
&self,
69+
abs_axis: u8,
70+
abs_info: &mut InputAbsInfo,
71+
) -> Result<(), InputBackendError> {
72+
let mut linux_abs_info = LinuxAbsInfo::default();
73+
match unsafe { eviocgabs(self.fd.as_raw_fd(), abs_axis, &mut linux_abs_info) } {
74+
Ok(_) => {
75+
*abs_info = InputAbsInfo {
76+
min: linux_abs_info.minimum,
77+
max: linux_abs_info.maximum,
78+
fuzz: linux_abs_info.fuzz,
79+
flat: linux_abs_info.flat,
80+
res: linux_abs_info.resolution,
81+
};
82+
Ok(())
83+
}
84+
Err(e) => {
85+
error!("Failed to get device abs_info (eviocgabs): {e}");
86+
Err(InputBackendError::InternalError)
87+
}
88+
}
89+
}
90+
91+
fn query_properties(&self, properties: &mut [u8]) -> Result<u8, InputBackendError> {
92+
match unsafe { eviocgprop(self.fd.as_raw_fd(), properties) } {
93+
Ok(len) => Ok(len as u8),
94+
Err(e) => {
95+
error!("Failed to query device properties (eviocgprop): {e}");
96+
Err(InputBackendError::InternalError)
97+
}
98+
}
99+
}
100+
}
101+
102+
impl ObjectNew<BorrowedFd<'static>> for PassthroughInputBackend {
103+
fn new(userdata: Option<&BorrowedFd<'static>>) -> Self {
104+
let fd = userdata
105+
.copied()
106+
.expect("Missing argument for PassthroughInputBackend::new");
107+
108+
make_non_blocking(&fd)
109+
.expect("Cannot make device fd non-blocking (Invalid file descriptor?)");
110+
Self { fd }
111+
}
112+
}
113+
114+
impl InputEventsImpl for PassthroughInputBackend {
115+
fn get_read_notify_fd(&self) -> Result<BorrowedFd<'_>, InputBackendError> {
116+
Ok(self.fd)
117+
}
118+
119+
fn next_event(&mut self) -> Result<Option<InputEvent>, InputBackendError> {
120+
let mut linux_event = unsafe { std::mem::zeroed::<LinuxInputEvent>() };
121+
let event_slice = unsafe {
122+
std::slice::from_raw_parts_mut(
123+
&mut linux_event as *mut _ as *mut u8,
124+
size_of::<LinuxInputEvent>(),
125+
)
126+
};
127+
128+
match unistd::read(self.fd, event_slice) {
129+
Ok(bytes_read) if bytes_read == size_of::<LinuxInputEvent>() => {
130+
trace!("Forwarding input: {linux_event:?}");
131+
Ok(Some(InputEvent {
132+
type_: linux_event.type_,
133+
code: linux_event.code,
134+
value: linux_event.value,
135+
}))
136+
}
137+
Ok(_bytes_read) => {
138+
error!("Partial read from /dev/input was unexpected, not implemented!");
139+
Err(InputBackendError::InternalError)
140+
}
141+
Err(Errno::EAGAIN) => Ok(None),
142+
Err(e) => {
143+
error!("Failed to read event from input device: {e}");
144+
Err(InputBackendError::InternalError)
145+
}
146+
}
147+
}
148+
}
149+
150+
#[repr(C)]
151+
#[derive(Debug)]
152+
struct LinuxInputEvent {
153+
time: libc::timeval,
154+
type_: u16,
155+
code: u16,
156+
value: u32,
157+
}
158+
159+
#[repr(C)]
160+
#[derive(Debug, Default)]
161+
struct LinuxAbsInfo {
162+
value: u32,
163+
minimum: u32,
164+
maximum: u32,
165+
fuzz: u32,
166+
flat: u32,
167+
resolution: u32,
168+
}
169+
170+
ioctl_read!(eviocgid, b'E', 0x02, InputDeviceIds); // Kernel uapi struct is the same as virtio
171+
ioctl_read_buf!(eviocgname, b'E', 0x06, u8);
172+
ioctl_read_buf!(eviocguniq, b'E', 0x08, u8);
173+
ioctl_read_buf!(eviocgprop, b'E', 0x09, u8);
174+
175+
unsafe fn eviocgbit(fd: RawFd, evt: u8, buf: &mut [u8]) -> Result<u32, Errno> {
176+
let ioctl_num = nix::request_code_read!(b'E', 0x20 + evt, buf.len());
177+
178+
let n = libc::ioctl(fd, ioctl_num as _, buf.as_mut_ptr());
179+
if n < 0 {
180+
return Err(Errno::last());
181+
}
182+
Ok(n as u32)
183+
}
184+
185+
unsafe fn eviocgabs(fd: RawFd, axis: u8, abs_info: &mut LinuxAbsInfo) -> Result<u32, Errno> {
186+
let ioctl_num = nix::request_code_read!(b'E', 0x40 + axis, size_of::<LinuxAbsInfo>());
187+
188+
let n = libc::ioctl(fd, ioctl_num as _, abs_info as *mut _);
189+
if n < 0 {
190+
return Err(Errno::last());
191+
}
192+
Ok(mem::size_of::<InputAbsInfo>() as u32)
193+
}
194+
195+
fn make_non_blocking(fd: &impl AsFd) -> Result<(), nix::Error> {
196+
let flags = fcntl(fd, F_GETFL)?;
197+
fcntl(
198+
fd,
199+
F_SETFL(OFlag::from_bits_retain(flags) | OFlag::O_NONBLOCK),
200+
)?;
201+
202+
Ok(())
203+
}
204+
205+
fn find_length(bytes: &[u8]) -> usize {
206+
bytes
207+
.iter()
208+
.rposition(|b| *b != 0)
209+
.map(|idx| idx + 1)
210+
.unwrap_or(0)
211+
}

src/libkrun/src/lib.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ use std::ffi::{c_void, CStr};
3232
use std::fs::File;
3333
#[cfg(target_os = "linux")]
3434
use std::os::fd::AsRawFd;
35+
#[cfg(feature = "input")]
36+
use std::os::fd::BorrowedFd;
3537
use std::os::fd::{FromRawFd, RawFd};
3638
use std::path::PathBuf;
3739
use std::slice;
@@ -1412,6 +1414,36 @@ pub extern "C" fn krun_add_input_device(
14121414
-libc::ENOTSUP
14131415
}
14141416

1417+
#[cfg(feature = "input")]
1418+
#[allow(clippy::missing_safety_doc)]
1419+
#[no_mangle]
1420+
pub extern "C" fn krun_add_input_device_fd(ctx_id: u32, input_fd: i32) -> i32 {
1421+
use devices::virtio::input::passthrough::PassthroughInputBackend;
1422+
use krun_input::{IntoInputConfig, IntoInputEvents};
1423+
1424+
if input_fd < 0 {
1425+
return -libc::EINVAL;
1426+
}
1427+
// TODO: currently we let the fd (and it's Box allocation) live forever, we should eventually fix
1428+
// this
1429+
let input_fd = unsafe {
1430+
// SAFETY: The user provided fd should be valid. Its lifetime is 'static because it will
1431+
// exist until libkrun _exits the process
1432+
BorrowedFd::borrow_raw(input_fd)
1433+
};
1434+
let borrowed_fd: &'static BorrowedFd<'static> = Box::leak(Box::new(input_fd));
1435+
1436+
let config_backend = PassthroughInputBackend::into_input_config(Some(borrowed_fd));
1437+
let events_backend = PassthroughInputBackend::into_input_events(Some(borrowed_fd));
1438+
1439+
with_cfg(ctx_id, |cfg| {
1440+
cfg.vmr
1441+
.input_backends
1442+
.push((config_backend, events_backend));
1443+
KRUN_SUCCESS
1444+
})
1445+
}
1446+
14151447
#[cfg(feature = "input")]
14161448
#[allow(clippy::missing_safety_doc)]
14171449
#[no_mangle]
@@ -1447,6 +1479,13 @@ pub unsafe extern "C" fn krun_add_input_device(
14471479
})
14481480
}
14491481

1482+
#[cfg(not(feature = "input"))]
1483+
#[allow(clippy::missing_safety_doc)]
1484+
#[no_mangle]
1485+
pub unsafe extern "C" fn krun_add_input_device_fd(_ctx_id: u32, _input_fd: i32) -> i32 {
1486+
-libc::ENOTSUP
1487+
}
1488+
14501489
#[cfg(feature = "gpu")]
14511490
#[allow(clippy::missing_safety_doc)]
14521491
#[no_mangle]

0 commit comments

Comments
 (0)