diff --git a/Makefile.dep b/Makefile.dep index 9418201ff6..adabdabd7c 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -874,6 +874,9 @@ FEATURES_USED := $(sort $(FEATURES_REQUIRED) $(filter $(FEATURES_OPTIONAL),$(FEA # all periph features correspond to a periph submodule USEMODULE += $(filter periph_%,$(FEATURES_USED)) +# select cpu_check_address pseudomodule if the corresponding feature is used +USEMODULE += $(filter cpu_check_address, $(FEATURES_USED)) + # recursively catch transitive dependencies USEMODULE := $(sort $(USEMODULE)) USEPKG := $(sort $(USEPKG)) diff --git a/cpu/cortexm_common/Makefile.features b/cpu/cortexm_common/Makefile.features index 7fd73728ca..dfc9ccd52a 100644 --- a/cpu/cortexm_common/Makefile.features +++ b/cpu/cortexm_common/Makefile.features @@ -1,2 +1,3 @@ FEATURES_PROVIDED += periph_pm FEATURES_PROVIDED += cpp +FEATURES_PROVIDED += cpu_check_address diff --git a/cpu/cortexm_common/cortexm_init.c b/cpu/cortexm_common/cortexm_init.c index 0c02496c65..26db85d6f1 100644 --- a/cpu/cortexm_common/cortexm_init.c +++ b/cpu/cortexm_common/cortexm_init.c @@ -73,3 +73,53 @@ void cortexm_init(void) cortexm_init_isr_priorities(); cortexm_init_misc(); } + +bool cpu_check_address(volatile const char *address) +{ +#if defined(CPU_ARCH_CORTEX_M3) || defined(CPU_ARCH_CORTEX_M4) || \ + defined(CPU_ARCH_CORTEX_M4F) || defined(CPU_ARCH_CORTEX_M7) + static const uint32_t BFARVALID_MASK = (0x80 << SCB_CFSR_BUSFAULTSR_Pos); + + bool is_valid = true; + + /* Clear BFARVALID flag */ + SCB->CFSR |= BFARVALID_MASK; + + /* Ignore BusFault by enabling BFHFNMIGN and disabling interrupts */ + uint32_t mask = __get_FAULTMASK(); + __disable_fault_irq(); + SCB->CCR |= SCB_CCR_BFHFNMIGN_Msk; + + *address; + /* Check BFARVALID flag */ + if ((SCB->CFSR & BFARVALID_MASK) != 0) { + /* Bus Fault occured reading the address */ + is_valid = false; + } + + /* Reenable BusFault by clearing BFHFNMIGN */ + SCB->CCR &= ~SCB_CCR_BFHFNMIGN_Msk; + __set_FAULTMASK(mask); + + return is_valid; +#else + /* Cortex-M0 doesn't have BusFault so we need to catch HardFault */ + (void)address; + + bool result; + + __asm__ volatile ( + "movs r5, #1 \n" /* R5 will be set to 0 by HardFault handler */ + /* to indicate HardFault has occured */ + "ldr r1, =0xDEADF00D \n" /* set magic number */ + "ldr r2, =0xCAFEBABE \n" /* 2nd magic to be sure */ + "ldrb r3, %1 \n" /* probe address */ + "mov %0, r5 \n" /* store result */ + : "=r"(result) + : "m"(*address) + : "r1", "r2", "r3", "r5", "cc" + ); + + return result; +#endif +} diff --git a/cpu/cortexm_common/include/cpu.h b/cpu/cortexm_common/include/cpu.h index 676b086ebc..7bab7a80bc 100644 --- a/cpu/cortexm_common/include/cpu.h +++ b/cpu/cortexm_common/include/cpu.h @@ -228,6 +228,17 @@ static inline uint32_t cpu_get_image_baseaddr(void) } #endif +/** + * @brief Checks is memory address valid or not + * + * This function can be used to check for memory size, + * peripherals availability, etc. + * + * @param[in] address Address to check + * @return true if address is valid + */ +bool cpu_check_address(volatile const char *address); + #ifdef __cplusplus } #endif diff --git a/cpu/cortexm_common/vectors_cortexm.c b/cpu/cortexm_common/vectors_cortexm.c index 8657e92dc7..74f821b10a 100644 --- a/cpu/cortexm_common/vectors_cortexm.c +++ b/cpu/cortexm_common/vectors_cortexm.c @@ -165,6 +165,7 @@ __attribute__((naked)) void hard_fault_default(void) /* Get stack pointer where exception stack frame lies */ __asm__ volatile ( + ".syntax unified \n" /* Check that msp is valid first because we want to stack all the * r4-r11 registers so that we can use r0, r1, r2, r3 for other things. */ "mov r0, sp \n" /* r0 = msp */ @@ -188,6 +189,24 @@ __attribute__((naked)) void hard_fault_default(void) " use_psp: \n" /* else { */ "mrs r0, psp \n" /* r0 = psp */ " out: \n" /* } */ +#if (defined(CPU_ARCH_CORTEX_M0) || defined(CPU_ARCH_CORTEX_M0PLUS)) \ + && defined(MODULE_CPU_CHECK_ADDRESS) + /* catch intended HardFaults on Cortex-M0 to probe memory addresses */ + "ldr r1, [r0, #0x04] \n" /* read R1 from the stack */ + "ldr r2, =0xDEADF00D \n" /* magic number to be found */ + "cmp r1, r2 \n" /* compare with the magic number */ + "bne regular_handler \n" /* no magic -> handle as usual */ + "ldr r1, [r0, #0x08] \n" /* read R2 from the stack */ + "ldr r2, =0xCAFEBABE \n" /* 2nd magic number to be found */ + "cmp r1, r2 \n" /* compare with 2nd magic number */ + "bne regular_handler \n" /* no magic -> handle as usual */ + "ldr r1, [r0, #0x18] \n" /* read PC from the stack */ + "adds r1, r1, #2 \n" /* move to the next instruction */ + "str r1, [r0, #0x18] \n" /* modify PC in the stack */ + "ldr r5, =0 \n" /* set R5 to indicate HardFault */ + "bx lr \n" /* exit the exception handler */ + " regular_handler: \n" +#endif #if defined(CPU_ARCH_CORTEX_M0) || defined(CPU_ARCH_CORTEX_M0PLUS) \ || defined(CPU_ARCH_CORTEX_M23) "push {r4-r7} \n" /* save r4..r7 to the stack */ diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 20e7f562a2..3f0b799ed4 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -8,6 +8,7 @@ PSEUDOMODULES += conn_can_isotp_multi PSEUDOMODULES += cord_ep_standalone PSEUDOMODULES += core_% PSEUDOMODULES += cortexm_fpu +PSEUDOMODULES += cpu_check_address PSEUDOMODULES += ecc_% PSEUDOMODULES += emb6_router PSEUDOMODULES += event_% diff --git a/tests/cpu_cortexm_address_check/Makefile b/tests/cpu_cortexm_address_check/Makefile new file mode 100644 index 0000000000..cb9552c7e3 --- /dev/null +++ b/tests/cpu_cortexm_address_check/Makefile @@ -0,0 +1,10 @@ +BOARD ?= samr21-xpro + +include ../Makefile.tests_common + +FEATURES_REQUIRED += cpu_check_address + +USEMODULE += shell +USEMODULE += shell_commands + +include $(RIOTBASE)/Makefile.include diff --git a/tests/cpu_cortexm_address_check/README.md b/tests/cpu_cortexm_address_check/README.md new file mode 100644 index 0000000000..9469e3b11a --- /dev/null +++ b/tests/cpu_cortexm_address_check/README.md @@ -0,0 +1,12 @@ +# Cortex-M check for memory address validity + +## Introduction +Cortex-M3/M4/M7-based MCUs allow to check memory address validity +by temporarily blocking BusFault handler. + +Validity check can be used to determine RAM/flash/EEPROM sizes, +peripherals availability, etc., to create firmware that runs +effectively on different MCUs without recompiling. + +NB: Cortex-M0 and Cortex-M0+ don't have BusFault events, all +bus errors escalate to HardFault immediately. \ No newline at end of file diff --git a/tests/cpu_cortexm_address_check/main.c b/tests/cpu_cortexm_address_check/main.c new file mode 100644 index 0000000000..774e4db85d --- /dev/null +++ b/tests/cpu_cortexm_address_check/main.c @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2018 Unwired Devices LLC + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Test application for Cortex-M address checks + * + * @author Oleg Artamonov + * + * @} + */ + +#include +#include +#include + +#include "cpu.h" +#include "shell.h" + +static int cmd_check(int argc, char **argv) +{ + if (argc < 2) { + printf("usage: %s \n", argv[0]); + printf("\t example: %s 0x08080000\n", argv[0]); + return 1; + } + + char *address; + uint32_t address_value = strtoul(argv[1], NULL, 0); + + address = (char *)address_value; + + if (cpu_check_address(address)) + printf("Address 0x%08" PRIx32 " is valid. Feel free to access it\n", address_value); + else + printf("Address 0x%08" PRIx32 " is NOT valid. Accessing it will result in BusFault\n", address_value); + + return 0; +} + +static const shell_command_t shell_commands[] = { + { "check", "Check address", cmd_check}, + { NULL, NULL, NULL } +}; + +int main(void) +{ + puts("Address check test\n"); + puts("Please refer to the README.md for more details\n"); + puts("usage: check "); + puts("\texample: check 0x08080000"); + + /* run the shell */ + char line_buf[SHELL_DEFAULT_BUFSIZE]; + shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE); + return 0; +}