From: clicedvly768 <harepro29@gmail.com>
To: ojeda@kernel.org, dmitry.torokhov@gmail.com
Cc: rust-for-linux@vger.kernel.org, linux-input@vger.kernel.org,
clicedvly768 <harepro29@gmail.com>
Subject: [PATCH 2/3] rust: serio: add serio bus abstractions
Date: Sat, 7 Nov 2026 00:17:13 +0500 [thread overview]
Message-ID: <20261106191714.334055-3-harepro29@gmail.com> (raw)
In-Reply-To: <20261106191714.334055-1-harepro29@gmail.com>
Add safe Rust abstractions for the serio (Serial I/O) bus subsystem.
This introduces:
- `DeviceId`: a transparent wrapper around `struct serio_device_id` for
device matching tables.
- `Device`: a borrowed serio port device passed to driver callbacks.
- `IrqReturn`: typed enum for serio interrupt handler status.
- `Driver` trait: safe interface for serio drivers (`connect`,
`interrupt`, `disconnect`).
- `Registration<T>`: safe RAII registration adapter that manages the
underlying `struct serio_driver` pinned on the kernel heap, registering
it via `serio_register_driver` and unregistering on drop.
- C helper wrappers for inline/macro serio functions in <linux/serio.h>.
Signed-off-by: clicedvly768 <harepro29@gmail.com>
---
rust/bindings/bindings_helper.h | 3 +-
rust/helpers/helpers.c | 3 +-
rust/helpers/serio.c | 23 ++++
rust/kernel/lib.rs | 3 +-
rust/kernel/serio.rs | 223 ++++++++++++++++++++++++++++++++
5 files changed, 252 insertions(+), 3 deletions(-)
create mode 100644 rust/helpers/serio.c
create mode 100644 rust/kernel/serio.rs
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 301c75cfeb61..0e44af5d770c 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -64,6 +64,7 @@
#include <linux/fwctl.h>
#include <linux/fs.h>
#include <linux/i2c.h>
+#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/io-pgtable.h>
#include <linux/ioport.h>
@@ -88,6 +89,7 @@
#include <linux/sched.h>
#include <linux/security.h>
#include <linux/serdev.h>
+#include <linux/serio.h>
#include <linux/slab.h>
#include <linux/sys_soc.h>
#include <linux/task_work.h>
@@ -171,4 +173,3 @@ const unsigned long RUST_CONST_HELPER_GPU_BUDDY_TRIM_DISABLE = GPU_BUDDY_TRIM_DI
#include "../../drivers/android/binder/rust_binder.h"
#include "../../drivers/android/binder/rust_binder_events.h"
#endif
-#include <linux/input.h>
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 0bc33a7b4351..61cf3f77e85a 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -67,6 +67,7 @@
#include "fs.c"
#include "fwctl.c"
#include "gpu.c"
+#include "input.c"
#include "interrupt.c"
#include "io.c"
#include "irq.c"
@@ -95,6 +96,7 @@
#include "scatterlist.c"
#include "security.c"
#include "serdev.c"
+#include "serio.c"
#include "signal.c"
#include "slab.c"
#include "spinlock.c"
@@ -109,4 +111,3 @@
#include "wait.c"
#include "workqueue.c"
#include "xarray.c"
-#include "input.c"
diff --git a/rust/helpers/serio.c b/rust/helpers/serio.c
new file mode 100644
index 000000000000..cd061495d9b1
--- /dev/null
+++ b/rust/helpers/serio.c
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/serio.h>
+
+__rust_helper void *rust_helper_serio_get_drvdata(struct serio *serio)
+{
+ return serio_get_drvdata(serio);
+}
+
+__rust_helper void rust_helper_serio_set_drvdata(struct serio *serio, void *data)
+{
+ serio_set_drvdata(serio, data);
+}
+
+__rust_helper int rust_helper_serio_register_driver(struct serio_driver *drv)
+{
+ return __serio_register_driver(drv, THIS_MODULE, KBUILD_MODNAME);
+}
+
+__rust_helper void rust_helper_serio_unregister_driver(struct serio_driver *drv)
+{
+ serio_unregister_driver(drv);
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 35cbde5f6a97..e4a4525fafdd 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -88,6 +88,7 @@
#[doc(hidden)]
pub mod impl_flags;
pub mod init;
+pub mod input;
pub mod interop;
pub mod interrupt;
pub mod io;
@@ -132,6 +133,7 @@
pub mod seq_file;
#[cfg(CONFIG_RUST_SERIAL_DEV_BUS_ABSTRACTIONS)]
pub mod serdev;
+pub mod serio;
pub mod sizes;
#[cfg(CONFIG_SOC_BUS)]
pub mod soc;
@@ -314,4 +316,3 @@ pub fn file_from_location<'a>(loc: &'a core::panic::Location<'a>) -> &'a core::f
c"<Location::file_as_c_str() not supported>"
}
}
-pub mod input;
diff --git a/rust/kernel/serio.rs b/rust/kernel/serio.rs
new file mode 100644
index 000000000000..3b752ce4b64d
--- /dev/null
+++ b/rust/kernel/serio.rs
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Serio (Serial I/O) bus abstractions for safe Rust drivers.
+//!
+//! Provides a safe Rust interface for serio devices and drivers in the Linux kernel,
+//! supporting input devices such as serial gamepads (e.g. Gravis Stinger), mice, and keyboards.
+
+use crate::{
+ bindings,
+ error::{to_result, Result},
+ prelude::*,
+ types::ForeignOwnable,
+};
+use core::{marker::PhantomData, pin::Pin, ptr::NonNull};
+
+/// Identifies supported serio hardware devices for auto-probing and matching.
+#[repr(transparent)]
+#[derive(Copy, Clone)]
+pub struct DeviceId(bindings::serio_device_id);
+
+impl DeviceId {
+ /// Creates a new serio device identifier.
+ pub const fn new(type_: u8, extra: u8, id: u8, proto: u8) -> Self {
+ Self(bindings::serio_device_id {
+ type_,
+ extra,
+ id,
+ proto,
+ })
+ }
+}
+
+/// Status returned by a serio driver interrupt handler.
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub enum IrqReturn {
+ /// Interrupt was not handled by this driver.
+ None,
+ /// Interrupt was successfully handled.
+ Handled,
+}
+
+impl IrqReturn {
+ fn to_c(self) -> bindings::irqreturn_t {
+ match self {
+ Self::None => bindings::irqreturn::IRQ_NONE,
+ Self::Handled => bindings::irqreturn::IRQ_HANDLED,
+ }
+ }
+}
+
+/// A serio port device instance passed to driver callbacks.
+pub struct Device {
+ ptr: NonNull<bindings::serio>,
+}
+
+impl Device {
+ /// Returns the raw pointer to the underlying `struct serio`.
+ pub fn as_raw(&self) -> *mut bindings::serio {
+ self.ptr.as_ptr()
+ }
+
+ /// Reads the physical device path into a buffer.
+ pub fn phys(&self) -> &[u8; 32] {
+ // SAFETY: `self.ptr` points to a valid `struct serio`.
+ unsafe { &(*self.ptr.as_ptr()).phys }
+ }
+}
+
+/// Trait implemented by serio device drivers.
+pub trait Driver: Send + Sync + Sized {
+ /// Context data stored per connected device instance.
+ type Data: ForeignOwnable + Send + Sync;
+
+ /// Device ID match table.
+ const ID_TABLE: &'static [DeviceId];
+
+ /// Called when a matching serio port is detected.
+ fn connect(dev: &mut Device) -> Result<Self::Data>;
+
+ /// Called in interrupt context when a new byte arrives from the serio port.
+ fn interrupt(
+ data: &mut <Self::Data as ForeignOwnable>::BorrowedMut<'_>,
+ byte: u8,
+ flags: u32,
+ ) -> IrqReturn;
+
+ /// Called when the device is disconnected.
+ fn disconnect(_data: Self::Data) {}
+}
+
+/// Driver adapter that bridges the safe Rust [`Driver`] trait to the C serio bus subsystem.
+pub struct Registration<T: Driver> {
+ drv: Pin<KBox<bindings::serio_driver>>,
+ _p: PhantomData<T>,
+}
+
+impl<T: Driver> Registration<T> {
+ /// Registers a serio driver with the kernel.
+ pub fn new(name: &'static crate::str::CStr) -> Result<Self> {
+ let drv = bindings::serio_driver {
+ description: name.as_char_ptr().cast(),
+ id_table: T::ID_TABLE.as_ptr().cast::<bindings::serio_device_id>(),
+ manual_bind: false,
+ write_wakeup: None,
+ interrupt: Some(Self::interrupt_callback),
+ connect: Some(Self::connect_callback),
+ reconnect: None,
+ fast_reconnect: None,
+ disconnect: Some(Self::disconnect_callback),
+ cleanup: None,
+ driver: bindings::device_driver {
+ name: name.as_char_ptr().cast(),
+ ..Default::default()
+ },
+ };
+
+ let mut drv_box = Pin::new(KBox::new(drv, GFP_KERNEL)?);
+
+ // SAFETY: `drv_box` is pinned on the kernel heap at a permanent address.
+ let ret = unsafe { bindings::serio_register_driver(drv_box.as_mut().get_mut()) };
+ to_result(ret)?;
+
+ Ok(Self {
+ drv: drv_box,
+ _p: PhantomData,
+ })
+ }
+
+ /// # Safety
+ ///
+ /// Must only be invoked by the serio subsystem when a matching port is probed.
+ /// `serio` and `drv` must be valid non-null pointers provided by the kernel.
+ unsafe extern "C" fn connect_callback(
+ serio: *mut bindings::serio,
+ drv: *mut bindings::serio_driver,
+ ) -> core::ffi::c_int {
+ let Some(serio_nn) = NonNull::new(serio) else {
+ return -(crate::error::code::EINVAL.to_errno());
+ };
+
+ let mut dev = Device { ptr: serio_nn };
+
+ let data = match T::connect(&mut dev) {
+ Ok(d) => d,
+ Err(e) => return e.to_errno(),
+ };
+
+ let raw_data = data.into_foreign();
+ // SAFETY: `serio` is valid; store driver data pointer.
+ unsafe {
+ bindings::serio_set_drvdata(serio, raw_data.cast());
+ }
+
+ // SAFETY: Open serio port to start receiving characters.
+ let ret = unsafe { bindings::serio_open(serio, drv) };
+ if ret != 0 {
+ // SAFETY: Clean up driver data if open failed.
+ unsafe {
+ bindings::serio_set_drvdata(serio, core::ptr::null_mut());
+ let data = <T::Data as ForeignOwnable>::from_foreign(raw_data);
+ T::disconnect(data);
+ }
+ return ret;
+ }
+
+ 0
+ }
+
+ /// # Safety
+ ///
+ /// Must only be invoked by the serio subsystem during an interrupt on a connected port.
+ /// `serio` must be a valid pointer.
+ unsafe extern "C" fn interrupt_callback(
+ serio: *mut bindings::serio,
+ data: core::ffi::c_uchar,
+ flags: core::ffi::c_uint,
+ ) -> bindings::irqreturn_t {
+ // SAFETY: `serio` is valid.
+ let raw = unsafe { bindings::serio_get_drvdata(serio) };
+ if raw.is_null() {
+ return bindings::irqreturn::IRQ_NONE;
+ }
+
+ // SAFETY: `raw` was created via `into_foreign()` in `connect_callback`.
+ let mut borrowed = unsafe { <T::Data as ForeignOwnable>::borrow_mut(raw) };
+ let ret = T::interrupt(&mut borrowed, data, flags);
+ ret.to_c()
+ }
+
+ /// # Safety
+ ///
+ /// Must only be invoked by the serio subsystem when disconnecting a port.
+ /// `serio` must be a valid pointer.
+ unsafe extern "C" fn disconnect_callback(serio: *mut bindings::serio) {
+ // SAFETY: `serio` is valid.
+ let raw = unsafe { bindings::serio_get_drvdata(serio) };
+ if raw.is_null() {
+ return;
+ }
+
+ // SAFETY: Close port, clear drvdata, and pass owned data back to disconnect.
+ unsafe {
+ bindings::serio_close(serio);
+ bindings::serio_set_drvdata(serio, core::ptr::null_mut());
+ let data = <T::Data as ForeignOwnable>::from_foreign(raw);
+ T::disconnect(data);
+ }
+ }
+}
+
+impl<T: Driver> Drop for Registration<T> {
+ fn drop(&mut self) {
+ // SAFETY: Unregister driver with kernel.
+ unsafe {
+ bindings::serio_unregister_driver(self.drv.as_mut().get_mut());
+ }
+ }
+}
+
+// SAFETY: `Registration` can be safely sent to another thread.
+unsafe impl<T: Driver> Send for Registration<T> {}
+// SAFETY: `Registration` can be safely shared across threads.
+unsafe impl<T: Driver> Sync for Registration<T> {}
--
2.56.0
next prev parent reply other threads:[~2026-11-06 19:17 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-06 19:17 [PATCH 0/3] rust: input: add input/serio abstractions and port Gravis Stinger driver clicedvly768
2026-11-06 19:17 ` [PATCH 1/3] rust: input: add input device abstractions clicedvly768
2026-11-06 19:29 ` sashiko-bot
2026-11-06 19:17 ` clicedvly768 [this message]
2026-11-06 19:35 ` [PATCH 2/3] rust: serio: add serio bus abstractions sashiko-bot
2026-11-06 19:17 ` [PATCH 3/3] input: joystick: stinger: port Gravis Stinger driver to safe Rust clicedvly768
2026-11-06 19:42 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261106191714.334055-3-harepro29@gmail.com \
--to=harepro29@gmail.com \
--cc=dmitry.torokhov@gmail.com \
--cc=linux-input@vger.kernel.org \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.