blob: a1a125e849831a35c619573f61b6cd87d5f04023 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include "arch/arm64/registers.h"
#include <arch/arm64.h>
#include <arch/regs.h>
#include <kernel/thread.h>
#include <vm/vm.h>
void arm64_set_debug_state_for_thread(Thread* thread, bool active) {
iframe_t* iframe = thread->arch().suspended_general_regs;
DEBUG_ASSERT(iframe);
if (active) {
iframe->mdscr |= ARM64_MDSCR_EL1_MDE; // MDE enables hardware exceptions.
iframe->mdscr |= ARM64_MDSCR_EL1_KDE; // KDE enables local debugging in EL0.
} else {
iframe->mdscr &= ~ARM64_MDSCR_EL1_MDE; // MDE enables hardware exceptions.
iframe->mdscr &= ~ARM64_MDSCR_EL1_KDE; // KDE enables local debugging in EL0.
}
}
static bool arm64_validate_hw_breakpoints(arm64_debug_state_t* state,
uint32_t* active_breakpoints) {
uint32_t breakpoint_count = 0;
// Validate that the addresses are valid. HW breakpoints that are beyond the
// arch resource count will be ignored. This is because zircon will never
// update or use those values in any way, so they will always be zero,
// independent of what the user provided within those.
size_t hw_bp_count = arm64_hw_breakpoint_count();
for (size_t i = 0; i < ARM64_MAX_HW_BREAKPOINTS; i++) {
uint32_t dbgbcr = state->hw_bps[i].dbgbcr;
uint64_t dbgbvr = state->hw_bps[i].dbgbvr;
// Breakpoints values beyond the CPU count won't ever be written to the
// debug state, so they can be ignored.
if (i >= hw_bp_count) {
break;
}
// Verify that the breakpoint refers to userspace.
if (dbgbvr != 0 && !is_user_accessible(dbgbvr)) {
return false;
}
state->hw_bps[i].dbgbvr &= ARM64_DBGBVR_USER_MASK;
// If the address is valid and the breakpoint is activated, we mask in
// the other necessary value and count it for bookkeepping.
if (dbgbcr & ARM64_DBGBCR_E_MASK) {
state->hw_bps[i].dbgbcr = ARM64_DBGBCR_ACTIVE_MASK;
breakpoint_count++;
} else {
state->hw_bps[i].dbgbcr = 0;
}
}
*active_breakpoints = breakpoint_count;
return true;
}
static bool arm64_validate_hw_watchpoints(arm64_debug_state_t* state,
uint32_t* active_watchpoints) {
uint32_t watchpoint_count = 0;
// Validate that the addresses are valid. HW watchpoint that are beyond the
// arch resource count will be ignored. This is because zircon will never
// update or use those values in any way, so they will always be zero,
// independent of what the user provided within those.
size_t hw_wp_count = arm64_hw_watchpoint_count();
for (size_t i = 0; i < hw_wp_count; i++) {
uint32_t& dbgwcr = state->hw_wps[i].dbgwcr;
uint64_t& dbgwvr = state->hw_wps[i].dbgwvr;
// Watchpoints values beyond the CPU count won't ever be written to the
// debug state, so they can be ignored.
if (i >= hw_wp_count) {
break;
}
// Verify that the breakpoint refers to userspace.
if (dbgwvr != 0 && !is_user_accessible(dbgwvr)) {
return false;
}
dbgwvr &= ARM64_DBGWVR_USER_MASK;
// If the address is valid and the watchpoint is active, we mask in
// the other necessary bits.
if (ARM64_DBGWCR_E_GET(dbgwcr)) {
dbgwcr &= ARM64_DBGWCR_ACTIVE_MASK;
// See zircon/hw/debug/arm64.h for details on the fields set for PAC, HMC and SSC.
ARM64_DBGWCR_PAC_SET(&dbgwcr, 0b10);
ARM64_DBGWCR_SSC_SET(&dbgwcr, 0b01);
watchpoint_count++;
}
}
*active_watchpoints = watchpoint_count;
return true;
}
bool arm64_validate_debug_state(arm64_debug_state_t* state, uint32_t* active_breakpoints,
uint32_t* active_watchpoints) {
if (!arm64_validate_hw_breakpoints(state, active_breakpoints) ||
!arm64_validate_hw_watchpoints(state, active_watchpoints)) {
return false;
}
return true;
}
uint8_t arm64_hw_breakpoint_count() {
uint64_t dfr0 = __arm_rsr64("id_aa64dfr0_el1");
uint8_t count =
(uint8_t)(((dfr0 & ARM64_ID_AADFR0_EL1_BRPS) >> ARM64_ID_AADFR0_EL1_BRPS_SHIFT) + 1lu);
// ARMv8 assures at least 2 hw registers.
DEBUG_ASSERT(count >= ARM64_MIN_HW_BREAKPOINTS && count <= ARM64_MAX_HW_BREAKPOINTS);
return count;
}
uint8_t arm64_hw_watchpoint_count() {
uint64_t dfr0 = __arm_rsr64("id_aa64dfr0_el1");
uint8_t count =
(uint8_t)(((dfr0 & ARM64_ID_AADFR0_EL1_WRPS) >> ARM64_ID_AADFR0_EL1_WRPS_SHIFT) + 1lu);
// ARMv8 assures at least 2 hw registers.
DEBUG_ASSERT(count >= ARM64_MIN_HW_WATCHPOINTS && count <= ARM64_MAX_HW_WATCHPOINTS);
return count;
}
// Read Debug State ------------------------------------------------------------------------------
#define READ_HW_BREAKPOINT(index, dbgbcr_val, dbgbvr_val) \
dbgbcr_val = __arm_rsr("dbgbcr" #index "_el1"); \
dbgbvr_val = __arm_rsr64("dbgbvr" #index "_el1");
#define READ_HW_BREAKPOINT_CASE(index, dbgbcr_val, dbgbvr_val) \
case index: \
READ_HW_BREAKPOINT(index, dbgbcr_val, dbgbvr_val); \
break;
static void arm64_read_hw_breakpoint_by_index(unsigned int index, uint32_t* dbgbcr,
uint64_t* dbgbvr) {
uint32_t read_dbgbcr = 0;
uint64_t read_dbgbvr = 0;
switch (index) {
READ_HW_BREAKPOINT_CASE(0, read_dbgbcr, read_dbgbvr);
READ_HW_BREAKPOINT_CASE(1, read_dbgbcr, read_dbgbvr);
READ_HW_BREAKPOINT_CASE(2, read_dbgbcr, read_dbgbvr);
READ_HW_BREAKPOINT_CASE(3, read_dbgbcr, read_dbgbvr);
READ_HW_BREAKPOINT_CASE(4, read_dbgbcr, read_dbgbvr);
READ_HW_BREAKPOINT_CASE(5, read_dbgbcr, read_dbgbvr);
READ_HW_BREAKPOINT_CASE(6, read_dbgbcr, read_dbgbvr);
READ_HW_BREAKPOINT_CASE(7, read_dbgbcr, read_dbgbvr);
READ_HW_BREAKPOINT_CASE(8, read_dbgbcr, read_dbgbvr);
READ_HW_BREAKPOINT_CASE(9, read_dbgbcr, read_dbgbvr);
READ_HW_BREAKPOINT_CASE(10, read_dbgbcr, read_dbgbvr);
READ_HW_BREAKPOINT_CASE(11, read_dbgbcr, read_dbgbvr);
READ_HW_BREAKPOINT_CASE(12, read_dbgbcr, read_dbgbvr);
READ_HW_BREAKPOINT_CASE(13, read_dbgbcr, read_dbgbvr);
READ_HW_BREAKPOINT_CASE(14, read_dbgbcr, read_dbgbvr);
READ_HW_BREAKPOINT_CASE(15, read_dbgbcr, read_dbgbvr);
default:
DEBUG_ASSERT(false);
}
*dbgbcr = read_dbgbcr;
*dbgbvr = read_dbgbvr;
}
#undef READ_HW_BREAKPOINT
#undef READ_HW_BREAKPOINT_CASE
void arm64_read_hw_debug_regs(arm64_debug_state_t* debug_state) {
// We clear the state out.
*debug_state = {};
// Only write in the registers that are present in the CPU implementation.
uint8_t count = arm64_hw_breakpoint_count();
for (unsigned int i = 0; i < count; i++) {
uint32_t* dbgbcr = &debug_state->hw_bps[i].dbgbcr;
uint64_t* dbgbvr = &debug_state->hw_bps[i].dbgbvr;
arm64_read_hw_breakpoint_by_index(i, dbgbcr, dbgbvr);
}
}
// Writing Debug State ---------------------------------------------------------------------------
#define WRITE_HW_BREAKPOINT(index, dbgbcr_val, dbgbvr_val) \
__arm_wsr("dbgbcr" #index "_el1", dbgbcr_val); \
__arm_wsr64("dbgbvr" #index "_el1", dbgbvr_val); \
__isb(ARM_MB_SY);
#define WRITE_HW_BREAKPOINT_CASE(index, dbgbcr_val, dbgbvr_val) \
case index: \
WRITE_HW_BREAKPOINT(index, dbgbcr_val, dbgbvr_val); \
break;
static void arm64_write_hw_breakpoint_by_index(unsigned int index, uint32_t dbgbcr,
uint64_t dbgbvr) {
switch (index) {
WRITE_HW_BREAKPOINT_CASE(0, dbgbcr, dbgbvr);
WRITE_HW_BREAKPOINT_CASE(1, dbgbcr, dbgbvr);
WRITE_HW_BREAKPOINT_CASE(2, dbgbcr, dbgbvr);
WRITE_HW_BREAKPOINT_CASE(3, dbgbcr, dbgbvr);
WRITE_HW_BREAKPOINT_CASE(4, dbgbcr, dbgbvr);
WRITE_HW_BREAKPOINT_CASE(5, dbgbcr, dbgbvr);
WRITE_HW_BREAKPOINT_CASE(6, dbgbcr, dbgbvr);
WRITE_HW_BREAKPOINT_CASE(7, dbgbcr, dbgbvr);
WRITE_HW_BREAKPOINT_CASE(8, dbgbcr, dbgbvr);
WRITE_HW_BREAKPOINT_CASE(9, dbgbcr, dbgbvr);
WRITE_HW_BREAKPOINT_CASE(10, dbgbcr, dbgbvr);
WRITE_HW_BREAKPOINT_CASE(11, dbgbcr, dbgbvr);
WRITE_HW_BREAKPOINT_CASE(12, dbgbcr, dbgbvr);
WRITE_HW_BREAKPOINT_CASE(13, dbgbcr, dbgbvr);
WRITE_HW_BREAKPOINT_CASE(14, dbgbcr, dbgbvr);
WRITE_HW_BREAKPOINT_CASE(15, dbgbcr, dbgbvr);
default:
DEBUG_ASSERT(false);
}
}
#undef WRITE_HW_BREAKPOINT
#undef WRITE_HW_BREAKPOINT_CASE
#define WRITE_HW_WATCHPOINT(index, dbgwcr, dbgwvr) \
__arm_wsr("dbgwcr" #index "_el1", dbgwcr); \
__arm_wsr64("dbgwvr" #index "_el1", dbgwvr); \
__isb(ARM_MB_SY);
#define WRITE_HW_WATCHPOINT_CASE(index, dbgwcr, dbgwvr) \
case index: \
WRITE_HW_WATCHPOINT(index, dbgwcr, dbgwvr); \
break;
static void arm64_write_hw_watchpoint_by_index(unsigned int index, uint32_t dbgwcr,
uint64_t dbgwvr) {
switch (index) {
WRITE_HW_WATCHPOINT_CASE(0, dbgwcr, dbgwvr);
WRITE_HW_WATCHPOINT_CASE(1, dbgwcr, dbgwvr);
WRITE_HW_WATCHPOINT_CASE(2, dbgwcr, dbgwvr);
WRITE_HW_WATCHPOINT_CASE(3, dbgwcr, dbgwvr);
WRITE_HW_WATCHPOINT_CASE(4, dbgwcr, dbgwvr);
WRITE_HW_WATCHPOINT_CASE(5, dbgwcr, dbgwvr);
WRITE_HW_WATCHPOINT_CASE(6, dbgwcr, dbgwvr);
WRITE_HW_WATCHPOINT_CASE(7, dbgwcr, dbgwvr);
WRITE_HW_WATCHPOINT_CASE(8, dbgwcr, dbgwvr);
WRITE_HW_WATCHPOINT_CASE(9, dbgwcr, dbgwvr);
WRITE_HW_WATCHPOINT_CASE(10, dbgwcr, dbgwvr);
WRITE_HW_WATCHPOINT_CASE(11, dbgwcr, dbgwvr);
WRITE_HW_WATCHPOINT_CASE(12, dbgwcr, dbgwvr);
WRITE_HW_WATCHPOINT_CASE(13, dbgwcr, dbgwvr);
WRITE_HW_WATCHPOINT_CASE(14, dbgwcr, dbgwvr);
WRITE_HW_WATCHPOINT_CASE(15, dbgwcr, dbgwvr);
default:
DEBUG_ASSERT(false);
}
}
#undef WRITE_HW_WATCHPOINT
#undef WRITE_HW_WATCHPOINT_CASE
void arm64_write_hw_debug_regs(const arm64_debug_state_t* debug_state) {
// Write the HW Breakpoints.
uint64_t bps_count = arm64_hw_breakpoint_count();
for (unsigned int i = 0; i < bps_count; i++) {
uint32_t dbgbcr = debug_state->hw_bps[i].dbgbcr;
uint64_t dbgbvr = debug_state->hw_bps[i].dbgbvr;
arm64_write_hw_breakpoint_by_index(i, dbgbcr, dbgbvr);
}
// Write the HW Watchpoints.
uint64_t wps_count = arm64_hw_watchpoint_count();
for (unsigned int i = 0; i < wps_count; i++) {
uint32_t dbgwcr = debug_state->hw_wps[i].dbgwcr;
uint64_t dbgwvr = debug_state->hw_wps[i].dbgwvr;
arm64_write_hw_watchpoint_by_index(i, dbgwcr, dbgwvr);
}
}
void arm64_clear_hw_debug_regs() {
for (unsigned int i = 0; i < ARM64_MAX_HW_BREAKPOINTS; i++) {
arm64_write_hw_breakpoint_by_index(i, 0, 0);
arm64_write_hw_watchpoint_by_index(i, 0, 0);
}
}
#ifndef NDEBUG
// Debug only.
void arm64_print_debug_registers(const arm64_debug_state_t* debug_state) {
printf("HW breakpoints:\n");
for (uint32_t i = 0; i < ARM64_MAX_HW_BREAKPOINTS; i++) {
uint32_t dbgbcr = debug_state->hw_bps[i].dbgbcr;
uint64_t dbgbvr = debug_state->hw_bps[i].dbgbvr;
if (!ARM64_DBGBCR_E_GET(dbgbcr))
continue;
printf(
"%02u. DBGBVR: 0x%lx, "
"DBGBCR: E=%d, PMC=%d, BAS=%d, HMC=%d, SSC=%d, LBN=%d, BT=%d\n",
i, dbgbvr, (int)(dbgbcr & ARM64_DBGBCR_E),
(int)((dbgbcr & ARM64_DBGBCR_PMC_MASK) >> ARM64_DBGBCR_PMC_SHIFT),
(int)((dbgbcr & ARM64_DBGBCR_BAS_MASK) >> ARM64_DBGBCR_BAS_SHIFT),
(int)((dbgbcr & ARM64_DBGBCR_HMC_MASK) >> ARM64_DBGBCR_HMC_SHIFT),
(int)((dbgbcr & ARM64_DBGBCR_SSC_MASK) >> ARM64_DBGBCR_SSC_SHIFT),
(int)((dbgbcr & ARM64_DBGBCR_LBN_MASK) >> ARM64_DBGBCR_LBN_SHIFT),
(int)((dbgbcr & ARM64_DBGBCR_BT_MASK) >> ARM64_DBGBCR_BT_SHIFT));
}
printf("HW watchpoints:\n");
for (uint32_t i = 0; i < ARM64_MAX_HW_WATCHPOINTS; i++) {
uint32_t dbgwcr = debug_state->hw_wps[i].dbgwcr;
uint64_t dbgwvr = debug_state->hw_wps[i].dbgwvr;
if (!ARM64_DBGWCR_E_GET(dbgwcr))
continue;
printf(
"%02u. DBGWVR: 0x%lx, DBGWCR: "
"E=%d, PAC=%d, LSC=%d, BAS=0x%x, HMC=%d, SSC=%d, LBN=%d, WT=%d, MASK=0x%x\n",
i, dbgwvr, (int)(dbgwcr & ARM64_DBGWCR_E_MASK),
(int)((dbgwcr & ARM64_DBGWCR_PAC_MASK) >> ARM64_DBGWCR_PAC_SHIFT),
(int)((dbgwcr & ARM64_DBGWCR_LSC_MASK) >> ARM64_DBGWCR_LSC_SHIFT),
(unsigned int)((dbgwcr & ARM64_DBGWCR_BAS_MASK) >> ARM64_DBGWCR_BAS_SHIFT),
(int)((dbgwcr & ARM64_DBGWCR_HMC_MASK) >> ARM64_DBGWCR_HMC_SHIFT),
(int)((dbgwcr & ARM64_DBGWCR_SSC_MASK) >> ARM64_DBGWCR_SSC_SHIFT),
(int)((dbgwcr & ARM64_DBGWCR_LBN_MASK) >> ARM64_DBGWCR_LBN_SHIFT),
(int)((dbgwcr & ARM64_DBGWCR_WT_MASK) >> ARM64_DBGWCR_WT_SHIFT),
(unsigned int)((dbgwcr & ARM64_DBGWCR_MSK_MASK) >> ARM64_DBGWCR_MSK_SHIFT));
}
}
void arm64_print_mdscr() {
uint32_t mdscr = __arm_rsr("mdscr_el1");
printf(
"SS=%d, ERR=%d, TDCC=%d, KDE=%d, HDE=%d, MDE=%d, RAZ/WI=%d, "
"TDA=%d, INTdis=%d, "
"TXU=%d, RXO=%d, TXfull=%d, RXfull=%d\n",
(int)((mdscr & ARM64_MDSCR_EL1_SS) >> ARM64_MDSCR_EL1_SS_SHIFT),
(int)((mdscr & ARM64_MDSCR_EL1_ERR) >> ARM64_MDSCR_EL1_ERR_SHIFT),
(int)((mdscr & ARM64_MDSCR_EL1_TDCC) >> ARM64_MDSCR_EL1_TDCC_SHIFT),
(int)((mdscr & ARM64_MDSCR_EL1_KDE) >> ARM64_MDSCR_EL1_KDE_SHIFT),
(int)((mdscr & ARM64_MDSCR_EL1_HDE) >> ARM64_MDSCR_EL1_HDE_SHIFT),
(int)((mdscr & ARM64_MDSCR_EL1_MDE) >> ARM64_MDSCR_EL1_MDE_SHIFT),
(int)((mdscr & ARM64_MDSCR_EL1_RAZ_WI) >> ARM64_MDSCR_EL1_RAZ_WI_SHIFT),
(int)((mdscr & ARM64_MDSCR_EL1_TDA) >> ARM64_MDSCR_EL1_TDA_SHIFT),
(int)((mdscr & ARM64_MDSCR_EL1_INTDIS) >> ARM64_MDSCR_EL1_INTDIS_SHIFT),
(int)((mdscr & ARM64_MDSCR_EL1_TXU) >> ARM64_MDSCR_EL1_TXU_SHIFT),
(int)((mdscr & ARM64_MDSCR_EL1_RXO) >> ARM64_MDSCR_EL1_RXO_SHIFT),
(int)((mdscr & ARM64_MDSCR_EL1_TXfull) >> ARM64_MDSCR_EL1_TXfull_SHIFT),
(int)((mdscr & ARM64_MDSCR_EL1_RXfull) >> ARM64_MDSCR_EL1_RXfull_SHIFT));
}
#endif
void PrintFrame(FILE* f, const iframe_t& frame) {
fprintf(f, "iframe %p:\n", &frame);
fprintf(f, "x0 %#18" PRIx64 " x1 %#18" PRIx64 " x2 %#18" PRIx64 " x3 %#18" PRIx64 "\n",
frame.r[0], frame.r[1], frame.r[2], frame.r[3]);
fprintf(f, "x4 %#18" PRIx64 " x5 %#18" PRIx64 " x6 %#18" PRIx64 " x7 %#18" PRIx64 "\n",
frame.r[4], frame.r[5], frame.r[6], frame.r[7]);
fprintf(f, "x8 %#18" PRIx64 " x9 %#18" PRIx64 " x10 %#18" PRIx64 " x11 %#18" PRIx64 "\n",
frame.r[8], frame.r[9], frame.r[10], frame.r[11]);
fprintf(f, "x12 %#18" PRIx64 " x13 %#18" PRIx64 " x14 %#18" PRIx64 " x15 %#18" PRIx64 "\n",
frame.r[12], frame.r[13], frame.r[14], frame.r[15]);
fprintf(f, "x16 %#18" PRIx64 " x17 %#18" PRIx64 " x18 %#18" PRIx64 " x19 %#18" PRIx64 "\n",
frame.r[16], frame.r[17], frame.r[18], frame.r[19]);
fprintf(f, "x20 %#18" PRIx64 " x21 %#18" PRIx64 " x22 %#18" PRIx64 " x23 %#18" PRIx64 "\n",
frame.r[20], frame.r[21], frame.r[22], frame.r[23]);
fprintf(f, "x24 %#18" PRIx64 " x25 %#18" PRIx64 " x26 %#18" PRIx64 " x27 %#18" PRIx64 "\n",
frame.r[24], frame.r[25], frame.r[26], frame.r[27]);
fprintf(f, "x28 %#18" PRIx64 " x29 %#18" PRIx64 " lr %#18" PRIx64 " usp %#18" PRIx64 "\n",
frame.r[28], frame.r[29], frame.lr, frame.usp);
fprintf(f, "elr %#18" PRIx64 "\n", frame.elr);
fprintf(f, "spsr %#18" PRIx64 "\n", frame.spsr);
}