diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 608b466f358..71e4d7e44e4 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -952,6 +952,7 @@ config ARCH_ARMV7A bool default n select ARCH_HAVE_CPUINFO + select ARCH_HAVE_DEBUG select ARCH_HAVE_PERF_EVENTS select ARM_HAVE_WFE_SEV diff --git a/arch/arm/src/armv7-a/CMakeLists.txt b/arch/arm/src/armv7-a/CMakeLists.txt index 94aa50daf44..dcb6cdad907 100644 --- a/arch/arm/src/armv7-a/CMakeLists.txt +++ b/arch/arm/src/armv7-a/CMakeLists.txt @@ -55,6 +55,10 @@ if(CONFIG_ARMV7A_GICv2M) list(APPEND SRCS arm_gicv2m.c) endif() +if(CONFIG_ARCH_HAVE_DEBUG) + list(APPEND SRCS arm_hwdebug.c) +endif() + if(CONFIG_ARMV7A_HAVE_PTM) list(APPEND SRCS arm_timer.c) endif() diff --git a/arch/arm/src/armv7-a/Make.defs b/arch/arm/src/armv7-a/Make.defs index 99a998b4a85..690226f943f 100644 --- a/arch/arm/src/armv7-a/Make.defs +++ b/arch/arm/src/armv7-a/Make.defs @@ -46,6 +46,10 @@ ifeq ($(CONFIG_ARMV7A_GICv2M),y) CMN_CSRCS += arm_gicv2m.c endif +ifeq ($(CONFIG_ARCH_HAVE_DEBUG),y) + CMN_CSRCS += arm_hwdebug.c +endif + ifeq ($(CONFIG_ARMV7A_HAVE_PTM),y) CMN_CSRCS += arm_timer.c endif diff --git a/arch/arm/src/armv7-a/arm_hwdebug.c b/arch/arm/src/armv7-a/arm_hwdebug.c new file mode 100644 index 00000000000..62f4dca0bda --- /dev/null +++ b/arch/arm/src/armv7-a/arm_hwdebug.c @@ -0,0 +1,497 @@ +/**************************************************************************** + * arch/arm/src/armv7-a/arm_hwdebug.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Watchpoint and breakpoint control register share bits */ + +#define CP14_DBGBWCR_E CP14_DBGBCR_E +#define CP14_DBGBWCR_PAC_USER CP14_DBGBCR_PAC_USER +#define CP14_DBGBWCR_LSC_OFFSET CP14_DBGBCR_LSC_OFFSET +#define CP14_DBGBWCR_LSC_EXECUTE CP14_DBGBCR_LSC_EXECUTE +#define CP14_DBGBWCR_LSC_LOAD CP14_DBGBCR_LSC_LOAD +#define CP14_DBGBWCR_LSC_STORE CP14_DBGBCR_LSC_STORE +#define CP14_DBGBWCR_BAS_OFFSET CP14_DBGBCR_BAS_OFFSET +#define CP14_DBGBWCR_BAS_LEN_1 CP14_DBGBCR_BAS_LEN_1 +#define CP14_DBGBWCR_BAS_LEN_2 CP14_DBGBCR_BAS_LEN_2 +#define CP14_DBGBWCR_BAS_LEN_4 CP14_DBGBCR_BAS_LEN_4 +#define CP14_DBGBWCR_BAS_LEN_8 CP14_DBGBCR_BAS_LEN_8 + +/* Encode the bp control register field as a 32-bit value */ + +#define CP14_DBGBWCR_VAL(type, len) \ + (arm_convert_type(type) | arm_convert_size(len) | \ + CP14_DBGBCR_PAC_ALL | CP14_DBGBWCR_E) + +/**************************************************************************** + * Private Type + ****************************************************************************/ + +struct arm_debugpoint_s +{ + int type; + void *addr; + size_t size; + debug_callback_t callback; + void *arg; +}; + +struct arm_debug_s +{ + /* Breakpoint currently in use for each BRP, WRP */ + + struct arm_debugpoint_s brps[CP14_DBGDIDR_MAX_BRP]; + struct arm_debugpoint_s wrps[CP14_DBGDIDR_MAX_WRP]; +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static struct arm_debug_s g_arm_debug[CONFIG_SMP_NCPUS]; + +/**************************************************************************** + * Private Function + ****************************************************************************/ + +static uint32_t arm_convert_type(int type) +{ + switch (type) + { + case DEBUGPOINT_WATCHPOINT_RO: + return CP14_DBGBWCR_LSC_LOAD << CP14_DBGBWCR_LSC_OFFSET; + + case DEBUGPOINT_WATCHPOINT_WO: + return CP14_DBGBWCR_LSC_STORE << CP14_DBGBWCR_LSC_OFFSET; + + case DEBUGPOINT_WATCHPOINT_RW: + return (CP14_DBGBWCR_LSC_LOAD | CP14_DBGBWCR_LSC_STORE) + << CP14_DBGBWCR_LSC_OFFSET; + + case DEBUGPOINT_BREAKPOINT: + case DEBUGPOINT_STEPPOINT: + default: + return CP14_DBGBWCR_LSC_EXECUTE << CP14_DBGBWCR_LSC_OFFSET; + } +} + +static uint32_t arm_convert_size(size_t len) +{ + switch (len) + { + case 1: + return CP14_DBGBWCR_BAS_LEN_1 << CP14_DBGBWCR_BAS_OFFSET; + + case 2: + return CP14_DBGBWCR_BAS_LEN_2 << CP14_DBGBWCR_BAS_OFFSET; + + case 4: + return CP14_DBGBWCR_BAS_LEN_4 << CP14_DBGBWCR_BAS_OFFSET; + + case 8: + default: + return CP14_DBGBWCR_BAS_LEN_8 << CP14_DBGBWCR_BAS_OFFSET; + } +} + +/* Determine number of usable WRPs available. */ + +static int arm_get_num_wrps(void) +{ + return ((CP14_GET(DBGDIDR) >> CP14_DBGDIDR_WRPS_OFFSET) + & CP14_DBGDIDR_WRPS_MASK) + 1; +} + +/* Determine number of usable BRPs available. */ + +static int arm_get_num_brps(void) +{ + return ((CP14_GET(DBGDIDR) >> CP14_DBGDIDR_BRPS_OFFSET) + & CP14_DBGDIDR_BRPS_MASK) + 1; +} + +/**************************************************************************** + * Name: up_watchpoint_add + * + * Description: + * Add a watchpoint on the address. + * + * Input Parameters: + * type - The type of the watchpoint + * addr - The address to be watched + * size - The size of the address to be watched + * + * Returned Value: + * Index in wprs array on success; a negated errno value on failure + * + * Notes: + * The size of the watchpoint is determined by the hardware. + * + ****************************************************************************/ + +static int arm_watchpoint_add(int type, uint32_t addr, size_t size) +{ + int num = arm_get_num_wrps(); + int i; + + for (i = 0; i < num; i++) + { + if (!(CP14_GETN(DBGWCR, i) & CP14_DBGBWCR_E)) + { + CP14_SETN(DBGWVR, i, CP14_MASK_ADDR(addr)); + CP14_SETN(DBGWCR, i, CP14_DBGBWCR_VAL(type, size)); + return i; + } + } + + return -ENOSPC; +} + +/**************************************************************************** + * Name: arm_watchpoint_remove + * + * Description: + * Remove a watchpoint on the address. + * + * Input Parameters: + * addr - The address to be watched. + * + * Returned Value: + * Index in wprs array on success; a negated errno value on failure + * + ****************************************************************************/ + +static int arm_watchpoint_remove(uint32_t addr) +{ + int num = arm_get_num_wrps(); + int i; + + for (i = 0; i < num; i++) + { + if (CP14_GETN(DBGWVR, i) == CP14_MASK_ADDR(addr)) + { + CP14_SETN(DBGWCR, i, 0); + return i; + } + } + + return -ENOENT; +} + +/**************************************************************************** + * Name: arm_breakpoint_add + * + * Description: + * Add a breakpoint on addr. + * + * Input Parameters: + * addr - The address to break. + * + * Returned Value: + * Index in bprs array on success; a negated errno value on failure + * + * Notes: + * 1. If breakpoint is already set, it will do nothing. + * 2. If all comparators are in use, it will return -1. + * 3. When the breakpoint trigger, if enable monitor exception already , + * will cause a debug monitor exception, oaddr=0x4020392ctherwise will + * cause a hard fault. + * + ****************************************************************************/ + +static int arm_breakpoint_add(uintptr_t addr) +{ + int num = arm_get_num_brps(); + int i; + + for (i = 0; i < num; i++) + { + if (!(CP14_GETN(DBGBCR, i) & CP14_DBGBWCR_E)) + { + CP14_SETN(DBGBVR, i, CP14_MASK_ADDR(addr)); + CP14_SETN(DBGBCR, i, CP14_DBGBWCR_VAL(DEBUGPOINT_BREAKPOINT, 8)); + return i; + } + } + + return -ENOSPC; +} + +/**************************************************************************** + * Name: arm_breakpoint_remove + * + * Description: + * Remove a breakpoint on addr. + * + * Input Parameters: + * addr - The address to remove. + * + * Returned Value: + * Index in bprs array on success; a negated errno value on failure + * + ****************************************************************************/ + +static int arm_breakpoint_remove(uintptr_t addr) +{ + int num = arm_get_num_brps(); + int i; + + for (i = 0; i < num; i++) + { + if (CP14_GETN(DBGBVR, i) == CP14_MASK_ADDR(addr)) + { + CP14_SETN(DBGBCR, i, 0); + return i; + } + } + + return -ENOENT; +} + +static void arm_watchpoint_match(uint32_t addr) +{ + struct arm_debugpoint_s *dp = g_arm_debug[this_cpu()].wrps; + int num = arm_get_num_wrps(); + int i; + + for (i = 0; i < num; i++) + { + if (CP14_MASK_ADDR(dp[i].addr) == CP14_MASK_ADDR(addr)) + { + dp[i].callback(dp[i].type, dp[i].addr, + dp[i].size, dp[i].arg); + break; + } + } +} + +static void arm_breakpoint_match(uint32_t addr) +{ + struct arm_debugpoint_s *dp = g_arm_debug[this_cpu()].brps; + int num = arm_get_num_brps(); + int i; + + for (i = 0; i < num; i++) + { + if (CP14_MASK_ADDR(dp[i].addr) == CP14_MASK_ADDR(addr)) + { + dp[i].callback(dp[i].type, dp[i].addr, + dp[i].size, dp[i].arg); + break; + } + } +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: arm_enable_dbgmonitor + * + * Description: + * This function enables the debug monitor exception. + * + ****************************************************************************/ + +int arm_enable_dbgmonitor(void) +{ + /* Determine how many BRPs/WRPs are available. + * And work out the maximum supported watchpoint length. + */ + + binfo("found %d breakpoint and %d watchpoint registers.\n", + arm_get_num_brps(), arm_get_num_wrps()); + + /* If monitor mode is already enabled, just return. */ + + if (CP14_GET(DBGDSCRINT) & CP14_DBGDSCRINT_MDBGEN) + { + return OK; + } + + CP14_MOD(DBGDSCREXT, CP14_DBGDSCRINT_MDBGEN, CP14_DBGDSCRINT_MDBGEN); + + /* Check that the write made it through. */ + + if (!(CP14_GET(DBGDSCRINT) & CP14_DBGDSCRINT_MDBGEN)) + { + return -EPERM; + } + + return OK; +} + +/**************************************************************************** + * Name: arm_dbgmonitor + * + * Description: + * This is Debug Monitor exception handler. This function is entered when + * the processor enters debug mode. The debug monitor handler will handle + * debug events, and resume execution. + * + ****************************************************************************/ + +int arm_dbgmonitor(int irq, void *context, void *arg) +{ + switch (((CP14_GET(DBGDSCRINT)) >> CP14_DBGDSCRINT_MOE_OFFSET) + & CP14_DBGDSCRINT_MOE_MASK) + { + case CP14_DBGDSCRINT_MOE_BREAKPOINT: + case CP14_DBGDSCRINT_MOE_CFI_BREAKPOINT: + arm_breakpoint_match((uintptr_t)context); + break; + case CP14_DBGDSCRINT_MOE_ASYNC_WATCHPOINT: + case CP14_DBGDSCRINT_MOE_SYNC_WATCHPOINT: + arm_watchpoint_match((uintptr_t)context); + break; + } + + return OK; +} + +/**************************************************************************** + * Name: up_debugpoint_add + * + * Description: + * Add a debugpoint. + * + * Input Parameters: + * type - The debugpoint type. optional value: + * DEBUGPOINT_WATCHPOINT_RO - Read only watchpoint. + * DEBUGPOINT_WATCHPOINT_WO - Write only watchpoint. + * DEBUGPOINT_WATCHPOINT_RW - Read and write watchpoint. + * DEBUGPOINT_BREAKPOINT - Breakpoint. + * DEBUGPOINT_STEPPOINT - Single step. + * addr - The address to be debugged. + * size - The watchpoint size. only for watchpoint. + * callback - The callback function when debugpoint triggered. + * if NULL, the debugpoint will be removed. + * arg - The argument of callback function. + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +int up_debugpoint_add(int type, void *addr, size_t size, + debug_callback_t callback, void *arg) +{ + struct arm_debugpoint_s *dp; + int cpu = this_cpu(); + int ret = -EINVAL; + + if (type == DEBUGPOINT_BREAKPOINT) + { + ret = arm_breakpoint_add((uintptr_t)addr); + dp = g_arm_debug[cpu].brps; + } + else if (type == DEBUGPOINT_WATCHPOINT_RO || + type == DEBUGPOINT_WATCHPOINT_WO || + type == DEBUGPOINT_WATCHPOINT_RW) + { + ret = arm_watchpoint_add(type, (uintptr_t)addr, size); + dp = g_arm_debug[cpu].wrps; + } + + if (ret < 0) + { + return ret; + } + + dp[ret].type = type; + dp[ret].addr = addr; + dp[ret].size = size; + dp[ret].callback = callback; + dp[ret].arg = arg; + + return OK; +} + +/**************************************************************************** + * Name: up_debugpoint_remove + * + * Description: + * Remove a debugpoint. + * + * Input Parameters: + * type - The debugpoint type. optional value: + * DEBUGPOINT_WATCHPOINT_RO - Read only watchpoint. + * DEBUGPOINT_WATCHPOINT_WO - Write only watchpoint. + * DEBUGPOINT_WATCHPOINT_RW - Read and write watchpoint. + * DEBUGPOINT_BREAKPOINT - Breakpoint. + * DEBUGPOINT_STEPPOINT - Single step. + * addr - The address to be debugged. + * size - The watchpoint size. only for watchpoint. + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +int up_debugpoint_remove(int type, void *addr, size_t size) +{ + struct arm_debugpoint_s *dp; + int cpu = this_cpu(); + int ret = -EINVAL; + + if (type == DEBUGPOINT_BREAKPOINT) + { + ret = arm_breakpoint_remove((uintptr_t)addr); + dp = g_arm_debug[cpu].brps; + } + else if (type == DEBUGPOINT_WATCHPOINT_RO || + type == DEBUGPOINT_WATCHPOINT_WO || + type == DEBUGPOINT_WATCHPOINT_RW) + { + ret = arm_watchpoint_remove((uintptr_t)addr); + dp = g_arm_debug[cpu].wrps; + } + + if (ret < 0) + { + return ret; + } + + dp[ret].type = 0; + dp[ret].addr = 0; + dp[ret].size = 0; + dp[ret].callback = NULL; + dp[ret].arg = NULL; + + return OK; +} diff --git a/arch/arm/src/common/arm_internal.h b/arch/arm/src/common/arm_internal.h index b3dbf45a3b4..bb8ddc8e9bf 100644 --- a/arch/arm/src/common/arm_internal.h +++ b/arch/arm/src/common/arm_internal.h @@ -332,8 +332,6 @@ EXTERN const void * const _vectors[]; int arm_svcall(int irq, void *context, void *arg); int arm_hardfault(int irq, void *context, void *arg); -int arm_enable_dbgmonitor(void); -int arm_dbgmonitor(int irq, void *context, void *arg); # if defined(CONFIG_ARCH_ARMV7M) || defined(CONFIG_ARCH_ARMV8M) @@ -495,6 +493,11 @@ void arm_stack_check_init(void) noinstrument_function; void arm_coredump_add_region(void); #endif +#ifdef CONFIG_ARCH_HAVE_DEBUG +int arm_enable_dbgmonitor(void); +int arm_dbgmonitor(int irq, void *context, void *arg); +#endif + #undef EXTERN #ifdef __cplusplus }