diff options
| author | jamie <git@skilet.ro> | 2026-08-29 22:51:38 +0100 |
|---|---|---|
| committer | jamie <git@skilet.ro> | 2026-08-29 22:55:36 +0100 |
| commit | 6ade07db214e8571a439b474e6c4e6a4c861d098 (patch) | |
| tree | 0c4864b5d4a2abc0b05cfdb83596d87edb018dac /src/hid.rs | |
| parent | 4c42e8c9d7ec1b2e94267038d5ee38a714ca2955 (diff) | |
Diffstat (limited to 'src/hid.rs')
| -rw-r--r-- | src/hid.rs | 148 |
1 files changed, 108 insertions, 40 deletions
@@ -6,6 +6,7 @@ //! `HIDIOCSFEATURE`, `HIDIOCGINPUT`, `HIDIOCGSOUTPUT` ioctls plus plain //! `read`/`write` on the interrupt endpoints. +use std::cmp::Ordering; use std::fs; use std::io; use std::os::fd::AsRawFd; @@ -27,10 +28,12 @@ const IOC_READ: u32 = 2; const IOC_WRITE: u32 = 1; fn ioc(dir: u32, ty: u8, nr: u32, size: u32) -> libc::c_ulong { - ((dir << IOC_DIRSHIFT) - | ((ty as u32) << IOC_TYPESHIFT) - | (nr << IOC_NRSHIFT) - | (size << IOC_SIZESHIFT)) as libc::c_ulong + u64::from( + (dir << IOC_DIRSHIFT) + | (u32::from(ty) << IOC_TYPESHIFT) + | (nr << IOC_NRSHIFT) + | (size << IOC_SIZESHIFT), + ) } const HIDIOCGFEATURE_NR: u32 = 0x06; @@ -62,11 +65,19 @@ pub struct HidDevice { /// Enumerate hidraw devices matching `vid`/`pid` by reading the sysfs `uevent` /// of each `/sys/class/hidraw/hidrawN`. +/// +/// # Errors +/// +/// Returns an error if `/sys/class/hidraw` cannot be listed. pub fn find_devices(vid: u16, pid: u16) -> io::Result<Vec<HidDevice>> { find_with(|v, p| v == vid && p == pid) } /// Find the first (only) MMX 150 dongle HID device. +/// +/// # Errors +/// +/// Propagates errors from [`find_devices`]. pub fn find_mmx150() -> io::Result<Option<HidDevice>> { Ok(find_devices(BEYERDYNAMIC_VID, MMX150_DONGLE_PID)? .into_iter() @@ -74,6 +85,10 @@ pub fn find_mmx150() -> io::Result<Option<HidDevice>> { } /// Enumerate all hidraw devices belonging to a vendor. +/// +/// # Errors +/// +/// Returns an error if `/sys/class/hidraw` cannot be listed. pub fn find_devices_by_vendor(vid: u16) -> io::Result<Vec<HidDevice>> { find_with(|v, _| v == vid) } @@ -122,12 +137,17 @@ pub struct Hidraw { } impl Hidraw { - pub fn open(devnode: impl AsRef<Path>) -> io::Result<Hidraw> { + /// Open a hidraw device node for reading and writing. + /// + /// # Errors + /// + /// Returns an error if the node cannot be opened for read/write access. + pub fn open(devnode: impl AsRef<Path>) -> io::Result<Self> { let file = fs::OpenOptions::new() .read(true) .write(true) .open(devnode.as_ref())?; - Ok(Hidraw { + Ok(Self { file, devnode: devnode.as_ref().to_path_buf(), }) @@ -135,33 +155,63 @@ impl Hidraw { /// `HIDIOCGFEATURE`: fetch `report_id` into `data` (report length + 1 /// bytes including the report-id byte). Returns bytes received. + /// + /// # Errors + /// + /// Returns an error if the ioctl fails. pub fn get_feature(&self, report_id: u8, len: usize) -> io::Result<Vec<u8>> { let mut buf = vec![0u8; len]; - buf[0] = report_id; - self.raw_ioctl(hid_get_feature_cmd(len as u32), &mut buf)?; + if let Some(first) = buf.first_mut() { + *first = report_id; + } + self.raw_ioctl(hid_get_feature_cmd(report_size(len)), &mut buf)?; Ok(buf) } /// `HIDIOCSFEATURE`: set a feature report (`data[0]` is the report id). + /// + /// # Errors + /// + /// Returns an error if the ioctl fails. pub fn set_feature(&self, data: &[u8]) -> io::Result<usize> { - self.raw_ioctl(hid_set_feature_cmd(data.len() as u32), &mut data.to_vec()) + self.raw_ioctl( + hid_set_feature_cmd(report_size(data.len())), + &mut data.to_vec(), + ) } /// `HIDIOCGINPUT`: fetch an input report by id. + /// + /// # Errors + /// + /// Returns an error if the ioctl fails. pub fn get_input(&self, report_id: u8, len: usize) -> io::Result<Vec<u8>> { let mut buf = vec![0u8; len]; - buf[0] = report_id; - self.raw_ioctl(hid_get_input_cmd(len as u32), &mut buf)?; + if let Some(first) = buf.first_mut() { + *first = report_id; + } + self.raw_ioctl(hid_get_input_cmd(report_size(len)), &mut buf)?; Ok(buf) } /// `HIDIOCGSOUTPUT`: send an output report (`data[0]` is the report id) /// via a control transfer, without going through the interrupt endpoint. + /// + /// # Errors + /// + /// Returns an error if the ioctl fails. pub fn send_output(&self, data: &[u8]) -> io::Result<usize> { - self.raw_ioctl(hid_get_output_cmd(data.len() as u32), &mut data.to_vec()) + self.raw_ioctl( + hid_get_output_cmd(report_size(data.len())), + &mut data.to_vec(), + ) } /// Read from the interrupt IN endpoint (asynchronous input reports). + /// + /// # Errors + /// + /// Returns an error if the read fails. pub fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { use std::io::Read; self.file.read(buf) @@ -169,50 +219,63 @@ impl Hidraw { /// Wait up to `timeout` for one input report on the interrupt IN /// endpoint. Returns `Ok(None)` if none arrived in time. + /// + /// # Errors + /// + /// Returns an error if `select` or `read` fails with anything other than + /// `EINTR`. pub fn read_with_timeout(&mut self, timeout: Duration) -> io::Result<Option<Vec<u8>>> { - let deadline = Instant::now() + timeout; + let deadline = Instant::now() + .checked_add(timeout) + .unwrap_or_else(Instant::now); loop { - let now = Instant::now(); - if now >= deadline { + let Some(remaining) = deadline.checked_duration_since(Instant::now()) else { return Ok(None); - } + }; + let fd = self.file.as_raw_fd(); let mut set = unsafe { std::mem::zeroed::<libc::fd_set>() }; - unsafe { libc::FD_ZERO(&mut set) }; - unsafe { libc::FD_SET(self.file.as_raw_fd(), &mut set) }; - let remaining = deadline.saturating_duration_since(now); + unsafe { libc::FD_ZERO(std::ptr::addr_of_mut!(set)) }; + unsafe { libc::FD_SET(fd, std::ptr::addr_of_mut!(set)) }; let mut tv = libc::timeval { - tv_sec: remaining.as_secs() as libc::time_t, - tv_usec: remaining.subsec_micros() as libc::suseconds_t, + tv_sec: libc::time_t::try_from(remaining.as_secs()).unwrap_or(libc::time_t::MAX), + tv_usec: libc::suseconds_t::from(remaining.subsec_micros()), }; let n = unsafe { libc::select( - self.file.as_raw_fd() + 1, - &mut set, + fd.saturating_add(1), + std::ptr::addr_of_mut!(set), std::ptr::null_mut(), std::ptr::null_mut(), - &mut tv, + std::ptr::addr_of_mut!(tv), ) }; - if n < 0 { - let err = io::Error::last_os_error(); - if err.kind() == io::ErrorKind::Interrupted { - continue; + match n.cmp(&0) { + Ordering::Less => { + let err = io::Error::last_os_error(); + if err.kind() != io::ErrorKind::Interrupted { + return Err(err); + } + } + Ordering::Equal => return Ok(None), + Ordering::Greater => { + let mut buf = [0u8; 256]; + match self.read(&mut buf) { + Ok(len) => { + return Ok(Some(buf.get(..len).unwrap_or(&[]).to_vec())); + } + Err(e) if e.kind() == io::ErrorKind::Interrupted => {} + Err(e) => return Err(e), + } } - return Err(err); - } - if n == 0 { - return Ok(None); - } - let mut buf = [0u8; 256]; - match self.read(&mut buf) { - Ok(len) => return Ok(Some(buf[..len].to_vec())), - Err(e) if e.kind() == io::ErrorKind::Interrupted => continue, - Err(e) => return Err(e), } } } /// Write to the interrupt OUT endpoint. + /// + /// # Errors + /// + /// Returns an error if the write fails. pub fn write(&mut self, buf: &[u8]) -> io::Result<usize> { use std::io::Write; self.file.write(buf) @@ -224,13 +287,13 @@ impl Hidraw { libc::ioctl( self.file.as_raw_fd(), request, - buf.as_mut_ptr() as *mut libc::c_void, + buf.as_mut_ptr().cast::<libc::c_void>(), ) }; if ret < 0 { return Err(io::Error::last_os_error()); } - Ok(ret as usize) + Ok(usize::try_from(ret).unwrap_or(0)) } } @@ -240,6 +303,11 @@ impl std::os::fd::AsRawFd for Hidraw { } } +/// Clamp a report length to what the ioctl size field can hold. +fn report_size(len: usize) -> u32 { + u32::try_from(len).unwrap_or(u32::MAX) +} + fn read_sysfs(path: &Path) -> Option<String> { fs::read_to_string(path).ok() } |
