From 53f1915a6b205838752df8b87cb1550451039ae1 Mon Sep 17 00:00:00 2001 From: Graham Sanderson Date: Wed, 5 May 2021 11:45:39 -0500 Subject: [PATCH] Add hardware_exception for setting exception handlers at runtime (#380) --- docs/index.h | 1 + src/rp2_common/CMakeLists.txt | 1 + .../hardware_exception/CMakeLists.txt | 1 + src/rp2_common/hardware_exception/exception.c | 65 +++++++++++ .../include/hardware/exception.h | 106 ++++++++++++++++++ .../hardware_irq/include/hardware/irq.h | 2 +- src/rp2_common/pico_standard_link/crt0.S | 8 ++ test/kitchen_sink/CMakeLists.txt | 1 + test/kitchen_sink/kitchen_sink.c | 1 + 9 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 src/rp2_common/hardware_exception/CMakeLists.txt create mode 100644 src/rp2_common/hardware_exception/exception.c create mode 100644 src/rp2_common/hardware_exception/include/hardware/exception.h diff --git a/docs/index.h b/docs/index.h index 53f4674..c7ef735 100644 --- a/docs/index.h +++ b/docs/index.h @@ -17,6 +17,7 @@ * \defgroup hardware_clocks hardware_clocks * \defgroup hardware_divider hardware_divider * \defgroup hardware_dma hardware_dma + * \defgroup hardware_exception hardware_exception * \defgroup hardware_flash hardware_flash * \defgroup hardware_gpio hardware_gpio * \defgroup hardware_i2c hardware_i2c diff --git a/src/rp2_common/CMakeLists.txt b/src/rp2_common/CMakeLists.txt index 5cd20a7..8e48562 100644 --- a/src/rp2_common/CMakeLists.txt +++ b/src/rp2_common/CMakeLists.txt @@ -10,6 +10,7 @@ pico_add_subdirectory(hardware_adc) pico_add_subdirectory(hardware_clocks) pico_add_subdirectory(hardware_dma) pico_add_subdirectory(hardware_divider) +pico_add_subdirectory(hardware_exception) pico_add_subdirectory(hardware_flash) pico_add_subdirectory(hardware_gpio) pico_add_subdirectory(hardware_i2c) diff --git a/src/rp2_common/hardware_exception/CMakeLists.txt b/src/rp2_common/hardware_exception/CMakeLists.txt new file mode 100644 index 0000000..a994dc0 --- /dev/null +++ b/src/rp2_common/hardware_exception/CMakeLists.txt @@ -0,0 +1 @@ +pico_simple_hardware_target(exception) \ No newline at end of file diff --git a/src/rp2_common/hardware_exception/exception.c b/src/rp2_common/hardware_exception/exception.c new file mode 100644 index 0000000..d451280 --- /dev/null +++ b/src/rp2_common/hardware_exception/exception.c @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "hardware/exception.h" +#include "hardware/regs/m0plus.h" +#include "hardware/platform_defs.h" +#include "hardware/structs/scb.h" + +#include "pico/mutex.h" +#include "pico/assert.h" + +#ifndef exception_is_compile_time_default +static bool exception_is_compile_time_default(exception_handler_t handler) { + extern char __default_isrs_start; + extern char __default_isrs_end; + return ((uintptr_t)handler) >= (uintptr_t)&__default_isrs_start && + ((uintptr_t)handler) < (uintptr_t)&__default_isrs_end; +} +#endif + +static inline exception_handler_t *get_vtable(void) { + return (exception_handler_t *) scb_hw->vtor; +} + +static void set_raw_exception_handler_and_restore_interrupts(enum exception_number num, exception_handler_t handler, uint32_t save) { + // update vtable (vtable_handler may be same or updated depending on cases, but we do it anyway for compactness) + get_vtable()[16 + num] = handler; + __dmb(); + restore_interrupts(save); +} + +static inline void check_exception_param(__unused enum exception_number num) { + invalid_params_if(EXCEPTION, num < NMI_EXCEPTION || num >=0); +} + +exception_handler_t exception_get_vtable_handler(enum exception_number num) { + check_exception_param(num); + return get_vtable()[16 + num]; +} + +exception_handler_t exception_set_exclusive_handler(enum exception_number num, exception_handler_t handler) { + check_exception_param(num); +#if !PICO_NO_RAM_VECTOR_TABLE + uint32_t save = save_and_disable_interrupts(); + exception_handler_t current = exception_get_vtable_handler(num); + hard_assert(exception_is_compile_time_default(current)); + set_raw_exception_handler_and_restore_interrupts(num, handler, save); +#else + panic_unsupported(); +#endif + return current; +} + +void exception_restore_handler(enum exception_number num, exception_handler_t original_handler) { + hard_assert(exception_is_compile_time_default(original_handler)); +#if !PICO_NO_RAM_VECTOR_TABLE + uint32_t save = save_and_disable_interrupts(); + set_raw_exception_handler_and_restore_interrupts(num, original_handler, save); +#else + panic_unsupported(); +#endif +} \ No newline at end of file diff --git a/src/rp2_common/hardware_exception/include/hardware/exception.h b/src/rp2_common/hardware_exception/include/hardware/exception.h new file mode 100644 index 0000000..a07fc01 --- /dev/null +++ b/src/rp2_common/hardware_exception/include/hardware/exception.h @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _HARDWARE_EXCEPTION_H_ +#define _HARDWARE_EXCEPTION_H_ + +#include "pico.h" +#include "hardware/address_mapped.h" +#include "hardware/regs/m0plus.h" + +/** \file exception.h + * \defgroup hardware_exception hardware_exception + * + * Methods for setting processor exception handlers + * + * Exceptions are identified by a \ref exception_num which is a number from -15 to -1; these are the numbers relative to + * the index of the first IRQ vector in the vector table. (i.e. vector table index is exception_num plus 16) + * + * There is one set of exception handlers per core, so the exception handlers for each core as set by these methods are independent. + * + * \note That all exception APIs affect the executing core only (i.e. the core calling the function). + */ + +// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_EXCEPTION, Enable/disable assertions in the exception module, type=bool, default=0, group=hardware_exception +#ifndef PARAM_ASSERTIONS_ENABLED_EXCEPTION +#define PARAM_ASSERTIONS_ENABLED_EXCEPTION 0 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/*! \brief Exception number definitions + * + * Note for consistency with irq numbers, these numbers are defined to be negative. The VTABLE index is + * the number here plus 16. + * + * Name | Value | Exception + * ---------------------|-------|---------- + * NMI_EXCEPTION | -14 | Non Maskable Interrupt + * HARDFAULT_EXCEPTION | -13 | HardFault + * SVCALL_EXCEPTION | -5 | SV Call + * PENDSV_EXCEPTION | -2 | Pend SV + * SYSTICK_EXCEPTION | -1 | System Tick + * + * \ingroup hardware_exception + */ +enum exception_number { + NMI_EXCEPTION = -14, /* Non Maskable Interrupt */ + HARDFAULT_EXCEPTION = -13, /* HardFault Interrupt */ + SVCALL_EXCEPTION = -5, /* SV Call Interrupt */ + PENDSV_EXCEPTION = -2, /* Pend SV Interrupt */ + SYSTICK_EXCEPTION = -1, /* System Tick Interrupt */ +}; + +/*! \brief Exception handler function type + * \ingroup hardware_exception + * + * All exceptions handlers should be of this type, and follow normal ARM EABI register saving conventions + */ +typedef void (*exception_handler_t)(void); + +/*! \brief Set the exception handler for an exception on the executing core. + * \ingroup hardware_exception + * + * This method will assert if an exception handler has been set for this exception number on this core via + * this method, without an intervening restore via exception_restore_handler. + * + * \note this method may not be used to override an exception handler that was specified at link time by + * providing a strong replacement for the weakly defined stub exception handlers. It will assert in this case too. + * + * \param num Exception number + * \param handler The handler to set + * \see exception_number + */ +exception_handler_t exception_set_exclusive_handler(enum exception_number num, exception_handler_t handler); + +/*! \brief Restore the original exception handler for an exception on this core + * \ingroup hardware_exception + * + * This method may be used to restore the exception handler for an exception on this core to the state + * prior to the call to exception_set_exclusive_handler(), so that exception_set_exclusive_handler() + * may be called again in the future. + * + * \param num Exception number \ref exception_nums + * \param original_handler The original handler returned from \ref exception_set_exclusive_handler + * \see exception_set_exclusive_handler() + */ +void exception_restore_handler(enum exception_number, exception_handler_t original_handler); + +/*! \brief Get the current exception handler for the specified exception from the currently installed vector table + * of the execution core + * \ingroup hardware_exception + * + * \param num Exception number + * \return the address stored in the VTABLE for the given exception number + */ +exception_handler_t exception_get_vtable_handler(enum exception_number num); +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/rp2_common/hardware_irq/include/hardware/irq.h b/src/rp2_common/hardware_irq/include/hardware/irq.h index 79ad4bd..428f4ad 100644 --- a/src/rp2_common/hardware_irq/include/hardware/irq.h +++ b/src/rp2_common/hardware_irq/include/hardware/irq.h @@ -255,7 +255,7 @@ static inline void irq_clear(uint int_num) { void irq_set_pending(uint num); -/*! \brief Perform IRQ priority intiialization for the current core +/*! \brief Perform IRQ priority initialization for the current core * * \note This is an internal method and user should generally not call it. */ diff --git a/src/rp2_common/pico_standard_link/crt0.S b/src/rp2_common/pico_standard_link/crt0.S index 95a44ff..3e6fc2e 100644 --- a/src/rp2_common/pico_standard_link/crt0.S +++ b/src/rp2_common/pico_standard_link/crt0.S @@ -74,6 +74,11 @@ __vectors: .word isr_irq30 .word isr_irq31 +// all default exception handlers do nothing, and we can check for them being set to our +// default values by them pointing to between __defaults_isrs_start and __default_isrs_end +.global __default_isrs_start +__default_isrs_start: + // Declare a weak symbol for each ISR. // By default, they will fall through to the undefined IRQ handler below (breakpoint), // but can be overridden by C functions with correct name. @@ -94,6 +99,9 @@ decl_isr_bkpt isr_svcall decl_isr_bkpt isr_pendsv decl_isr_bkpt isr_systick +.global __default_isrs_end +__default_isrs_end: + .macro decl_isr name .weak \name .type \name,%function diff --git a/test/kitchen_sink/CMakeLists.txt b/test/kitchen_sink/CMakeLists.txt index a0bfe1a..68d7a61 100644 --- a/test/kitchen_sink/CMakeLists.txt +++ b/test/kitchen_sink/CMakeLists.txt @@ -6,6 +6,7 @@ target_link_libraries(kitchen_sink_libs INTERFACE hardware_adc hardware_clocks hardware_divider + hardware_exception hardware_dma hardware_flash hardware_gpio diff --git a/test/kitchen_sink/kitchen_sink.c b/test/kitchen_sink/kitchen_sink.c index 3fc7b53..e607b80 100644 --- a/test/kitchen_sink/kitchen_sink.c +++ b/test/kitchen_sink/kitchen_sink.c @@ -11,6 +11,7 @@ #include "hardware/clocks.h" #include "hardware/divider.h" #include "hardware/dma.h" +#include "hardware/exception.h" #include "hardware/flash.h" #include "hardware/gpio.h" #include "hardware/i2c.h"