diff --git a/arch/arm/cortex_m/link.x b/arch/arm/cortex_m/link.x index b1081f18..8b1d73b9 100644 --- a/arch/arm/cortex_m/link.x +++ b/arch/arm/cortex_m/link.x @@ -159,6 +159,12 @@ SECTIONS KEEP(*(.bk_app_array)) PROVIDE_HIDDEN (__bk_app_array_end = .); + . = ALIGN(4); + PROVIDE_HIDDEN(__isr_array_start = .); + KEEP(*(SORT_BY_INIT_PRIORITY(.isr.reg.*))) + KEEP(*(.isr.reg)) + PROVIDE_HIDDEN(__isr_array_end = .); + . = ALIGN(4); __start___llvm_prf_cnts = .; KEEP(*(__llvm_prf_cnts)) diff --git a/driver/BUILD.gn b/driver/BUILD.gn index 9daa365f..e3c7a9f5 100644 --- a/driver/BUILD.gn +++ b/driver/BUILD.gn @@ -21,10 +21,12 @@ build_rust("blueos_driver") { "//external/vendor/bitflags-2.10.0:bitflags", "//external/vendor/cfg-if-1.0.4:cfg_if", "//external/vendor/safe-mmio-0.2.5:safe_mmio", + "//external/vendor/spin-0.9.8:spin", "//external/vendor/tock-registers-0.9.0:tock_registers", "//external/vendor/zerocopy-0.8.27:zerocopy", "//kernel/hal:blueos_hal", ] + board_deps + proc_macro_deps = [ "//kernel/macro:blueos_macro" ] features = [] rustflags = [ diff --git a/driver/src/systimer/esp32_sys_timer.rs b/driver/src/systimer/esp32_sys_timer.rs index bf7e5bd4..ce06c144 100644 --- a/driver/src/systimer/esp32_sys_timer.rs +++ b/driver/src/systimer/esp32_sys_timer.rs @@ -192,7 +192,9 @@ impl Clock for Esp32SysTimer u64 { Self::registers().unit0_op.modify(UNIT_OP::UPDATE::SET); - while !Self::registers().unit0_op.is_set(UNIT_OP::VALUE_VALID) {} + while !Self::registers().unit0_op.is_set(UNIT_OP::VALUE_VALID) { + Self::registers().unit0_op.modify(UNIT_OP::UPDATE::SET); + } let mut lo_prev = Self::registers() .unit0_value_lo @@ -220,16 +222,16 @@ impl Clock for Esp32SysTimer; INTERRUPT_TABLE_LEN] = [None; INTERRUPT_TABLE_LEN]; + +/// Safety: ISR_DESC only be read in the interrupt handler, +/// and only be written in the boot process early, so it's +/// safe to use unsafe to write it. +pub fn init_interrupt_registry() { + extern "C" { + static __isr_array_start: usize; + static __isr_array_end: usize; + } + + unsafe { + let mut p = core::ptr::addr_of!(__isr_array_start); + while p < core::ptr::addr_of!(__isr_array_end) { + let r = &*(p as *const IsrReg); + assert!( + r.no < INTERRUPT_TABLE_LEN, + "ISR number {} exceeds the maximum limit {}", + r.no, + INTERRUPT_TABLE_LEN + ); + ISR_DESC[r.no] = Some(r.desc); + p = p.add(1); + } + } +} + +/// This function is used to register the raw interrupt handler for the given irq number. +/// The handler should be defined in the assembly file, and the caller should ensure that +/// the handler is properly defined and linked. This function is unsafe because it allows +/// registering a raw interrupt handler, which may lead to undefined behavior if not used +/// correctly. +/// Safety: race condition may occur if this function is called while the corresponding +/// interrupt is enabled and can be triggered, so the caller should ensure that the interrupt +/// is disabled before calling this function, and enable it after the handler is registered. +pub unsafe fn register_raw_isr(irq: IrqNumber, handler: unsafe extern "C" fn()) { + __INTERRUPT_HANDLERS__[irq.0 as usize] = Vector { handler }; +} diff --git a/kernel/src/boards/gd32e507_eval/handler.rs b/kernel/src/boards/gd32e507_eval/handler.rs deleted file mode 100644 index 547b6023..00000000 --- a/kernel/src/boards/gd32e507_eval/handler.rs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2025 vivo Mobile Communication Co., Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::{ - arch::{ - self, - irq::{InterruptTable, Vector, INTERRUPT_TABLE_LEN}, - }, - boot::_start, - time, -}; - -extern "C" { - fn uart0_handler(); -} - -#[doc(hidden)] -#[link_section = ".interrupt.handlers"] -#[no_mangle] -static __INTERRUPT_HANDLERS__: InterruptTable = { - let mut tbl = [Vector { reserved: 0 }; INTERRUPT_TABLE_LEN]; - tbl[37] = Vector { - handler: uart0_handler, - }; // USART0 - tbl -}; diff --git a/kernel/src/boards/gd32e507_eval/mod.rs b/kernel/src/boards/gd32e507_eval/mod.rs index 4e1ffd30..40485c90 100644 --- a/kernel/src/boards/gd32e507_eval/mod.rs +++ b/kernel/src/boards/gd32e507_eval/mod.rs @@ -13,7 +13,6 @@ // limitations under the License. mod config; -mod handler; use crate::{ arch, arch::irq::IrqNumber, boot, boot::INIT_BSS_DONE, devices::clock::systick, irq::IrqTrace, @@ -77,13 +76,19 @@ const HZ: usize = config::PLL_SYS_FREQ as usize; pub type ClockImpl = systick::SysTickClock; pub(crate) fn init() { - unsafe { copy_data() }; + unsafe { + copy_data(); + arch::irq::init_interrupt_registry(); + }; boot::init_runtime(); use blueos_hal::clock_control::ClockControl; blueos_driver::clock_control::gd32_clock_control::Gd32ClockControl::init(); unsafe { boot::init_heap() }; arch::irq::init(); + unsafe { + arch::irq::register_raw_isr(config::USBFS_IRQn, uart0_handler); + } arch::irq::enable_irq_with_priority(config::USBFS_IRQn, arch::irq::Priority::Normal); ClockImpl::init(); } @@ -123,8 +128,7 @@ crate::define_pin_states! { ) } -#[no_mangle] -pub unsafe extern "C" fn uart0_handler() { +unsafe extern "C" fn uart0_handler() { let _trace = IrqTrace::new(config::USBFS_IRQn); use blueos_hal::HasInterruptReg; let uart = get_device!(console_uart); diff --git a/kernel/src/boards/qemu_mps2_an385/handlers.rs b/kernel/src/boards/qemu_mps2_an385/handlers.rs deleted file mode 100644 index 06ba097e..00000000 --- a/kernel/src/boards/qemu_mps2_an385/handlers.rs +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright (c) 2025 vivo Mobile Communication Co., Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::{ - arch, - arch::irq::{InterruptTable, Vector, INTERRUPT_TABLE_LEN}, - boot::_start, - time, -}; - -extern "C" { - pub fn uart0rx_handler(); - pub fn uart0tx_handler(); -} - -#[no_mangle] -#[linkage = "weak"] -pub unsafe extern "C" fn uart1rx_handler() { - unreachable!("Should not reach here"); -} - -#[no_mangle] -#[linkage = "weak"] -pub unsafe extern "C" fn uart1tx_handler() { - unreachable!("Should not reach here"); -} - -unsafe extern "C" fn do_nothing() {} - -unsafe extern "C" fn busy() { - unreachable!("Should not be busy!"); -} - -macro_rules! default_irq_handler { - ($handler_name:ident) => { - unsafe extern "C" fn $handler_name() { - $crate::debug!("{}", stringify!($handler_name)); - } - }; -} -default_irq_handler!(uart2rx_handler); -default_irq_handler!(uart2tx_handler); -default_irq_handler!(gpio0all_handler); -default_irq_handler!(gpio1all_handler); -default_irq_handler!(timer0_handler); -default_irq_handler!(timer1_handler); -default_irq_handler!(dualtimer_handler); -default_irq_handler!(spi_0_1_handler); -default_irq_handler!(uart_0_1_2_ovf_handler); -default_irq_handler!(ethernet_handler); -default_irq_handler!(i2s_handler); -default_irq_handler!(touchscreen_handler); -default_irq_handler!(gpio2_handler); -default_irq_handler!(gpio3_handler); -default_irq_handler!(uart3rx_handler); -default_irq_handler!(uart3tx_handler); -default_irq_handler!(uart4rx_handler); -default_irq_handler!(uart4tx_handler); -default_irq_handler!(spi_2_handler); -default_irq_handler!(spi_3_4_handler); -default_irq_handler!(gpio0_0_handler); -default_irq_handler!(gpio0_1_handler); -default_irq_handler!(gpio0_2_handler); -default_irq_handler!(gpio0_3_handler); -default_irq_handler!(gpio0_4_handler); -default_irq_handler!(gpio0_5_handler); -default_irq_handler!(gpio0_6_handler); -default_irq_handler!(gpio0_7_handler); - -#[used] -#[link_section = ".interrupt.handlers"] -#[no_mangle] -pub static __INTERRUPT_HANDLERS__: InterruptTable = { - let mut tbl = [Vector { reserved: 0 }; INTERRUPT_TABLE_LEN]; - tbl[0] = Vector { - handler: uart0rx_handler, - }; - tbl[1] = Vector { - handler: uart0tx_handler, - }; - tbl[2] = Vector { - handler: uart1rx_handler, - }; - tbl[3] = Vector { - handler: uart1tx_handler, - }; - tbl[4] = Vector { - handler: uart2rx_handler, - }; - tbl[5] = Vector { - handler: uart2tx_handler, - }; - tbl[6] = Vector { - handler: gpio0all_handler, - }; - tbl[7] = Vector { - handler: gpio1all_handler, - }; - tbl[8] = Vector { - handler: timer0_handler, - }; - tbl[9] = Vector { - handler: timer1_handler, - }; - tbl[10] = Vector { - handler: dualtimer_handler, - }; - tbl[11] = Vector { - handler: spi_0_1_handler, - }; - tbl[12] = Vector { - handler: uart_0_1_2_ovf_handler, - }; - tbl[13] = Vector { - handler: ethernet_handler, - }; - tbl[14] = Vector { - handler: i2s_handler, - }; - tbl[15] = Vector { - handler: touchscreen_handler, - }; - tbl[16] = Vector { - handler: gpio2_handler, - }; - tbl[17] = Vector { - handler: gpio3_handler, - }; - tbl[18] = Vector { - handler: uart3rx_handler, - }; - tbl[19] = Vector { - handler: uart3tx_handler, - }; - tbl[20] = Vector { - handler: uart4rx_handler, - }; - tbl[21] = Vector { - handler: uart4tx_handler, - }; - tbl[22] = Vector { - handler: spi_2_handler, - }; - tbl[23] = Vector { - handler: spi_3_4_handler, - }; - tbl[24] = Vector { - handler: gpio0_0_handler, - }; - tbl[25] = Vector { - handler: gpio0_1_handler, - }; - tbl[26] = Vector { - handler: gpio0_2_handler, - }; - tbl[27] = Vector { - handler: gpio0_3_handler, - }; - tbl[28] = Vector { - handler: gpio0_4_handler, - }; - tbl[29] = Vector { - handler: gpio0_5_handler, - }; - tbl[30] = Vector { - handler: gpio0_6_handler, - }; - tbl[31] = Vector { - handler: gpio0_7_handler, - }; - tbl -}; diff --git a/kernel/src/boards/qemu_mps2_an385/mod.rs b/kernel/src/boards/qemu_mps2_an385/mod.rs index 5b9b30ce..60bd1fcf 100644 --- a/kernel/src/boards/qemu_mps2_an385/mod.rs +++ b/kernel/src/boards/qemu_mps2_an385/mod.rs @@ -13,7 +13,6 @@ // limitations under the License. pub mod config; -mod handlers; use crate::{ arch, boards::config::{ @@ -80,11 +79,16 @@ pub type ClockImpl = systick::SysTickClock; pub(crate) fn init() { unsafe { copy_data(); + arch::irq::init_interrupt_registry(); } boot::init_runtime(); unsafe { boot::init_heap() }; arch::irq::init(); ClockImpl::init(); + unsafe { + arch::irq::register_raw_isr(UART0RX_IRQn, uart0rx_handler); + arch::irq::register_raw_isr(UART0TX_IRQn, uart0tx_handler); + } arch::irq::enable_irq_with_priority(UART0RX_IRQn, arch::irq::Priority::Normal); arch::irq::enable_irq_with_priority(UART0TX_IRQn, arch::irq::Priority::Normal); } @@ -101,8 +105,7 @@ crate::define_peripheral! { )}), } -#[no_mangle] -pub unsafe extern "C" fn uart0rx_handler() { +unsafe extern "C" fn uart0rx_handler() { let _trace = IrqTrace::new(UART0RX_IRQn); let uart = get_device!(console_uart); if let Some(handler) = unsafe { @@ -115,8 +118,7 @@ pub unsafe extern "C" fn uart0rx_handler() { uart.clear_interrupt(blueos_driver::uart::InterruptType::Rx); } -#[no_mangle] -pub unsafe extern "C" fn uart0tx_handler() { +unsafe extern "C" fn uart0tx_handler() { let _trace = IrqTrace::new(UART0TX_IRQn); let uart = get_device!(console_uart); if let Some(handler) = unsafe { diff --git a/kernel/src/boards/qemu_mps3_an547/handlers.rs b/kernel/src/boards/qemu_mps3_an547/handlers.rs deleted file mode 100644 index 984b5367..00000000 --- a/kernel/src/boards/qemu_mps3_an547/handlers.rs +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright (c) 2025 vivo Mobile Communication Co., Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::{ - arch::{ - self, - irq::{InterruptTable, Vector, INTERRUPT_TABLE_LEN}, - }, - boot::_start, - time, -}; - -unsafe extern "C" fn do_nothing() {} - -unsafe extern "C" fn busy() { - unreachable!("Should not be busy!") -} - -macro_rules! default_irq_handler { - ($handler_name:ident) => { - unsafe extern "C" fn $handler_name() { - $crate::debug!("{}", stringify!($handler_name)); - } - }; -} - -extern "C" { - fn uart0rx_handler(); - fn uart0tx_handler(); -} -default_irq_handler!(nonsec_watchdog_reset_req_handler); -default_irq_handler!(nonsec_watchdog_handler); -default_irq_handler!(slowclk_timer_handler); -default_irq_handler!(tfm_timer0_irq_handler); -default_irq_handler!(timer1_handler); -default_irq_handler!(timer2_handler); -default_irq_handler!(mpc_handler); -default_irq_handler!(ppc_handler); -default_irq_handler!(msc_handler); -default_irq_handler!(bridge_error_handler); -default_irq_handler!(mgmt_ppu_handler); -default_irq_handler!(sys_ppu_handler); -default_irq_handler!(cpu0_ppu_handler); -default_irq_handler!(debug_ppu_handler); -default_irq_handler!(timer3_aon_handler); -default_irq_handler!(cpu0_cti_0_handler); -default_irq_handler!(cpu0_cti_1_handler); -default_irq_handler!(system_timestamp_counter_handler); -default_irq_handler!(uartrx1_handler); -default_irq_handler!(uarttx1_handler); -default_irq_handler!(uartrx2_handler); -default_irq_handler!(uarttx2_handler); -default_irq_handler!(uartrx3_handler); -default_irq_handler!(uarttx3_handler); -default_irq_handler!(uartrx4_handler); -default_irq_handler!(uarttx4_handler); - -#[doc(hidden)] -#[link_section = ".interrupt.handlers"] -#[no_mangle] -static __INTERRUPT_HANDLERS__: InterruptTable = { - let mut tbl = [Vector { reserved: 0 }; INTERRUPT_TABLE_LEN]; - - tbl[0] = Vector { - handler: nonsec_watchdog_reset_req_handler, - }; - tbl[1] = Vector { - handler: nonsec_watchdog_handler, - }; - tbl[2] = Vector { - handler: slowclk_timer_handler, - }; - tbl[3] = Vector { - handler: tfm_timer0_irq_handler, - }; - tbl[4] = Vector { - handler: timer1_handler, - }; - tbl[5] = Vector { - handler: timer2_handler, - }; - tbl[6] = Vector { reserved: 0 }; - tbl[7] = Vector { reserved: 0 }; - tbl[8] = Vector { reserved: 0 }; - tbl[9] = Vector { - handler: mpc_handler, - }; - tbl[10] = Vector { - handler: ppc_handler, - }; - tbl[11] = Vector { - handler: msc_handler, - }; - tbl[12] = Vector { - handler: bridge_error_handler, - }; - tbl[13] = Vector { reserved: 0 }; - tbl[14] = Vector { - handler: mgmt_ppu_handler, - }; - tbl[15] = Vector { - handler: sys_ppu_handler, - }; - tbl[16] = Vector { - handler: cpu0_ppu_handler, - }; - tbl[17] = Vector { reserved: 0 }; - tbl[18] = Vector { reserved: 0 }; - tbl[19] = Vector { reserved: 0 }; - tbl[20] = Vector { reserved: 0 }; - tbl[21] = Vector { reserved: 0 }; - tbl[22] = Vector { reserved: 0 }; - tbl[23] = Vector { reserved: 0 }; - tbl[24] = Vector { reserved: 0 }; - tbl[25] = Vector { - handler: debug_ppu_handler, - }; - tbl[27] = Vector { - handler: timer3_aon_handler, - }; - tbl[28] = Vector { - handler: cpu0_cti_0_handler, - }; - tbl[29] = Vector { - handler: cpu0_cti_1_handler, - }; - tbl[30] = Vector { reserved: 0 }; - tbl[31] = Vector { reserved: 0 }; - tbl[32] = Vector { - handler: system_timestamp_counter_handler, - }; - // In the new version of QEMU (9.20), the UART RX interrupt and TX interrupt have been swapped. - // For details, see `fix RX/TX interrupts order `_ - // default set as new version of QEMU - tbl[33] = Vector { - handler: uart0rx_handler, - }; - tbl[34] = Vector { - handler: uart0tx_handler, - }; - tbl[35] = Vector { - handler: uartrx1_handler, - }; - tbl[36] = Vector { - handler: uarttx1_handler, - }; - tbl[37] = Vector { - handler: uartrx2_handler, - }; - tbl[38] = Vector { - handler: uarttx2_handler, - }; - tbl[39] = Vector { - handler: uartrx3_handler, - }; - tbl[40] = Vector { - handler: uarttx3_handler, - }; - tbl[41] = Vector { - handler: uartrx4_handler, - }; - tbl[42] = Vector { - handler: uarttx4_handler, - }; - - tbl -}; diff --git a/kernel/src/boards/qemu_mps3_an547/mod.rs b/kernel/src/boards/qemu_mps3_an547/mod.rs index 5f4561ba..e3d2ebbe 100644 --- a/kernel/src/boards/qemu_mps3_an547/mod.rs +++ b/kernel/src/boards/qemu_mps3_an547/mod.rs @@ -13,7 +13,6 @@ // limitations under the License. pub mod config; -mod handlers; use crate::{ arch, @@ -92,11 +91,16 @@ pub(crate) fn init() { unsafe { copy_data(); + arch::irq::init_interrupt_registry(); } boot::init_runtime(); unsafe { boot::init_heap() }; arch::irq::init(); ClockImpl::init(); + unsafe { + arch::irq::register_raw_isr(UART0RX_IRQn, uart0rx_handler); + arch::irq::register_raw_isr(UART0TX_IRQn, uart0tx_handler); + } arch::irq::enable_irq_with_priority(UART0RX_IRQn, arch::irq::Priority::Normal); arch::irq::enable_irq_with_priority(UART0TX_IRQn, arch::irq::Priority::Normal); } @@ -113,8 +117,7 @@ crate::define_peripheral! { crate::define_pin_states!(None); -#[no_mangle] -pub unsafe extern "C" fn uart0rx_handler() { +unsafe extern "C" fn uart0rx_handler() { let _trace = IrqTrace::new(UART0RX_IRQn); let uart = get_device!(console_uart); if let Some(handler) = unsafe { @@ -126,8 +129,8 @@ pub unsafe extern "C" fn uart0rx_handler() { } uart.clear_interrupt(blueos_driver::uart::InterruptType::Rx); } -#[no_mangle] -pub unsafe extern "C" fn uart0tx_handler() { + +unsafe extern "C" fn uart0tx_handler() { let _trace = IrqTrace::new(UART0TX_IRQn); let uart = get_device!(console_uart); if let Some(handler) = unsafe { diff --git a/kernel/src/boards/raspberry_pico2_cortexm/mod.rs b/kernel/src/boards/raspberry_pico2_cortexm/mod.rs index 7ab026a1..13b9814a 100644 --- a/kernel/src/boards/raspberry_pico2_cortexm/mod.rs +++ b/kernel/src/boards/raspberry_pico2_cortexm/mod.rs @@ -13,7 +13,6 @@ // limitations under the License. mod block; -mod handler; use crate::{ arch::{self, irq::IrqNumber}, @@ -93,12 +92,16 @@ pub(crate) fn init() { SCB_CPACR_PTR.write_volatile(temp); core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); copy_data(); + arch::irq::init_interrupt_registry(); } boot::init_runtime(); blueos_driver::clock_control::rpi_pico::RpiPicoClockControl::init(); unsafe { boot::init_heap() }; arch::irq::init(); + unsafe { + arch::irq::register_raw_isr(UART0_IRQN, uart0_handler); + } arch::irq::enable_irq_with_priority(UART0_IRQN, arch::irq::Priority::Normal); ClockImpl::init(); } @@ -141,7 +144,6 @@ crate::define_bus! { ) } -#[no_mangle] pub unsafe extern "C" fn uart0_handler() { use blueos_hal::HasInterruptReg; let _trace = IrqTrace::new(UART0_IRQN); diff --git a/macro/src/lib.rs b/macro/src/lib.rs index 17447020..93388091 100644 --- a/macro/src/lib.rs +++ b/macro/src/lib.rs @@ -13,8 +13,11 @@ // limitations under the License. use proc_macro::TokenStream; -use quote::quote; -use syn::{Data, Field, Fields}; +use quote::{format_ident, quote}; +use syn::{ + parse::Parser, parse_macro_input, punctuated::Punctuated, token::Comma, Expr, ExprLit, + ItemStatic, Lit, Meta, StaticMutability, +}; #[proc_macro] pub fn current_board_mod(_item: TokenStream) -> TokenStream { @@ -30,3 +33,103 @@ pub fn current_board_mod(_item: TokenStream) -> TokenStream { } .into() } + +/// Defines an interrupt service routine (ISR) and registers it to the specified interrupt number. +/// +/// EXAMPLE: +/// ``` +/// struct RxIsr; +/// +/// #[blueos_macro::interrupt(no = 1)] +/// static RX_ISR: RxIsr = RxIsr; +/// +/// impl blueos_hal::isr::IsrDesc for RxIsr { +/// fn service_isr(&self) { +/// // ISR implementation +/// } +/// } +/// ``` +#[proc_macro_attribute] +pub fn interrupt(attr: TokenStream, item: TokenStream) -> TokenStream { + let no = match parse_interrupt_no(attr) { + Ok(no) => no, + Err(e) => return e.to_compile_error().into(), + }; + + let item_static = parse_macro_input!(item as ItemStatic); + if matches!(item_static.mutability, StaticMutability::Mut(_)) { + return syn::Error::new_spanned( + &item_static, + "#[interrupt] only supports immutable `static` items", + ) + .to_compile_error() + .into(); + } + + let ident = &item_static.ident; + let reg_ident = format_ident!("__BLUEOS_INTERRUPT_REG_{}", ident); + let section = format!(".isr.reg.{no}"); + + quote! { + #item_static + + const _: () = { + #[repr(C)] + struct __BlueOsInterruptReg { + no: usize, + desc: &'static dyn ::blueos_hal::isr::IsrDesc, + } + + #[used] + #[unsafe(link_section = #section)] + static #reg_ident: blueos_hal::isr::IsrReg = blueos_hal::isr::IsrReg { + no: #no, + desc: &#ident as &'static dyn ::blueos_hal::isr::IsrDesc, + }; + }; + } + .into() +} + +fn parse_interrupt_no(attr: TokenStream) -> Result { + let parser = Punctuated::::parse_terminated; + let metas = parser.parse2(attr.into())?; + let mut no: Option = None; + + for meta in metas { + match meta { + Meta::NameValue(name_value) if name_value.path.is_ident("no") => { + if no.is_some() { + return Err(syn::Error::new_spanned( + name_value, + "`no` can only be specified once", + )); + } + let Expr::Lit(ExprLit { + lit: Lit::Int(lit_int), + .. + }) = name_value.value + else { + return Err(syn::Error::new_spanned( + name_value, + "`no` expects an integer literal, e.g. #[interrupt(no = 1)]", + )); + }; + no = Some(lit_int.base10_parse::()?); + } + other => { + return Err(syn::Error::new_spanned( + other, + "unsupported interrupt attribute, expected #[interrupt(no = N)]", + )); + } + } + } + + no.ok_or_else(|| { + syn::Error::new( + proc_macro2::Span::call_site(), + "missing `no` in #[interrupt]", + ) + }) +}