lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20260126-rust-tty-printk-driver-v1-2-28604e7e100e@gmail.com>
Date: Mon, 26 Jan 2026 12:22:09 +0000
From: SeungJong Ha via B4 Relay <devnull+engineer.jjhama.gmail.com@...nel.org>
To: Miguel Ojeda <ojeda@...nel.org>, Boqun Feng <boqun.feng@...il.com>, 
 Gary Guo <gary@...yguo.net>, 
 Björn Roy Baron <bjorn3_gh@...tonmail.com>, 
 Benno Lossin <lossin@...nel.org>, Andreas Hindborg <a.hindborg@...nel.org>, 
 Alice Ryhl <aliceryhl@...gle.com>, Trevor Gross <tmgross@...ch.edu>, 
 Danilo Krummrich <dakr@...nel.org>, Arnd Bergmann <arnd@...db.de>, 
 Greg Kroah-Hartman <gregkh@...uxfoundation.org>
Cc: rust-for-linux@...r.kernel.org, linux-kernel@...r.kernel.org, 
 SeungJong Ha <engineer.jjhama@...il.com>
Subject: [PATCH RFC 2/3] rust: tty: add TTY subsystem abstractions

From: SeungJong Ha <engineer.jjhama@...il.com>

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<DriverData, DriverState> 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<Ops> 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<T>) 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 <engineer.jjhama@...il.com>
---
 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 = "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_driver.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_data`.
+///   Use `Arc<T>` for shared data across multiple opens.
+/// - `DriverState`: Driver-level data stored in `tty_driver->driver_state` (shared by all ttys).
+///   Use `Arc<T>` for shared state.
+#[repr(transparent)]
+pub struct Tty<DriverData = (), DriverState = ()>(
+    *mut bindings::tty_struct,
+    PhantomData<(DriverData, DriverState)>,
+);
+
+impl<DriverData, DriverState> Tty<DriverData, DriverState> {
+    /// 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<T: Send + Sync, DriverState> Tty<Arc<T>, DriverState> {
+    /// Sets driver-specific data in the `driver_data` field, taking ownership of the Arc.
+    ///
+    /// Returns the previously set data, if any.
+    pub fn set_driver_data(&self, data: Arc<T>) -> Option<Arc<T>> {
+        let old = self.take_driver_data();
+        // SAFETY: self.0 is valid.
+        unsafe {
+            (*self.0).driver_data = Arc::into_raw(data) as *mut _;
+        }
+        old
+    }
+
+    /// Takes the driver-specific data from the `driver_data` field, returning ownership.
+    ///
+    /// Returns `None` if no data was set.
+    pub fn take_driver_data(&self) -> Option<Arc<T>> {
+        // SAFETY: self.0 is valid.
+        let ptr = unsafe { (*self.0).driver_data };
+        if ptr.is_null() {
+            return None;
+        }
+        // SAFETY: self.0 is valid.
+        unsafe {
+            (*self.0).driver_data = core::ptr::null_mut();
+        }
+        // SAFETY: ptr was set via set_driver_data from an Arc<T>.
+        Some(unsafe { Arc::from_raw(ptr.cast()) })
+    }
+
+    /// Returns a reference to the driver-specific data in the `driver_data` field.
+    ///
+    /// Returns `None` if no data was set.
+    pub fn driver_data(&self) -> Option<&T> {
+        // SAFETY: self.0 is valid.
+        let ptr = unsafe { (*self.0).driver_data };
+        if ptr.is_null() {
+            return None;
+        }
+        // SAFETY: ptr was set via set_driver_data from an Arc<T>.
+        Some(unsafe { &*ptr.cast::<T>() })
+    }
+}
+
+impl<DriverData, T: Send + Sync> Tty<DriverData, Arc<T>> {
+    /// 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 cloned Arc,
+    /// incrementing the reference count.
+    pub fn driver_state(&self) -> Option<Arc<T>> {
+        // SAFETY: self.0 is valid.
+        let driver = unsafe { (*self.0).driver };
+        if driver.is_null() {
+            return None;
+        }
+        // SAFETY: driver is valid.
+        let state = unsafe { (*driver).driver_state };
+        if state.is_null() {
+            return None;
+        }
+        // SAFETY: state was set via set_driver_state from an Arc<T>.
+        // We reconstruct the Arc, clone it, then forget the original to avoid
+        // decrementing the stored refcount.
+        let arc = unsafe { Arc::from_raw(state.cast::<T>()) };
+        let cloned = arc.clone();
+        core::mem::forget(arc);
+        Some(cloned)
+    }
+
+    /// Takes the driver-level state from `tty_driver->driver_state`, returning ownership.
+    ///
+    /// Returns `None` if no state was set.
+    pub fn take_driver_state(&self) -> Option<Arc<T>> {
+        // SAFETY: self.0 is valid.
+        let driver = unsafe { (*self.0).driver };
+        if driver.is_null() {
+            return None;
+        }
+        // SAFETY: driver is valid.
+        let ptr = unsafe { (*driver).driver_state };
+        if ptr.is_null() {
+            return None;
+        }
+        // SAFETY: driver is valid.
+        unsafe {
+            (*driver).driver_state = core::ptr::null_mut();
+        }
+        // SAFETY: ptr was set via set_driver_state from an Arc<T>.
+        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<T>) -> Option<Arc<T>> {
+        // SAFETY: self.0 is valid.
+        let driver = unsafe { (*self.0).driver };
+        if driver.is_null() {
+            return None;
+        }
+        // Take old state first.
+        let old = self.take_driver_state();
+        // SAFETY: driver is valid.
+        unsafe {
+            (*driver).driver_state = 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 drivers.
+
+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 = bindings::tty_driver_flag_TTY_DRIVER_RESET_TERMIOS as usize;
+    /// Driver will guarantee not to set any special character handling flags.
+    pub const REAL_RAW: usize = bindings::tty_driver_flag_TTY_DRIVER_REAL_RAW as usize;
+    /// Do not create numbered /dev nodes (e.g., /dev/ttyprintk instead of /dev/ttyprintk0).
+    pub const UNNUMBERED_NODE: usize =
+        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 = bindings::OPOST;
+    /// Map CR to NL on output.
+    pub const OCRNL: u32 = bindings::OCRNL;
+    /// No CR output at column 0.
+    pub const ONOCR: u32 = bindings::ONOCR;
+    /// NL performs CR function.
+    pub const ONLRET: u32 = bindings::ONLRET;
+}
+
+/// Major device number for TTY aux devices.
+pub const TTYAUX_MAJOR: i32 = bindings::TTYAUX_MAJOR as i32;
+
+/// TTY driver types.
+#[repr(u32)]
+#[derive(Copy, Clone, Debug)]
+pub enum DriverType {
+    /// System TTY.
+    System = bindings::tty_driver_type_TTY_DRIVER_TYPE_SYSTEM,
+    /// Console TTY.
+    Console = bindings::tty_driver_type_TTY_DRIVER_TYPE_CONSOLE,
+    /// Serial TTY.
+    Serial = bindings::tty_driver_type_TTY_DRIVER_TYPE_SERIAL,
+    /// PTY.
+    Pty = 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<T>` 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<T>` for shared state across all ttys, or `()` if not needed.
+    /// 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<Self::DriverData, Self::DriverState>,
+        file: *mut bindings::file,
+    ) -> Result<()>;
+
+    /// Called when the TTY device is closed.
+    fn close(tty: &Tty<Self::DriverData, Self::DriverState>, file: *mut bindings::file);
+
+    /// Called to write data to the device.
+    fn write(tty: &Tty<Self::DriverData, Self::DriverState>, buf: &[u8]) -> Result<usize>;
+
+    /// Returns the number of bytes that can be written.
+    fn write_room(_tty: &Tty<Self::DriverData, Self::DriverState>) -> u32 {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+
+    /// Called on hangup.
+    fn hangup(_tty: &Tty<Self::DriverData, Self::DriverState>) {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+}
+
+/// A vtable for the TTY operations.
+struct OperationsVTable<T: Operations>(PhantomData<T>);
+
+/// Type alias for the TTY type used in operations callbacks.
+type OpsTty<T> = Tty<<T as Operations>::DriverData, <T as Operations>::DriverState>;
+
+impl<T: Operations> OperationsVTable<T> {
+    /// # 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 = unsafe { OpsTty::<T>::from_raw(tty) };
+
+        match T::open(&tty_ref, filp) {
+            Ok(()) => 0,
+            Err(e) => 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 = unsafe { OpsTty::<T>::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 == 0 {
+            return 0;
+        }
+
+        // SAFETY: Kernel guarantees buf is valid for count bytes.
+        let slice = unsafe { core::slice::from_raw_parts(buf, count) };
+
+        // SAFETY: tty is valid, driver_data was set by driver in open.
+        let tty_ref = unsafe { OpsTty::<T>::from_raw(tty) };
+
+        match T::write(&tty_ref, slice) {
+            Ok(n) => n as isize,
+            Err(e) => e.to_errno() as isize,
+        }
+    }
+
+    /// # Safety
+    ///
+    /// `tty` must be a valid pointer.
+    unsafe extern "C" fn write_room(tty: *mut bindings::tty_struct) -> core::ffi::c_uint {
+        // SAFETY: tty is valid, driver_data was set by driver in open.
+        let tty_ref = unsafe { OpsTty::<T>::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 = unsafe { OpsTty::<T>::from_raw(tty) };
+        T::hangup(&tty_ref);
+    }
+
+    const VTABLE: bindings::tty_operations = 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 ports
+/// with [`link_port`](Self::link_port), then call [`build`](Self::build) to
+/// register and obtain a [`TtyDriver`].
+///
+/// # Example
+///
+/// ```ignore
+/// let driver = KBox::pin_init(
+///     TtyDriverBuilder::<MyOps>::new(opts, module)?
+///         .link_port(&port, 0)
+///         .build(),
+///     GFP_KERNEL,
+/// )?;
+/// ```
+pub struct TtyDriverBuilder<T: Operations> {
+    driver_ptr: *mut bindings::tty_driver,
+    _t: PhantomData<T>,
+}
+
+impl<T: Operations> TtyDriverBuilder<T> {
+    /// Creates a new TTY driver builder.
+    pub fn new(opts: Options, module: &'static crate::ThisModule) -> Result<Self> {
+        // SAFETY: FFI call with valid arguments.
+        let driver_ptr = unsafe { bindings::__tty_alloc_driver(1, module.as_ptr(), opts.flags) };
+
+        if driver_ptr.is_null() || (driver_ptr as isize) < 0 && (driver_ptr 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 = opts.driver_name.as_char_ptr();
+            (*driver_ptr).name = opts.name.as_char_ptr();
+            (*driver_ptr).major = opts.major;
+            (*driver_ptr).minor_start = opts.minor_start;
+            (*driver_ptr).type_ = opts.driver_type as u32;
+
+            // Set termios.
+            let mut termios = bindings::tty_std_termios;
+            termios.c_oflag = oflag::OPOST | oflag::OCRNL | oflag::ONOCR | oflag::ONLRET;
+            (*driver_ptr).init_termios = termios;
+
+            // Set operations vtable.
+            (*driver_ptr).ops = OperationsVTable::<T>::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 [`build`](Self::build).
+    pub fn link_port(self, port: &DriverPort<T::PortOps>, line: u32) -> Self {
+        // 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<TtyDriver<T>, Error> {
+        let driver_ptr = self.driver_ptr;
+        // Prevent Drop from freeing the driver_ptr; TtyDriver takes ownership.
+        core::mem::forget(self);
+
+        try_pin_init!(TtyDriver::<T> {
+            inner <- Opaque::try_ffi_init(move |slot: *mut *mut bindings::tty_driver| {
+                // SAFETY: driver_ptr is valid.
+                let ret = unsafe { bindings::tty_register_driver(driver_ptr) };
+                if ret != 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<T: Operations> Drop for TtyDriverBuilder<T> {
+    fn drop(&mut self) {
+        // SAFETY: driver_ptr is valid, not yet registered.
+        unsafe { bindings::tty_driver_kref_put(self.driver_ptr) };
+    }
+}
+
+impl<T, S> TtyDriverBuilder<T>
+where
+    T: Operations<DriverState = Arc<S>>,
+    S: Send + Sync,
+{
+    /// Sets the driver-level state, taking ownership of the Arc.
+    ///
+    /// The state can be accessed via [`Tty::driver_state`] in TTY operation callbacks.
+    ///
+    /// # Note
+    ///
+    /// The caller must call [`TtyDriver::take_driver_state`] before the driver is
+    /// dropped to reclaim the state's memory. Failure to do so will result in a
+    /// memory leak.
+    pub fn set_driver_state(self, state: Arc<S>) -> Self {
+        // SAFETY: driver_ptr is valid.
+        unsafe {
+            (*self.driver_ptr).driver_state = Arc::into_raw(state) as *mut _;
+        }
+        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<T: Operations> {
+    #[pin]
+    inner: Opaque<*mut bindings::tty_driver>,
+    _t: PhantomData<T>,
+}
+
+// SAFETY: It is allowed to call `tty_unregister_driver` on a different thread.
+unsafe impl<T: Operations> Send for TtyDriver<T> {}
+// SAFETY: All `&self` methods are safe to call in parallel.
+unsafe impl<T: Operations> Sync for TtyDriver<T> {}
+
+impl<T: Operations> TtyDriver<T> {
+    /// 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<O: PortOperations + 'static>(&self, port: &DriverPort<O>, 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<T, S> TtyDriver<T>
+where
+    T: Operations<DriverState = Arc<S>>,
+    S: Send + Sync,
+{
+    /// Takes the driver state, returning ownership of the Arc.
+    ///
+    /// Returns `None` if no state was set. This should be called before the driver
+    /// is dropped to reclaim the state's memory.
+    pub fn take_driver_state(&self) -> Option<Arc<S>> {
+        // SAFETY: driver_ptr is valid.
+        let ptr = unsafe { (*self.driver_ptr()).driver_state };
+        if ptr.is_null() {
+            return None;
+        }
+        // SAFETY: driver_ptr is valid.
+        unsafe {
+            (*self.driver_ptr()).driver_state = core::ptr::null_mut();
+        }
+        // SAFETY: ptr was set via set_driver_state from an Arc<S>.
+        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 = unsafe { (*self.driver_ptr()).driver_state };
+        if ptr.is_null() {
+            return None;
+        }
+        // SAFETY: ptr was set via set_driver_state from an Arc<S>.
+        Some(unsafe { &*ptr.cast::<S>() })
+    }
+}
+
+#[pinned_drop]
+impl<T: Operations> PinnedDrop for TtyDriver<T> {
+    fn drop(self: Pin<&mut Self>) {
+        // SAFETY: inner contains a valid registered driver.
+        unsafe {
+            let ptr = *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 field.
+
+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<Ops: Operations> {
+    #[pin]
+    port: TtyPort<Ops>,
+    #[pin]
+    data: Ops::PortData,
+}
+
+impl<Ops: Operations> DriverPort<Ops> {
+    /// Creates a pin-initializer for a new driver port.
+    pub fn new(
+        data_init: impl PinInit<Ops::PortData, core::convert::Infallible>,
+    ) -> impl PinInit<Self, Error> {
+        try_pin_init!(Self {
+            port <- TtyPort::<Ops>::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<Ops>`.
+    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 both.
+unsafe impl<Ops: Operations> Send for DriverPort<Ops> where Ops::PortData: Send {}
+// SAFETY: DriverPort is Send/Sync if Ops::PortData is, since TtyPort is both.
+unsafe impl<Ops: Operations> Sync for DriverPort<Ops> 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<Ops: Operations>(Opaque<bindings::tty_port>, PhantomData<Ops>);
+
+impl<Ops: Operations> TtyPort<Ops> {
+    /// Creates a pin-initializer that calls `tty_port_init()` and sets the ops vtable.
+    fn new() -> impl PinInit<Self, Error> {
+        // SAFETY: tty_port_init initializes the port, vtable is static.
+        unsafe {
+            pin_init::pin_init_from_closure(|slot: *mut Self| {
+                let port_ptr = slot.cast::<bindings::tty_port>();
+                bindings::tty_port_init(port_ptr);
+                (*port_ptr).ops = OperationsVTable::<Ops>::build();
+                Ok(())
+            })
+        }
+    }
+
+    fn as_raw(&self) -> *mut bindings::tty_port {
+        self.0.get()
+    }
+}
+
+// SAFETY: TtyPort operations are internally synchronized by the kernel.
+unsafe impl<Ops: Operations> Send for TtyPort<Ops> {}
+// SAFETY: TtyPort operations are internally synchronized by the kernel.
+unsafe impl<Ops: Operations> Sync for TtyPort<Ops> {}
+
+impl<Ops: Operations> Drop for TtyPort<Ops> {
+    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 specifies
+/// 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<Self>) {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+}
+
+/// Vtable adapter for port operations.
+struct OperationsVTable<Ops: Operations>(PhantomData<Ops>);
+
+impl<Ops: Operations> OperationsVTable<Ops> {
+    /// # Safety
+    /// `port` must be a valid `tty_port` within a `DriverPort<Ops>`.
+    unsafe extern "C" fn shutdown(port: *mut bindings::tty_port) {
+        // SAFETY: Port was registered with this vtable.
+        let driver_port = unsafe { DriverPort::<Ops>::from_raw(port) };
+        Ops::shutdown(driver_port);
+    }
+
+    const VTABLE: bindings::tty_port_operations = bindings::tty_port_operations {
+        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
+    }
+}

-- 
2.43.0



Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ