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

* Re: [PATCH v1] rust: console: add abstraction for kernel console drivers
  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
  0 siblings, 1 reply; 4+ messages in thread
From: Greg Kroah-Hartman @ 2026-02-27 23:14 UTC (permalink / raw)
  To: Matthew Wood
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Breno Leito, rust-for-linux, linux-kernel

On Fri, Feb 27, 2026 at 01:53:56PM -0800, Matthew Wood wrote:
> 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.

I'd prefer to see it then, with the user, before even reviewing this
one, sorry.  That way we "know" it at least works for you.

thanks,

greg k-h

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

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

On Fri, Feb 27, 2026 at 06:14:43PM -0500, Greg Kroah-Hartman wrote:
> > This abstraction is a dependency for a Rust netconsole implementation
> > I am working on.
> 
> I'd prefer to see it then, with the user, before even reviewing this
> one, sorry.  That way we "know" it at least works for you.
> 
Thanks for the reply Greg, that's a very fair request. Since the
complete netconsole Rust implementation crosses several subsytems
(module-support, net, configfs, console) and I haven't yet
submitted such a large series before:

1) Should I submit the complete implementation w/ dependencies as one
series?

2) If I create and include a minimum-viable functional example using
this console abstraction, would that suffice?

Regards,
Matthew

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

* Re: [PATCH v1] rust: console: add abstraction for kernel console drivers
  2026-02-27 23:44   ` Matthew Wood
@ 2026-02-28  0:17     ` Miguel Ojeda
  0 siblings, 0 replies; 4+ messages in thread
From: Miguel Ojeda @ 2026-02-28  0:17 UTC (permalink / raw)
  To: Matthew Wood
  Cc: Greg Kroah-Hartman, Miguel Ojeda, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Danilo Krummrich, Breno Leito, rust-for-linux,
	linux-kernel

On Sat, Feb 28, 2026 at 12:44 AM Matthew Wood <thepacketgeek@gmail.com> wrote:
>
> 1) Should I submit the complete implementation w/ dependencies as one
> series?

It depends a bit on the case, but yes, one option is sending the
entire set. It doesn't hurt if it is not very large, at least once,
possibly as an RFC, and then you can use it as a reference, e.g. via a
link later on, when you submit the subsets.

Another common approach is to provide a link to a branch with the
entire set for reference. That is also useful on its own -- some
people prefer to fetch a fully prepared branch to test. So I would
recommend doing that even if you send the entire series once.

> 2) If I create and include a minimum-viable functional example using
> this console abstraction, would that suffice?

That definitely helps reviewers, please do so. Sometimes people add a
sample in `samples/rust/` for that as an extra patch.

Nevertheless, maintainers will likely want to have a "real user" as the goal.

I hope that helps!

Cheers,
Miguel

^ permalink raw reply	[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