From e38bc99c9ade6e74f39565b41afb64f4a0136337 Mon Sep 17 00:00:00 2001 From: qinwei1 Date: Tue, 31 Oct 2023 00:25:42 +0800 Subject: [PATCH 1/2] arm64: add breakpoint/watchpoint implement for Arm64 Platform Summary This is a breakpoint/watchpoint implement for NuttX at Arm64 Platform. I will give a procfs example to show how to trigger watchpoint exception through write/read a global variable. Same method can be use to trigger breakpoint exception at code running TODO: 1. singlestep is need to be implement 2. SMP case Signed-off-by: qinwei1 --- arch/arm64/Kconfig | 4 + arch/arm64/src/common/Make.defs | 4 + arch/arm64/src/common/arm64_hwdebug.c | 1311 ++++++++++++++++++++++ arch/arm64/src/common/arm64_hwdebug.h | 337 ++++++ arch/arm64/src/common/arm64_initialize.c | 9 + 5 files changed, 1665 insertions(+) create mode 100644 arch/arm64/src/common/arm64_hwdebug.c create mode 100644 arch/arm64/src/common/arm64_hwdebug.h diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 70a7af908122a..c5694d3147eff 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -208,6 +208,7 @@ config ARCH_CORTEX_A53 select ARCH_HAVE_FPU select ARCH_HAVE_TESTSET select ARM64_HAVE_NEON + select ARCH_HAVE_DEBUG config ARCH_CORTEX_A55 bool @@ -232,6 +233,7 @@ config ARCH_CORTEX_A57 select ARCH_HAVE_FPU select ARCH_HAVE_TESTSET select ARM64_HAVE_NEON + select ARCH_HAVE_DEBUG config ARCH_CORTEX_A72 bool @@ -244,6 +246,7 @@ config ARCH_CORTEX_A72 select ARCH_HAVE_FPU select ARCH_HAVE_TESTSET select ARM64_HAVE_NEON + select ARCH_HAVE_DEBUG config ARCH_CORTEX_R82 bool @@ -255,6 +258,7 @@ config ARCH_CORTEX_R82 select ARCH_HAVE_FPU select ARCH_HAVE_TESTSET select ARM64_HAVE_NEON + select ARCH_HAVE_DEBUG config ARCH_FAMILY string diff --git a/arch/arm64/src/common/Make.defs b/arch/arm64/src/common/Make.defs index 4fe84f6e910e3..57dd0ad04bc37 100644 --- a/arch/arm64/src/common/Make.defs +++ b/arch/arm64/src/common/Make.defs @@ -119,3 +119,7 @@ endif ifeq ($(CONFIG_ARM64_SEMIHOSTING_HOSTFS),y) CMN_CSRCS += arm64_hostfs.c endif + +ifeq ($(CONFIG_ARCH_HAVE_DEBUG),y) + CMN_CSRCS += arm64_hwdebug.c +endif diff --git a/arch/arm64/src/common/arm64_hwdebug.c b/arch/arm64/src/common/arm64_hwdebug.c new file mode 100644 index 0000000000000..01e9770a65614 --- /dev/null +++ b/arch/arm64/src/common/arm64_hwdebug.c @@ -0,0 +1,1311 @@ +/**************************************************************************** + * arch/arm64/src/common/arm64_hwdebug.c + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "arm64_hwdebug.h" +#include "arm64_fatal.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define READ_WB_REG_CASE(OFF, N, REG, VAL) \ + case (OFF + N): \ + AARCH64_DBG_READ(N, REG, VAL); \ + break + +#define WRITE_WB_REG_CASE(OFF, N, REG, VAL) \ + case (OFF + N): \ + AARCH64_DBG_WRITE(N, REG, VAL); \ + break + +#define GEN_READ_WB_REG_CASES(OFF, REG, VAL) \ + READ_WB_REG_CASE(OFF, 0, REG, VAL); \ + READ_WB_REG_CASE(OFF, 1, REG, VAL); \ + READ_WB_REG_CASE(OFF, 2, REG, VAL); \ + READ_WB_REG_CASE(OFF, 3, REG, VAL); \ + READ_WB_REG_CASE(OFF, 4, REG, VAL); \ + READ_WB_REG_CASE(OFF, 5, REG, VAL); \ + READ_WB_REG_CASE(OFF, 6, REG, VAL); \ + READ_WB_REG_CASE(OFF, 7, REG, VAL); \ + READ_WB_REG_CASE(OFF, 8, REG, VAL); \ + READ_WB_REG_CASE(OFF, 9, REG, VAL); \ + READ_WB_REG_CASE(OFF, 10, REG, VAL); \ + READ_WB_REG_CASE(OFF, 11, REG, VAL); \ + READ_WB_REG_CASE(OFF, 12, REG, VAL); \ + READ_WB_REG_CASE(OFF, 13, REG, VAL); \ + READ_WB_REG_CASE(OFF, 14, REG, VAL); \ + READ_WB_REG_CASE(OFF, 15, REG, VAL) + +#define GEN_WRITE_WB_REG_CASES(OFF, REG, VAL) \ + WRITE_WB_REG_CASE(OFF, 0, REG, VAL); \ + WRITE_WB_REG_CASE(OFF, 1, REG, VAL); \ + WRITE_WB_REG_CASE(OFF, 2, REG, VAL); \ + WRITE_WB_REG_CASE(OFF, 3, REG, VAL); \ + WRITE_WB_REG_CASE(OFF, 4, REG, VAL); \ + WRITE_WB_REG_CASE(OFF, 5, REG, VAL); \ + WRITE_WB_REG_CASE(OFF, 6, REG, VAL); \ + WRITE_WB_REG_CASE(OFF, 7, REG, VAL); \ + WRITE_WB_REG_CASE(OFF, 8, REG, VAL); \ + WRITE_WB_REG_CASE(OFF, 9, REG, VAL); \ + WRITE_WB_REG_CASE(OFF, 10, REG, VAL); \ + WRITE_WB_REG_CASE(OFF, 11, REG, VAL); \ + WRITE_WB_REG_CASE(OFF, 12, REG, VAL); \ + WRITE_WB_REG_CASE(OFF, 13, REG, VAL); \ + WRITE_WB_REG_CASE(OFF, 14, REG, VAL); \ + WRITE_WB_REG_CASE(OFF, 15, REG, VAL) + +enum hw_breakpoint_ops +{ + HW_BREAKPOINT_INSTALL, + HW_BREAKPOINT_UNINSTALL, + HW_BREAKPOINT_RESTORE +}; + +enum dbg_active_el +{ + DBG_ACTIVE_EL0 = 0, + DBG_ACTIVE_EL1, +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static struct arm64_breakpoint_context g_cpu_bp_ctx[CONFIG_SMP_NCPUS]; +static struct arm64_breakpoint_context g_cpu_wp_ctx[CONFIG_SMP_NCPUS]; +static struct arm64_debugpoint_slot g_debugpoint_slots[CONFIG_SMP_NCPUS]; + +static int g_mde_ref_count[CONFIG_SMP_NCPUS]; +static int g_kde_ref_count[CONFIG_SMP_NCPUS]; + +static struct list_node g_break_inst_hook_list_el0; +static struct list_node g_break_inst_hook_list_el1; + +static spinlock_t g_debugpoint_slots_lock; +static spinlock_t g_debug_hook_lock; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static uint64_t read_wb_reg(int reg, int n) +{ + uint64_t val = 0; + + switch (reg + n) + { + GEN_READ_WB_REG_CASES(AARCH64_DBG_REG_BVR, + AARCH64_DBG_REG_NAME_BVR, val); + GEN_READ_WB_REG_CASES(AARCH64_DBG_REG_BCR, + AARCH64_DBG_REG_NAME_BCR, val); + GEN_READ_WB_REG_CASES(AARCH64_DBG_REG_WVR, + AARCH64_DBG_REG_NAME_WVR, val); + GEN_READ_WB_REG_CASES(AARCH64_DBG_REG_WCR, + AARCH64_DBG_REG_NAME_WCR, val); + + default: + { + sinfo("attempt to read from unknown breakpoint register %d\n", n); + } + } + + return val; +} + +static void write_wb_reg(int reg, int n, uint64_t val) +{ + switch (reg + n) + { + GEN_WRITE_WB_REG_CASES(AARCH64_DBG_REG_BVR, + AARCH64_DBG_REG_NAME_BVR, val); + GEN_WRITE_WB_REG_CASES(AARCH64_DBG_REG_BCR, + AARCH64_DBG_REG_NAME_BCR, val); + GEN_WRITE_WB_REG_CASES(AARCH64_DBG_REG_WVR, + AARCH64_DBG_REG_NAME_WVR, val); + GEN_WRITE_WB_REG_CASES(AARCH64_DBG_REG_WCR, + AARCH64_DBG_REG_NAME_WCR, val); + + default: + { + sinfo("attempt to write to unknown breakpoint register %d\n", n); + } + } + + ARM64_ISB(); +} + +static uint8_t hw_breakpoint_count(void) +{ + uint64_t dfr0 = read_sysreg(id_aa64dfr0_el1); + uint8_t count = (uint8_t)(((dfr0 & ARM64_ID_AADFR0_EL1_BRPS) >> + ARM64_ID_AADFR0_EL1_BRPS_SHIFT) + 1lu); + + return count; +} + +static uint8_t hw_watchpoint_count(void) +{ + uint64_t dfr0 = read_sysreg(id_aa64dfr0_el1); + uint8_t count = (uint8_t)(((dfr0 & ARM64_ID_AADFR0_EL1_WRPS) >> + ARM64_ID_AADFR0_EL1_WRPS_SHIFT) + 1lu); + + return count; +} + +/* MDSCR access routines. */ + +static void mdscr_write(uint32_t mdscr) +{ + irqstate_t flags; + + flags = spin_lock_irqsave(&g_debug_hook_lock); + write_sysreg(mdscr, mdscr_el1); + spin_unlock_irqrestore(&g_debug_hook_lock, flags); +} + +static uint32_t mdscr_read(void) +{ + return read_sysreg(mdscr_el1); +} + +static void enable_debug_monitors(enum dbg_active_el el) +{ + uint32_t mdscr; + uint32_t enable = 0; + uint8_t cpu; + + cpu = this_cpu(); + g_mde_ref_count[cpu]++; + g_kde_ref_count[cpu]++; + + if (g_mde_ref_count[cpu] == 1) + { + enable = DBG_MDSCR_MDE; + } + + if (el == DBG_ACTIVE_EL1 && g_kde_ref_count[cpu] == 1) + { + enable |= DBG_MDSCR_KDE; + } + + if (enable) + { + mdscr = mdscr_read(); + mdscr |= enable; + mdscr_write(mdscr); + } +} + +static void disable_debug_monitors(enum dbg_active_el el) +{ + uint32_t mdscr; + uint32_t disable = 0; + uint8_t cpu; + + cpu = this_cpu(); + + g_mde_ref_count[cpu]--; + g_kde_ref_count[cpu]--; + + if (g_mde_ref_count[cpu] == 0) + { + disable = ~DBG_MDSCR_MDE; + } + + if (el == DBG_ACTIVE_EL1 && g_kde_ref_count[cpu] == 0) + { + disable &= ~DBG_MDSCR_KDE; + } + + if (disable) + { + mdscr = mdscr_read(); + mdscr &= disable; + mdscr_write(mdscr); + } +} + +static void register_debug_hook(struct list_node *list_head, + struct list_node *entry) +{ + irqstate_t flags; + + flags = spin_lock_irqsave(&g_debug_hook_lock); + if (list_is_empty(entry)) + { + list_add_tail(list_head, entry); + } + + spin_unlock_irqrestore(&g_debug_hook_lock, flags); +} + +static void unregister_debug_hook(struct list_node *node) +{ + irqstate_t flags; + + flags = spin_lock_irqsave(&g_debug_hook_lock); + list_delete_init(node); + spin_unlock_irqrestore(&g_debug_hook_lock, flags); +} + +static int call_break_inst_hook(struct regs_context *regs, uint64_t esr) +{ + struct list_node *list; + break_func_t func = NULL; + struct break_inst_hook *curr; + struct break_inst_hook *next; + uint64_t comment; + int el; + irqstate_t flags; + + el = arm64_current_el(); + switch (el) + { + case MODE_EL1: + { + list = &g_break_inst_hook_list_el1; + break; + } + + case MODE_EL0: + { + list = &g_break_inst_hook_list_el0; + break; + } + + case MODE_EL2: + default: + { + return DBG_HOOK_ERROR; + } + } + + /* brk exception disables interrupt, this function is + * entirely not preemptible and wait, and we can use + * list safely here. + */ + + flags = spin_lock_irqsave(&g_debug_hook_lock); + list_for_every_entry_safe(list, curr, next, struct break_inst_hook, entry) + { + comment = esr & ESR_ELX_BRK64_ISS_COMMENT_MASK; + + if ((comment & ~curr->mask) == curr->imm) + { + func = curr->func; + } + } + + spin_unlock_irqrestore(&g_debug_hook_lock, flags); + + return func ? func(regs, esr) : DBG_HOOK_ERROR; +} + +static int arm64_brk_handler(struct regs_context *regs, uint64_t far, + uint64_t esr) +{ + int el; + int ret; + + ret = call_break_inst_hook(regs, esr); + + if (ret == 0) + { + return ret; + } + + el = arm64_current_el(); + sinfo("Unexpected kernel BRK exception at EL%d\n", el); + return -EFAULT; +} + +static struct arch_hw_breakpoint *arm64_hw_breakpoint_find(int handle) +{ + uint8_t cpu; + + VERIFY(handle > 0 && handle <= ARM64_MAX_HBP_SLOTS); + + cpu = this_cpu(); + + return (handle < ARM64_MAX_BRP) ? + &g_cpu_bp_ctx[cpu].on_reg[handle]: + &g_cpu_wp_ctx[cpu].on_reg[handle - ARM64_MAX_BRP]; +} + +static void arm64_hw_breakpoint_put(int handle) +{ + uint8_t cpu; + struct arm64_breakpoint_context *ctx; + + VERIFY(handle > 0 && handle <= ARM64_MAX_HBP_SLOTS); + + cpu = this_cpu(); + ctx = (handle < ARM64_MAX_BRP) ? + &g_cpu_bp_ctx[cpu] : &g_cpu_wp_ctx[cpu]; + + handle = handle < ARM64_MAX_BRP ? handle: handle - ARM64_MAX_BRP; + ctx->on_reg[handle].in_used = 0; +} + +static int arm64_hw_breakpoint_getslot(struct arch_hw_breakpoint **info, + int type) +{ + int i; + int handle = -ENOSPC; + uint8_t cpu; + + struct arm64_breakpoint_context *ctx; + + cpu = this_cpu(); + ctx = (type == DEBUGPOINT_BREAKPOINT) ? + &g_cpu_bp_ctx[cpu] : &g_cpu_wp_ctx[cpu]; + + for (i = 0; ctx->core_num; i++) + { + if (!ctx->on_reg[i].in_used) + { + ctx->on_reg[i].in_used = 1; + *info = &ctx->on_reg[i]; + handle = i; + break; + } + } + + return (type == DEBUGPOINT_BREAKPOINT) ? + handle : handle + ARM64_MAX_BRP; +} + +/* Construct an arch_hw_breakpoint */ + +static int arch_build_bp_info(struct arch_hw_breakpoint *hw, uintptr_t addr, + size_t size, int type) +{ + /* Type */ + + switch (type) + { + case DEBUGPOINT_BREAKPOINT: + { + hw->ctrl.type = ARM_BREAKPOINT_EXECUTE; + break; + } + + case DEBUGPOINT_WATCHPOINT_RO: + { + hw->ctrl.type = ARM_BREAKPOINT_LOAD; + break; + } + + case DEBUGPOINT_WATCHPOINT_WO: + { + hw->ctrl.type = ARM_BREAKPOINT_STORE; + break; + } + + case DEBUGPOINT_WATCHPOINT_RW: + { + hw->ctrl.type = ARM_BREAKPOINT_LOAD | ARM_BREAKPOINT_STORE; + break; + } + + default: + { + return -EINVAL; + } + } + + /* Len */ + + switch (size) + { + case BREAKPOINT_LEN_1: + { + hw->ctrl.len = ARM_BREAKPOINT_LEN_1; + break; + } + + case BREAKPOINT_LEN_2: + { + hw->ctrl.len = ARM_BREAKPOINT_LEN_2; + break; + } + + case BREAKPOINT_LEN_3: + { + hw->ctrl.len = ARM_BREAKPOINT_LEN_3; + break; + } + + case BREAKPOINT_LEN_4: + { + hw->ctrl.len = ARM_BREAKPOINT_LEN_4; + break; + } + + case BREAKPOINT_LEN_5: + { + hw->ctrl.len = ARM_BREAKPOINT_LEN_5; + break; + } + + case BREAKPOINT_LEN_6: + { + hw->ctrl.len = ARM_BREAKPOINT_LEN_6; + break; + } + + case BREAKPOINT_LEN_7: + { + hw->ctrl.len = ARM_BREAKPOINT_LEN_7; + break; + } + + case BREAKPOINT_LEN_8: + { + hw->ctrl.len = ARM_BREAKPOINT_LEN_8; + break; + } + + default: + { + return -EINVAL; + } + } + + /* On AArch64, we only permit breakpoints of length 4, whereas + * AArch32 also requires breakpoints of length 2 for Thumb. + * Watchpoints can be of length 1, 2, 4 or 8 bytes. + */ + + if (hw->ctrl.type == ARM_BREAKPOINT_EXECUTE) + { + if (hw->ctrl.len != ARM_BREAKPOINT_LEN_2 && + hw->ctrl.len != ARM_BREAKPOINT_LEN_4) + { + return -EINVAL; + } + } + + /* Address */ + + hw->address = addr; + hw->type = type; + hw->size = size; + + /* Privilege + * Note that we disallow combined EL0/EL1 breakpoints because + * that would complicate the stepping code. + */ + + hw->ctrl.privilege = AARCH64_BREAKPOINT_EL1; + + /* Enabled? */ + + hw->ctrl.enabled = 0; + + return 0; +} + +/* Validate the arch-specific HW Breakpoint register settings. */ + +int arm64_hw_breakpoint_build(struct arch_hw_breakpoint **hw_ret, + uintptr_t addr, + size_t size, int type) +{ + int ret; + int handle; + uint64_t alignment_mask; + uint64_t offset; + struct arch_hw_breakpoint *hw = NULL; + + handle = arm64_hw_breakpoint_getslot(&hw, type); + if (handle < 0) + { + return handle; + } + + /* Build the arch_hw_breakpoint. */ + + ret = arch_build_bp_info(hw, addr, size, type); + if (ret < 0) + { + goto error_return; + } + + /* Check address alignment. + * We don't do any clever alignment correction for watchpoints + * because using 64-bit unaligned addresses is deprecated for + * AArch64. + * + * AArch32 tasks expect some simple alignment fixups, so emulate + * that here. + */ + + if (hw->ctrl.len == ARM_BREAKPOINT_LEN_8) + { + alignment_mask = 0x7; + } + else + { + alignment_mask = 0x3; + } + + offset = hw->address & alignment_mask; + switch (offset) + { + case 0: + { + /* Aligned */ + + break; + } + + case 1: + case 2: + { + /* Allow halfword watchpoints and breakpoints. */ + + if (hw->ctrl.len == ARM_BREAKPOINT_LEN_2) + { + break; + } + } + + case 3: + { + /* Allow single byte watchpoint. */ + + if (hw->ctrl.len == ARM_BREAKPOINT_LEN_1) + { + break; + } + } + + default: + { + ret = -EINVAL; + goto error_return; + } + } + + hw->address &= ~alignment_mask; + hw->ctrl.len <<= offset; + *hw_ret = hw; + + return handle; + +error_return: + arm64_hw_breakpoint_put(handle); + return ret; +} + +static int arm64_hw_breakpoint_control(int handle, + enum hw_breakpoint_ops ops) +{ + struct arm64_breakpoint_context *ctx; + struct arch_hw_breakpoint *info; + enum dbg_active_el dbg_el = DBG_ACTIVE_EL1; + + int i; + int ctrl_reg; + int val_reg; + int reg_enable; + uint32_t ctrl; + uint8_t cpu; + + VERIFY(handle > 0 && handle <= ARM64_MAX_HBP_SLOTS); + + cpu = this_cpu(); + ctx = (handle < ARM64_MAX_BRP) ? + &g_cpu_bp_ctx[cpu] : &g_cpu_wp_ctx[cpu]; + + VERIFY(handle > 0 && handle <= ARM64_MAX_HBP_SLOTS); + + info = arm64_hw_breakpoint_find(handle); + + if (info->ctrl.type == ARM_BREAKPOINT_EXECUTE) + { + /* Breakpoint */ + + ctrl_reg = AARCH64_DBG_REG_BCR; + val_reg = AARCH64_DBG_REG_BVR; + reg_enable = !ctx->disabled; + } + else + { + /* Watchpoint */ + + ctrl_reg = AARCH64_DBG_REG_WCR; + val_reg = AARCH64_DBG_REG_WVR; + reg_enable = !ctx->disabled; + } + + i = (handle < ARM64_MAX_BRP) ? handle: handle - ARM64_MAX_BRP; + + switch (ops) + { + case HW_BREAKPOINT_INSTALL: + { + /* Ensure debug monitors are enabled at the correct exception + * level. + */ + + enable_debug_monitors(dbg_el); + } + + case HW_BREAKPOINT_RESTORE: + { + /* Setup the address register. */ + + write_wb_reg(val_reg, i, info->address); + + /* Setup the control register. */ + + ctrl = encode_ctrl_reg(info->ctrl); + write_wb_reg(ctrl_reg, i, reg_enable ? ctrl | 0x1 : ctrl & ~0x1); + break; + } + + case HW_BREAKPOINT_UNINSTALL: + { + /* Reset the control register. */ + + write_wb_reg(ctrl_reg, i, 0); + + /* Release the debug monitors for the correct exception + * level. + */ + + disable_debug_monitors(dbg_el); + break; + } + } + + return 0; +} + +static int arm64_breakpoint_report(struct arch_hw_breakpoint *bp, + uint64_t addr, struct regs_context *regs) +{ + bp->trigger = addr; + bp->handle_fn(bp->type, (void *)addr, bp->size, bp->arg); + + return 0; +} + +static int arm64_watchpoint_report(struct arch_hw_breakpoint *wp, + uint64_t addr, struct regs_context *regs) +{ + wp->trigger = addr; + wp->handle_fn(wp->type, (void *)addr, wp->size, wp->arg); + + return 0; +} + +/* Enable/disable all of the breakpoints active at the specified + * exception level at the register level. + * This is used when single-stepping after a breakpoint exception. + */ + +static void arm64_toggle_bp_registers(int reg, enum dbg_active_el el, + int enable) +{ + struct arm64_breakpoint_context *ctx; + int i; + uint32_t ctrl; + uint8_t cpu; + + cpu = this_cpu(); + + switch (reg) + { + case AARCH64_DBG_REG_BCR: + { + ctx = &g_cpu_bp_ctx[cpu]; + break; + } + + case AARCH64_DBG_REG_WCR: + { + ctx = &g_cpu_wp_ctx[cpu]; + break; + } + + default: + { + return; + } + } + + for (i = 0; i < ctx->core_num; ++i) + { + if (!ctx->on_reg[i].in_used) + { + continue; + } + + ctrl = read_wb_reg(reg, i); + if (enable) + { + ctrl |= 0x1; + } + else + { + ctrl &= ~0x1; + } + + write_wb_reg(reg, i, ctrl); + } +} + +static int arm64_breakpoint_handler(struct regs_context *regs, + uint64_t unused, uint64_t esr) +{ + struct arm64_breakpoint_context *ctx; + struct arch_hw_breakpoint *bp; + struct arch_hw_breakpoint_ctrl ctrl; + int i; + int step = 0; + uint32_t ctrl_reg; + uint64_t addr; + uint64_t val; + uint8_t cpu; + int el; + + addr = regs->elr; + + cpu = this_cpu(); + ctx = &g_cpu_bp_ctx[cpu]; + + for (i = 0; i < ctx->core_num; ++i) + { + if (!ctx->on_reg[i].in_used) + { + continue; + } + + bp = &ctx->on_reg[i]; + + /* Check if the breakpoint value matches. */ + + val = read_wb_reg(AARCH64_DBG_REG_BVR, i); + if (val != (addr & ~0x3)) + { + continue; + } + + /* Possible match, check the byte address select to confirm. */ + + ctrl_reg = read_wb_reg(AARCH64_DBG_REG_BCR, i); + decode_ctrl_reg(ctrl_reg, &ctrl); + + if (!((1 << (addr & 0x3)) & ctrl.len)) + { + continue; + } + + bp->trigger = addr; + step = arm64_breakpoint_report(bp, addr, regs); + } + + if (step == 1) + { + return 0; + } + + el = arm64_current_el(); + switch (el) + { + case MODE_EL1: + { + arm64_toggle_bp_registers(AARCH64_DBG_REG_BCR, DBG_ACTIVE_EL1, 0); + break; + } + + case MODE_EL0: + { + arm64_toggle_bp_registers(AARCH64_DBG_REG_BCR, DBG_ACTIVE_EL0, 0); + break; + } + + case MODE_EL2: + default: + { + break; + } + } + + return 0; +} + +static int arm64_watchpoint_handler(struct regs_context *regs, uint64_t addr, + uint64_t esr) +{ + struct arm64_breakpoint_context *ctx; + struct arch_hw_breakpoint *wp; + struct arch_hw_breakpoint_ctrl ctrl; + int i; + int step = 0; + int access; + uint32_t ctrl_reg; + uint64_t val; + uint8_t cpu; + int el; + + cpu = this_cpu(); + ctx = &g_cpu_wp_ctx[cpu]; + + /* Find all watchpoints that match the reported address. If no exact + * match is found. Attribute the hit to the closest watchpoint. + */ + + for (i = 0; i < ctx->core_num; ++i) + { + if (!ctx->on_reg[i].in_used) + { + continue; + } + + wp = &ctx->on_reg[i]; + + /* Check that the access type matches. + * 0 => load, otherwise => store + */ + + access = (esr & AARCH64_ESR_ACCESS_MASK) ? + ARM_BREAKPOINT_STORE :ARM_BREAKPOINT_LOAD; + + if (!(access & wp->ctrl.type)) + { + continue; + } + + /* Check if the watchpoint value and byte select match. */ + + val = read_wb_reg(AARCH64_DBG_REG_WVR, i); + ctrl_reg = read_wb_reg(AARCH64_DBG_REG_WCR, i); + decode_ctrl_reg(ctrl_reg, &ctrl); + + if (val != addr) + { + continue; + } + + step = arm64_watchpoint_report(wp, addr, regs); + } + + if (step == 1) + { + return 0; + } + + /* We always disable EL0 watchpoints because the kernel can + * cause these to fire via an unprivileged access. + */ + + arm64_toggle_bp_registers(AARCH64_DBG_REG_WCR, DBG_ACTIVE_EL0, 0); + + el = arm64_current_el(); + switch (el) + { + case MODE_EL1: + { + arm64_toggle_bp_registers(AARCH64_DBG_REG_WCR, DBG_ACTIVE_EL1, 0); + break; + } + + case MODE_EL0: + case MODE_EL2: + default: + { + break; + } + } + + return 0; +} + +static int arm64_single_step_handler(struct regs_context *regs, + uint64_t far, uint64_t esr) +{ + return 0; +} + +static int arm64_clear_os_lock(unsigned int cpu) +{ + write_sysreg(0, osdlr_el1); + write_sysreg(0, oslar_el1); + + ARM64_ISB(); + return 0; +} + +/* CPU initialisation. */ + +static int arm64_hw_breakpoint_reset(unsigned int cpu) +{ + struct arm64_breakpoint_context *bp_ctx; + struct arm64_breakpoint_context *wp_ctx; + int i; + + bp_ctx = &g_cpu_bp_ctx[cpu]; + wp_ctx = &g_cpu_wp_ctx[cpu]; + + /* When a CPU goes through cold-boot, it does not have any installed + * slot, so it is safe to share the same function for restoring and + * resetting breakpoints; when a CPU is hotplugged in, it goes + * through the slots, which are all empty, hence it just resets control + * and value for debug registers. + */ + + for (i = 0; i < bp_ctx->core_num; ++i) + { + write_wb_reg(AARCH64_DBG_REG_BCR, i, 0UL); + write_wb_reg(AARCH64_DBG_REG_BVR, i, 0UL); + } + + for (i = 0; i < wp_ctx->core_num; ++i) + { + write_wb_reg(AARCH64_DBG_REG_WCR, i, 0UL); + write_wb_reg(AARCH64_DBG_REG_WVR, i, 0UL); + } + + arm64_clear_os_lock(cpu); + + sinfo("reset done"); + + return 0; +} + +/**************************************************************************** + * Name: arm64_hw_breakpoint_enable + * + * Description: + * enable a debugpoint. + * + * Input Parameters: + * handle - The Handle number for this breakpoint + * enable - enable/disable this breakpoint + * true - enable + * false - disable + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int arm64_hw_breakpoint_enable(int handle, bool enable) +{ + if (enable) + { + return arm64_hw_breakpoint_control(handle, HW_BREAKPOINT_INSTALL); + } + else + { + return arm64_hw_breakpoint_control(handle, HW_BREAKPOINT_UNINSTALL); + } +} + +static struct arm64_debugpoint *arm64_debugpoint_slot_find(int type, + void *addr, + size_t size) +{ + int i; + uint8_t cpu; + struct arm64_debugpoint *dbpoint = NULL; + struct arm64_debugpoint_slot *slots; + + cpu = this_cpu(); + slots = &g_debugpoint_slots[cpu]; + + for (i = 0; i < ARM64_MAX_HBP_SLOTS; i++) + { + if (slots->slot[i].in_used) + { + if ((slots->slot[i].type == type) && + (slots->slot[i].addr == addr) && + (slots->slot[i].size == size)) + { + dbpoint = &slots->slot[i]; + break; + } + } + } + + return dbpoint; +} + +static struct arm64_debugpoint *arm64_debugpoint_slot_getfree(void) +{ + int i; + uint8_t cpu; + struct arm64_debugpoint *dbpoint = NULL; + struct arm64_debugpoint_slot *slots; + + cpu = this_cpu(); + slots = &g_debugpoint_slots[cpu]; + + for (i = 0; i < ARM64_MAX_HBP_SLOTS; i++) + { + if (!slots->slot[i].in_used) + { + dbpoint = &slots->slot[i]; + dbpoint->in_used = 1; + break; + } + } + + return dbpoint; +} + +static void arm64_debugpoint_slot_release(struct arm64_debugpoint *dbpoint) +{ + dbpoint->in_used = 0; + dbpoint->addr = 0; + dbpoint->type = 0; + dbpoint->size = 0; + dbpoint->hbp_slot = 0; + dbpoint->in_used = 0; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void arm64_register_user_break_hook(struct break_inst_hook *hook) +{ + register_debug_hook(&hook->entry, &g_break_inst_hook_list_el0); +} + +void arm64_unregister_user_break_hook(struct break_inst_hook *hook) +{ + unregister_debug_hook(&hook->entry); +} + +void arm64_register_kernel_break_hook(struct break_inst_hook *hook) +{ + register_debug_hook(&hook->entry, &g_break_inst_hook_list_el1); +} + +void arm64_unregister_kernel_break_hook(struct break_inst_hook *hook) +{ + unregister_debug_hook(&hook->entry); +} + +/**************************************************************************** + * 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(arm64 specific). + * BREAKPOINT_LEN_1 = 1, + * BREAKPOINT_LEN_2 = 2, + * BREAKPOINT_LEN_3 = 3, + * BREAKPOINT_LEN_4 = 4, + * BREAKPOINT_LEN_5 = 5, + * BREAKPOINT_LEN_6 = 6, + * BREAKPOINT_LEN_7 = 7, + * BREAKPOINT_LEN_8 = 8 + * 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 arch_hw_breakpoint *bp; + struct arm64_debugpoint *dbpoint; + + int handle; + int ret; + irqstate_t flags; + + flags = spin_lock_irqsave(&g_debugpoint_slots_lock); + dbpoint = arm64_debugpoint_slot_find(type, addr, size); + if (dbpoint != NULL) + { + sinfo("the debugpoint has been register\n"); + ret = -EEXIST; + goto error_return; + } + + /* get free debugpoint */ + + dbpoint = arm64_debugpoint_slot_getfree(); + if (dbpoint == 0) + { + sinfo("Not more slot can be use\n"); + ret = -ENOSPC; + goto error_return; + } + + handle = arm64_hw_breakpoint_build(&bp, (uintptr_t)addr, size, type); + if (handle < 0) + { + arm64_debugpoint_slot_release(dbpoint); + ret = handle; + goto error_return; + } + + bp->handle_fn = callback; + bp->arg = arg; + + dbpoint->addr = addr; + dbpoint->size = size; + dbpoint->type = type; + dbpoint->hbp_slot = handle; + + ret = arm64_hw_breakpoint_enable(handle, true); + +error_return: + spin_unlock_irqrestore(&g_debugpoint_slots_lock, flags); + return ret; +} + +/**************************************************************************** + * Name: up_debugpoint_remove + * + * Description: + * Remove a debugpoint.before remove the watchpoint/breakpoint, + * it will disable frist + * + * 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 arm64_debugpoint *dbpoint; + int ret; + int handle; + irqstate_t flags; + + /* ret == 0, the breakpoint hasn't been registered + * ret != 0, the breakpoint has been register + */ + + flags = spin_lock_irqsave(&g_debugpoint_slots_lock); + dbpoint = arm64_debugpoint_slot_find(type, addr, size); + if (dbpoint == NULL) + { + sinfo("the debugpoint hasn't been register\n"); + spin_unlock_irqrestore(&g_debugpoint_slots_lock, flags); + return -ENODEV; + } + + handle = dbpoint->hbp_slot; + + VERIFY(handle > 0 && handle <= ARM64_MAX_HBP_SLOTS); + + ret = arm64_hw_breakpoint_enable(handle, false); + arm64_hw_breakpoint_put(handle); + arm64_debugpoint_slot_release(dbpoint); + + spin_unlock_irqrestore(&g_debugpoint_slots_lock, flags); + + return ret; +} + +/* One-time initialisation. */ + +void arm64_hwdebug_init(void) +{ + struct arm64_breakpoint_context *bp_ctx; + struct arm64_breakpoint_context *wp_ctx; + uint8_t cpu; + + cpu = this_cpu(); + bp_ctx = &g_cpu_bp_ctx[cpu]; + wp_ctx = &g_cpu_wp_ctx[cpu]; + + bp_ctx->core_num = hw_breakpoint_count(); + wp_ctx->core_num = hw_watchpoint_count(); + + sinfo("found %d breakpoint and %d watchpoint registers.\n", + bp_ctx->core_num, wp_ctx->core_num); + + list_initialize(&g_break_inst_hook_list_el0); + list_initialize(&g_break_inst_hook_list_el1); + spin_initialize(&g_debug_hook_lock, SP_UNLOCKED); + + spin_initialize(&g_debugpoint_slots_lock, SP_UNLOCKED); + + bp_ctx->disabled = 0; + wp_ctx->disabled = 0; + + /* Register debug fatal handlers. */ + + arm64_register_debug_hook(DBG_ESR_EVT_HWBP, arm64_breakpoint_handler); + arm64_register_debug_hook(DBG_ESR_EVT_HWWP, arm64_watchpoint_handler); + arm64_register_debug_hook(DBG_ESR_EVT_HWSS, arm64_single_step_handler); + arm64_register_debug_hook(DBG_ESR_EVT_BRK, arm64_brk_handler); + + arm64_hw_breakpoint_reset(cpu); +} diff --git a/arch/arm64/src/common/arm64_hwdebug.h b/arch/arm64/src/common/arm64_hwdebug.h new file mode 100644 index 0000000000000..2ccaa051ff9d5 --- /dev/null +++ b/arch/arm64/src/common/arm64_hwdebug.h @@ -0,0 +1,337 @@ +/**************************************************************************** + * arch/arm64/src/common/arm64_hwdebug.h + * + * 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. + * + * + ****************************************************************************/ + +#ifndef __ARCH_ARM64_SRC_COMMON_ARM64_HWDEBUG_H +#define __ARCH_ARM64_SRC_COMMON_ARM64_HWDEBUG_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include "arm64_arch.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +enum +{ + BREAKPOINT_LEN_1 = 1, + BREAKPOINT_LEN_2 = 2, + BREAKPOINT_LEN_3 = 3, + BREAKPOINT_LEN_4 = 4, + BREAKPOINT_LEN_5 = 5, + BREAKPOINT_LEN_6 = 6, + BREAKPOINT_LEN_7 = 7, + BREAKPOINT_LEN_8 = 8, +}; + +#define DBG_HOOK_HANDLED 0 +#define DBG_HOOK_ERROR 1 + +/* BRK instruction trap from AArch64 state */ + +#define ESR_ELX_BRK64_ISS_COMMENT_MASK 0xffff + +/* ISS field definitions for System instruction traps */ + +#define ESR_ELX_SYS64_ISS_RES0_SHIFT 22 +#define ESR_ELX_SYS64_ISS_RES0_MASK \ + (UL(0x7) << ESR_ELX_SYS64_ISS_RES0_SHIFT) +#define ESR_ELX_SYS64_ISS_DIR_MASK 0x1 +#define ESR_ELX_SYS64_ISS_DIR_READ 0x1 +#define ESR_ELX_SYS64_ISS_DIR_WRITE 0x0 + +#define ESR_ELX_SYS64_ISS_RT_SHIFT 5 +#define ESR_ELX_SYS64_ISS_RT_MASK \ + (UL(0x1f) << ESR_ELX_SYS64_ISS_RT_SHIFT) +#define ESR_ELX_SYS64_ISS_CRM_SHIFT 1 +#define ESR_ELX_SYS64_ISS_CRM_MASK \ + (UL(0xf) << ESR_ELX_SYS64_ISS_CRM_SHIFT) +#define ESR_ELX_SYS64_ISS_CRN_SHIFT 10 +#define ESR_ELX_SYS64_ISS_CRN_MASK \ + (UL(0xf) << ESR_ELX_SYS64_ISS_CRN_SHIFT) +#define ESR_ELX_SYS64_ISS_OP1_SHIFT 14 +#define ESR_ELX_SYS64_ISS_OP1_MASK \ + (UL(0x7) << ESR_ELX_SYS64_ISS_OP1_SHIFT) +#define ESR_ELX_SYS64_ISS_OP2_SHIFT 17 +#define ESR_ELX_SYS64_ISS_OP2_MASK \ + (UL(0x7) << ESR_ELX_SYS64_ISS_OP2_SHIFT) +#define ESR_ELX_SYS64_ISS_OP0_SHIFT 20 +#define ESR_ELX_SYS64_ISS_OP0_MASK \ + (UL(0x3) << ESR_ELX_SYS64_ISS_OP0_SHIFT) + +#define ESR_ELX_SYS64_ISS_SYS_MASK (ESR_ELX_SYS64_ISS_OP0_MASK | \ + ESR_ELX_SYS64_ISS_OP1_MASK | \ + ESR_ELX_SYS64_ISS_OP2_MASK | \ + ESR_ELX_SYS64_ISS_CRN_MASK | \ + ESR_ELX_SYS64_ISS_CRM_MASK) + +#define ESR_ELX_SYS64_ISS_SYS_VAL(op0, op1, op2, crn, crm) \ + (((op0) << ESR_ELX_SYS64_ISS_OP0_SHIFT) | \ + ((op1) << ESR_ELX_SYS64_ISS_OP1_SHIFT) | \ + ((op2) << ESR_ELX_SYS64_ISS_OP2_SHIFT) | \ + ((crn) << ESR_ELX_SYS64_ISS_CRN_SHIFT) | \ + ((crm) << ESR_ELX_SYS64_ISS_CRM_SHIFT)) + +#define ESR_ELX_SYS64_ISS_SYS_OP_MASK (ESR_ELX_SYS64_ISS_SYS_MASK | \ + ESR_ELX_SYS64_ISS_DIR_MASK) +#define ESR_ELX_SYS64_ISS_RT(esr) (((esr) & ESR_ELX_SYS64_ISS_RT_MASK) \ + >> ESR_ELX_SYS64_ISS_RT_SHIFT) + +/* Low-level stepping controls. */ + +#define DBG_MDSCR_SS (1 << 0) +#define DBG_SPSR_SS (1 << 21) + +/* MDSCR_EL1 enabling bits */ + +#define DBG_MDSCR_KDE (1 << 13) +#define DBG_MDSCR_MDE (1 << 15) +#define DBG_MDSCR_MASK ~(DBG_MDSCR_KDE | DBG_MDSCR_MDE) + +/* Privilege Levels */ + +#define AARCH64_BREAKPOINT_EL1 1 +#define AARCH64_BREAKPOINT_EL0 2 + +/* Breakpoint */ + +#define ARM_BREAKPOINT_EXECUTE 0 + +/* Watchpoints */ + +#define ARM_BREAKPOINT_LOAD 1 +#define ARM_BREAKPOINT_STORE 2 +#define AARCH64_ESR_ACCESS_MASK (1 << 6) + +/* Lengths */ + +#define ARM_BREAKPOINT_LEN_1 0x1 +#define ARM_BREAKPOINT_LEN_2 0x3 +#define ARM_BREAKPOINT_LEN_3 0x7 +#define ARM_BREAKPOINT_LEN_4 0xf +#define ARM_BREAKPOINT_LEN_5 0x1f +#define ARM_BREAKPOINT_LEN_6 0x3f +#define ARM_BREAKPOINT_LEN_7 0x7f +#define ARM_BREAKPOINT_LEN_8 0xff + +/* Kernel stepping */ + +#define ARM_KERNEL_STEP_NONE 0 +#define ARM_KERNEL_STEP_ACTIVE 1 +#define ARM_KERNEL_STEP_SUSPEND 2 + +/* MDSCR_EL1 + * Monitor Debug System Control Register. It's the + * main control register for the debug implementation. + * + * Initial value for MSDCR_EL1 when starting userspace, + * which disables all debug exceptions. + * + * Instruction Breakpoint Exceptions (software breakpoints) + * cannot be disabled and MDSCR does not affect + * single-step behaviour. + */ + +#define MSDCR_EL1_INITIAL_VALUE 0 + +#define ARM64_MDSCR_EL1_SS (1u << 0) +#define ARM64_MDSCR_EL1_SS_SHIFT 0 +#define ARM64_MDSCR_EL1_ERR (1u << 6) +#define ARM64_MDSCR_EL1_ERR_SHIFT 6 +#define ARM64_MDSCR_EL1_TDCC (1u << 12) +#define ARM64_MDSCR_EL1_TDCC_SHIFT 12 +#define ARM64_MDSCR_EL1_KDE (1u << 13) +#define ARM64_MDSCR_EL1_KDE_SHIFT 13 +#define ARM64_MDSCR_EL1_HDE (1u << 14) +#define ARM64_MDSCR_EL1_HDE_SHIFT 14 +#define ARM64_MDSCR_EL1_MDE (1u << 15) +#define ARM64_MDSCR_EL1_MDE_SHIFT 15 +#define ARM64_MDSCR_EL1_RAZ_WI 0x000e0000lu +#define ARM64_MDSCR_EL1_RAZ_WI_SHIFT 16 +#define ARM64_MDSCR_EL1_TDA (1u << 21) +#define ARM64_MDSCR_EL1_TDA_SHIFT 21 +#define ARM64_MDSCR_EL1_INTDIS 0x000c0000u +#define ARM64_MDSCR_EL1_INTDIS_SHIFT 22 +#define ARM64_MDSCR_EL1_TXU (1u << 26) +#define ARM64_MDSCR_EL1_TXU_SHIFT 26 +#define ARM64_MDSCR_EL1_RXO (1u << 27) +#define ARM64_MDSCR_EL1_RXO_SHIFT 27 +#define ARM64_MDSCR_EL1_TXfull (1u << 29) +#define ARM64_MDSCR_EL1_TXfull_SHIFT 29 +#define ARM64_MDSCR_EL1_RXfull (1u << 30) +#define ARM64_MDSCR_EL1_RXfull_SHIFT 30 + +/* ID_AA64DFR0 + * Debug Feature Register 0. This register is used to query the system + * for the debug capabilities present within the chip. + */ + +#define ARM64_ID_AADFR0_EL1_DEBUG_VER 0x0000000000000Flu +#define ARM64_ID_AADFR0_EL1_TRACE_VER 0x000000000000F0lu +#define ARM64_ID_AADFR0_EL1_PMU_VER 0x00000000000F00lu + +/* Defines the amount of HW breakpoints. */ + +#define ARM64_ID_AADFR0_EL1_BRPS 0x0000000000F000lu +#define ARM64_ID_AADFR0_EL1_BRPS_SHIFT 12lu + +/* Defines the amount of HW data watchpoints. */ + +#define ARM64_ID_AADFR0_EL1_WRPS 0x00000000F00000lu +#define ARM64_ID_AADFR0_EL1_WRPS_SHIFT 20lu +#define ARM64_ID_AADFR0_EL1_CTX_CMP 0x000000F0000000lu +#define ARM64_ID_AADFR0_EL1_PMS_VER 0x00000F00000000lu + +/* Limits */ + +#define ARM64_MAX_BRP 16 +#define ARM64_MAX_WRP 16 +#define ARM64_MAX_HBP_SLOTS (ARM64_MAX_BRP + ARM64_MAX_WRP) + +/* Virtual debug register bases. */ + +#define AARCH64_DBG_REG_BVR 0 +#define AARCH64_DBG_REG_BCR (AARCH64_DBG_REG_BVR + ARM64_MAX_BRP) +#define AARCH64_DBG_REG_WVR (AARCH64_DBG_REG_BCR + ARM64_MAX_BRP) +#define AARCH64_DBG_REG_WCR (AARCH64_DBG_REG_WVR + ARM64_MAX_WRP) + +/* Debug register names. */ + +#define AARCH64_DBG_REG_NAME_BVR bvr +#define AARCH64_DBG_REG_NAME_BCR bcr +#define AARCH64_DBG_REG_NAME_WVR wvr +#define AARCH64_DBG_REG_NAME_WCR wcr + +/* Accessor macros for the debug registers. */ + +#define AARCH64_DBG_READ(N, REG, VAL) \ + do \ + { \ + VAL = read_sysreg(dbg ## REG ## N ## _el1);\ + } while (0) + +#define AARCH64_DBG_WRITE(N, REG, VAL) \ + do \ + { \ + write_sysreg(VAL, dbg ## REG ## N ## _el1); \ + } while (0) + +/**************************************************************************** + * Type Declarations + ****************************************************************************/ + +struct arch_hw_breakpoint_ctrl +{ + uint32_t __reserved : 19; + uint32_t len : 8; + uint32_t type : 2; + uint32_t privilege : 2; + uint32_t enabled : 1; +}; + +struct arch_hw_breakpoint +{ + uint64_t address; + uint64_t trigger; + struct arch_hw_breakpoint_ctrl ctrl; + int in_used; + + /* callback handler */ + + debug_callback_t handle_fn; + void *arg; + int type; + size_t size; +}; + +struct arm64_breakpoint_context +{ + /* Breakpoint currently in use for each BRP. */ + + struct arch_hw_breakpoint on_reg[ARM64_MAX_BRP]; + + /* Number of BRP registers on this CPU. */ + + int core_num; + + int disabled; +}; + +struct arm64_debugpoint +{ + void *addr; + int type; + size_t size; + int hbp_slot; + int in_used; +}; + +struct arm64_debugpoint_slot +{ + struct arm64_debugpoint slot[ARM64_MAX_HBP_SLOTS]; +}; + +typedef int (*break_func_t)(struct regs_context *regs, uint64_t esr); + +struct break_inst_hook +{ + break_func_t func; + uint16_t imm; + uint16_t mask; + struct list_node entry; +}; + +static inline uint32_t encode_ctrl_reg(struct arch_hw_breakpoint_ctrl ctrl) +{ + uint32_t val = + (ctrl.len << 5) | (ctrl.type << 3) | + (ctrl.privilege << 1) | ctrl.enabled; + + return val; +} + +static inline void decode_ctrl_reg(uint32_t reg, + struct arch_hw_breakpoint_ctrl *ctrl) +{ + ctrl->enabled = reg & 0x1; + reg >>= 1; + ctrl->privilege = reg & 0x3; + reg >>= 2; + ctrl->type = reg & 0x3; + reg >>= 2; + ctrl->len = reg & 0xff; +} + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +void arm64_hwdebug_init(void); +void arm64_hwdebug_secondary_init(void); + +#endif /* __ARCH_ARM64_SRC_COMMON_ARM64_HWDEBUG_H */ diff --git a/arch/arm64/src/common/arm64_initialize.c b/arch/arm64/src/common/arm64_initialize.c index fdceba25e973b..642b899ea5922 100644 --- a/arch/arm64/src/common/arm64_initialize.c +++ b/arch/arm64/src/common/arm64_initialize.c @@ -43,6 +43,11 @@ #ifdef CONFIG_ARCH_FPU #include "arm64_fpu.h" #endif + +#ifdef CONFIG_ARCH_HAVE_DEBUG +#include "arm64_hwdebug.h" +#endif + #include "arm64_internal.h" #include "chip.h" @@ -212,4 +217,8 @@ void up_initialize(void) #endif #endif + +#ifdef CONFIG_ARCH_HAVE_DEBUG + arm64_hwdebug_init(); +#endif } From ede365faad0d13d43c883ad96592245f74e829da Mon Sep 17 00:00:00 2001 From: xuxingliang Date: Sat, 6 Jul 2024 23:03:07 +0800 Subject: [PATCH 2/2] arch64/debug: do not reset break/watch points If we attach debugger firstly, the reset operation will cause debugger to be disconnected. Signed-off-by: xuxingliang --- arch/arm64/src/common/arm64_hwdebug.c | 39 +-------------------------- 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/arch/arm64/src/common/arm64_hwdebug.c b/arch/arm64/src/common/arm64_hwdebug.c index 01e9770a65614..74f223c22dfb8 100644 --- a/arch/arm64/src/common/arm64_hwdebug.c +++ b/arch/arm64/src/common/arm64_hwdebug.c @@ -982,43 +982,6 @@ static int arm64_clear_os_lock(unsigned int cpu) return 0; } -/* CPU initialisation. */ - -static int arm64_hw_breakpoint_reset(unsigned int cpu) -{ - struct arm64_breakpoint_context *bp_ctx; - struct arm64_breakpoint_context *wp_ctx; - int i; - - bp_ctx = &g_cpu_bp_ctx[cpu]; - wp_ctx = &g_cpu_wp_ctx[cpu]; - - /* When a CPU goes through cold-boot, it does not have any installed - * slot, so it is safe to share the same function for restoring and - * resetting breakpoints; when a CPU is hotplugged in, it goes - * through the slots, which are all empty, hence it just resets control - * and value for debug registers. - */ - - for (i = 0; i < bp_ctx->core_num; ++i) - { - write_wb_reg(AARCH64_DBG_REG_BCR, i, 0UL); - write_wb_reg(AARCH64_DBG_REG_BVR, i, 0UL); - } - - for (i = 0; i < wp_ctx->core_num; ++i) - { - write_wb_reg(AARCH64_DBG_REG_WCR, i, 0UL); - write_wb_reg(AARCH64_DBG_REG_WVR, i, 0UL); - } - - arm64_clear_os_lock(cpu); - - sinfo("reset done"); - - return 0; -} - /**************************************************************************** * Name: arm64_hw_breakpoint_enable * @@ -1307,5 +1270,5 @@ void arm64_hwdebug_init(void) arm64_register_debug_hook(DBG_ESR_EVT_HWSS, arm64_single_step_handler); arm64_register_debug_hook(DBG_ESR_EVT_BRK, arm64_brk_handler); - arm64_hw_breakpoint_reset(cpu); + arm64_clear_os_lock(cpu); }