public inbox for rust-for-linux@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v1] rust: console: add abstraction for kernel console drivers
@ 2026-02-27 21:53 Matthew Wood
  2026-02-27 23:14 ` Greg Kroah-Hartman
  0 siblings, 1 reply; 4+ messages in thread
From: Matthew Wood @ 2026-02-27 21:53 UTC (permalink / raw)
  To: Miguel Ojeda, Greg Kroah-Hartman, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Danilo Krummrich
  Cc: Breno Leito, rust-for-linux, linux-kernel

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<T>` struct that wraps `struct console` using pin-init
    and `Opaque<T>`, 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<T>` 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 <thepacketgeek@gmail.com>
---
 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_helper.h
index 083cc44aa952..eeddf2374f00 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -43,6 +43,7 @@
 #include <linux/clk.h>
 #include <linux/completion.h>
 #include <linux/configfs.h>
+#include <linux/console.h>
 #include <linux/cpu.h>
 #include <linux/cpufreq.h>
 #include <linux/cpumask.h>
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 <linux/console.h>
+
+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 = bindings::cons_flags_CON_PRINTBUFFER as u16;
+    /// Indicates console is backing /dev/console.
+    pub const CON_CONSDEV: u16 = bindings::cons_flags_CON_CONSDEV as u16;
+    /// Console is enabled.
+    pub const CON_ENABLED: u16 = bindings::cons_flags_CON_ENABLED as u16;
+    /// Early boot console.
+    pub const CON_BOOT: u16 = bindings::cons_flags_CON_BOOT as u16;
+    /// Console can be called from any context.
+    pub const CON_ANYTIME: u16 = bindings::cons_flags_CON_ANYTIME as u16;
+    /// Braille device.
+    pub const CON_BRL: u16 = bindings::cons_flags_CON_BRL as u16;
+    /// Console supports extended output format.
+    pub const CON_EXTENDED: u16 = bindings::cons_flags_CON_EXTENDED as u16;
+    /// Console is suspended.
+    pub const CON_SUSPENDED: u16 = 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<T: ConsoleOps>(PhantomData<T>);
+
+impl<T: ConsoleOps> ConsoleOpsAdapter<T> {
+    /// Write callback for the console.
+    ///
+    /// # Safety
+    ///
+    /// `con` must be a valid pointer to a `bindings::console` that was
+    /// created by `Console<T>` 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 = unsafe { (*con).data };
+        if data.is_null() {
+            return;
+        }
+
+        // SAFETY: `data` points to a valid `T` per the type invariants.
+        let ops = unsafe { &*(data as *const T) };
+
+        // SAFETY: `s` is valid for `count` bytes.
+        let msg = 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 = unsafe { (*con).data };
+        if data.is_null() {
+            return 0;
+        }
+
+        // SAFETY: `data` points to a valid `T` per the type invariants.
+        let ops = unsafe { &*(data as *const T) };
+
+        let options_cstr = 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(()) => 0,
+            Err(e) => 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<T: ConsoleOps> {
+    #[pin]
+    inner: Opaque<bindings::console>,
+    #[pin]
+    data: T,
+    registered: bool,
+}
+
+// SAFETY: Console can be sent between threads if T can.
+unsafe impl<T: ConsoleOps + Send> Send for Console<T> {}
+
+// SAFETY: Console operations are synchronized by the caller.
+unsafe impl<T: ConsoleOps + Sync> Sync for Console<T> {}
+
+impl<T: ConsoleOps> Console<T> {
+    /// 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<T, Error>,
+    ) -> impl PinInit<Self, Error> {
+        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 = name.to_bytes();
+                    let name_len = 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 = console_flags as i16;
+
+                    // Set the write callback.
+                    (*slot).write = Some(ConsoleOpsAdapter::<T>::write_callback);
+
+                    // Set the setup callback if T implements it.
+                    if T::HAS_SETUP {
+                        (*slot).setup = Some(ConsoleOpsAdapter::<T>::setup_callback);
+                    }
+                }
+                Ok::<(), Error>(())
+            }),
+            data <- data,
+            registered: true,  // Will be set after registration in pin_chain
+        })
+        .pin_chain(|this| {
+            // Set the data pointer to our ops.
+            // SAFETY: `this` is pinned and valid.
+            unsafe {
+                let con = this.inner.get();
+                (*con).data = &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<T: ConsoleOps> PinnedDrop for Console<T> {
+    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;
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-02-28  0:18 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-27 21:53 [PATCH v1] rust: console: add abstraction for kernel console drivers Matthew Wood
2026-02-27 23:14 ` Greg Kroah-Hartman
2026-02-27 23:44   ` Matthew Wood
2026-02-28  0:17     ` Miguel Ojeda

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox