Linux Input/HID development
 help / color / mirror / Atom feed
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


  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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox