Skip to content

Commit

Permalink
Merge pull request raspberrypi#809 from wedsonaf/irq
Browse files Browse the repository at this point in the history
rust: irq: add support for handling interrupts
  • Loading branch information
wedsonaf authored Jul 5, 2022
2 parents a8819ab + f471bf5 commit 1ae6be8
Showing 1 changed file with 303 additions and 2 deletions.
305 changes: 303 additions & 2 deletions rust/kernel/irq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@
#![allow(dead_code)]

use crate::{bindings, error::from_kernel_result, types::PointerWrapper, Error, Result};
use core::ops::Deref;
use crate::{
bindings,
error::{from_kernel_result, to_result},
str::CString,
types::PointerWrapper,
Error, Result, ScopeGuard,
};
use core::{fmt, marker::PhantomData, ops::Deref};

/// The type of irq hardware numbers.
pub type HwNumber = bindings::irq_hw_number_t;
Expand Down Expand Up @@ -329,6 +335,301 @@ impl Descriptor {
}
}

struct InternalRegistration<T: PointerWrapper> {
irq: u32,
data: *mut core::ffi::c_void,
name: CString,
_p: PhantomData<T>,
}

impl<T: PointerWrapper> 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_pointer`] 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_pointer() as *mut _;
let name = CString::try_from_fmt(name)?;
let guard = ScopeGuard::new(|| {
// SAFETY: `ptr` came from a previous call to `into_pointer`.
unsafe { T::from_pointer(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: PointerWrapper> 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_pointer` from `try_new` in the success case.
unsafe { T::from_pointer(self.data) };
}
}

/// An irq handler.
pub trait Handler {
/// The context data associated with and made available to the handler.
type Data: PointerWrapper;

/// Called from interrupt context when the irq happens.
fn handle_irq(data: <Self::Data as PointerWrapper>::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_pointer` was called, so it is safe to borrow from it here
// because `from_pointer` is called only after the irq is unregistered.
let data = unsafe { H::Data::borrow(raw_data) };
H::handle_irq(data) as _
}
}

/// A threaded irq handler.
pub trait ThreadedHandler {
/// The context data associated with and made available to the handlers.
type Data: PointerWrapper;

/// Called from interrupt context when the irq first happens.
fn handle_primary_irq(_data: <Self::Data as PointerWrapper>::Borrowed<'_>) -> Return {
Return::WakeThread
}

/// Called from the handler thread.
fn handle_threaded_irq(data: <Self::Data as PointerWrapper>::Borrowed<'_>) -> Return;
}

/// The registration of a threaded interrupt handler.
///
/// # Examples
///
/// The following is an example of a threaded handler with a ref-counted u32 as data:
///
/// ```
/// # use kernel::prelude::*;
/// use kernel::{irq, sync::{Ref, RefBorrow}};
///
/// struct Example;
///
/// impl irq::ThreadedHandler for Example {
/// type Data = Ref<u32>;
///
/// fn handle_threaded_irq(_data: RefBorrow<'_, u32>) -> irq::Return {
/// irq::Return::None
/// }
/// }
///
/// fn request_irq(irq: u32, data: Ref<u32>) -> Result<irq::ThreadedRegistration<Example>> {
/// irq::ThreadedRegistration::try_new(
/// irq, data, irq::flags::SHARED, fmt!("example_{irq}"))
/// }
/// ```
pub struct ThreadedRegistration<H: ThreadedHandler>(InternalRegistration<H::Data>);

impl<H: ThreadedHandler> ThreadedRegistration<H> {
/// Registers a new threaded 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: both `primary_handler` and `threaded_handler` only call `H::Data::borrow` on
// `raw_data`.
Ok(Self(unsafe {
InternalRegistration::try_new(
irq,
Some(Self::primary_handler),
Some(Self::threaded_handler),
flags,
data,
name,
)?
}))
}

unsafe extern "C" fn primary_handler(
_irq: core::ffi::c_int,
raw_data: *mut core::ffi::c_void,
) -> bindings::irqreturn_t {
// SAFETY: On registration, `into_pointer` was called, so it is safe to borrow from it here
// because `from_pointer` is called only after the irq is unregistered.
let data = unsafe { H::Data::borrow(raw_data) };
H::handle_primary_irq(data) as _
}

unsafe extern "C" fn threaded_handler(
_irq: core::ffi::c_int,
raw_data: *mut core::ffi::c_void,
) -> bindings::irqreturn_t {
// SAFETY: On registration, `into_pointer` was called, so it is safe to borrow from it here
// because `from_pointer` is called only after the irq is unregistered.
let data = unsafe { H::Data::borrow(raw_data) };
H::handle_threaded_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 _;
}

/// A guard to call `chained_irq_exit` after `chained_irq_enter` was called.
///
/// It is also used as evidence that a previous `chained_irq_enter` was called. So there are no
Expand Down

0 comments on commit 1ae6be8

Please sign in to comment.