1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
//! Abstractions for deferred interrupt tasks, a companion to regular interrupt handlers.
//!
//! Deferred interrupt tasks are similar to the concept of "top half" and "bottom half"
//! interrupt handlers in other OSes, in which the top half is the short, latency-sensitive
//! function that runs immediately when the interrupt request is serviced,
//! while the bottom half is the more complex function that runs in a deferred manner
//! to handle longer operations.
//! Other terminology is often used, including "first-level" and
//! "second-level" interrupt handlers, or "hard" and "soft" interrupt handlers.
//!
//! That being said, this implementation of deferred interrupt tasks
//! differs from both tasklets and workqueues in Linux.
//! We also do not use the "top half" or "bottom half" terminology
//! because it is confusing and difficult to remember which is which.
//! Instead, we refer to the first latency-sensitive part as the
//! *interrupt handler* and the second later part as the *deferred task*.
//! The "interrupt handler" runs immediately (in a synchronous fashion)
//! when the interrupt occurs, while the deferred task runs asynchronously
//! at some time in the future, ideally as soon as possible.
//!
//! The general idea is that an interrupt handler should be short
//! and do the minimum amount of work possible in order to keep
//! the system responsive, because all (or most) other interrupts
//! are typically disabled while the interrupt handler executes to completion.
//! Thus, most of the work should be deferred until later, such that the
//! interrupt handler itself only does a couple of quick things:
//! * Notifies the deferred task that work is ready to be done,
//! optionally providing details about what work it needs to do,
//! * Acknowledges the interrupt such that the hardware knows it was handled.
//!
//! The deferred task is tied directly to a single interrupt handler in a 1-to-1 manner
//! at the time of creation, which occurs in [`register_interrupt_handler()`].
//! Therefore, it is both efficient and easy to use.
//! In the simplest of cases, such as a serial port device, the interrupt handler
//! only needs to mark the deferred task as unblocked (runnable)
//! and then acknowledge the interrupt.
//! No other data exchange is needed between the interrupt handler and the
//! deferred task.
//! For more complicated cases, the interrupt handler may need to do a minimal
//! amount of bookkeeping tasks (such as advancing a ringbuffer index)
//! and potentially send some information about what the deferred task should do.
//! It is typically best to use a lock-free queue or an interrupt-safe mutex
//! to share such information between the interrupt handler and deferred task.
//!
#![no_std]
#![cfg_attr(target_arch = "x86_64", feature(abi_x86_interrupt))]
extern crate alloc;
use log::error;
use debugit::debugit;
use alloc::string::String;
use task::{get_my_current_task, JoinableTaskRef};
use interrupts::{InterruptHandler, InterruptNumber};
/// The errors that may occur in [`register_interrupt_handler()`].
#[derive(Debug)]
pub enum InterruptRegistrationError {
/// The given `irq` number was already in use and is registered to
/// the interrupt handler at the given `existing_handler_address`.
IrqInUse {
irq: InterruptNumber,
existing_handler_address: usize
},
/// The given error occurred when spawning the deferred interrupt task.
SpawnError(&'static str),
}
/// Registers an interrupt handler and spawns a companion "deferred task"
/// that asynchronously handles the longer-running operations related to that interrupt.
///
/// # Arguments
/// * `interrupt_number`: the interrupt number (IRQ vector) that is being requested.
/// * `interrupt_handler`: the handler to be registered,
/// which will be invoked when the interrupt occurs.
/// * `deferred_interrupt_action`: the closure/function callback that will be invoked
/// in an asynchronous manner after the `interrupt_handler` runs. See the below section.
/// * `deferred_action_argument`: the argument that will be passed to the above
/// `deferred_interrupt_action` function.
/// * `deferred_task_name`: the optional name that will be given to the newly-spawned deferred task.
///
/// # How deferred interrupt tasks work
/// This deferred interrupt task spawned and returned by this function
/// is essentially an infinite loop that repeatedly invokes the `deferred_interrupt_action`.
/// The task will put itself to sleep (block itself) in between each invocation,
/// so it is the job of the given `interrupt_handler` to notify/wake up this task
/// when there is work to be done.
/// This design avoids the need for the `deferred_interrupt_action` to manually handle
/// repeated calls in and amongst the sleep/wake behavior.
///
/// It is the caller's responsibility to notify or otherwise wake up the deferred interrupt task
/// in the given `interrupt_handler` (or elsewhere, arbitrarily).
/// WIthout doing this, the `deferred_interrupt_action` will never be invoked.
/// The returned [`JoinableTaskRef`] is useful for doing this, as you can `unblock` it when it needs to run,
/// e.g., when an interrupt has occurred.
///
/// # Return
/// * `Ok(JoinableTaskRef)` if successfully registered, in which the returned task is the
/// long-running loop that repeatedly invokes the given `deferred_interrupt_action`.
/// * `Err(existing_handler_address)` if the given `interrupt_number` was already in use.
pub fn register_interrupt_handler<DIA, Arg, Success, Failure, S>(
interrupt_number: InterruptNumber,
interrupt_handler: InterruptHandler,
deferred_interrupt_action: DIA,
deferred_action_argument: Arg,
deferred_task_name: Option<S>,
) -> Result<JoinableTaskRef, InterruptRegistrationError>
where DIA: Fn(&Arg) -> Result<Success, Failure> + Send + 'static,
Arg: Send + 'static,
S: Into<String>,
{
// First, attempt to register the interrupt handler.
interrupts::register_interrupt(interrupt_number as _, interrupt_handler)
.map_err(|existing_handler_address| {
#[cfg(target_arch = "aarch64")]
let existing_handler_address = existing_handler_address as usize;
error!("Interrupt number {:#X} was already taken by handler at {:#X}! Sharing IRQs is currently unsupported.",
interrupt_number, existing_handler_address
);
InterruptRegistrationError::IrqInUse {
irq: interrupt_number,
existing_handler_address,
}
})?;
// Spawn the deferred task, which should be initially blocked from running.
// It will be unblocked by the interrupt handler whenever it needs to run.
let mut tb = spawn::new_task_builder(
deferred_task_entry_point::<DIA, Arg, Success, Failure>,
(deferred_interrupt_action, deferred_action_argument),
).block();
if let Some(name) = deferred_task_name {
tb = tb.name(name.into());
}
tb.spawn().map_err(InterruptRegistrationError::SpawnError)
}
/// The entry point for a new deferred interrupt task.
///
/// TODO: upon entry, this function should set itself to high priority.
///
/// Note: we could use restartable tasks for this, but the current requirement
/// of the function itself and its arguments being `Clone`able may be overly restrictive.
fn deferred_task_entry_point<DIA, Arg, Success, Failure>(
(deferred_interrupt_action, deferred_action_argument): (DIA, Arg),
) -> !
where DIA: Fn(&Arg) -> Result<Success, Failure>,
Arg: Send + 'static,
{
let curr_task = get_my_current_task().expect("BUG: deferred_task_entry_point: couldn't get current task.");
// trace!("Entered {:?}:\n\t action: {:?}\n\t arg: {:?}",
// curr_task.name, debugit!(deferred_interrupt_action), debugit!(deferred_action_argument)
// );
loop {
let _res = deferred_interrupt_action(&deferred_action_argument);
// Note: here, upon failure, we could return from this loop task entirely instead of just logging the error.
// Or, we could accept a boolean/cfg that determines whether we should bail or continue looping.
match _res {
Ok(_success) => {
// debug!("Deferred interrupt action returned success: {:?}", debugit!(_success));
}
Err(failure) => error!("Deferred interrupt action returned failure: {:?}", debugit!(failure)),
}
if curr_task.block().is_err() {
error!("deferred_task_entry_point: couldn't block {:?}", curr_task);
}
scheduler::schedule();
}
}