irq-framework 0.4.0

A no_std dynamic IRQ registration and dispatch framework
Documentation
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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
use alloc::boxed::Box;

/// An IRQ controller domain id.
#[repr(transparent)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct IrqDomainId(pub u16);

/// Hardware interrupt line number within an IRQ domain.
#[repr(transparent)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct HwIrq(pub u32);

/// A framework IRQ id, scoped by controller domain.
#[repr(C)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct IrqId {
    /// IRQ controller domain.
    pub domain: IrqDomainId,
    /// Hardware interrupt line within the domain.
    pub hwirq: HwIrq,
}

impl IrqId {
    /// Creates an IRQ id from a domain and hardware line.
    pub const fn new(domain: IrqDomainId, hwirq: HwIrq) -> Self {
        Self { domain, hwirq }
    }
}

/// CPU trap vector observed at the architecture trap boundary.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct TrapVector(pub usize);

/// A firmware or controller interrupt source that can be resolved to [`IrqId`].
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum IrqSource {
    /// ACPI Global System Interrupt.
    AcpiGsi(u32),
    /// ACPI Global System Interrupt with explicit routing metadata.
    AcpiGsiRoute(AcpiGsiRoute),
    /// Explicit controller-domain line.
    ControllerLine {
        /// IRQ controller domain.
        domain: IrqDomainId,
        /// Hardware interrupt line within the domain.
        hwirq: HwIrq,
    },
}

/// Controller interrupt trigger configuration.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum IrqTrigger {
    /// Edge-triggered interrupt.
    Edge,
    /// Level-triggered interrupt.
    Level,
}

/// ACPI IRQ trigger configuration.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AcpiIrqTrigger {
    /// Edge-triggered interrupt.
    Edge,
    /// Level-triggered interrupt.
    Level,
}

/// ACPI IRQ polarity configuration.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AcpiIrqPolarity {
    /// Active-high interrupt.
    ActiveHigh,
    /// Active-low interrupt.
    ActiveLow,
}

/// ACPI GSI controller kind.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AcpiGsiController {
    /// I/O APIC controller.
    IoApic,
    /// LoongArch PCH-PIC controller.
    PchPic,
}

/// Fully described ACPI GSI routing metadata.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct AcpiGsiRoute {
    /// Global System Interrupt number.
    pub gsi: u32,
    /// CPU trap vector programmed by the platform controller, if known.
    pub vector: usize,
    /// Controller kind.
    pub controller: AcpiGsiController,
    /// ACPI controller id.
    pub controller_id: u16,
    /// Controller MMIO base address.
    pub controller_address: u64,
    /// Controller-local input line.
    pub controller_input: u8,
    /// Trigger configuration.
    pub trigger: AcpiIrqTrigger,
    /// Polarity configuration.
    pub polarity: AcpiIrqPolarity,
}

/// A logical CPU id.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct CpuId(pub usize);

/// A compact CPU mask for low-level IRQ affinity.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct CpuMask {
    bits: u128,
}

impl CpuMask {
    /// Creates an empty CPU mask.
    pub const fn empty() -> Self {
        Self { bits: 0 }
    }

    /// Creates a CPU mask containing a single CPU.
    pub fn from_cpu(cpu: CpuId) -> Self {
        let mut mask = Self::empty();
        mask.insert(cpu);
        mask
    }

    /// Creates a CPU mask containing CPUs in the range `0..cpu_count`.
    pub fn first_n(cpu_count: usize) -> Self {
        let mut mask = Self::empty();
        let end = cpu_count.min(u128::BITS as usize);
        for cpu in 0..end {
            mask.insert(CpuId(cpu));
        }
        mask
    }

    /// Adds a CPU to this mask.
    pub fn insert(&mut self, cpu: CpuId) {
        if cpu.0 < u128::BITS as usize {
            self.bits |= 1u128 << cpu.0;
        }
    }

    /// Removes a CPU from this mask.
    pub fn remove(&mut self, cpu: CpuId) {
        if cpu.0 < u128::BITS as usize {
            self.bits &= !(1u128 << cpu.0);
        }
    }

    /// Returns whether the CPU is present in this mask.
    pub const fn contains(self, cpu: CpuId) -> bool {
        cpu.0 < u128::BITS as usize && (self.bits & (1u128 << cpu.0)) != 0
    }

    /// Returns whether no CPU is present in this mask.
    pub const fn is_empty(self) -> bool {
        self.bits == 0
    }

    /// Iterates over the CPUs in this mask.
    pub fn iter(self) -> CpuMaskIter {
        CpuMaskIter { bits: self.bits }
    }
}

/// Iterator over [`CpuMask`].
pub struct CpuMaskIter {
    bits: u128,
}

impl Iterator for CpuMaskIter {
    type Item = CpuId;

    fn next(&mut self) -> Option<Self::Item> {
        if self.bits == 0 {
            return None;
        }
        let cpu = self.bits.trailing_zeros() as usize;
        self.bits &= !(1u128 << cpu);
        Some(CpuId(cpu))
    }
}

/// IRQ registration scope.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum IrqScope {
    /// The action is visible on every CPU.
    Global,
    /// The action is CPU-local and only visible to matching CPUs.
    PerCpu {
        /// Target CPUs.
        cpus: CpuMask,
    },
}

/// Hardware routing preference for an IRQ line.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum IrqAffinity {
    /// The platform may route the line to any CPU.
    Any,
    /// Route the line to one fixed logical CPU.
    Fixed(CpuId),
}

/// Execution contract for an IRQ action.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum IrqExecution {
    /// The handler may run concurrently if the controller delivers it that way.
    Concurrent,
    /// The framework prevents nested/concurrent calls to this action.
    NonReentrant,
}

/// Whether an IRQ line is exclusive or shared.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ShareMode {
    /// No other action can share the IRQ.
    Exclusive,
    /// Multiple actions can share the IRQ.
    Shared,
}

/// Whether an IRQ action should be enabled after registration.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AutoEnable {
    /// Register the action but leave it disabled.
    No,
    /// Enable the action after registration.
    Yes,
}

/// Return value from a raw IRQ handler.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum IrqReturn {
    /// This action did not handle the IRQ.
    Unhandled,
    /// This action handled the IRQ.
    Handled,
    /// This action handled the IRQ and asks the OS adapter to wake deferred work.
    Wake,
}

/// Aggregated dispatch result.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct IrqOutcome {
    /// At least one action handled the IRQ.
    pub handled: bool,
    /// At least one action requested a wakeup.
    pub wake: bool,
    /// Number of handlers called by this dispatch.
    pub called: usize,
}

/// IRQ status snapshot.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct IrqStatus {
    /// Whether this action is enabled in the framework.
    pub action_enabled: bool,
    /// Whether the platform line is enabled.
    pub line_enabled: bool,
    /// Whether the platform reports the IRQ pending.
    pub pending: bool,
    /// Whether the platform reports the IRQ in service.
    pub in_service: bool,
    /// Number of in-flight dispatches for this descriptor.
    pub in_flight: usize,
    /// Whether this action is currently running.
    pub action_running: bool,
}

/// IRQ framework errors.
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
pub enum IrqError {
    /// Invalid IRQ number.
    #[error("invalid IRQ number")]
    InvalidIrq,
    /// Invalid CPU id.
    #[error("invalid CPU id")]
    InvalidCpu,
    /// The target CPU is offline.
    #[error("target CPU is offline")]
    CpuOffline,
    /// A synchronous IRQ operation timed out.
    #[error("IRQ operation timed out")]
    Timeout,
    /// IRQ line/action sharing rules reject the operation.
    #[error("IRQ line is busy")]
    Busy,
    /// Allocation failed.
    #[error("IRQ allocation failed")]
    NoMemory,
    /// The requested descriptor or action does not exist.
    #[error("IRQ descriptor or action was not found")]
    NotFound,
    /// This operation is not legal from IRQ context.
    #[error("operation is not legal from IRQ context")]
    InIrqContext,
    /// The platform adapter does not support this operation.
    #[error("IRQ operation is not supported")]
    Unsupported,
    /// The platform controller reported an error.
    #[error("interrupt controller failed")]
    Controller,
}

/// Context passed to IRQ handlers.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct IrqContext {
    /// IRQ number being dispatched.
    pub irq: IrqId,
    /// CPU handling the IRQ.
    pub cpu: CpuId,
}

/// Boxed IRQ handler ABI.
pub type BoxedIrqHandler = Box<dyn FnMut(IrqContext) -> IrqReturn + Send + 'static>;

/// Boxed IRQ handler ABI for callbacks that may run concurrently.
pub type ConcurrentBoxedIrqHandler = Box<dyn Fn(IrqContext) -> IrqReturn + Send + Sync + 'static>;

pub(crate) enum IrqHandler {
    NonReentrant(BoxedIrqHandler),
    Concurrent(ConcurrentBoxedIrqHandler),
}

/// External capabilities supplied by the OS/platform adapter.
pub trait IrqOps {
    /// Saved local IRQ state.
    type LocalIrqState: Copy;

    /// Returns the current CPU.
    fn current_cpu(&self) -> CpuId;

    /// Returns whether the CPU is online.
    fn cpu_online(&self, cpu: CpuId) -> bool;

    /// Returns whether the current execution context is an IRQ context.
    fn in_irq_context(&self) -> bool;

    /// Saves and disables local IRQs for metadata lock acquisition.
    fn local_irq_save(&self) -> Self::LocalIrqState;

    /// Restores local IRQ state saved by [`IrqOps::local_irq_save`].
    fn local_irq_restore(&self, state: Self::LocalIrqState);

    /// Runs a thunk synchronously on the target CPU.
    fn run_on_cpu_sync(
        &self,
        cpu: CpuId,
        f: unsafe fn(*mut ()),
        arg: *mut (),
    ) -> Result<(), IrqError>;

    /// Routes a global IRQ line to the requested CPU affinity.
    fn set_affinity(&self, _irq: IrqId, _affinity: IrqAffinity) -> Result<(), IrqError> {
        Err(IrqError::Unsupported)
    }

    /// Enables or disables an IRQ line.
    fn set_enabled(&self, irq: IrqId, cpu: Option<CpuId>, enabled: bool) -> Result<(), IrqError>;

    /// Returns whether the IRQ line is enabled.
    fn is_enabled(&self, irq: IrqId, cpu: Option<CpuId>) -> Result<bool, IrqError>;

    /// Returns whether the IRQ line is pending.
    fn is_pending(&self, irq: IrqId, cpu: Option<CpuId>) -> Result<bool, IrqError>;

    /// Returns whether the IRQ line is in service.
    fn is_in_service(&self, irq: IrqId, cpu: Option<CpuId>) -> Result<bool, IrqError>;

    /// Relaxes a spin wait.
    fn relax(&self);
}

/// Request parameters for an IRQ action.
pub struct IrqRequest {
    pub(crate) handler: Option<IrqHandler>,
    pub(crate) scope: IrqScope,
    pub(crate) affinity: IrqAffinity,
    pub(crate) execution: IrqExecution,
    pub(crate) share_mode: ShareMode,
    pub(crate) auto_enable: AutoEnable,
}

impl IrqRequest {
    /// Creates a new exclusive, global, auto-enabled IRQ request.
    pub fn new(handler: impl FnMut(IrqContext) -> IrqReturn + Send + 'static) -> Self {
        Self {
            handler: Some(IrqHandler::NonReentrant(Box::new(handler))),
            scope: IrqScope::Global,
            affinity: IrqAffinity::Any,
            execution: IrqExecution::NonReentrant,
            share_mode: ShareMode::Exclusive,
            auto_enable: AutoEnable::Yes,
        }
    }

    /// Creates a new exclusive, global, auto-enabled concurrent IRQ request.
    pub fn new_concurrent(
        handler: impl Fn(IrqContext) -> IrqReturn + Send + Sync + 'static,
    ) -> Self {
        Self {
            handler: Some(IrqHandler::Concurrent(Box::new(handler))),
            scope: IrqScope::Global,
            affinity: IrqAffinity::Any,
            execution: IrqExecution::Concurrent,
            share_mode: ShareMode::Exclusive,
            auto_enable: AutoEnable::Yes,
        }
    }

    pub(crate) fn supports_concurrent(&self) -> bool {
        matches!(self.handler.as_ref(), Some(IrqHandler::Concurrent(_)))
    }

    /// Sets the IRQ scope.
    pub fn scope(mut self, scope: IrqScope) -> Self {
        self.scope = scope;
        self
    }

    /// Sets the IRQ affinity.
    pub fn affinity(mut self, affinity: IrqAffinity) -> Self {
        self.affinity = affinity;
        self
    }

    /// Sets the action execution contract.
    pub fn execution(mut self, execution: IrqExecution) -> Self {
        self.execution = execution;
        self
    }

    /// Sets the sharing mode.
    pub fn share_mode(mut self, share_mode: ShareMode) -> Self {
        self.share_mode = share_mode;
        self
    }

    /// Sets whether the action should be enabled after request.
    pub fn auto_enable(mut self, auto_enable: AutoEnable) -> Self {
        self.auto_enable = auto_enable;
        self
    }

    /// Returns whether the action should be enabled after request.
    pub const fn auto_enable_mode(&self) -> AutoEnable {
        self.auto_enable
    }
}

/// Token returned from request and used for later lifecycle operations.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct IrqHandle {
    pub(crate) irq: IrqId,
    pub(crate) id: u64,
}

impl IrqHandle {
    /// Returns the IRQ number associated with this handle.
    pub const fn irq(self) -> IrqId {
        self.irq
    }

    /// Returns the framework-local action id.
    pub const fn id(self) -> u64 {
        self.id
    }
}