From nobody Sun Feb 8 17:36:58 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