summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndreas Hindborg <a.hindborg@samsung.com>2023-08-28 16:08:10 +0200
committerDanilo Krummrich <dakr@redhat.com>2023-11-22 22:00:54 +0100
commit24e0ea9eeb1a5a5983fae0a6a468665b1e05c04d (patch)
treece68342dfbdc57cb7993fe3f9297680e4d8132e7
parenta19ee862eca76a1727c81310eb9074872d4a6691 (diff)
rust: add irq abstractions
Based on irq.rs from https://github.com/Rust-for-Linux/linux/commit/575b1c015f5fdaac4b8b85ba3d579c324c3c2f41
-rw-r--r--rust/bindings/bindings_helper.h2
-rw-r--r--rust/kernel/irq.rs216
-rw-r--r--rust/kernel/lib.rs1
3 files changed, 219 insertions, 0 deletions
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 076a6af14395..5da948cadf2b 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -9,6 +9,8 @@
#include <kunit/test.h>
#include <linux/device.h>
#include <linux/errname.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/refcount.h>
#include <linux/wait.h>
diff --git a/rust/kernel/irq.rs b/rust/kernel/irq.rs
new file mode 100644
index 000000000000..2e369c18ecb9
--- /dev/null
+++ b/rust/kernel/irq.rs
@@ -0,0 +1,216 @@
+use crate::{
+ error::to_result,
+ error::Result,
+ str::CString,
+ types::{ForeignOwnable, ScopeGuard},
+};
+use core::fmt;
+use core::marker::PhantomData;
+
+struct InternalRegistration<T: ForeignOwnable> {
+ irq: u32,
+ data: *mut core::ffi::c_void,
+ name: CString,
+ _p: PhantomData<T>,
+}
+
+impl<T: ForeignOwnable> InternalRegistration<T> {
+ /// Registers a new irq handler.
+ ///
+ /// # Safety
+ ///
+ /// Callers must ensure that `handler` and `thread_fn` are compatible with the registration,
+ /// that is, that they only use their second argument while the call is happening and that they
+ /// only call [`T::borrow`] on it (e.g., they shouldn't call [`T::from_foreign`] and consume
+ /// it).
+ unsafe fn try_new(
+ irq: core::ffi::c_uint,
+ handler: bindings::irq_handler_t,
+ thread_fn: bindings::irq_handler_t,
+ flags: usize,
+ data: T,
+ name: fmt::Arguments<'_>,
+ ) -> Result<Self> {
+ let ptr = data.into_foreign() as *mut _;
+ let name = CString::try_from_fmt(name)?;
+ let guard = ScopeGuard::new(|| {
+ // SAFETY: `ptr` came from a previous call to `into_foreign`.
+ unsafe { T::from_foreign(ptr) };
+ });
+ // SAFETY: `name` and `ptr` remain valid as long as the registration is alive.
+ to_result(unsafe {
+ bindings::request_threaded_irq(
+ irq,
+ handler,
+ thread_fn,
+ flags as _,
+ name.as_char_ptr(),
+ ptr,
+ )
+ })?;
+ guard.dismiss();
+ Ok(Self {
+ irq,
+ name,
+ data: ptr,
+ _p: PhantomData,
+ })
+ }
+}
+
+impl<T: ForeignOwnable> Drop for InternalRegistration<T> {
+ fn drop(&mut self) {
+ // Unregister irq handler.
+ //
+ // SAFETY: When `try_new` succeeds, the irq was successfully requested, so it is ok to free
+ // it here.
+ unsafe { bindings::free_irq(self.irq, self.data) };
+
+ // Free context data.
+ //
+ // SAFETY: This matches the call to `into_foreign` from `try_new` in the success case.
+ unsafe { T::from_foreign(self.data) };
+ }
+}
+
+/// An irq handler.
+pub trait Handler {
+ /// The context data associated with and made available to the handler.
+ type Data: ForeignOwnable;
+
+ /// Called from interrupt context when the irq happens.
+ fn handle_irq(data: <Self::Data as ForeignOwnable>::Borrowed<'_>) -> Return;
+}
+
+/// The registration of an interrupt handler.
+///
+/// # Examples
+///
+/// The following is an example of a regular handler with a boxed `u32` as data.
+///
+/// ```
+/// # use kernel::prelude::*;
+/// use kernel::irq;
+///
+/// struct Example;
+///
+/// impl irq::Handler for Example {
+/// type Data = Box<u32>;
+///
+/// fn handle_irq(_data: &u32) -> irq::Return {
+/// irq::Return::None
+/// }
+/// }
+///
+/// fn request_irq(irq: u32, data: Box<u32>) -> Result<irq::Registration<Example>> {
+/// irq::Registration::try_new(irq, data, irq::flags::SHARED, fmt!("example_{irq}"))
+/// }
+/// ```
+pub struct Registration<H: Handler>(InternalRegistration<H::Data>);
+
+impl<H: Handler> Registration<H> {
+ /// Registers a new irq handler.
+ ///
+ /// The valid values of `flags` come from the [`flags`] module.
+ pub fn try_new(
+ irq: u32,
+ data: H::Data,
+ flags: usize,
+ name: fmt::Arguments<'_>,
+ ) -> Result<Self> {
+ // SAFETY: `handler` only calls `H::Data::borrow` on `raw_data`.
+ Ok(Self(unsafe {
+ InternalRegistration::try_new(irq, Some(Self::handler), None, flags, data, name)?
+ }))
+ }
+
+ unsafe extern "C" fn handler(
+ _irq: core::ffi::c_int,
+ raw_data: *mut core::ffi::c_void,
+ ) -> bindings::irqreturn_t {
+ // SAFETY: On registration, `into_foreign` was called, so it is safe to borrow from it here
+ // because `from_foreign` is called only after the irq is unregistered.
+ let data = unsafe { H::Data::borrow(raw_data) };
+ H::handle_irq(data) as _
+ }
+}
+
+/// The return value from interrupt handlers.
+pub enum Return {
+ /// The interrupt was not from this device or was not handled.
+ None = bindings::irqreturn_IRQ_NONE as _,
+
+ /// The interrupt was handled by this device.
+ Handled = bindings::irqreturn_IRQ_HANDLED as _,
+
+ /// The handler wants the handler thread to wake up.
+ WakeThread = bindings::irqreturn_IRQ_WAKE_THREAD as _,
+}
+
+/// Container for interrupt flags.
+pub mod flags {
+ use crate::bindings;
+
+ /// Use the interrupt line as already configured.
+ pub const TRIGGER_NONE: usize = bindings::IRQF_TRIGGER_NONE as _;
+
+ /// The interrupt is triggered when the signal goes from low to high.
+ pub const TRIGGER_RISING: usize = bindings::IRQF_TRIGGER_RISING as _;
+
+ /// The interrupt is triggered when the signal goes from high to low.
+ pub const TRIGGER_FALLING: usize = bindings::IRQF_TRIGGER_FALLING as _;
+
+ /// The interrupt is triggered while the signal is held high.
+ pub const TRIGGER_HIGH: usize = bindings::IRQF_TRIGGER_HIGH as _;
+
+ /// The interrupt is triggered while the signal is held low.
+ pub const TRIGGER_LOW: usize = bindings::IRQF_TRIGGER_LOW as _;
+
+ /// Allow sharing the irq among several devices.
+ pub const SHARED: usize = bindings::IRQF_SHARED as _;
+
+ /// Set by callers when they expect sharing mismatches to occur.
+ pub const PROBE_SHARED: usize = bindings::IRQF_PROBE_SHARED as _;
+
+ /// Flag to mark this interrupt as timer interrupt.
+ pub const TIMER: usize = bindings::IRQF_TIMER as _;
+
+ /// Interrupt is per cpu.
+ pub const PERCPU: usize = bindings::IRQF_PERCPU as _;
+
+ /// Flag to exclude this interrupt from irq balancing.
+ pub const NOBALANCING: usize = bindings::IRQF_NOBALANCING as _;
+
+ /// Interrupt is used for polling (only the interrupt that is registered first in a shared
+ /// interrupt is considered for performance reasons).
+ pub const IRQPOLL: usize = bindings::IRQF_IRQPOLL as _;
+
+ /// Interrupt is not reenabled after the hardirq handler finished. Used by threaded interrupts
+ /// which need to keep the irq line disabled until the threaded handler has been run.
+ pub const ONESHOT: usize = bindings::IRQF_ONESHOT as _;
+
+ /// Do not disable this IRQ during suspend. Does not guarantee that this interrupt will wake
+ /// the system from a suspended state.
+ pub const NO_SUSPEND: usize = bindings::IRQF_NO_SUSPEND as _;
+
+ /// Force enable it on resume even if [`NO_SUSPEND`] is set.
+ pub const FORCE_RESUME: usize = bindings::IRQF_FORCE_RESUME as _;
+
+ /// Interrupt cannot be threaded.
+ pub const NO_THREAD: usize = bindings::IRQF_NO_THREAD as _;
+
+ /// Resume IRQ early during syscore instead of at device resume time.
+ pub const EARLY_RESUME: usize = bindings::IRQF_EARLY_RESUME as _;
+
+ /// If the IRQ is shared with a NO_SUSPEND user, execute this interrupt handler after
+ /// suspending interrupts. For system wakeup devices users need to implement wakeup detection
+ /// in their interrupt handlers.
+ pub const COND_SUSPEND: usize = bindings::IRQF_COND_SUSPEND as _;
+
+ /// Don't enable IRQ or NMI automatically when users request it. Users will enable it
+ /// explicitly by `enable_irq` or `enable_nmi` later.
+ pub const NO_AUTOEN: usize = bindings::IRQF_NO_AUTOEN as _;
+
+ /// Exclude from runnaway detection for IPI and similar handlers, depends on `PERCPU`.
+ pub const NO_DEBUG: usize = bindings::IRQF_NO_DEBUG as _;
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index c0c617ab0601..976d1e2084b9 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -38,6 +38,7 @@ pub mod driver;
pub mod error;
pub mod init;
pub mod ioctl;
+pub mod irq;
#[cfg(CONFIG_KUNIT)]
pub mod kunit;
pub mod pages;