From nobody Sat Apr 18 11:08:48 2026 Received: from mail-dy1-f176.google.com (mail-dy1-f176.google.com [74.125.82.176]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7DDEB155A5D for ; Fri, 27 Feb 2026 21:53:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.176 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772229241; cv=none; b=Mh7rz5nbFUY1Xn092OtZFSf8diFKSMzJRRzEm2M0NEYkAGCxpqDUqPgU1n6GlpcHYGtvo25wjM8bCpVLIuvMcDGQYFRMbA3z2RcWatEiKanqogngq4RVtpaTb+HqY27Fk49Gt2pWNIxrGtZ8jf1MLueNmDPnb7fudu7R2+B9+sk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772229241; c=relaxed/simple; bh=aw7jen9Y6mLuF43Ihyg0muw+h1WHGZzxGUd9JmT4Jjo=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=f45J9SJvkwt8ir8f+yIAw1zJG+7RZE4az+IYGs2kMoim5eBwwt5QtLybEbjTVpn/KndxPBnWimzq0+6DuUTRFYBw42ja2Sf+3e12ejpNjpgzou+wLhFThuOs3chQf4fhfmRJhFYcCgwZnHtewZqB95gTGNKJftppmSDyFe9ulXY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=OPDWjswI; arc=none smtp.client-ip=74.125.82.176 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="OPDWjswI" Received: by mail-dy1-f176.google.com with SMTP id 5a478bee46e88-2bd9a485bd6so4911237eec.1 for ; Fri, 27 Feb 2026 13:53:59 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1772229239; x=1772834039; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=TURvnTXNSG+YFpbkN95wBHl3Myvzl19SQUgrJTkS/64=; b=OPDWjswICUHgR+z6uWA59Eqe0HfzvpC41g1uVsZPcR4ybiMT/UME1jbY8YGuHLnwlS K41A6LYFfLc76ZEhAwKmCbQb8+ifVibybhKxA59P7t5XXoxQwYMGXrEl1oktC+OO7sqH ce5EaYBvHlofU1nN/fNlZkq2H9KYReDJBhKZg0uFdqE4FowOSusSA2t2NOXmozE0aO8m SIN4WlXQhkvPbTgYB30AAzM6p4K+Vj2x75ZXYHxB6Y+21hTV8NydlSkyd17cnwMc2q+Y H3qzXWYTrK+ds39tKw4+JA5z0ArkF+ZxawHzk9FTBQFPqUjamPkpXFh/GingqdwaYsD4 ZXsg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772229239; x=1772834039; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=TURvnTXNSG+YFpbkN95wBHl3Myvzl19SQUgrJTkS/64=; b=JoUTT/oICVVED0bSQLA682G/xrdfUXCVlpyUhYlWOOV+E9B7CBJHAV3ru4O8VUs0hG gaU2Wpde04bEQCLdf3AhF2YmTrcGBQLKcNLuhY0YgeLSGCcw+BDBHtSSLxnBKeNz7hLP LAZ64MgeIGOscWY9MaOB4ZqR0L74uzTajzV5O70gZf1nwj6BTuiP8o3m7hy8MBcjgvob OydGS8/3ou6BwIB+Oi94WKRdI/OWB0BdfBdAfLKtfHItUBZKSlPKJlm/0fAzilb10Mo9 tFz8cFfWfhqB4JBPnYamS7YuIixXPNN70XJAOXMvdzI93OdL2PoXFhmAGsaUF1vcy6fr Au3A== X-Forwarded-Encrypted: i=1; AJvYcCWAiVtK3A0Sx15qmlHRshpxlHq/X8oHOu859uMjZfBTy/Mg6ZtJ/hdmBFjGiHn8mwEXvK4KFi/8k1GHPnw=@vger.kernel.org X-Gm-Message-State: AOJu0Yy14klfgYKKZZnYdUddQ82xK2bug+7JlgJuJ6h0cjDuDao42uHM VFXEPvAf5/usRhJdijrg9ctWItX29ZnoonIqSCnzLLQd5cjA8HDPowdY X-Gm-Gg: ATEYQzzAIyFuDr+S9XVFj0YB4tzkHfhmcVHv4/IEwVTOi9P1IDHsKPrmpcODUKH/uxj drYIR7yNgr8xXfAMrOq0NTiW0r2OKUsDxwGachota0MlBEGAd26l3JvJmohGrOVtny/oq8LEk2X IkhSsk8qXnhKCgFh9iP/fh3RnwX7LIA3vCUhzhY62z5JUBZ4DBlMalXxTucYNATH5P0g+vpNmIy T9VhnR7Ao29R0CjsfMfr5vQ95l2hDSFO9om3mlLtN4y4iVVYLhza/UMUbG8sauDtxButjEE319q rTDI0H5fwmV3uNwEIgIYU41F+Kittd9gC6Gf7dQ6kSzEkzVmw7kP3Fru598WxJds8TPT6Dru+2C MVKa0xiRT7AKtYBGkN8mXHFQzf/5i39veN8wmF7LHuH60dbdfDDM5aa4xcIjobXex1+IKEIJl8j cz/THvB+WUVWdES0kttcQnYIJtTYuAhg== X-Received: by 2002:a05:7301:1f10:b0:2ba:6b03:909b with SMTP id 5a478bee46e88-2bde1c989b0mr2264381eec.19.1772229238416; Fri, 27 Feb 2026 13:53:58 -0800 (PST) Received: from localhost ([2600:1700:22f5:908f:1457:7499:d258:358f]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2bdd1bcde84sm4427866eec.4.2026.02.27.13.53.57 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 27 Feb 2026 13:53:58 -0800 (PST) From: Matthew Wood To: Miguel Ojeda , Greg Kroah-Hartman , Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich Cc: Breno Leito , rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v1] rust: console: add abstraction for kernel console drivers Date: Fri, 27 Feb 2026 13:53:56 -0800 Message-ID: <20260227215357.667257-1-thepacketgeek@gmail.com> X-Mailer: git-send-email 2.52.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add a safe Rust abstraction for the kernel's `struct console`, enabling console drivers to be implemented in Rust that provides: - `ConsoleOps` trait with a required `write` callback and an optional `setup` callback, mirroring the C console_operations interface. The trait requires `Send + Sync` to reflect that console write may be called from any context including IRQ. - `Console` struct that wraps `struct console` using pin-init and `Opaque`, with automatic unregistration via `PinnedDrop`. Registration is performed through a pin-initializer returned by `Console::register()`, which uses `pin_chain` to set the data pointer and call `register_console()` after the struct is pinned. - `ConsoleOpsAdapter` that provides the extern "C" callbacks bridging from the kernel's function pointers to the Rust trait methods. The `#[vtable]` attribute on `ConsoleOps` enables compile-time detection of whether `setup` is implemented via `T::HAS_SETUP`. - Console flag constants re-exported from the C `enum cons_flags`. C helper functions are added for `register_console()`, `unregister_console()`, and `console_is_registered()` as these are either inlines or macros that cannot be called directly from Rust through bindgen. This abstraction is a dependency for a Rust netconsole implementation I am working on. Signed-off-by: Matthew Wood --- rust/bindings/bindings_helper.h | 1 + rust/helpers/console.c | 22 +++ rust/helpers/helpers.c | 1 + rust/kernel/console.rs | 230 ++++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 1 + 5 files changed, 255 insertions(+) create mode 100644 rust/helpers/console.c create mode 100644 rust/kernel/console.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helpe= r.h index 083cc44aa952..eeddf2374f00 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/helpers/console.c b/rust/helpers/console.c new file mode 100644 index 000000000000..b101c6c749fd --- /dev/null +++ b/rust/helpers/console.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Rust helpers for console. + */ + +#include + +void rust_helper_register_console(struct console *console) +{ + register_console(console); +} + +int rust_helper_unregister_console(struct console *console) +{ + return unregister_console(console); +} + +bool rust_helper_console_is_registered(struct console *console) +{ + return console_is_registered(console); +} diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index a3c42e51f00a..2b818126ce02 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -22,6 +22,7 @@ #include "build_bug.c" #include "clk.c" #include "completion.c" +#include "console.c" #include "cpu.c" #include "cpufreq.c" #include "cpumask.c" diff --git a/rust/kernel/console.rs b/rust/kernel/console.rs new file mode 100644 index 000000000000..d78b04a9846b --- /dev/null +++ b/rust/kernel/console.rs @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Console driver abstraction. +//! +//! This module provides safe Rust wrappers for implementing kernel console +//! drivers, which receive kernel log messages and output them to a device. +//! +//! C header: [`include/linux/console.h`](srctree/include/linux/console.h) + +use crate::{bindings, error::Result, prelude::*, str::CStr, types::Opaque}; +use core::marker::PhantomData; + +/// Console flags from `enum cons_flags`. +pub mod flags { + /// Used by newly registered consoles to avoid duplicate output. + pub const CON_PRINTBUFFER: u16 =3D bindings::cons_flags_CON_PRINTBUFFE= R as u16; + /// Indicates console is backing /dev/console. + pub const CON_CONSDEV: u16 =3D bindings::cons_flags_CON_CONSDEV as u16; + /// Console is enabled. + pub const CON_ENABLED: u16 =3D bindings::cons_flags_CON_ENABLED as u16; + /// Early boot console. + pub const CON_BOOT: u16 =3D bindings::cons_flags_CON_BOOT as u16; + /// Console can be called from any context. + pub const CON_ANYTIME: u16 =3D bindings::cons_flags_CON_ANYTIME as u16; + /// Braille device. + pub const CON_BRL: u16 =3D bindings::cons_flags_CON_BRL as u16; + /// Console supports extended output format. + pub const CON_EXTENDED: u16 =3D bindings::cons_flags_CON_EXTENDED as u= 16; + /// Console is suspended. + pub const CON_SUSPENDED: u16 =3D bindings::cons_flags_CON_SUSPENDED as= u16; +} + +/// Operations that a console driver must implement. +/// +/// The `write` callback is the only required operation. It will be called +/// to output kernel log messages. +#[vtable] +pub trait ConsoleOps: Sized + Send + Sync { + /// Writes a message to the console. + /// + /// This is called with a buffer containing the message to output. + /// The implementation should send the message to the console device. + /// + /// # Context + /// + /// This may be called from any context, including IRQ context. + /// Implementations must not sleep. + fn write(&self, msg: &[u8]); + + /// Sets up the console. + /// + /// This is called when the console is registered. + /// The `options` parameter contains any boot command line options. + fn setup(&self, _options: Option<&CStr>) -> Result { + Ok(()) + } +} + +/// Adapter for console operations vtable. +struct ConsoleOpsAdapter(PhantomData); + +impl ConsoleOpsAdapter { + /// Write callback for the console. + /// + /// # Safety + /// + /// `con` must be a valid pointer to a `bindings::console` that was + /// created by `Console` and has valid `data` pointing to `T`. + unsafe extern "C" fn write_callback( + con: *mut bindings::console, + s: *const u8, + count: core::ffi::c_uint, + ) { + // SAFETY: By function safety requirements, `con` is valid. + let data =3D unsafe { (*con).data }; + if data.is_null() { + return; + } + + // SAFETY: `data` points to a valid `T` per the type invariants. + let ops =3D unsafe { &*(data as *const T) }; + + // SAFETY: `s` is valid for `count` bytes. + let msg =3D unsafe { core::slice::from_raw_parts(s, count as usize= ) }; + + ops.write(msg); + } + + /// Setup callback for the console. + /// + /// # Safety + /// + /// `con` must be a valid pointer to a `bindings::console`. + unsafe extern "C" fn setup_callback( + con: *mut bindings::console, + options: *mut u8, + ) -> core::ffi::c_int { + // SAFETY: By function safety requirements, `con` is valid. + let data =3D unsafe { (*con).data }; + if data.is_null() { + return 0; + } + + // SAFETY: `data` points to a valid `T` per the type invariants. + let ops =3D unsafe { &*(data as *const T) }; + + let options_cstr =3D if options.is_null() { + None + } else { + // SAFETY: If not null, `options` points to a null-terminated = string. + Some(unsafe { CStr::from_char_ptr(options.cast()) }) + }; + + match ops.setup(options_cstr) { + Ok(()) =3D> 0, + Err(e) =3D> e.to_errno(), + } + } +} + +/// A registered kernel console. +/// +/// This struct wraps the kernel's `struct console` and provides safe +/// registration and unregistration of console drivers. +/// +/// # Invariants +/// +/// - `inner` contains a valid `console` structure. +/// - When registered, the console's `data` field points to valid `T`. +#[pin_data(PinnedDrop)] +pub struct Console { + #[pin] + inner: Opaque, + #[pin] + data: T, + registered: bool, +} + +// SAFETY: Console can be sent between threads if T can. +unsafe impl Send for Console {} + +// SAFETY: Console operations are synchronized by the caller. +unsafe impl Sync for Console {} + +impl Console { + /// Creates an initializer for registering a new console. + /// + /// # Arguments + /// + /// * `name` - The name of the console (up to 15 characters). + /// * `flags` - Console flags from the `flags` module. + /// * `data` - The console operations implementation. + pub fn register( + name: &'static CStr, + console_flags: u16, + data: impl PinInit, + ) -> impl PinInit { + try_pin_init!(Self { + inner <- Opaque::try_ffi_init(|slot: *mut bindings::console| { + // SAFETY: `slot` is valid for writing. + unsafe { + // Zero-initialize the struct. + core::ptr::write_bytes(slot, 0, 1); + + // Copy the name (up to 15 chars + null). + let name_bytes =3D name.to_bytes(); + let name_len =3D core::cmp::min(name_bytes.len(), 15); + core::ptr::copy_nonoverlapping( + name_bytes.as_ptr().cast(), + (*slot).name.as_mut_ptr(), + name_len, + ); + + // Set flags. + (*slot).flags =3D console_flags as i16; + + // Set the write callback. + (*slot).write =3D Some(ConsoleOpsAdapter::::write_c= allback); + + // Set the setup callback if T implements it. + if T::HAS_SETUP { + (*slot).setup =3D Some(ConsoleOpsAdapter::::set= up_callback); + } + } + Ok::<(), Error>(()) + }), + data <- data, + registered: true, // Will be set after registration in pin_ch= ain + }) + .pin_chain(|this| { + // Set the data pointer to our ops. + // SAFETY: `this` is pinned and valid. + unsafe { + let con =3D this.inner.get(); + (*con).data =3D &this.data as *const T as *mut core::ffi::= c_void; + } + + // Register the console. + // SAFETY: The console structure is properly initialized. + unsafe { bindings::register_console(this.inner.get()) }; + + Ok(()) + }) + } + + /// Returns whether the console is currently registered. + pub fn is_registered(&self) -> bool { + self.registered + } + + /// Returns a pointer to the underlying console struct. + pub fn as_ptr(&self) -> *mut bindings::console { + self.inner.get() + } + + /// Returns a reference to the console data. + pub fn data(&self) -> &T { + &self.data + } +} + +#[pinned_drop] +impl PinnedDrop for Console { + fn drop(self: Pin<&mut Self>) { + if self.registered { + // SAFETY: The console was registered during initialization. + unsafe { bindings::unregister_console(self.inner.get()) }; + } + } +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 3da92f18f4ee..6381225cbe9c 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -78,6 +78,7 @@ pub mod clk; #[cfg(CONFIG_CONFIGFS_FS)] pub mod configfs; +pub mod console; pub mod cpu; #[cfg(CONFIG_CPU_FREQ)] pub mod cpufreq; --=20 2.52.0