From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=209.85.128.44 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1794064375; b=Gv1cLd/ozMYm6c6IKWHT9yN3bn7u5teUVsH9LMu/O0l2DBpKSYSsTqLK5YXVjHIZOhMz VtxrcWFaZVq/XSB6WZ+P/38qxKQmtEHG4IKQVQUQzcl4wMD06kVzwKwnd4MMFwcTyiNUv f4OqzY26zUbNMjvSnoeaUoRuUmevAsVqW4Nf7OpSKfz0x8E6gIdQTNPL9J54W+Yf2QOsI a3gGsjfP0U1x/bat4OyWWKlXbt/0CBJZtlWID6y5iRgUBxN3V8WIU2geMIjRE9mJnD3cB NicKyaAAuAaBg1pegRf6X29/vaKTlIf8bq0TxUrH9fQ4G7pqOLlGPx41rASS+bYsOyg== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1794064375; h=DMARC-Filter:Received:DKIM-Signature:X-Google-DKIM-Signature: X-Forwarded-Encrypted:X-Gm-Message-State:X-Gm-Gg:X-Received:Received: From:To:Cc:Subject:Date:Message-ID:X-Mailer:In-Reply-To:References: MIME-Version:Content-Transfer-Encoding; bh=RacPJkBXQjWo1L6NvBAwFVNdOm+cWYSxFk+Cu0fqNro=; b=DKEfSqYgojonsTM35ZqLtEeXlgWpQHk5/LmKjfpndpB2IKlcuayH//88gi8nIgQ7GvtU csi65bYP4XN462wtWzm1aU5mDzAAPHZSiFvZ3BtqU87qqkvsBrlNQYkRQGbXIXIRQiI2X Z2xC7sqbaIMSjj+FjJB0PdQ43MqEZZvXMhz0daJ7VqBEFdbZhOAAVsQzDc2Lk6fFQPbfj 077865QGZ4KqPUKOAeEJgiyrZDvz/b+np/jCihmjvzdgNwPlUYRc+3g3ZsQptIcaAKr3J V2AhihSqYGnMmYrJLlkx1W8cLw0+pR6cyBABdEC/iUOGWLHJpRNDmQ9O+88orAI7nbQ== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=NKRjwYIY; arc=none smtp.remote-ip=209.85.128.44 Received: from mail-wm1-f44.google.com (mail-wm1-f44.google.com [209.85.128.44]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 316BC606CE7C for ; Sat, 7 Nov 2026 15:12:54 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 316BC606CE7C Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=NKRjwYIY Received: by mail-wm1-f44.google.com with SMTP id 5b1f17b1804b1-4a35db1f202so12270755e9.3 for ; Sat, 07 Nov 2026 07:12:53 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1794064372; x=1794669172; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=RacPJkBXQjWo1L6NvBAwFVNdOm+cWYSxFk+Cu0fqNro=; b=NKRjwYIYfJPFhrTLd+SaaMyLJf5hC7zIjaDxl0QzJ0W9HIFvBMDc7IkoTYvuvepnZG 3tsofXxal0CYh1eWtLs3XgoqSleBy50zxSgG+BZUoIRhRvCud6wDXYEy1I7STWwwCu43 287x5f+42XkYwpPnbYjpqSWvjCCTTj7C1YB6NQz73qRGuy+kC2K4VDK/NVK8hDeuoO/4 W+afAzY7qfmCp8ssOONRlF+t4kND2Qj4vD9zRYFx0sbwNcK+xIUjkCUlw2X8jKmK3e15 gxe0TSDLlR5quzQU1JCUyRh5uRLKTkUb5KgYslb9pQZc8YcdH/GjWB7jqbf2mPvma97V TzGw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1794064372; x=1794669172; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=RacPJkBXQjWo1L6NvBAwFVNdOm+cWYSxFk+Cu0fqNro=; b=LYKgMldr0kQjGnKmFA+JFypnF8s+3ugHxnXBgPY91StFS8TJYiZcg46EcsVVFsW8Fo CwdUFJ0+F5HZrrQbUo2jYjrQKauS3qu1o4lCZ0pdEn0REQRLXFeVicFzQ158qOnnmmI3 WCLXU6keonwqZMDw76IPbFwIkpN4+yd5adcBIHkG7rO8zQN94W1XozcdHKsHZJ5B+BqL xRc5c1+LkEyhaqAORvzm0mJYv1g2v0kQ8QDQk2k7PpbQLnweNpYQW8aO+cLf/qD7Hw69 HHBpwqENtSAREK0mUWFHZbovYC0V+QE84FSRKkrvUa7CqasHq8ccZA0eIr8su29WdgUR 1H1w== X-Forwarded-Encrypted: i=1; AKwUvBwuV2JldZ7198+vxOHHuux6E+F7KhtS41Fqew/vIkNey3xItMGMHntBcL/Cn3U6Q1+PydnE6HkS4mYjnJs=@vger.kernel.org X-Gm-Message-State: APuU4Z9NdKTyBPKR1cFUVoIr315xBA/8+V7H0UK81i3PCVwQeNA8EYgD t4sh7TCFUxdOMuSmC6UU+m2Qu102cmooYGFF/3crfS1QQr5A6OJHkhcz X-Gm-Gg: AYBFou2T14BTPXuQDT6BDhp94T9eKfsfvrk5P87WN1ZA3tzQ07cuTge8lu+QzRdz5q6 3/hm5o42pK+HZgAqo0rawh5pNZzT+loqyP1/xKpRmzn14gMtX6plvI0vYYzlzSY/6k57Xqcwl3Q 16+xBKO91l50mnpBSfUjdpaiP4ikzhnC+oFUrooOcpAVHuTyxtUN+Z1/5AkxaZ1yTkuyKWlLoRn udncuEDJDW+urZth5qHJgnKaUcMFN/6e8qhgGolz7SF1t1a4Ayhvntq8rCVndSg8JqeLCnVzbfO UT6nFn+dtCSvdZJSCAYJp1GE2K5wo2WxFVhk0oOEb2SfcLt8+POLRQMh2NPjM1hmWXvPi9nyxHH Pg72x7BmQ+6E+wEKJgcnZ6/UFUddnvzQG6meGyXO7eAz0c4vE9Kzdf0zKxeCv81qej0bAyjYyAJ TpbvYCUTOirfy9g4kb7TP0EzomToJ/8wSpmhEQSGgwEjAB434+E4R3gDVvdKimtPfxhR0+kEavN 3ZqrinxSNIgsC9jbNUcBFzPnBPPBMzlHuyRNSZyOMhRp7q656TOSooOc5nDH6ZkkhWl6gKuJHru VmHDKDLPGdfXt3mzogTt2uawOOVRVNMOg/LtmkflOMVHyH5ujhiKRWNzddgaFzeh4Zw6qNb4iMY UIyRwIGWFJffo25DdwMk8KNAV5F4CwxLnhNSTz6ToxPqHJ6Nk X-Received: by 2002:a05:600d:82e2:b0:4a2:18d4:f9e0 with SMTP id 5b1f17b1804b1-4b6df6deffbmr103079635e9.25.1794064372332; Sat, 07 Nov 2026 07:12:52 -0800 (PST) Received: from DESKTOP-KHFMJSR ([194.9.62.193]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4b7c49082b6sm32073065e9.2.2026.11.07.07.12.48 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 07 Nov 2026 07:12:51 -0800 (PST) From: Vecheslav Filatov To: dmitry.torokhov@gmail.com, ojeda@kernel.org, alex.gaynor@gmail.com, boqun.feng@gmail.com, gary@garyguo.net, bjorn3_gh@protonmail.com, lossin@kernel.org, a.hindborg@kernel.org, aliceryhl@google.com, tmgross@umich.edu, dakr@kernel.org Cc: linux-input@vger.kernel.org, rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, Vecheslav Filatov Subject: [PATCH v11 1/2] rust: input: add input device abstractions Date: Sat, 7 Nov 2026 20:12:37 +0500 Message-ID: <20261107151238.607451-2-harepro29@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261107151238.607451-1-harepro29@gmail.com> References: <20261107151238.607451-1-harepro29@gmail.com> X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Add safe Rust abstractions for Linux input devices (struct input_dev). This provides: - `AllocatedDevice`: a newly allocated, unregistered input device handle that allows configuring capabilities (keys, absolute axis limits, parent device) before registration. - Safe string methods: `set_name` and `set_phys` accept `&'static CStr` to prevent use-after-free since the input core does not copy strings. - `set_phys_from_parent`: dynamic physical location path formatting ("%s/input0") stored safely in an owned kernel heap buffer. - Device lifecycle and reference count management: `set_parent` takes a reference count via `bindings::get_device` to avoid dangling pointers, releasing it appropriately on drop or post-registration. - Bounds-checked `set_abs_params` validating `axis < ABS_CNT` (0x40) to prevent memory corruption in `dev->absinfo`. - `RegisteredDevice`: a registered input device handle providing safe, inlined methods to report keys, absolute coordinates, and synchronization events. Automatically calls `input_unregister_device` on drop (RAII). - Constants for bus types (`bus::*`) and event types (`event_type::*`). - Alphabetically sorted imports and module entries gated with `#[cfg(CONFIG_INPUT = "y")]` and `#if IS_REACHABLE(CONFIG_INPUT)` to avoid link failures when CONFIG_INPUT is disabled or modular. Signed-off-by: Vecheslav Filatov --- rust/bindings/bindings_helper.h | 1 + rust/helpers/helpers.c | 1 + rust/helpers/input.c | 22 +++ rust/kernel/input.rs | 253 ++++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 2 + 5 files changed, 279 insertions(+) create mode 100644 rust/helpers/input.c create mode 100644 rust/kernel/input.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 3791af0aadc7..4b09d3980e10 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -64,6 +64,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index d62c7be1ca7b..77cec85ad153 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" diff --git a/rust/helpers/input.c b/rust/helpers/input.c new file mode 100644 index 000000000000..f43a06acfdc3 --- /dev/null +++ b/rust/helpers/input.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +#if IS_REACHABLE(CONFIG_INPUT) + +__rust_helper void rust_helper_input_report_key(struct input_dev *dev, unsigned int code, int value) +{ + input_report_key(dev, code, value); +} + +__rust_helper void rust_helper_input_report_abs(struct input_dev *dev, unsigned int code, int value) +{ + input_report_abs(dev, code, value); +} + +__rust_helper void rust_helper_input_sync(struct input_dev *dev) +{ + input_sync(dev); +} + +#endif /* IS_REACHABLE(CONFIG_INPUT) */ diff --git a/rust/kernel/input.rs b/rust/kernel/input.rs new file mode 100644 index 000000000000..14977298ad47 --- /dev/null +++ b/rust/kernel/input.rs @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Input subsystem abstractions for safe Rust drivers. +//! +//! Provides a safe Rust object-oriented interface around Linux `struct input_dev`, +//! ensuring safe allocation, capability configuration, registration, and RAII cleanup. + +use crate::{ + bindings, // + device, // + error::to_result, // + prelude::*, // +}; +use core::ptr::NonNull; + +/// Bus types for input devices. +pub mod bus { + use super::*; + + /// PCI bus. + pub const PCI: u16 = bindings::BUS_PCI as u16; + /// USB bus. + pub const USB: u16 = bindings::BUS_USB as u16; + /// RS232 serial bus. + pub const RS232: u16 = bindings::BUS_RS232 as u16; +} + +/// Event types reported by input devices. +pub mod event_type { + use super::*; + + /// Key or button press/release events. + pub const KEY: u32 = bindings::EV_KEY; + /// Absolute axis movement events. + pub const ABS: u32 = bindings::EV_ABS; +} + +/// An allocated input device being configured prior to registration. +pub struct AllocatedDevice { + ptr: NonNull, + phys: Option>, +} + +impl AllocatedDevice { + /// Allocates a new input device. + #[inline] + pub fn new() -> Result { + // SAFETY: `input_allocate_device` returns either a valid pointer or NULL. + let ptr = unsafe { bindings::input_allocate_device() }; + let ptr = NonNull::new(ptr).ok_or(ENOMEM)?; + + Ok(Self { ptr, phys: None }) + } + + /// Sets the human-readable device name. + #[inline] + pub fn set_name(&mut self, name: &'static CStr) { + // SAFETY: `self.ptr` is valid and `name` has static lifetime. + unsafe { + (*self.ptr.as_ptr()).name = name.as_char_ptr().cast(); + } + } + + /// Sets the physical location string from a static C string. + #[inline] + pub fn set_phys(&mut self, phys: &'static CStr) { + // SAFETY: `self.ptr` is valid and `phys` has static lifetime. + unsafe { + (*self.ptr.as_ptr()).phys = phys.as_char_ptr().cast(); + } + } + + /// Sets the physical location string by formatting `/input0`. + #[inline] + pub fn set_phys_from_parent(&mut self, parent_phys: &[u8]) -> Result { + let mut buf = KBox::new([0u8; 64], GFP_KERNEL)?; + let len = parent_phys + .iter() + .position(|&b| b == 0) + .unwrap_or(parent_phys.len()); + let suffix = b"/input0\0"; + if len + suffix.len() > buf.len() { + return Err(EINVAL); + } + buf[..len].copy_from_slice(&parent_phys[..len]); + buf[len..len + suffix.len()].copy_from_slice(suffix); + + // SAFETY: `buf` is allocated on the kernel heap and its lifetime is tied to `self.phys`. + unsafe { + (*self.ptr.as_ptr()).phys = buf.as_ptr().cast(); + } + self.phys = Some(buf); + Ok(()) + } + + /// Sets device identifier (bus type, vendor, product, version). + #[inline] + pub fn set_id(&mut self, bustype: u16, vendor: u16, product: u16, version: u16) { + // SAFETY: `self.ptr` is valid. + unsafe { + let dev = self.ptr.as_ptr(); + (*dev).id.bustype = bustype; + (*dev).id.vendor = vendor; + (*dev).id.product = product; + (*dev).id.version = version; + } + } + + /// Sets the parent device for this input device. + /// + /// Holds a reference count on `parent` to prevent dangling pointers before registration. + #[inline] + pub fn set_parent(&mut self, parent: &device::Device) { + // SAFETY: `self.ptr` is valid, and `parent.as_raw()` returns a valid `struct device`. + // Acquire a reference to guarantee `parent` remains alive while referenced by `dev.parent`. + unsafe { + let old = (*self.ptr.as_ptr()).dev.parent; + if !old.is_null() { + bindings::put_device(old); + } + (*self.ptr.as_ptr()).dev.parent = bindings::get_device(parent.as_raw()); + } + } + + /// Enables support for a capability (e.g. key or axis). + #[inline] + pub fn set_capability(&mut self, type_: u32, code: u32) { + // SAFETY: `self.ptr` is valid and type/code are valid capability values. + unsafe { + bindings::input_set_capability(self.ptr.as_ptr(), type_, code); + } + } + + /// Sets parameter limits for an absolute axis. + /// + /// Bounds-checks `axis < ABS_CNT` (0x40) to prevent out-of-bounds writes + /// to the `absinfo` array in `struct input_dev`. + #[inline] + pub fn set_abs_params( + &mut self, + axis: u32, + min: i32, + max: i32, + fuzz: i32, + flat: i32, + ) -> Result { + if axis >= bindings::ABS_CNT { + return Err(EINVAL); + } + + // SAFETY: `self.ptr` is valid and `axis` has been verified within bounds. + unsafe { + bindings::input_set_abs_params(self.ptr.as_ptr(), axis, min, max, fuzz, flat); + } + Ok(()) + } + + /// Registers the input device with the kernel input core. + /// + /// Consumes the `AllocatedDevice` and returns a [`RegisteredDevice`]. + #[inline] + pub fn register(mut self) -> Result { + // SAFETY: `ptr` is valid and was allocated by `input_allocate_device`. + to_result(unsafe { bindings::input_register_device(self.ptr.as_ptr()) })?; + + // SAFETY: `device_add` during `input_register_device` acquires its own reference + // to `parent`, so we drop our pre-registration reference to avoid a leak. + unsafe { + let parent = (*self.ptr.as_ptr()).dev.parent; + if !parent.is_null() { + bindings::put_device(parent); + } + } + + let ptr = self.ptr; + let phys = self.phys.take(); + core::mem::forget(self); + + Ok(RegisteredDevice { ptr, _phys: phys }) + } +} + +impl Drop for AllocatedDevice { + #[inline] + fn drop(&mut self) { + // SAFETY: `self.ptr` was allocated by `input_allocate_device` and has not been registered. + unsafe { + let parent = (*self.ptr.as_ptr()).dev.parent; + if !parent.is_null() { + bindings::put_device(parent); + } + bindings::input_free_device(self.ptr.as_ptr()); + } + } +} + +// SAFETY: `AllocatedDevice` exclusively owns its `input_dev` pointer and can be transferred +// across threads. +unsafe impl Send for AllocatedDevice {} +// SAFETY: `AllocatedDevice` has exclusive ownership and thread-safe methods. +unsafe impl Sync for AllocatedDevice {} + +/// A registered input device capable of reporting events to userspace. +pub struct RegisteredDevice { + ptr: NonNull, + _phys: Option>, +} + +impl RegisteredDevice { + /// Reports a binary button or key state event. + #[inline] + pub fn report_key(&self, code: u32, value: bool) { + // SAFETY: `self.ptr` is valid and registered with the input core. + unsafe { + bindings::input_report_key(self.ptr.as_ptr(), code, if value { 1 } else { 0 }); + } + } + + /// Reports an absolute axis position value. + #[inline] + pub fn report_abs(&self, code: u32, value: i32) { + // SAFETY: `self.ptr` is valid and registered with the input core. + unsafe { + bindings::input_report_abs(self.ptr.as_ptr(), code, value); + } + } + + /// Flushes and synchronizes all reported events into a single input packet. + #[inline] + pub fn sync(&self) { + // SAFETY: `self.ptr` is valid and registered with the input core. + unsafe { + bindings::input_sync(self.ptr.as_ptr()); + } + } +} + +impl Drop for RegisteredDevice { + #[inline] + fn drop(&mut self) { + // SAFETY: `self.ptr` was successfully registered with `input_register_device`. + // Unregistering automatically unbinds and frees the device. + unsafe { + bindings::input_unregister_device(self.ptr.as_ptr()); + } + } +} + +// SAFETY: `RegisteredDevice` can be safely transferred across threads. +unsafe impl Send for RegisteredDevice {} +// SAFETY: Event reporting on `input_dev` is thread-safe (protected by internal locks in the +// input core). +unsafe impl Sync for RegisteredDevice {} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 1ab41129d557..ff5c978758d0 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -88,6 +88,8 @@ #[doc(hidden)] pub mod impl_flags; pub mod init; +#[cfg(CONFIG_INPUT = "y")] +pub mod input; pub mod interop; pub mod interrupt; pub mod io; -- 2.56.0