Skip to main content

ax_runtime/
irq.rs

1#[cfg(feature = "net")]
2use alloc::string::String;
3
4#[cfg(any(
5    target_arch = "loongarch64",
6    target_arch = "riscv64",
7    target_arch = "x86_64",
8))]
9use ax_hal::irq::CPU_LOCAL_IRQ_DOMAIN;
10#[cfg(any(
11    target_arch = "aarch64",
12    target_arch = "loongarch64",
13    target_arch = "riscv64",
14    target_arch = "x86_64",
15))]
16use ax_hal::irq::HwIrq;
17#[cfg(feature = "net")]
18use ax_hal::irq::IrqHandle;
19use ax_hal::irq::{IrqError, IrqId, IrqSource};
20
21/// Resolves an explicitly legacy numeric IRQ without truncating it.
22pub fn resolve_legacy_irq(irq: usize) -> Result<IrqId, IrqError> {
23    ax_hal::irq::try_legacy_irq(irq)
24}
25
26/// Resolves a discovered device IRQ binding through the platform IRQ domain.
27pub fn resolve_binding_irq(irq: ax_driver::BindingIrq) -> Result<IrqId, IrqError> {
28    if let Some(id) = irq.irq_id() {
29        return Ok(id);
30    }
31
32    match irq {
33        ax_driver::BindingIrq::Id(id) => Ok(id),
34        ax_driver::BindingIrq::Source(source) => resolve_binding_irq_source(source),
35    }
36}
37
38fn resolve_binding_irq_source(source: ax_driver::BindingIrqSource) -> Result<IrqId, IrqError> {
39    match source {
40        ax_driver::BindingIrqSource::AcpiGsi(gsi) => {
41            ax_hal::irq::resolve_irq_source(IrqSource::AcpiGsi(gsi))
42        }
43        ax_driver::BindingIrqSource::AcpiGsiRoute(route) => {
44            ax_hal::irq::resolve_irq_source(IrqSource::AcpiGsiRoute(route))
45        }
46        ax_driver::BindingIrqSource::FdtInterrupt(spec) => resolve_fdt_irq_spec(spec),
47    }
48}
49
50#[cfg(any(
51    target_arch = "aarch64",
52    target_arch = "loongarch64",
53    target_arch = "riscv64"
54))]
55fn resolve_fdt_irq_spec(spec: ax_driver::FdtIrqSpec) -> Result<IrqId, IrqError> {
56    let mut intc = rdrive::get::<rdif_intc::Intc>(spec.controller)
57        .map_err(|_| IrqError::Unsupported)?
58        .lock()
59        .map_err(|_| IrqError::Controller)?;
60    let translation = intc.translate_fdt(&spec.cells)?;
61    intc.configure(&translation)?;
62    Ok(translation.id)
63}
64
65#[cfg(not(any(
66    target_arch = "aarch64",
67    target_arch = "loongarch64",
68    target_arch = "riscv64"
69)))]
70fn resolve_fdt_irq_spec(_spec: ax_driver::FdtIrqSpec) -> Result<IrqId, IrqError> {
71    Err(IrqError::Unsupported)
72}
73
74/// Resolves a per-CPU trap IRQ through the platform IRQ domain.
75#[cfg(target_arch = "aarch64")]
76pub fn resolve_percpu_irq(irq: usize) -> IrqId {
77    let hwirq = HwIrq(u32::try_from(irq).expect("AArch64 per-CPU IRQ exceeds GIC INTID width"));
78    ax_hal::irq::resolve_percpu_irq(hwirq).expect("AArch64 per-CPU IRQ domain is not registered")
79}
80
81/// Resolves a per-CPU trap IRQ through the platform IRQ domain.
82#[cfg(any(target_arch = "loongarch64", target_arch = "x86_64"))]
83pub fn resolve_percpu_irq(irq: usize) -> IrqId {
84    IrqId::new(CPU_LOCAL_IRQ_DOMAIN, HwIrq(irq as u32))
85}
86
87/// Resolves a per-CPU trap IRQ through the platform IRQ domain.
88#[cfg(not(any(
89    target_arch = "aarch64",
90    target_arch = "loongarch64",
91    target_arch = "x86_64"
92)))]
93pub fn resolve_percpu_irq(irq: usize) -> IrqId {
94    #[cfg(target_arch = "riscv64")]
95    {
96        const RISCV_INTERRUPT_BIT: usize = 1usize << (usize::BITS as usize - 1);
97
98        if irq & RISCV_INTERRUPT_BIT != 0 {
99            return IrqId::new(
100                CPU_LOCAL_IRQ_DOMAIN,
101                HwIrq((irq & !RISCV_INTERRUPT_BIT) as u32),
102            );
103        }
104    }
105
106    resolve_legacy_irq(irq).expect("legacy per-CPU IRQ exceeds platform IRQ id width")
107}
108
109#[cfg(feature = "net")]
110pub(crate) struct RuntimeNetIrqRegistrar;
111
112#[cfg(feature = "net")]
113pub(crate) static NET_IRQ_REGISTRAR: RuntimeNetIrqRegistrar = RuntimeNetIrqRegistrar;
114
115#[cfg(feature = "net")]
116struct RuntimeNetIrqRegistration {
117    name: String,
118    handle: IrqHandle,
119    owner_cpu: usize,
120}
121
122#[cfg(feature = "net")]
123impl ax_net::PinnedNetIrqRegistration for RuntimeNetIrqRegistration {
124    fn owner_cpu(&self) -> usize {
125        self.owner_cpu
126    }
127
128    fn enable(&self) -> Result<(), ax_net::PinnedNetIrqError> {
129        ax_hal::irq::enable_irq(self.handle).map_err(map_net_irq_error)
130    }
131
132    fn disable_and_synchronize(&self) -> Result<(), ax_net::PinnedNetIrqError> {
133        match ax_hal::irq::disable_irq(self.handle) {
134            Ok(()) | Err(IrqError::NotFound) => {}
135            Err(error) => return Err(map_net_irq_error(error)),
136        }
137        match ax_hal::irq::synchronize_irq(self.handle) {
138            Ok(()) | Err(IrqError::NotFound) => Ok(()),
139            Err(error) => Err(map_net_irq_error(error)),
140        }
141    }
142}
143
144#[cfg(feature = "net")]
145impl Drop for RuntimeNetIrqRegistration {
146    fn drop(&mut self) {
147        if let Err(error) = ax_hal::irq::free_irq(self.handle) {
148            warn!(
149                "failed to free network IRQ registration {}: {error:?}",
150                self.name
151            );
152        }
153    }
154}
155
156#[cfg(feature = "net")]
157fn map_net_irq_error(err: IrqError) -> ax_net::PinnedNetIrqError {
158    match err {
159        IrqError::InvalidIrq | IrqError::InvalidCpu => ax_net::PinnedNetIrqError::Invalid,
160        IrqError::Busy => ax_net::PinnedNetIrqError::AffinityConflict,
161        IrqError::Unsupported | IrqError::CpuOffline => ax_net::PinnedNetIrqError::Unsupported,
162        IrqError::NoMemory
163        | IrqError::NotFound
164        | IrqError::Timeout
165        | IrqError::Controller
166        | IrqError::InIrqContext => ax_net::PinnedNetIrqError::Other,
167    }
168}
169
170#[cfg(feature = "net")]
171impl ax_net::PinnedNetIrqRegistrar for RuntimeNetIrqRegistrar {
172    fn register(
173        &self,
174        name: String,
175        irq: IrqId,
176        owner_cpu: usize,
177        action: ax_net::PinnedNetIrqAction,
178    ) -> Result<alloc::boxed::Box<dyn ax_net::PinnedNetIrqRegistration>, ax_net::PinnedNetIrqError>
179    {
180        let mut action = action;
181        let request = ax_hal::irq::IrqRequest::new(move |_context| match action.run() {
182            ax_net::PinnedNetIrqOutcome::Unhandled => ax_hal::irq::IrqReturn::Unhandled,
183            ax_net::PinnedNetIrqOutcome::Handled => ax_hal::irq::IrqReturn::Handled,
184            ax_net::PinnedNetIrqOutcome::Wake => ax_hal::irq::IrqReturn::Wake,
185        })
186        .execution(ax_hal::irq::IrqExecution::NonReentrant)
187        .share_mode(ax_hal::irq::ShareMode::Shared)
188        .auto_enable(ax_hal::irq::AutoEnable::No)
189        .affinity(ax_hal::irq::IrqAffinity::Fixed(ax_hal::irq::CpuId(
190            owner_cpu,
191        )));
192        let handle = ax_hal::irq::request_irq(irq, request).map_err(map_net_irq_error)?;
193        info!("registered {name} IRQ {irq:?} on fixed CPU {owner_cpu}");
194        Ok(alloc::boxed::Box::new(RuntimeNetIrqRegistration {
195            name,
196            handle,
197            owner_cpu,
198        }))
199    }
200}
201
202/// Arms the shared physical IPI delivery edge for one CPU.
203///
204/// Callers must publish their logical pending state before this doorbell. The
205/// shared edge coalesces repeated notifications until the target CPU claims
206/// the physical interrupt; logical owners remain responsible for draining
207/// their own state.
208pub fn notify_cpu(cpu_id: usize) -> Result<(), ax_hal::irq::IrqError> {
209    #[cfg(any(feature = "ipi", feature = "wake-ipi"))]
210    {
211        if cpu_id >= ax_hal::cpu_num() {
212            return Err(ax_hal::irq::IrqError::InvalidCpu);
213        }
214        ax_ipi::notify_cpu(ax_hal::irq::CpuId(cpu_id)).map(|_| ())
215    }
216    #[cfg(not(any(feature = "ipi", feature = "wake-ipi")))]
217    {
218        let _ = cpu_id;
219        Err(ax_hal::irq::IrqError::Unsupported)
220    }
221}
222
223mod worker;
224pub use worker::FixedIrqWorkerSignal;