[PATCH v6 4/6] rust: irq: add support for threaded IRQs and handlers

Daniel Almeida
2025-07-03T19:31:16Z

This patch adds support for threaded IRQs and handlers through
irq::ThreadedRegistration and the irq::ThreadedHandler trait.

Threaded interrupts are more permissive in the sense that further
processing is possible in a kthread. This means that said execution takes
place outside of interrupt context, which is rather restrictive in many
ways.

Registering a threaded irq is dependent upon having an IrqRequest that
was previously allocated by a given device. This will be introduced in
subsequent patches.

Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>
---
rust/kernel/irq.rs | 5 +-
rust/kernel/irq/request.rs | 239 ++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 241 insertions(+), 3 deletions(-)

diff --git a/rust/kernel/irq.rs b/rust/kernel/irq.rs
index 01bd08884b72c2a3a9460897bce751c732a19794..aaa40001bafca617c588c799bb41144921595cae 100644
--- a/rust/kernel/irq.rs
+++ b/rust/kernel/irq.rs
@@ -16,4 +16,7 @@
/// IRQ allocation and handling.
pub mod request;

-pub use request::{Handler, IrqRequest, IrqReturn, Registration};
+pub use request::{
+ Handler, IrqRequest, IrqReturn, Registration, ThreadedHandler, ThreadedIrqReturn,
+ ThreadedRegistration,
+};
diff --git a/rust/kernel/irq/request.rs b/rust/kernel/irq/request.rs
index 4f4beaa3c7887660440b9ddc52414020a0d165ac..bd489b8d23868a3b315a4f212f94a31870c9f02e 100644
--- a/rust/kernel/irq/request.rs
+++ b/rust/kernel/irq/request.rs
@@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
// SPDX-FileCopyrightText: Copyright 2025 Collabora ltd.

-//! This module provides types like [`Registration`] which allow users to
-//! register handlers for a given IRQ line.
+//! This module provides types like [`Registration`] and
+//! [`ThreadedRegistration`], which allow users to register handlers for a given
+//! IRQ line.

use core::marker::PhantomPinned;

@@ -271,3 +272,237 @@ pub fn synchronize(&self, dev: &Device<Bound>) -> Result {
let handler = unsafe { &*(ptr as *const T) };
T::handle(handler).into_inner()
}
+
+/// The value that can be returned from `ThreadedHandler::handle_irq`.
+pub enum ThreadedIrqReturn {
+ /// The interrupt was not from this device or was not handled.
+ None,
+
+ /// The interrupt was handled by this device.
+ Handled,
+
+ /// The handler wants the handler thread to wake up.
+ WakeThread,
+}
+
+impl ThreadedIrqReturn {
+ fn into_inner(self) -> u32 {
+ match self {
+ ThreadedIrqReturn::None => bindings::irqreturn_IRQ_NONE,
+ ThreadedIrqReturn::Handled => bindings::irqreturn_IRQ_HANDLED,
+ ThreadedIrqReturn::WakeThread => bindings::irqreturn_IRQ_WAKE_THREAD,
+ }
+ }
+}
+
+/// Callbacks for a threaded IRQ handler.
+pub trait ThreadedHandler: Sync {
+ /// The hard IRQ handler.
+ ///
+ /// This is executed in interrupt context, hence all corresponding
+ /// limitations do apply. All work that does not necessarily need to be
+ /// executed from interrupt context, should be deferred to the threaded
+ /// handler, i.e. [`ThreadedHandler::handle_threaded`].
+ fn handle(&self) -> ThreadedIrqReturn;
+
+ /// The threaded IRQ handler.
+ ///
+ /// This is executed in process context. The kernel creates a dedicated
+ /// kthread for this purpose.
+ fn handle_threaded(&self) -> IrqReturn;
+}
+
+impl<T: ?Sized + ThreadedHandler + Send> ThreadedHandler for Arc<T> {
+ fn handle(&self) -> ThreadedIrqReturn {
+ T::handle(self)
+ }
+
+ fn handle_threaded(&self) -> IrqReturn {
+ T::handle_threaded(self)
+ }
+}
+
+impl<T: ?Sized + ThreadedHandler, A: Allocator> ThreadedHandler for Box<T, A> {
+ fn handle(&self) -> ThreadedIrqReturn {
+ T::handle(self)
+ }
+
+ fn handle_threaded(&self) -> IrqReturn {
+ T::handle_threaded(self)
+ }
+}
+
+/// A registration of a threaded IRQ handler for a given IRQ line.
+///
+/// Two callbacks are required: one to handle the IRQ, and one to handle any
+/// other work in a separate thread.
+///
+/// The thread handler is only called if the IRQ handler returns `WakeThread`.
+///
+/// # Examples
+///
+/// The following is an example of using `ThreadedRegistration`. It uses a
+/// [`AtomicU32`](core::sync::AtomicU32) to provide the interior mutability.
+///
+/// ```
+/// use core::sync::atomic::AtomicU32;
+/// use core::sync::atomic::Ordering;
+///
+/// use kernel::prelude::*;
+/// use kernel::device::Bound;
+/// use kernel::irq::flags;
+/// use kernel::irq::ThreadedIrqReturn;
+/// use kernel::irq::ThreadedRegistration;
+/// use kernel::irq::IrqRequest;
+/// use kernel::irq::IrqReturn;
+/// use kernel::sync::Arc;
+/// use kernel::c_str;
+/// use kernel::alloc::flags::GFP_KERNEL;
+///
+/// // Declare a struct that will be passed in when the interrupt fires. The u32
+/// // merely serves as an example of some internal data.
+/// struct Data(AtomicU32);
+///
+/// // [`kernel::irq::request::ThreadedHandler::handle`] takes `&self`. This example
+/// // illustrates how interior mutability can be used when sharing the data
+/// // between process context and IRQ context.
+/// type Handler = Data;
+///
+/// impl kernel::irq::request::ThreadedHandler for Handler {
+/// // This is executing in IRQ context in some CPU. Other CPUs can still
+/// // try to access the data.
+/// fn handle(&self) -> ThreadedIrqReturn {
+/// self.0.fetch_add(1, Ordering::Relaxed);
+/// // By returning `WakeThread`, we indicate to the system that the
+/// // thread function should be called. Otherwise, return
+/// // ThreadedIrqReturn::Handled.
+/// ThreadedIrqReturn::WakeThread
+/// }
+///
+/// // This will run (in a separate kthread) if and only if `handle`
+/// // returns `WakeThread`.
+/// fn handle_threaded(&self) -> IrqReturn {
+/// self.0.fetch_add(1, Ordering::Relaxed);
+/// IrqReturn::Handled
+/// }
+/// }
+///
+/// // Registers a threaded IRQ handler for the given IrqRequest.
+/// //
+/// // This is executing in process context and assumes that `request` was
+/// // previously acquired from a device.
+/// fn register_threaded_irq(handler: Handler, request: IrqRequest<'_>) -> Result<Arc<ThreadedRegistration<Handler>>> {
+/// let registration = ThreadedRegistration::new(request, flags::SHARED, c_str!("my_device"), handler);
+///
+/// let registration = Arc::pin_init(registration, GFP_KERNEL)?;
+///
+/// // The data can be accessed from process context too.
+/// registration.handler().0.fetch_add(1, Ordering::Relaxed);
+///
+/// Ok(registration)
+/// }
+/// # Ok::<(), Error>(())
+/// ```
+///
+/// # Invariants
+///
+/// * We own an irq handler using `&T` as its private data.
+///
+#[pin_data]
+pub struct ThreadedRegistration<T: ThreadedHandler + 'static> {
+ #[pin]
+ inner: Devres<RegistrationInner>,
+
+ #[pin]
+ handler: T,
+
+ /// Pinned because we need address stability so that we can pass a pointer
+ /// to the callback.
+ #[pin]
+ _pin: PhantomPinned,
+}
+
+impl<T: ThreadedHandler + 'static> ThreadedRegistration<T> {
+ /// Registers the IRQ handler with the system for the given IRQ number.
+ pub fn new<'a>(
+ request: IrqRequest<'a>,
+ flags: Flags,
+ name: &'static CStr,
+ handler: T,
+ ) -> impl PinInit<Self, Error> + 'a {
+ try_pin_init!(&this in Self {
+ handler,
+ inner <- Devres::new(
+ request.dev,
+ try_pin_init!(RegistrationInner {
+ // SAFETY: `this` is a valid pointer to the `ThreadedRegistration` instance.
+ cookie: unsafe { &raw mut (*this.as_ptr()).handler }.cast(),
+ irq: {
+ // SAFETY:
+ // - The callbacks are valid for use with request_threaded_irq.
+ // - If this succeeds, the slot is guaranteed to be valid until the
+ // destructor of Self runs, which will deregister the callbacks
+ // before the memory location becomes invalid.
+ to_result(unsafe {
+ bindings::request_threaded_irq(
+ request.irq,
+ Some(handle_threaded_irq_callback::<T>),
+ Some(thread_fn_callback::<T>),
+ flags.into_inner() as usize,
+ name.as_char_ptr(),
+ (&raw mut (*this.as_ptr()).handler).cast(),
+ )
+ })?;
+ request.irq
+ }
+ })
+ ),
+ _pin: PhantomPinned,
+ })
+ }
+
+ /// Returns a reference to the handler that was registered with the system.
+ pub fn handler(&self) -> &T {
+ &self.handler
+ }
+
+ /// Wait for pending IRQ handlers on other CPUs.
+ ///
+ /// This will attempt to access the inner [`Devres`] container.
+ pub fn try_synchronize(&self) -> Result {
+ let inner = self.inner.try_access().ok_or(ENODEV)?;
+ inner.synchronize();
+ Ok(())
+ }
+
+ /// Wait for pending IRQ handlers on other CPUs.
+ pub fn synchronize(&self, dev: &Device<Bound>) -> Result {
+ let inner = self.inner.access(dev)?;
+ inner.synchronize();
+ Ok(())
+ }
+}
+
+/// # Safety
+///
+/// This function should be only used as the callback in `request_threaded_irq`.
+unsafe extern "C" fn handle_threaded_irq_callback<T: ThreadedHandler>(
+ _irq: i32,
+ ptr: *mut core::ffi::c_void,
+) -> core::ffi::c_uint {
+ // SAFETY: `ptr` is a pointer to T set in `ThreadedRegistration::new`
+ let handler = unsafe { &*(ptr as *const T) };
+ T::handle(handler).into_inner()
+}
+
+/// # Safety
+///
+/// This function should be only used as the callback in `request_threaded_irq`.
+unsafe extern "C" fn thread_fn_callback<T: ThreadedHandler>(
+ _irq: i32,
+ ptr: *mut core::ffi::c_void,
+) -> core::ffi::c_uint {
+ // SAFETY: `ptr` is a pointer to T set in `ThreadedRegistration::new`
+ let handler = unsafe { &*(ptr as *const T) };
+ T::handle_threaded(handler).into_inner()
+}

--
2.50.0