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
// SPDX-License-Identifier: MPL-2.0

//! Software interrupt.

#![allow(unused_variables)]

use alloc::boxed::Box;
use core::sync::atomic::{AtomicBool, AtomicU8, Ordering};

use spin::Once;

use crate::{cpu_local, task::disable_preempt, CpuLocal};

/// A representation of a software interrupt (softirq) line.
///
/// # Overview
///
/// Softirq is an interrupt mechanism in the kernel that enables bottom-half processing;
/// they are cheaper to execute compared to regular interrupts because softirqs are less
/// time-critical and thus can be processed in a more flexible manner.
///
/// The `SoftIrqLine` struct encapsulates the data and functionality associated with each
/// softirq line, including an identifier and an associated callback that gets triggered
/// when the softirq is raised.
///
/// The `SoftIrqLine` with the smaller ID has the higher execution priority.
///
/// # Example
///
/// ```
/// // Define an unused softirq id.
/// const MY_SOFTIRQ_ID: u8 = 4;
/// // Enable the softirq line of this id.
/// SoftIrqLine::get(MY_SOFTIRQ_ID).enable(|| {
///     // Define the action to take when the softirq with MY_SOFTIRQ_ID is raised
///     // ...
/// });
/// // Later on:
/// SoftIrqLine::get(MY_SOFTIRQ_ID).raise(); // This will trigger the registered callback
/// ```
pub struct SoftIrqLine {
    id: u8,
    callback: Once<Box<dyn Fn() + 'static + Sync + Send>>,
}

impl SoftIrqLine {
    /// The number of softirq lines.
    const NR_LINES: u8 = 8;

    /// Gets a softirq line.
    ///
    /// The value of `id` must be within `0..NR_LINES`.
    pub fn get(id: u8) -> &'static SoftIrqLine {
        &LINES.get().unwrap()[id as usize]
    }

    const fn new(id: u8) -> Self {
        Self {
            id,
            callback: Once::new(),
        }
    }

    /// Gets the ID of this softirq line.
    pub fn id(&self) -> u8 {
        self.id
    }

    /// Raises the softirq, marking it as pending.
    ///
    /// If this line is not enabled yet, the method has no effect.
    pub fn raise(&self) {
        CpuLocal::borrow_with(&PENDING_MASK, |mask| {
            mask.fetch_or(1 << self.id, Ordering::Release);
        });
    }

    /// Enables a softirq line by registering its callback.
    ///
    /// # Panics
    ///
    /// Each softirq can only be enabled once.
    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);
    }

    /// Returns whether this softirq line is enabled.
    pub fn is_enabled(&self) -> bool {
        ENABLED_MASK.load(Ordering::Acquire) & (1 << self.id) != 0
    }
}

/// A slice that stores the [`SoftIrqLine`]s, whose ID is equal to its offset in the slice.
static LINES: Once<[SoftIrqLine; SoftIrqLine::NR_LINES as usize]> = Once::new();

pub(super) fn init() {
    let lines: [SoftIrqLine; SoftIrqLine::NR_LINES as usize] =
        array_init::array_init(|i| SoftIrqLine::new(i as u8));
    LINES.call_once(|| lines);
}

static ENABLED_MASK: AtomicU8 = AtomicU8::new(0);

cpu_local! {
    static PENDING_MASK: AtomicU8 = AtomicU8::new(0);
    static IS_ENABLED: AtomicBool = AtomicBool::new(true);
}

/// Enables softirq in current processor.
fn enable_softirq_local() {
    CpuLocal::borrow_with(&IS_ENABLED, |is_enabled| {
        is_enabled.store(true, Ordering::Release)
    })
}

/// Disables softirq in current processor.
fn disable_softirq_local() {
    CpuLocal::borrow_with(&IS_ENABLED, |is_enabled| {
        is_enabled.store(false, Ordering::Release)
    })
}

/// Checks whether the softirq is enabled in current processor.
fn is_softirq_enabled() -> bool {
    CpuLocal::borrow_with(&IS_ENABLED, |is_enabled| is_enabled.load(Ordering::Acquire))
}

/// Processes pending softirqs.
///
/// The processing instructions will iterate for `SOFTIRQ_RUN_TIMES` times. If any softirq
/// is raised during the iteration, it will be processed.
pub(crate) fn process_pending() {
    const SOFTIRQ_RUN_TIMES: u8 = 5;

    if !is_softirq_enabled() {
        return;
    }

    let preempt_guard = disable_preempt();
    disable_softirq_local();

    CpuLocal::borrow_with(&PENDING_MASK, |mask| {
        for i in 0..SOFTIRQ_RUN_TIMES {
            // will not reactive in this handling.
            let mut action_mask = {
                let pending_mask = mask.fetch_and(0, Ordering::Acquire);
                pending_mask & ENABLED_MASK.load(Ordering::Acquire)
            };

            if action_mask == 0 {
                return;
            }
            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();
}