use alloc::boxed::Box;
use core::sync::atomic::{AtomicU8, Ordering};
use spin::Once;
use crate::{cpu_local_cell, task::disable_preempt};
pub struct SoftIrqLine {
id: u8,
callback: Once<Box<dyn Fn() + 'static + Sync + Send>>,
}
impl SoftIrqLine {
const NR_LINES: u8 = 8;
pub fn get(id: u8) -> &'static SoftIrqLine {
&LINES.get().unwrap()[id as usize]
}
const fn new(id: u8) -> Self {
Self {
id,
callback: Once::new(),
}
}
pub fn id(&self) -> u8 {
self.id
}
pub fn raise(&self) {
PENDING_MASK.bitor_assign(1 << self.id);
}
pub fn enable<F>(&self, callback: F)
where
F: Fn() + 'static + Sync + Send,
{
assert!(!self.is_enabled());
self.callback.call_once(|| Box::new(callback));
ENABLED_MASK.fetch_or(1 << self.id, Ordering::Release);
}
pub fn is_enabled(&self) -> bool {
ENABLED_MASK.load(Ordering::Acquire) & (1 << self.id) != 0
}
}
static LINES: Once<[SoftIrqLine; SoftIrqLine::NR_LINES as usize]> = Once::new();
pub unsafe fn init() {
let lines: [SoftIrqLine; SoftIrqLine::NR_LINES as usize] =
core::array::from_fn(|i| SoftIrqLine::new(i as u8));
LINES.call_once(|| lines);
}
static ENABLED_MASK: AtomicU8 = AtomicU8::new(0);
cpu_local_cell! {
static PENDING_MASK: u8 = 0;
static IS_ENABLED: bool = true;
}
fn enable_softirq_local() {
IS_ENABLED.store(true);
}
fn disable_softirq_local() {
IS_ENABLED.store(false);
}
fn is_softirq_enabled() -> bool {
IS_ENABLED.load()
}
pub(crate) fn process_pending() {
const SOFTIRQ_RUN_TIMES: u8 = 5;
if !is_softirq_enabled() {
return;
}
let _preempt_guard = disable_preempt();
disable_softirq_local();
for _i in 0..SOFTIRQ_RUN_TIMES {
let mut action_mask = {
let pending_mask = PENDING_MASK.load();
PENDING_MASK.store(0);
pending_mask & ENABLED_MASK.load(Ordering::Acquire)
};
if action_mask == 0 {
break;
}
while action_mask > 0 {
let action_id = u8::trailing_zeros(action_mask) as u8;
SoftIrqLine::get(action_id).callback.get().unwrap()();
action_mask &= action_mask - 1;
}
}
enable_softirq_local();
}