poll: First alpha implementation

This commit is contained in:
Johannes Leupolz 2026-04-03 20:37:40 +00:00
parent 8d4a0c9413
commit 5cffaec3da
5 changed files with 53 additions and 29 deletions

View file

@ -18,7 +18,7 @@ use anyhow::Context;
use cuse_lowlevel::fuse_lowlevel;
use nix::sys::epoll::{Epoll, EpollCreateFlags, EpollEvent, EpollFlags};
use crate::cuse_device::state::{get_vuinput_state, VuFileHandle};
use crate::cuse_device::state::{get_vuinput_state, PollPhase, VuFileHandle};
pub static EVDEV_WRITE_WATCHER: OnceLock<Mutex<EvdevWriteWatcher>> = OnceLock::new();
@ -113,7 +113,7 @@ fn evdev_write_watch_loop(shutdown: Arc<AtomicBool>, epoll: Arc<Epoll>) {
fuse_lowlevel::fuse_pollhandle_destroy(handle.as_ptr());
}
}
state.poll.readable = true;
state.poll.pollphase = PollPhase::Readable;
state.poll.pending.clear();
}
}

View file

@ -38,7 +38,6 @@ pub fn vuinput_make_cuse_ops() -> cuse_lowlevel::cuse_lowlevel_ops {
release: Some(vuinput_release::vuinput_release),
fsync: None,
ioctl: Some(vuinput_ioctl::vuinput_ioctl),
//poll: Some(vuinput_poll::vuinput_poll),
poll: None,
poll: Some(vuinput_poll::vuinput_poll),
}
}

View file

@ -43,6 +43,24 @@ impl KeyTracker {
}
}
/// EMPTY -> READY -> READING -> { EMPTY | READY }
/// EMPTY -> READABLE (new data arrives / watcher can observe)
/// READABLE -> READING (read callback starts draining)
/// READING -> EMPTY (read drained everything)
/// READING -> READABLE (read finished, but more data still remains)
#[derive(Debug)]
pub enum PollPhase {
Empty,
Readable,
Reading,
}
impl Default for PollPhase {
fn default() -> Self {
Self::Empty
}
}
/// this data structure ensures poll and read are synchronized.
/// poll() and read() must synchronize through one shared readines
/// state, and the state transitions must be done under the same per-handle mutex.
@ -55,7 +73,7 @@ impl KeyTracker {
pub struct PollState {
/// Sticky readiness latch:
/// true once evdev became readable, false again after read/drain.
pub readable: bool,
pub pollphase: PollPhase,
/// Pending FUSE poll waiters for this device.
/// Optimized for the common case of 0 or 1 waiter, but supports
@ -66,7 +84,7 @@ pub struct PollState {
impl PollState {
pub fn new() -> PollState {
PollState {
readable: false,
pollphase: PollPhase::Empty,
pending: smallvec![],
}
}

View file

@ -12,6 +12,7 @@ use log::{debug, trace};
use std::io::{Read, Write};
use std::os::fd::AsRawFd;
use std::os::raw::c_char;
use std::ptr::NonNull;
use uinput_ioctls::*;
// https://github.com/libfuse/libfuse/blob/master/example/poll.c
@ -20,14 +21,22 @@ pub unsafe extern "C" fn vuinput_poll(
fi: *mut fuse_lowlevel::fuse_file_info,
ph: *mut fuse_lowlevel::fuse_pollhandle,
) {
let vuinput_state_mutex =
get_vuinput_state(&VuFileHandle::from_fuse_file_info(fi.as_ref().unwrap())).unwrap();
let mut vuinput_state = vuinput_state_mutex.lock().unwrap();
/*
let vuinput_state_mutex =
get_vuinput_state(&VuFileHandle::from_fuse_file_info(fi.as_ref().unwrap())).unwrap();
let mut vuinput_state = vuinput_state_mutex.lock().unwrap();
if state.poll.readable {
// return POLLIN immediately
} else if let Some(handle) = NonNull::new(ph) {
state.poll.add_waiter(handle); */
match vuinput_state.poll.pollphase {
PollPhase::Empty => {
let ph = NonNull::<fuse_lowlevel::fuse_pollhandle>::new(ph);
vuinput_state.poll.add_waiter(ph.unwrap());
}
PollPhase::Readable => {
fuse_lowlevel::fuse_lowlevel_notify_poll(ph);
fuse_lowlevel::fuse_pollhandle_destroy(ph);
}
PollPhase::Reading => {
fuse_lowlevel::fuse_lowlevel_notify_poll(ph);
fuse_lowlevel::fuse_pollhandle_destroy(ph);
}
}
}

View file

@ -3,15 +3,12 @@
// Author: Johannes Leupolz <dev@leupolz.eu>
use crate::cuse_device::*;
use crate::global_config::get_device_policy;
use ::cuse_lowlevel::*;
use libc::{__s32, __u16, input_event};
use libc::{__s32, __u16, input_event, EAGAIN};
use libc::{off_t, size_t, EIO};
use libc::{uinput_abs_setup, uinput_setup};
use log::{debug, trace};
use std::io::{Read, Write};
use std::os::fd::AsRawFd;
use std::os::raw::c_char;
use uinput_ioctls::*;
// TODO: compat-mode+ ensure sizeof(struct input_event)
@ -38,12 +35,12 @@ pub unsafe extern "C" fn vuinput_read(
let mut buffer: [u8; 24] = [0; 24];
vuinput_state.poll.pollphase = PollPhase::Reading;
// read up to 24 bytes
// todo: non-blocking or timeout
let result = vuinput_state.file.read(&mut buffer);
match result {
Ok(NORMAL_SIZE) => {
if (!is_compat) {
if !is_compat {
let buffer = buffer.as_ptr() as *const i8;
fuse_lowlevel::fuse_reply_buf(_req, buffer, 24);
} else {
@ -56,18 +53,19 @@ pub unsafe extern "C" fn vuinput_read(
}
}
Err(e) => {
debug!("fh {}: error reading from uinput: {e:?}", fh);
fuse_lowlevel::fuse_reply_err(_req, EIO);
if e.kind() == io::ErrorKind::WouldBlock {
// EAGAIN / EWOULDBLOCK
//println!("Received EAGAIN: The read would block!");
vuinput_state.poll.pollphase = PollPhase::Empty;
fuse_lowlevel::fuse_reply_err(_req, EAGAIN);
} else {
debug!("fh {}: error reading from uinput: {e:?}", fh);
fuse_lowlevel::fuse_reply_err(_req, EIO);
}
}
Ok(_) => {
debug!("fh {}: error reading from uinput: wrong size", fh);
fuse_lowlevel::fuse_reply_err(_req, EIO);
}
}
/*
if drained_to_eagain {
state.poll.readable = false;
} else {
state.poll.readable = true;
} */
}