From nobody Sat Feb 7 12:19:35 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 800BA33509F; Mon, 26 Jan 2026 12:22:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769430130; cv=none; b=uogwayWod1X4oAALOh0zyIgqunEfPkqskrBonQtJSskP6VMfM6sPPmkjG4zOlKNktP0Xuc3CQskbejXZR5QWTwc1+Q2mPPvDKgsUCVnNqikXjnp/bqlM+/K0AxsltLs/d9TjeWgXTD6na9nbZYkIdDf+wU99cxonkGQV9+A3rIk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769430130; c=relaxed/simple; bh=02sh3oTX0lvFzCHlAVRuBifMzJIUIAG9N3Y9jPZ7QJ4=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=I5n2VOp2fY2ht7N8QRhMwFgxFxUpUssiwewFUz5InNq1Nz8gAuVj/iJPSMPSH094a8EAY9KfqVkBM/yjmyt1a36gvwCOjcvxIzCG+yQKJdXIpzf9B4+toIbFxlNtUuznDHC5kH/uwXbtn9wXgl6R2s+PxOY7M3cyZ5UcNmN9K1k= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=rub2O9us; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="rub2O9us" Received: by smtp.kernel.org (Postfix) with ESMTPS id 4F6BAC19425; Mon, 26 Jan 2026 12:22:10 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1769430130; bh=02sh3oTX0lvFzCHlAVRuBifMzJIUIAG9N3Y9jPZ7QJ4=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=rub2O9usyZ8dOhClJyyls478YbdhZVXG+nqd2BSNoXrc0ri8O9nza+n9MMqQ7HzMv LOxtIBR56dSRfo+7pX1cwqGZVpA6M25ML8iQfEAcNiHXno8gvBY4TlHJIO71Jcp8sh wH3h8HYH5dv4yjWkXv7S2mydYydpKpeR1P1H5jEpTjMyQT38bVMaMQJB6zGobntsqW atH1YzoSIsNyICAzSxOrIz+gwM7DHXJh5HYXm/kRmmFidY44+BhOPrS3C9oTHXs1Ps PacKS57ZXdcdZBRLrAvTTvjEKogSLGnWk7OJqChVnYMYmcs6qvx1YGThU7+mTdsrWd MMqD1OutZd6ig== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 409AFCF65DB; Mon, 26 Jan 2026 12:22:10 +0000 (UTC) From: SeungJong Ha via B4 Relay Date: Mon, 26 Jan 2026 12:22:08 +0000 Subject: [PATCH RFC 1/3] rust: bindings: add TTY subsystem headers Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260126-rust-tty-printk-driver-v1-1-28604e7e100e@gmail.com> References: <20260126-rust-tty-printk-driver-v1-0-28604e7e100e@gmail.com> In-Reply-To: <20260126-rust-tty-printk-driver-v1-0-28604e7e100e@gmail.com> To: Miguel Ojeda , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Arnd Bergmann , Greg Kroah-Hartman Cc: rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, SeungJong Ha X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1769430128; l=837; i=engineer.jjhama@gmail.com; s=20260103; h=from:subject:message-id; bh=0vsfQQeWd++TfCsHXwKaK0gI8PJ/Xoa6JfQ9dg4v4UA=; b=Fy5475B4L9nOFeWUdI7UbNWWUKSEoKi0rb8FZIOg8/+VcC6R7PMXlB6lEd0lU4tXJ8tXtXEzr zCGJ67IKU46AR7F9h4/Xo6gH557CrdzWZSCDFwLWadrygVxhTLW6jp9 X-Developer-Key: i=engineer.jjhama@gmail.com; a=ed25519; pk=G5nmjm+RTiWBpyCvc5xjR1b3li/2zipLSMyz+T4fj5E= X-Endpoint-Received: by B4 Relay for engineer.jjhama@gmail.com/20260103 with auth_id=590 X-Original-From: SeungJong Ha Reply-To: engineer.jjhama@gmail.com From: SeungJong Ha Add bindings for the TTY subsystem by including the following headers: - linux/tty.h - linux/tty_driver.h - linux/tty_port.h These bindings are needed for the upcoming Rust TTY driver abstractions. Signed-off-by: SeungJong Ha --- rust/bindings/bindings_helper.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helpe= r.h index a067038b4b42..dc326eb84955 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -81,6 +81,9 @@ #include #include #include +#include +#include +#include #include #include #include --=20 2.43.0 From nobody Sat Feb 7 12:19:35 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id ACD7A3358AE; Mon, 26 Jan 2026 12:22:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769430130; cv=none; b=R1zBaZwlYSzabP44vv+OHwX/B9WuJefuOksMVFwM21h1VwLSPTCrof7YS6EeQhUNGz45MoDP9LVES9QYW5HW8Tliyf4XI3xJmQtT5vUw1t+2uvcQKYYG/sJCgT0O7bvW8aCtGvSKTmGwQjERnrcdMr0WDnJITevynjjprcYkUhI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769430130; c=relaxed/simple; bh=nS7MUbUuNRPjcJLT2F/6EOY/lkOnwEAd7n7L+ue33Uc=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=s+lMfY7DkvKeFOw8QIUKvJRuG9LOwps9D2FLdNrW7rsWI8P26ZbYypPukBzCLgojrD8KlMgYT0d9e2qBzBD097KInyZ5wS4FypuYqzDjDF9JV1/P654dagJlvkOhCmkCiZmXzmgBgNMrmBuhzcIRmewDBM66bk5188yXjcV2RCk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=uWes5z2L; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="uWes5z2L" Received: by smtp.kernel.org (Postfix) with ESMTPS id 60581C19422; Mon, 26 Jan 2026 12:22:10 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1769430130; bh=nS7MUbUuNRPjcJLT2F/6EOY/lkOnwEAd7n7L+ue33Uc=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=uWes5z2LIx2JTEBpJJ2oRfzYuhiwWeLqz6imvWN1/Y8HyxdIRyA5ClUeZUEKDAM2c jGc23kznKPMpJlb3wlYNi9XX5quhJrfDZLVo2iBdmCPotsKTS10IYaxEWidfh8MZED Gk55dRA1U8xCbVy4zyaO7sMKHYjDnpFp9y6X4psDmckLzvlb62crFFXMTbYg97UWUT lMq9Wah3E7Mi67Uy3QwwipImJWZfWE+WgmtqX2AYckvaEPwQliUnYU+58qvdZbHfW1 68nZah3SZJaMGUf43Sqp5v4hKOGaWsrLJF8fhypvPPtZESoatB9Vd1Ym6ULgEgFwut P7w4Bq1L54yPA== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 50E42CF65E9; Mon, 26 Jan 2026 12:22:10 +0000 (UTC) From: SeungJong Ha via B4 Relay Date: Mon, 26 Jan 2026 12:22:09 +0000 Subject: [PATCH RFC 2/3] rust: tty: add TTY subsystem abstractions Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260126-rust-tty-printk-driver-v1-2-28604e7e100e@gmail.com> References: <20260126-rust-tty-printk-driver-v1-0-28604e7e100e@gmail.com> In-Reply-To: <20260126-rust-tty-printk-driver-v1-0-28604e7e100e@gmail.com> To: Miguel Ojeda , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Arnd Bergmann , Greg Kroah-Hartman Cc: rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, SeungJong Ha X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1769430128; l=29559; i=engineer.jjhama@gmail.com; s=20260103; h=from:subject:message-id; bh=WQBEey82OuLBMZTWuHtXe1H87r381Rc30GoZYWPYHT0=; b=JAaR80nRGC3B2UkmEmL8m6kO/S3Qh1j8J6CZvDQUtD8gdSOkL3Ui8rNWyyKyuJdmCupOzS3sO tSiNWMpJz85A0R2g/fn6kCS1Vjwt5riyWoMfsV6ZMRszFY+kCp2ynpK X-Developer-Key: i=engineer.jjhama@gmail.com; a=ed25519; pk=G5nmjm+RTiWBpyCvc5xjR1b3li/2zipLSMyz+T4fj5E= X-Endpoint-Received: by B4 Relay for engineer.jjhama@gmail.com/20260103 with auth_id=590 X-Original-From: SeungJong Ha Reply-To: engineer.jjhama@gmail.com From: SeungJong Ha Add Rust abstractions for the TTY subsystem, providing safe wrappers for tty_struct, tty_driver, and tty_port. The abstractions are organized as follows: - tty.rs: Core Tty wrapper providing type-safe access to tty_struct with generic parameters for driver-specific data. - tty/driver.rs: TtyDriverBuilder and TtyDriver for creating and registering TTY drivers. Includes: - Operations trait for implementing TTY callbacks (open, close, write, write_room, hangup) - Driver flags, termios output flags, and driver type constants - tty/port.rs: DriverPort combining tty_port with driver-specific data following the C pattern of embedding tty_port as the first field. Includes Operations trait for port callbacks (shutdown). Key design decisions: - Generic DriverData and DriverState types allow drivers to specify their own data types (typically Arc) for per-tty and driver-wide state respectively. - Pin-initialization is used throughout for safe handling of self-referential structures. - The #[repr(C)] DriverPort layout enables container_of operations. This provides the foundation for implementing TTY drivers in Rust. Signed-off-by: SeungJong Ha --- rust/kernel/lib.rs | 2 + rust/kernel/tty.rs | 173 +++++++++++++++++ rust/kernel/tty/driver.rs | 478 ++++++++++++++++++++++++++++++++++++++++++= ++++ rust/kernel/tty/port.rs | 148 ++++++++++++++ 4 files changed, 801 insertions(+) diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index f812cf120042..0160bfb54547 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -147,6 +147,8 @@ pub mod time; pub mod tracepoint; pub mod transmute; +#[cfg(CONFIG_TTY)] +pub mod tty; pub mod types; pub mod uaccess; #[cfg(CONFIG_USB =3D "y")] diff --git a/rust/kernel/tty.rs b/rust/kernel/tty.rs new file mode 100644 index 000000000000..b2decd7e0b27 --- /dev/null +++ b/rust/kernel/tty.rs @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! TTY subsystem support. +//! +//! C headers: [`include/linux/tty.h`](srctree/include/linux/tty.h), +//! [`include/linux/tty_driver.h`](srctree/include/linux/tty_dr= iver.h), +//! [`include/linux/tty_port.h`](srctree/include/linux/tty_port= .h) +//! +//! This module provides TTY bindings for Rust TTY drivers. + +mod driver; +pub mod port; + +use core::marker::PhantomData; + +pub use driver::{ + flags, + oflag, + DriverType, + Operations, + Options, + TtyDriver, + TtyDriverBuilder, + TTYAUX_MAJOR, +}; +pub use port::{ + DriverPort, + Operations as PortOperations, +}; + +use crate::{ + bindings, + sync::Arc, +}; + +/// TTY struct wrapper, generic over driver data and driver state types. +/// +/// - `DriverData`: Per-tty instance data stored in `tty_struct->driver_da= ta`. +/// Use `Arc` for shared data across multiple opens. +/// - `DriverState`: Driver-level data stored in `tty_driver->driver_state= ` (shared by all ttys). +/// Use `Arc` for shared state. +#[repr(transparent)] +pub struct Tty( + *mut bindings::tty_struct, + PhantomData<(DriverData, DriverState)>, +); + +impl Tty { + /// Creates a TTY wrapper from a raw pointer. + /// + /// # Safety + /// + /// - `ptr` must be a valid pointer to a `tty_struct`. + pub unsafe fn from_raw(ptr: *mut bindings::tty_struct) -> Self { + Self(ptr, PhantomData) + } + + /// Returns the raw pointer. + pub fn as_raw(&self) -> *mut bindings::tty_struct { + self.0 + } +} + +impl Tty, DriverState> { + /// Sets driver-specific data in the `driver_data` field, taking owner= ship of the Arc. + /// + /// Returns the previously set data, if any. + pub fn set_driver_data(&self, data: Arc) -> Option> { + let old =3D self.take_driver_data(); + // SAFETY: self.0 is valid. + unsafe { + (*self.0).driver_data =3D Arc::into_raw(data) as *mut _; + } + old + } + + /// Takes the driver-specific data from the `driver_data` field, retur= ning ownership. + /// + /// Returns `None` if no data was set. + pub fn take_driver_data(&self) -> Option> { + // SAFETY: self.0 is valid. + let ptr =3D unsafe { (*self.0).driver_data }; + if ptr.is_null() { + return None; + } + // SAFETY: self.0 is valid. + unsafe { + (*self.0).driver_data =3D core::ptr::null_mut(); + } + // SAFETY: ptr was set via set_driver_data from an Arc. + Some(unsafe { Arc::from_raw(ptr.cast()) }) + } + + /// Returns a reference to the driver-specific data in the `driver_dat= a` field. + /// + /// Returns `None` if no data was set. + pub fn driver_data(&self) -> Option<&T> { + // SAFETY: self.0 is valid. + let ptr =3D unsafe { (*self.0).driver_data }; + if ptr.is_null() { + return None; + } + // SAFETY: ptr was set via set_driver_data from an Arc. + Some(unsafe { &*ptr.cast::() }) + } +} + +impl Tty> { + /// Returns a clone of the Arc holding the driver-level state. + /// + /// This is set by [`TtyDriverBuilder::set_driver_state`] and provides= access to + /// driver-level data from within TTY operation callbacks. Returns a c= loned Arc, + /// incrementing the reference count. + pub fn driver_state(&self) -> Option> { + // SAFETY: self.0 is valid. + let driver =3D unsafe { (*self.0).driver }; + if driver.is_null() { + return None; + } + // SAFETY: driver is valid. + let state =3D unsafe { (*driver).driver_state }; + if state.is_null() { + return None; + } + // SAFETY: state was set via set_driver_state from an Arc. + // We reconstruct the Arc, clone it, then forget the original to a= void + // decrementing the stored refcount. + let arc =3D unsafe { Arc::from_raw(state.cast::()) }; + let cloned =3D arc.clone(); + core::mem::forget(arc); + Some(cloned) + } + + /// Takes the driver-level state from `tty_driver->driver_state`, retu= rning ownership. + /// + /// Returns `None` if no state was set. + pub fn take_driver_state(&self) -> Option> { + // SAFETY: self.0 is valid. + let driver =3D unsafe { (*self.0).driver }; + if driver.is_null() { + return None; + } + // SAFETY: driver is valid. + let ptr =3D unsafe { (*driver).driver_state }; + if ptr.is_null() { + return None; + } + // SAFETY: driver is valid. + unsafe { + (*driver).driver_state =3D core::ptr::null_mut(); + } + // SAFETY: ptr was set via set_driver_state from an Arc. + Some(unsafe { Arc::from_raw(ptr.cast()) }) + } + + /// Sets the driver-level state in `tty_driver->driver_state`, taking = ownership. + /// + /// Returns the previously set state, if any. + pub fn set_driver_state(&self, state: Arc) -> Option> { + // SAFETY: self.0 is valid. + let driver =3D unsafe { (*self.0).driver }; + if driver.is_null() { + return None; + } + // Take old state first. + let old =3D self.take_driver_state(); + // SAFETY: driver is valid. + unsafe { + (*driver).driver_state =3D Arc::into_raw(state) as *mut _; + } + old + } +} diff --git a/rust/kernel/tty/driver.rs b/rust/kernel/tty/driver.rs new file mode 100644 index 000000000000..22a2210c3ef5 --- /dev/null +++ b/rust/kernel/tty/driver.rs @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! TTY driver support. +//! +//! Provides [`TtyDriverBuilder`] and [`TtyDriver`] for registering TTY dr= ivers. + +use core::marker::PhantomData; + +use super::{ + DriverPort, + PortOperations, + Tty, +}; +use crate::{ + bindings, + error::{ + Error, + Result, + VTABLE_DEFAULT_ERROR, + }, + prelude::*, + sync::Arc, + types::Opaque, +}; + +/// TTY driver flags. +pub mod flags { + use crate::bindings; + + /// Reset termios when the last process closes the device. + pub const RESET_TERMIOS: usize =3D bindings::tty_driver_flag_TTY_DRIVE= R_RESET_TERMIOS as usize; + /// Driver will guarantee not to set any special character handling fl= ags. + pub const REAL_RAW: usize =3D bindings::tty_driver_flag_TTY_DRIVER_REA= L_RAW as usize; + /// Do not create numbered /dev nodes (e.g., /dev/ttyprintk instead of= /dev/ttyprintk0). + pub const UNNUMBERED_NODE: usize =3D + bindings::tty_driver_flag_TTY_DRIVER_UNNUMBERED_NODE as usize; +} + +/// Termios output flags. +pub mod oflag { + use crate::bindings; + + /// Post-process output. + pub const OPOST: u32 =3D bindings::OPOST; + /// Map CR to NL on output. + pub const OCRNL: u32 =3D bindings::OCRNL; + /// No CR output at column 0. + pub const ONOCR: u32 =3D bindings::ONOCR; + /// NL performs CR function. + pub const ONLRET: u32 =3D bindings::ONLRET; +} + +/// Major device number for TTY aux devices. +pub const TTYAUX_MAJOR: i32 =3D bindings::TTYAUX_MAJOR as i32; + +/// TTY driver types. +#[repr(u32)] +#[derive(Copy, Clone, Debug)] +pub enum DriverType { + /// System TTY. + System =3D bindings::tty_driver_type_TTY_DRIVER_TYPE_SYSTEM, + /// Console TTY. + Console =3D bindings::tty_driver_type_TTY_DRIVER_TYPE_CONSOLE, + /// Serial TTY. + Serial =3D bindings::tty_driver_type_TTY_DRIVER_TYPE_SERIAL, + /// PTY. + Pty =3D bindings::tty_driver_type_TTY_DRIVER_TYPE_PTY, +} + +/// Options for creating a TTY driver. +#[derive(Copy, Clone)] +pub struct Options { + /// Driver name (shown in /proc/tty/drivers). + pub driver_name: &'static CStr, + /// Device name (used for /dev node). + pub name: &'static CStr, + /// Major device number. + pub major: i32, + /// Starting minor device number. + pub minor_start: i32, + /// Driver type. + pub driver_type: DriverType, + /// Driver flags (see [`flags`] module). + pub flags: usize, +} + +/// Trait implemented by TTY device drivers. +#[vtable] +pub trait Operations: Sized + Send + Sync { + /// Driver-specific data type stored in `tty_struct->driver_data`. + /// + /// Use `Arc` for shared data across multiple opens, or `()` if not= needed. + /// Access via [`Tty::driver_data`] (returns `Option` since it may not= be set until `open`). + type DriverData: Send + Sync; + + /// Driver-level state type stored in `tty_driver->driver_state`. + /// + /// Use `Arc` for shared state across all ttys, or `()` if not need= ed. + /// Access via [`Tty::driver_state`]. + type DriverState: Send + Sync; + + /// Port operations type. Must implement [`PortOperations`]. + type PortOps: PortOperations + 'static; + + /// Called when the TTY device is opened. + fn open( + tty: &Tty, + file: *mut bindings::file, + ) -> Result<()>; + + /// Called when the TTY device is closed. + fn close(tty: &Tty, file: *mut bi= ndings::file); + + /// Called to write data to the device. + fn write(tty: &Tty, buf: &[u8]) -= > Result; + + /// Returns the number of bytes that can be written. + fn write_room(_tty: &Tty) -> u32 { + build_error!(VTABLE_DEFAULT_ERROR) + } + + /// Called on hangup. + fn hangup(_tty: &Tty) { + build_error!(VTABLE_DEFAULT_ERROR) + } +} + +/// A vtable for the TTY operations. +struct OperationsVTable(PhantomData); + +/// Type alias for the TTY type used in operations callbacks. +type OpsTty =3D Tty<::DriverData, ::D= riverState>; + +impl OperationsVTable { + /// # Safety + /// + /// `tty` and `filp` must be valid pointers. + unsafe extern "C" fn open( + tty: *mut bindings::tty_struct, + filp: *mut bindings::file, + ) -> core::ffi::c_int { + // SAFETY: tty is valid, driver_data starts as null. + let tty_ref =3D unsafe { OpsTty::::from_raw(tty) }; + + match T::open(&tty_ref, filp) { + Ok(()) =3D> 0, + Err(e) =3D> e.to_errno(), + } + } + + /// # Safety + /// + /// `tty` and `filp` must be valid pointers. + unsafe extern "C" fn close(tty: *mut bindings::tty_struct, filp: *mut = bindings::file) { + // SAFETY: tty is valid, driver_data was set by driver in open. + let tty_ref =3D unsafe { OpsTty::::from_raw(tty) }; + T::close(&tty_ref, filp); + } + + /// # Safety + /// + /// `tty` must be valid, `buf` must be valid for `count` bytes. + unsafe extern "C" fn write( + tty: *mut bindings::tty_struct, + buf: *const u8, + count: usize, + ) -> isize { + if buf.is_null() || count =3D=3D 0 { + return 0; + } + + // SAFETY: Kernel guarantees buf is valid for count bytes. + let slice =3D unsafe { core::slice::from_raw_parts(buf, count) }; + + // SAFETY: tty is valid, driver_data was set by driver in open. + let tty_ref =3D unsafe { OpsTty::::from_raw(tty) }; + + match T::write(&tty_ref, slice) { + Ok(n) =3D> n as isize, + Err(e) =3D> e.to_errno() as isize, + } + } + + /// # Safety + /// + /// `tty` must be a valid pointer. + unsafe extern "C" fn write_room(tty: *mut bindings::tty_struct) -> cor= e::ffi::c_uint { + // SAFETY: tty is valid, driver_data was set by driver in open. + let tty_ref =3D unsafe { OpsTty::::from_raw(tty) }; + T::write_room(&tty_ref) + } + + /// # Safety + /// + /// `tty` must be a valid pointer. + unsafe extern "C" fn hangup(tty: *mut bindings::tty_struct) { + // SAFETY: tty is valid, driver_data was set by driver in open. + let tty_ref =3D unsafe { OpsTty::::from_raw(tty) }; + T::hangup(&tty_ref); + } + + const VTABLE: bindings::tty_operations =3D bindings::tty_operations { + open: Some(Self::open), + close: Some(Self::close), + write: Some(Self::write), + write_room: if T::HAS_WRITE_ROOM { + Some(Self::write_room) + } else { + None + }, + hangup: if T::HAS_HANGUP { + Some(Self::hangup) + } else { + None + }, + // All other operations are NULL. + lookup: None, + install: None, + remove: None, + shutdown: None, + cleanup: None, + put_char: None, + flush_chars: None, + chars_in_buffer: None, + ioctl: None, + compat_ioctl: None, + set_termios: None, + throttle: None, + unthrottle: None, + stop: None, + start: None, + break_ctl: None, + flush_buffer: None, + ldisc_ok: None, + set_ldisc: None, + wait_until_sent: None, + send_xchar: None, + tiocmget: None, + tiocmset: None, + resize: None, + get_icount: None, + get_serial: None, + set_serial: None, + show_fdinfo: None, + #[cfg(CONFIG_CONSOLE_POLL)] + poll_init: None, + #[cfg(CONFIG_CONSOLE_POLL)] + poll_get_char: None, + #[cfg(CONFIG_CONSOLE_POLL)] + poll_put_char: None, + proc_show: None, + }; + + const fn build() -> &'static bindings::tty_operations { + &Self::VTABLE + } +} + +/// Builder for creating and configuring a TTY driver before registration. +/// +/// Use [`TtyDriverBuilder::new`] to create a builder, optionally link por= ts +/// with [`link_port`](Self::link_port), then call [`build`](Self::build) = to +/// register and obtain a [`TtyDriver`]. +/// +/// # Example +/// +/// ```ignore +/// let driver =3D KBox::pin_init( +/// TtyDriverBuilder::::new(opts, module)? +/// .link_port(&port, 0) +/// .build(), +/// GFP_KERNEL, +/// )?; +/// ``` +pub struct TtyDriverBuilder { + driver_ptr: *mut bindings::tty_driver, + _t: PhantomData, +} + +impl TtyDriverBuilder { + /// Creates a new TTY driver builder. + pub fn new(opts: Options, module: &'static crate::ThisModule) -> Resul= t { + // SAFETY: FFI call with valid arguments. + let driver_ptr =3D unsafe { bindings::__tty_alloc_driver(1, module= .as_ptr(), opts.flags) }; + + if driver_ptr.is_null() || (driver_ptr as isize) < 0 && (driver_pt= r as isize) > -4096 { + if driver_ptr.is_null() { + return Err(ENOMEM); + } + return Err(Error::from_errno(driver_ptr as i32)); + } + + // Configure the driver. + // SAFETY: driver_ptr is valid. + unsafe { + (*driver_ptr).driver_name =3D opts.driver_name.as_char_ptr(); + (*driver_ptr).name =3D opts.name.as_char_ptr(); + (*driver_ptr).major =3D opts.major; + (*driver_ptr).minor_start =3D opts.minor_start; + (*driver_ptr).type_ =3D opts.driver_type as u32; + + // Set termios. + let mut termios =3D bindings::tty_std_termios; + termios.c_oflag =3D oflag::OPOST | oflag::OCRNL | oflag::ONOCR= | oflag::ONLRET; + (*driver_ptr).init_termios =3D termios; + + // Set operations vtable. + (*driver_ptr).ops =3D OperationsVTable::::build(); + } + + Ok(Self { + driver_ptr, + _t: PhantomData, + }) + } + + /// Links a port to this driver at the specified line index. + /// + /// For fixed-device drivers (e.g., ttyprintk), call this before [`bui= ld`](Self::build). + pub fn link_port(self, port: &DriverPort, line: u32) -> Se= lf { + // SAFETY: Both port and driver are valid. + unsafe { + bindings::tty_port_link_device(port.as_raw(), self.driver_ptr,= line); + } + self + } + + /// Registers the driver and returns a pin-initializer for [`TtyDriver= `]. + /// + /// The actual registration happens during pin-initialization. + pub fn build(self) -> impl PinInit, Error> { + let driver_ptr =3D self.driver_ptr; + // Prevent Drop from freeing the driver_ptr; TtyDriver takes owner= ship. + core::mem::forget(self); + + try_pin_init!(TtyDriver:: { + inner <- Opaque::try_ffi_init(move |slot: *mut *mut bindings::= tty_driver| { + // SAFETY: driver_ptr is valid. + let ret =3D unsafe { bindings::tty_register_driver(driver_= ptr) }; + if ret !=3D 0 { + // SAFETY: driver_ptr is valid, registration failed. + unsafe { bindings::tty_driver_kref_put(driver_ptr) }; + return Err(Error::from_errno(ret)); + } + // SAFETY: slot is valid for write. + unsafe { slot.write(driver_ptr) }; + Ok(()) + }), + _t: PhantomData, + }? Error) + } +} + +impl Drop for TtyDriverBuilder { + fn drop(&mut self) { + // SAFETY: driver_ptr is valid, not yet registered. + unsafe { bindings::tty_driver_kref_put(self.driver_ptr) }; + } +} + +impl TtyDriverBuilder +where + T: Operations>, + S: Send + Sync, +{ + /// Sets the driver-level state, taking ownership of the Arc. + /// + /// The state can be accessed via [`Tty::driver_state`] in TTY operati= on callbacks. + /// + /// # Note + /// + /// The caller must call [`TtyDriver::take_driver_state`] before the d= river is + /// dropped to reclaim the state's memory. Failure to do so will resul= t in a + /// memory leak. + pub fn set_driver_state(self, state: Arc) -> Self { + // SAFETY: driver_ptr is valid. + unsafe { + (*self.driver_ptr).driver_state =3D Arc::into_raw(state) as *m= ut _; + } + self + } +} + +/// A registered TTY driver. +/// +/// Created via [`TtyDriverBuilder::build`]. The driver is automatically +/// unregistered when dropped. +/// +/// For probe-based drivers, ports can be linked after creation using +/// [`link_port`](Self::link_port). +/// +/// # Invariants +/// +/// - `inner` contains a valid pointer to a registered `tty_driver`. +/// - Deregistration occurs exactly once in [`Drop`]. +#[pin_data(PinnedDrop)] +pub struct TtyDriver { + #[pin] + inner: Opaque<*mut bindings::tty_driver>, + _t: PhantomData, +} + +// SAFETY: It is allowed to call `tty_unregister_driver` on a different th= read. +unsafe impl Send for TtyDriver {} +// SAFETY: All `&self` methods are safe to call in parallel. +unsafe impl Sync for TtyDriver {} + +impl TtyDriver { + /// Returns the driver pointer. + fn driver_ptr(&self) -> *mut bindings::tty_driver { + // SAFETY: inner is initialized. + unsafe { *self.inner.get() } + } + + /// Links a port to this driver at the specified line index. + /// + /// For probe-based drivers (e.g., serial), call this at device probe = time. + pub fn link_port(&self, port: &DriverPort= , line: u32) { + // SAFETY: Both port and driver are valid. + unsafe { + bindings::tty_port_link_device(port.as_raw(), self.driver_ptr(= ), line); + } + } + + /// Returns a raw pointer to the TTY driver. + pub fn as_raw(&self) -> *mut bindings::tty_driver { + self.driver_ptr() + } +} + +impl TtyDriver +where + T: Operations>, + S: Send + Sync, +{ + /// Takes the driver state, returning ownership of the Arc. + /// + /// Returns `None` if no state was set. This should be called before t= he driver + /// is dropped to reclaim the state's memory. + pub fn take_driver_state(&self) -> Option> { + // SAFETY: driver_ptr is valid. + let ptr =3D unsafe { (*self.driver_ptr()).driver_state }; + if ptr.is_null() { + return None; + } + // SAFETY: driver_ptr is valid. + unsafe { + (*self.driver_ptr()).driver_state =3D core::ptr::null_mut(); + } + // SAFETY: ptr was set via set_driver_state from an Arc. + Some(unsafe { Arc::from_raw(ptr.cast()) }) + } + + /// Returns a reference to the driver state. + /// + /// Returns `None` if no state was set. + pub fn driver_state(&self) -> Option<&S> { + // SAFETY: driver_ptr is valid. + let ptr =3D unsafe { (*self.driver_ptr()).driver_state }; + if ptr.is_null() { + return None; + } + // SAFETY: ptr was set via set_driver_state from an Arc. + Some(unsafe { &*ptr.cast::() }) + } +} + +#[pinned_drop] +impl PinnedDrop for TtyDriver { + fn drop(self: Pin<&mut Self>) { + // SAFETY: inner contains a valid registered driver. + unsafe { + let ptr =3D *self.inner.get(); + bindings::tty_unregister_driver(ptr); + bindings::tty_driver_kref_put(ptr); + } + } +} diff --git a/rust/kernel/tty/port.rs b/rust/kernel/tty/port.rs new file mode 100644 index 000000000000..576e884ed3bc --- /dev/null +++ b/rust/kernel/tty/port.rs @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! TTY port support. +//! +//! Provides [`DriverPort`] which combines a TTY port with driver-specific= data, +//! following the C pattern of embedding `tty_port` as the first struct fi= eld. + +use core::marker::PhantomData; + +use pin_init::PinInit; + +use crate::{ + bindings, + error::VTABLE_DEFAULT_ERROR, + prelude::*, + types::Opaque, +}; + +/// A combined TTY port and driver data structure. +/// +/// Follows the C pattern of embedding `tty_port` as the first field. +/// The `#[repr(C)]` layout enables safe `container_of` operations. +#[repr(C)] +#[pin_data] +pub struct DriverPort { + #[pin] + port: TtyPort, + #[pin] + data: Ops::PortData, +} + +impl DriverPort { + /// Creates a pin-initializer for a new driver port. + pub fn new( + data_init: impl PinInit, + ) -> impl PinInit { + try_pin_init!(Self { + port <- TtyPort::::new(), + data <- data_init, + }? Error) + } + + /// Returns a reference to the port-specific data. + pub fn data(&self) -> &Ops::PortData { + &self.data + } + + /// Returns a raw pointer to the underlying `tty_port`. + pub(super) fn as_raw(&self) -> *mut bindings::tty_port { + self.port.as_raw() + } + + /// Converts a raw `tty_port` pointer back to `&DriverPort` (container= _of). + /// + /// # Safety + /// `ptr` must point to a `tty_port` within a valid `DriverPort`. + unsafe fn from_raw<'a>(ptr: *mut bindings::tty_port) -> &'a Self { + // SAFETY: DriverPort is #[repr(C)] with TtyPort as first field. + unsafe { &*(ptr as *const Self) } + } +} + +// SAFETY: DriverPort is Send/Sync if Ops::PortData is, since TtyPort is b= oth. +unsafe impl Send for DriverPort where Ops::PortData:= Send {} +// SAFETY: DriverPort is Send/Sync if Ops::PortData is, since TtyPort is b= oth. +unsafe impl Sync for DriverPort where Ops::PortData:= Sync {} + +/// Wrapper for `struct tty_port`. Typically used via [`DriverPort`]. +/// +/// # Invariants +/// Initialized via `tty_port_init()`, destroyed via `tty_port_destroy()` = on drop. +#[repr(transparent)] +struct TtyPort(Opaque, PhantomData); + +impl TtyPort { + /// Creates a pin-initializer that calls `tty_port_init()` and sets th= e ops vtable. + fn new() -> impl PinInit { + // SAFETY: tty_port_init initializes the port, vtable is static. + unsafe { + pin_init::pin_init_from_closure(|slot: *mut Self| { + let port_ptr =3D slot.cast::(); + bindings::tty_port_init(port_ptr); + (*port_ptr).ops =3D OperationsVTable::::build(); + Ok(()) + }) + } + } + + fn as_raw(&self) -> *mut bindings::tty_port { + self.0.get() + } +} + +// SAFETY: TtyPort operations are internally synchronized by the kernel. +unsafe impl Send for TtyPort {} +// SAFETY: TtyPort operations are internally synchronized by the kernel. +unsafe impl Sync for TtyPort {} + +impl Drop for TtyPort { + fn drop(&mut self) { + // SAFETY: Port was initialized in new(), must be destroyed. + unsafe { bindings::tty_port_destroy(self.0.get()) }; + } +} + +/// TTY port operations trait. +/// +/// Implement to define callbacks for port events. The `PortData` type spe= cifies +/// data stored alongside the port in [`DriverPort`]. +#[vtable] +pub trait Operations: Sized { + /// Port-specific data type stored in [`DriverPort`]. + type PortData: Sync; + + /// Called when the port is shut down (last user closes the device). + fn shutdown(_port: &DriverPort) { + build_error!(VTABLE_DEFAULT_ERROR) + } +} + +/// Vtable adapter for port operations. +struct OperationsVTable(PhantomData); + +impl OperationsVTable { + /// # Safety + /// `port` must be a valid `tty_port` within a `DriverPort`. + unsafe extern "C" fn shutdown(port: *mut bindings::tty_port) { + // SAFETY: Port was registered with this vtable. + let driver_port =3D unsafe { DriverPort::::from_raw(port) }; + Ops::shutdown(driver_port); + } + + const VTABLE: bindings::tty_port_operations =3D bindings::tty_port_ope= rations { + shutdown: if Ops::HAS_SHUTDOWN { + Some(Self::shutdown) + } else { + None + }, + carrier_raised: None, + dtr_rts: None, + activate: None, + destruct: None, + }; + + const fn build() -> &'static bindings::tty_port_operations { + &Self::VTABLE + } +} --=20 2.43.0 From nobody Sat Feb 7 12:19:35 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id ACE053358B5; Mon, 26 Jan 2026 12:22:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769430130; cv=none; b=JC4VaQfmagxHMzwZEdhjOJfX6LZtUq1Yc2cTHeuanYYxCE1JG26bOU/du2C8sJIh63QfVuxef4G+AlGvtlJAZC6OYa5tchMe+RGF+aR7MD1s/uBp5gCg4ubcctWgr7uFdP0bREpsKZJSSWFIJOpIg3F5IVzoAPm+nG9kni0fER8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769430130; c=relaxed/simple; bh=KSWmRjOoZPERCjQJyRqqDCBiCUV9pTU8pgWLSF7N9WU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=EAvC8uCGJiWjlIUcKr8tgJINqm1ZoMVZ4fKgOkTeeUQQ66awxjDCbREK7ySfFeLodyjzH0sfJpvK7hShZue88GAhZVDbcFCx3NykHHGOk2FbetqdfIwqMKgtbiu/5guIhd5V1tmqTVRnYBHd8w3j40ALjNOxbxtSlfFVV20BJo4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=fb6AQgGk; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="fb6AQgGk" Received: by smtp.kernel.org (Postfix) with ESMTPS id 69EC0C2BC86; Mon, 26 Jan 2026 12:22:10 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1769430130; bh=KSWmRjOoZPERCjQJyRqqDCBiCUV9pTU8pgWLSF7N9WU=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=fb6AQgGkn9OVE71Bp4E6lbeGdn2076Qaqjp551tRGrms53SdS04o3UBeO3ijJfin8 AOQPhZWlHG/lnv9pbysrsmP79lWIHSEVq7i2WoYNFcpzIe7/s41NjYuDvAHebqax3Z tY4qNWuyYKbKmDZj8itSbxnp61Pv/HsmBCAKnY+WzzETkAVHeLASWF2Vz1XF8KQNQh OpAQxK8WOH7ftt5ebTzQiqpgV1yEOG7wS/a5pxEmKtcfaJaBYiB2Y1kW/i2FNIn6YW nwl/uR4+7T5U3DrBfbhcgTXSlXmtvoTcr324Keg4gVZ6XTtOSU5kp9NVXVOovdQ/3c A5+mrxwSfh9vQ== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 605CACF65E7; Mon, 26 Jan 2026 12:22:10 +0000 (UTC) From: SeungJong Ha via B4 Relay Date: Mon, 26 Jan 2026 12:22:10 +0000 Subject: [PATCH RFC 3/3] char: rttyprintk: add Rust TTY printk driver Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260126-rust-tty-printk-driver-v1-3-28604e7e100e@gmail.com> References: <20260126-rust-tty-printk-driver-v1-0-28604e7e100e@gmail.com> In-Reply-To: <20260126-rust-tty-printk-driver-v1-0-28604e7e100e@gmail.com> To: Miguel Ojeda , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Arnd Bergmann , Greg Kroah-Hartman Cc: rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, SeungJong Ha X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1769430128; l=7897; i=engineer.jjhama@gmail.com; s=20260103; h=from:subject:message-id; bh=a2DRGFsUADXLosl2ifUZW9MiZCCY2Vpnl3B7hhzcS3s=; b=SrHjZWCLbU+bFkMTCRhqi6lLL4GS37SdtqxBHuAv5wz9+GYL9gQLMDzJLiOgy9cTAibetIEXA aQy0JHLOBzVA8mUOJmBcs+je03mqo4kwxlqKz2fgz0trjvyIP6cm2+k X-Developer-Key: i=engineer.jjhama@gmail.com; a=ed25519; pk=G5nmjm+RTiWBpyCvc5xjR1b3li/2zipLSMyz+T4fj5E= X-Endpoint-Received: by B4 Relay for engineer.jjhama@gmail.com/20260103 with auth_id=590 X-Original-From: SeungJong Ha Reply-To: engineer.jjhama@gmail.com From: SeungJong Ha Add a Rust implementation of the ttyprintk driver, demonstrating the new TTY Rust abstractions. This driver creates /dev/rttyprintk which allows user messages to be written to the kernel log via printk, similar to the existing C ttyprintk driver. Features: - Uses the new kernel::tty abstractions for type-safe TTY operations - Implements TtyDevice with open/close/write/write_room/hangup callbacks - Uses DriverPort with SpinLock-protected TpkState for thread-safe buffer management - Buffers up to 508 bytes per line, flushing on newline or buffer full - Messages are logged at KERN_INFO level with [U] prefix The driver serves as a reference implementation for Rust TTY drivers and demonstrates: - Pin-initialization patterns for TTY drivers and ports - Arc-based shared state between driver callbacks - Safe handling of driver_data and driver_state - Integration with kernel printk for output Signed-off-by: SeungJong Ha --- drivers/char/Kconfig | 13 ++++ drivers/char/Makefile | 1 + drivers/char/rttyprintk.rs | 180 +++++++++++++++++++++++++++++++++++++++++= ++++ 3 files changed, 194 insertions(+) diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index d2cfc584e202..66a482024ff4 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -31,6 +31,19 @@ config TTY_PRINTK_LEVEL help Printk log level to use for ttyprintk messages. =20 +config TTY_DEV_RUST_PRINTK + tristate "Rust TTY driver to output user messages via printk" + depends on RUST && TTY + default n + help + If you say Y here, the support for writing user messages (i.e. + console messages) via printk is available, implemented in Rust. + + This is the Rust implementation of the ttyprintk driver, + demonstrating rkernel domain isolation for kernel modules. + + If unsure, say N. + config PRINTER tristate "Parallel printer support" depends on PARPORT diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 1291369b9126..608bb6d724a0 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -5,6 +5,7 @@ =20 obj-y +=3D mem.o random.o obj-$(CONFIG_TTY_PRINTK) +=3D ttyprintk.o +obj-$(CONFIG_TTY_DEV_RUST_PRINTK) +=3D rttyprintk.o obj-y +=3D misc.o obj-$(CONFIG_TEST_MISC_MINOR) +=3D misc_minor_kunit.o obj-$(CONFIG_ATARI_DSP56K) +=3D dsp56k.o diff --git a/drivers/char/rttyprintk.rs b/drivers/char/rttyprintk.rs new file mode 100644 index 000000000000..f5394f605cf4 --- /dev/null +++ b/drivers/char/rttyprintk.rs @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust TTY printk driver. +//! +//! Allows user messages to be written to the kernel log via printk. + +use kernel::{ + bindings, + c_str, + new_spinlock, + prelude::*, + sync::{ + Arc, + SpinLock, + }, + tty::{ + self, + port, + DriverPort, + Tty, + }, +}; + +module! { + type: RttyPrintk, + name: "rttyprintk", + authors: ["SeungJong Ha"], + description: "Rust TTY driver to output user messages via printk", + license: "GPL", +} + +const TPK_STR_SIZE: usize =3D 508; +const TPK_MAX_ROOM: u32 =3D 4096; + +/// Mutable state protected by spinlock. +struct TpkState { + curr: usize, + buffer: [u8; TPK_STR_SIZE + 4], +} + +impl TpkState { + const fn new() -> Self { + Self { + curr: 0, + buffer: [0u8; TPK_STR_SIZE + 4], + } + } + + fn flush(&mut self) { + if self.curr > 0 { + self.buffer[self.curr] =3D 0; + // SAFETY: buffer is null-terminated. + unsafe { + bindings::_printk(c_str!("\x016[U] %s\n").as_char_ptr(), s= elf.buffer.as_ptr()); + } + self.curr =3D 0; + } + } + + fn do_write(&mut self, buf: &[u8]) -> usize { + for (i, &c) in buf.iter().enumerate() { + if self.curr >=3D TPK_STR_SIZE { + self.buffer[self.curr] =3D b'\\'; + self.curr +=3D 1; + self.flush(); + } + + match c { + b'\r' =3D> { + self.flush(); + if buf.get(i + 1) =3D=3D Some(&b'\n') { + continue; + } + } + b'\n' =3D> self.flush(), + _ =3D> { + self.buffer[self.curr] =3D c; + self.curr +=3D 1; + } + } + } + buf.len() + } +} + +struct TpkPortOps; +type TpkPort =3D DriverPort; + +#[vtable] +impl port::Operations for TpkPortOps { + type PortData =3D SpinLock; + + fn shutdown(port: &TpkPort) { + port.data().lock().flush(); + } +} + +struct TpkDevice; +type TpkTty =3D Tty, Arc>; + +#[vtable] +impl tty::Operations for TpkDevice { + type DriverData =3D Arc; + type DriverState =3D Arc; + type PortOps =3D TpkPortOps; + + fn open(tty: &TpkTty, _file: *mut bindings::file) -> Result<()> { + // Clone the Arc from driver_state and set it as driver_data. + // This mirrors the original ttyprintk.c pattern where tty->driver= _data is set + // to the port in open(). In practice, since Arc allows shared acc= ess and + // SpinLock protects the state, we could just use driver_state() d= irectly. + // However, we follow the original C code structure for consistenc= y. + let port =3D tty.driver_state().ok_or(ENXIO)?; + tty.set_driver_data(port); + Ok(()) + } + + fn close(tty: &TpkTty, _file: *mut bindings::file) { + // Take and drop driver_data, mirroring tpk_close() which sets + // tty->driver_data =3D NULL. The Arc will be dropped, decrementin= g refcount. + tty.take_driver_data(); + } + + fn write(tty: &TpkTty, buf: &[u8]) -> Result { + // Access port via driver_data (set in open), following original t= typrintk.c. + // SpinLock inside TpkState protects concurrent writes. + let port =3D tty.driver_data().ok_or(ENXIO)?; + Ok(port.data().lock().do_write(buf)) + } + + fn write_room(_tty: &TpkTty) -> u32 { + TPK_MAX_ROOM + } + + fn hangup(_tty: &TpkTty) {} +} + +struct RttyPrintk { + #[allow(dead_code)] + driver: Pin>>, +} + +impl kernel::Module for RttyPrintk { + fn init(module: &'static kernel::ThisModule) -> Result { + pr_info!("Rust TTY printk driver initializing\n"); + + let port =3D Arc::pin_init( + TpkPort::new(new_spinlock!(TpkState::new(), "tpk_lock")), + GFP_KERNEL, + )?; + + let opts =3D tty::Options { + driver_name: c_str!("rttyprintk"), + name: c_str!("rttyprintk"), + major: tty::TTYAUX_MAJOR, + minor_start: 4, + driver_type: tty::DriverType::Console, + flags: tty::flags::RESET_TERMIOS | tty::flags::REAL_RAW | tty:= :flags::UNNUMBERED_NODE, + }; + + // link_port needs a reference, set_driver_state takes ownership o= f the Arc. + let builder =3D tty::TtyDriverBuilder::::new(opts, modu= le)? + .link_port(&port, 0) + .set_driver_state(port); + + let driver =3D KBox::pin_init(builder.build(), GFP_KERNEL)?; + + pr_info!("Rust TTY printk driver registered at /dev/rttyprintk\n"); + + Ok(Self { driver }) + } +} + +impl Drop for RttyPrintk { + fn drop(&mut self) { + // Reclaim the driver state before the driver is unregistered. + self.driver.take_driver_state(); + pr_info!("Rust TTY printk driver unloading\n"); + } +} --=20 2.43.0