psoc-drivers 0.1.0

Hardware driver implementations for psoc-rs
//! Functions to manage interrupts.
//!
//! # Multiplexed interrupts
//!
//! Some devices support more interrupt sources than the CPU has interrupt lines. In this case, the
//! device has an interrupt controller that multiplexes interrupt sources to CPU interrupt lines.
//!
//! Currently, the only device using multiplexed interrupts is PSOC 6, which uses interrupt
//! multiplexing on the CM0 core (the CM4 has direct interrupt lines).
//!
//! The use of multiplexed interrupts is transparent to applications. This module implements a
//! software vector table that dispatches multiplexed interrupts to the appropriate handler.
// Copyright (c) 2026, Infineon Technologies AG or an affiliate of Infineon Technologies AG.
// SPDX-License-Identifier: Apache-2.0
//
// 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::regs;
pub use crate::regs::Interrupt;

cfg_select! {
    not(core = "cm0") => {
        use regs::NVIC;

        /// Disables `interrupt`.
        pub fn mask(interrupt: Interrupt) {
            NVIC::mask(interrupt);
        }

        /// Enables `interrupt`.
        pub fn unmask(interrupt: Interrupt) {
            unsafe { NVIC::unmask(interrupt) }
        }

        /// Sets the priority of `interrupt`.
        ///
        /// Note: only the most-significant bits of `priority` are used, depending on the number of
        /// priority bits implemented by the device (see [`regs::NVIC_PRIO_BITS`]).
        pub fn set_priority(interrupt: Interrupt, priority: u8) {
            // SAFETY: we use PRIMASK for critical-section, which is not broken by changing interrupt
            // priorities
            // On non-CM0 cores, set_priority is atomic
            unsafe {
                regs::CorePeripherals::steal()
                    .NVIC
                    .set_priority(interrupt, priority)
            }
        }

        /// Gets the priority of `interrupt`.
        pub fn get_priority(interrupt: Interrupt) -> u8 {
            regs::NVIC::get_priority(interrupt)
        }
    },
    all(core = "cm0", not(die = "psoc6_01")) => {
        use cortex_m::peripheral::scb::VectActive;
        use regs::{AsPtr, Reg, cpuss::Cm0Int0Status_SPEC};

        const NUM_HW_INTERRUPTS: u8 = 8;

        #[unsafe(link_section = ".vector_table.multiplexed_interrupts")]
        #[unsafe(no_mangle)]
        #[doc(hidden)]
        pub static __MULTIPLEXED_INTERRUPTS: [unsafe extern "C" fn(); 16] = *[
            [multiplexed_interrupt_handler as _; NUM_HW_INTERRUPTS as usize],
            // We don't use the available software interrupts
            [DefaultHandler as _; 8],
        ].as_flattened().as_array().unwrap();

        /// Disables `interrupt`.
        pub fn mask(interrupt: Interrupt) {
            // critical section avoids race wtih set_priority
            critical_section::with(|_cs| {
                unsafe { regs::CPUSS.cm0_system_int_ctl()[interrupt as usize].modify(|r| r.cpu_int_valid().set(false)) }
            })
        }

        /// Enables `interrupt`.
        pub fn unmask(interrupt: Interrupt) {
            critical_section::with(|_cs| {
                unsafe { regs::CPUSS.cm0_system_int_ctl()[interrupt as usize].modify(|r| r.cpu_int_valid().set(true)) }
            })
        }

        /// Sets the priority of `interrupt`.
        ///
        /// Note: only the most-significant bits of `priority` are used, depending on the number of
        /// priority bits implemented by the device (see [`regs::NVIC_PRIO_BITS`]).
        pub fn set_priority(interrupt: Interrupt, priority: u8) {
            // critical section avoids race with mask/unmask
            critical_section::with(|_cs| {
                unsafe {
                    regs::CPUSS.cm0_system_int_ctl()[interrupt as usize].modify(|r| r.cpu_int_idx().set(priority >> 5))
                }
            })
        }

        /// Gets the priority of `interrupt`.
        pub fn get_priority(interrupt: Interrupt) -> u8 {
            unsafe {
                regs::CPUSS.cm0_system_int_ctl()[interrupt as usize].read().cpu_int_idx().get() << 5
            }
        }

        unsafe extern "C" fn multiplexed_interrupt_handler() {
            if let VectActive::Interrupt { irqn:  n @ 0..NUM_HW_INTERRUPTS } = regs::SCB::vect_active() {
                unsafe {
                    let status_reg = Reg::<Cm0Int0Status_SPEC, regs::R>::from_ptr(regs::CPUSS.cm0_int0_status().as_ptr().byte_add(n as usize * 4));
                    let status = status_reg.read();

                    if status.system_int_valid().get() {
                        let interrupt = status.system_int_idx().get() as usize;
                        let handler = core::mem::transmute_copy::<regs::Vector, unsafe extern "C" fn()>(&regs::__INTERRUPTS[interrupt]);
                        handler();
                    }
                }
            }
        }

        unsafe extern "C" {
            fn DefaultHandler();
        }
    }
    all(core = "cm0", die = "psoc6_01") => {
        // psoc6_01 allows mapping one interrupt line to only one interrupt.

        use cortex_m::peripheral::scb::VectActive;
        use regs::AsPtr;

        const NUM_HW_INTERRUPTS: u8 = 32;

        #[unsafe(link_section = ".vector_table.multiplexed_interrupts")]
        #[unsafe(no_mangle)]
        #[doc(hidden)]
        pub static __MULTIPLEXED_INTERRUPTS: [unsafe extern "C" fn(); NUM_HW_INTERRUPTS as usize] = [multiplexed_interrupt_handler as _; NUM_HW_INTERRUPTS as usize];

        // (priority << 5) | line
        //
        // NOTE: we don't indicate whether an interrupt is unmapped
        static mut INTERRUPT_MAPPING: [u8; regs::__INTERRUPTS.len()] = [0; regs::__INTERRUPTS.len()];

        const NO_INTERRUPT: u8 = 240;

        /// Disables `interrupt`.
        pub fn mask(interrupt: Interrupt) {
            critical_section::with(|_cs| {
                unsafe {
                    let line = INTERRUPT_MAPPING[interrupt as usize] & 0x1F;
                    let regs = regs::CPUSS.cm0_int_ctl0().as_ptr().cast::<u32>();

                    let reg = regs.add(line as usize / 4);
                    let mut value = reg.read_volatile().to_le_bytes();
                    if value[line as usize % 4] == interrupt as u8 {
                        value[line as usize % 4] = NO_INTERRUPT;
                        reg.write_volatile(u32::from_le_bytes(value));
                    }
                }
            })
        }

        /// Enables `interrupt`.
        pub fn unmask(interrupt: Interrupt) {
            critical_section::with(|_cs| {
                unsafe {
                    let mapping = INTERRUPT_MAPPING[interrupt as usize];
                    let (priority, line) = (mapping >> 5, mapping & 0x1F);
                    let regs = regs::CPUSS.cm0_int_ctl0().as_ptr().cast::<u32>();

                    let reg = regs.add(line as usize / 4);
                    let value = reg.read_volatile().to_le_bytes();
                    if value[line as usize % 4] == interrupt as u8 {
                        // The interrupt is already set up.
                        return;
                    }

                    // Search for an available line.
                    for reg_offset in 0..(NUM_HW_INTERRUPTS / 4) {
                        let reg = regs.add(reg_offset as usize);
                        let mut value = reg.read_volatile().to_le_bytes();
                        for line in 0..4 {
                            if value[line] == NO_INTERRUPT {
                                value[line] = interrupt as u8;
                                reg.write_volatile(u32::from_le_bytes(value));
                                regs::CorePeripherals::steal().NVIC.set_priority(HwInterruptNumber(line as u16 + reg_offset as u16 * 4), priority);
                                INTERRUPT_MAPPING[interrupt as usize] = (priority << 5) | (reg_offset * 4 + line as u8);
                                return;
                            }
                        }
                    }

                    panic!("cannot unmask interrupt: no available interrupt lines");
                }
            })
        }

        /// Sets the priority of `interrupt`.
        ///
        /// Note: only the most-significant bits of `priority` are used, depending on the number of
        /// priority bits implemented by the device (see [`regs::NVIC_PRIO_BITS`]).
        pub fn set_priority(interrupt: Interrupt, priority: u8) {
            critical_section::with(|_cs| {
                unsafe {
                    let line = INTERRUPT_MAPPING[interrupt as usize] & 0x1F;
                    INTERRUPT_MAPPING[interrupt as usize] = (priority << 5) | line;

                    let reg = regs::CPUSS.cm0_int_ctl0().as_ptr().cast::<u32>().add(line as usize / 4);
                    let value = reg.read_volatile().to_le_bytes();
                    if value[line as usize % 4] == interrupt as u8 {
                        regs::CorePeripherals::steal().NVIC.set_priority(HwInterruptNumber(line as u16), priority);
                    };
                }
            });
        }

        /// Gets the priority of `interrupt`.
        pub fn get_priority(interrupt: Interrupt) -> u8 {
            critical_section::with(|_cs| {
                unsafe {
                    INTERRUPT_MAPPING[interrupt as usize] >> 5
                }
            })
        }

        #[derive(Clone, Copy)]
        struct HwInterruptNumber(u16);
        unsafe impl cortex_m::interrupt::InterruptNumber for HwInterruptNumber {
            fn number(self) -> u16 {
                self.0
            }
        }

        unsafe extern "C" fn multiplexed_interrupt_handler() {
            unsafe {
                let vect_num = critical_section::with(|_cs| {
                    if let VectActive::Interrupt { irqn:  n @ 0..NUM_HW_INTERRUPTS } = regs::SCB::vect_active() {
                        let reg = regs::CPUSS.cm0_int_ctl0().as_ptr().cast::<u32>().add(n as usize / 4);
                        let value = reg.read_volatile().to_le_bytes();
                        Some(value[n as usize % 4])
                    } else {
                        None
                    }
                });
                if let Some(interrupt) = vect_num && interrupt != NO_INTERRUPT {
                    let handler = core::mem::transmute_copy::<regs::Vector, unsafe extern "C" fn()>(&regs::__INTERRUPTS[interrupt as usize]);
                    handler();
                }
            }
        }
    }
}

#[unsafe(naked)]
#[unsafe(no_mangle)]
unsafe extern "C" fn __pre_init() {
    core::arch::naked_asm!(
        #[cfg(all(core = "cm0", not(die = "psoc6_01")))]
        "
        // Set priorities for multiplexed interrupts
        ldr r1,  ={NVIC_IPR}
        ldr  r0, =0x03020100
        str  r0, [r1]
        ldr  r0, =0x07060504
        str  r0, [r1, #4]

        // Set priorites for interrupt sources
        movs r0, #0
        ldr  r1, ={CPUSS_CM0_SYSTEM_INT_CTL0}
        movs r2, #{NUM_INTERRUPTS}
        lsls r2, r2, #2

      .Lloop:
        subs r2, r2, #4
        str  r0, [r1, r2]
        bne  .Lloop

        // Enable multiplexed interrupts
        movs r0, #0xFF
        ldr  r1, ={NVIC_ISER}
        str  r0, [r1]
        ",

        #[cfg(all(core = "cm0", die = "psoc6_01"))]
        "
        // Enable multiplexed interrupts
        movs r0, #0
        mvns r0, r0
        ldr  r1, ={NVIC_ISER}
        str  r0, [r1]
        ",

        "bx lr",

        "/* {NVIC_ISER} {NVIC_IPR} {CPUSS_CM0_SYSTEM_INT_CTL0} {NUM_INTERRUPTS} */",
        // TODO: use PAC definitions once feature(asm_const_ptr) is stable
        // https://github.com/rust-lang/rust/issues/128464
        NVIC_ISER = const 0xE000_E100usize,
        NVIC_IPR = const 0xE000_E400usize,
        CPUSS_CM0_SYSTEM_INT_CTL0 = const 0x40208000usize,
        NUM_INTERRUPTS = const regs::__INTERRUPTS.len()
    )
}