#include <config.h>
#ifdef CONFIG_HARDWARE_DEBUG_API
#include <arch/machine/debug.h>
#include <mode/machine/debug.h>
#include <arch/machine.h>
#include <machine/registerset.h>
#include <plat/api/constants.h>
#define X86_DEBUG_BP_SIZE_1B (0x0u)
#define X86_DEBUG_BP_SIZE_2B (0x1u)
#define X86_DEBUG_BP_SIZE_4B (0x3u)
#define X86_DEBUG_BP_SIZE_8B (0x2u)
#define X86_DEBUG_BP0_SIZE_SHIFT (18)
#define X86_DEBUG_BP1_SIZE_SHIFT (22)
#define X86_DEBUG_BP2_SIZE_SHIFT (26)
#define X86_DEBUG_BP3_SIZE_SHIFT (30)
#define X86_DEBUG_BP_TYPE_IO (0x2u)
#define X86_DEBUG_BP_TYPE_INSTR (0x0u)
#define X86_DEBUG_BP_TYPE_DATA_WRITE (0x1u)
#define X86_DEBUG_BP_TYPE_DATA_READWRITE (0x3u)
#define X86_DEBUG_BP0_TYPE_SHIFT (16)
#define X86_DEBUG_BP1_TYPE_SHIFT (20)
#define X86_DEBUG_BP2_TYPE_SHIFT (24)
#define X86_DEBUG_BP3_TYPE_SHIFT (28)
#define X86_DEBUG_EFLAGS_TRAP_FLAG ((word_t)BIT(8))
#define X86_DEBUG_EFLAGS_RESUME_FLAG ((word_t)BIT(16))
#define X86_DEBUG_DR6_SINGLE_STEP_FLAG ((word_t)BIT(14))
#define X86_DEBUG_DR6_BP_MASK (0xFu)
static bool_t byte8_bps_supported = false;
bool_t
byte8BreakpointsSupported(void)
{
return byte8_bps_supported;
}
static inline void
bitwiseAndDr6Reg(word_t mask)
{
word_t tmp;
tmp = readDr6Reg() & mask;
writeDr6Reg(tmp);
}
static inline word_t
readDr7Context(arch_tcb_t *uds)
{
return uds->tcbContext.breakpointState.dr[5];
}
static inline void
bitwiseOrDr7Context(arch_tcb_t *uds, word_t val)
{
uds->tcbContext.breakpointState.dr[5] |= val;
}
static inline void
bitwiseAndDr7Context(arch_tcb_t *uds, word_t mask)
{
uds->tcbContext.breakpointState.dr[5] &= mask;
}
static void
unsetDr7BitsFor(arch_tcb_t *uds, uint16_t bp_num)
{
word_t mask;
switch (bp_num) {
case 0:
mask = (0x3u << X86_DEBUG_BP0_SIZE_SHIFT) | (0x3u << X86_DEBUG_BP0_TYPE_SHIFT);
break;
case 1:
mask = (0x3u << X86_DEBUG_BP1_SIZE_SHIFT) | (0x3u << X86_DEBUG_BP1_TYPE_SHIFT);
break;
case 2:
mask = (0x3u << X86_DEBUG_BP2_SIZE_SHIFT) | (0x3u << X86_DEBUG_BP2_TYPE_SHIFT);
break;
default:
assert(bp_num == 3);
mask = (0x3u << X86_DEBUG_BP3_SIZE_SHIFT) | (0x3u << X86_DEBUG_BP3_TYPE_SHIFT);
break;
}
mask = ~mask;
bitwiseAndDr7Context(uds, mask);
}
static inline word_t
convertTypeAndAccessToArch(uint16_t bp_num, word_t type, word_t rw)
{
switch (type) {
case seL4_InstructionBreakpoint:
type = X86_DEBUG_BP_TYPE_INSTR;
break;
default:
assert(type == seL4_DataBreakpoint);
type = (rw == seL4_BreakOnWrite)
? X86_DEBUG_BP_TYPE_DATA_WRITE
: X86_DEBUG_BP_TYPE_DATA_READWRITE;
}
switch (bp_num) {
case 0:
return type << X86_DEBUG_BP0_TYPE_SHIFT;
case 1:
return type << X86_DEBUG_BP1_TYPE_SHIFT;
case 2:
return type << X86_DEBUG_BP2_TYPE_SHIFT;
default:
assert(bp_num == 3);
return type << X86_DEBUG_BP3_TYPE_SHIFT;
}
}
typedef struct {
word_t type, rw;
} convertedTypeAndAccess_t;
static inline convertedTypeAndAccess_t
convertArchToTypeAndAccess(word_t dr7, uint16_t bp_num)
{
convertedTypeAndAccess_t ret;
switch (bp_num) {
case 0:
dr7 &= 0x3u << X86_DEBUG_BP0_TYPE_SHIFT;
dr7 >>= X86_DEBUG_BP0_TYPE_SHIFT;
break;
case 1:
dr7 &= 0x3u << X86_DEBUG_BP1_TYPE_SHIFT;
dr7 >>= X86_DEBUG_BP1_TYPE_SHIFT;
break;
case 2:
dr7 &= 0x3u << X86_DEBUG_BP2_TYPE_SHIFT;
dr7 >>= X86_DEBUG_BP2_TYPE_SHIFT;
break;
default:
assert(bp_num == 3);
dr7 &= 0x3u << X86_DEBUG_BP3_TYPE_SHIFT;
dr7 >>= X86_DEBUG_BP3_TYPE_SHIFT;
}
switch (dr7) {
case X86_DEBUG_BP_TYPE_INSTR:
ret.type = seL4_InstructionBreakpoint;
ret.rw = seL4_BreakOnRead;
break;
case X86_DEBUG_BP_TYPE_DATA_WRITE:
ret.type = seL4_DataBreakpoint;
ret.rw = seL4_BreakOnWrite;
break;
default:
assert(dr7 == X86_DEBUG_BP_TYPE_DATA_READWRITE);
ret.type = seL4_DataBreakpoint;
ret.rw = seL4_BreakOnReadWrite;
break;
}
return ret;
}
static inline word_t
convertSizeToArch(uint16_t bp_num, word_t type, word_t size)
{
if (type == seL4_InstructionBreakpoint) {
size = 0;
} else {
switch (size) {
case 1:
size = X86_DEBUG_BP_SIZE_1B;
break;
case 2:
size = X86_DEBUG_BP_SIZE_2B;
break;
case 8:
size = X86_DEBUG_BP_SIZE_8B;
break;
default:
assert(size == 4);
size = X86_DEBUG_BP_SIZE_4B;
}
}
switch (bp_num) {
case 0:
return size << X86_DEBUG_BP0_SIZE_SHIFT;
case 1:
return size << X86_DEBUG_BP1_SIZE_SHIFT;
case 2:
return size << X86_DEBUG_BP2_SIZE_SHIFT;
default:
assert(bp_num == 3);
return size << X86_DEBUG_BP3_SIZE_SHIFT;
}
}
static inline word_t
convertArchToSize(word_t dr7, uint16_t bp_num)
{
word_t type;
switch (bp_num) {
case 0:
type = dr7 & (0x3u << X86_DEBUG_BP0_TYPE_SHIFT);
type >>= X86_DEBUG_BP0_TYPE_SHIFT;
dr7 &= 0x3u << X86_DEBUG_BP0_SIZE_SHIFT;
dr7 >>= X86_DEBUG_BP0_SIZE_SHIFT;
break;
case 1:
type = dr7 & (0x3u << X86_DEBUG_BP1_TYPE_SHIFT);
type >>= X86_DEBUG_BP1_TYPE_SHIFT;
dr7 &= 0x3u << X86_DEBUG_BP1_SIZE_SHIFT;
dr7 >>= X86_DEBUG_BP1_SIZE_SHIFT;
break;
case 2:
type = dr7 & (0x3u << X86_DEBUG_BP2_TYPE_SHIFT);
type >>= X86_DEBUG_BP2_TYPE_SHIFT;
dr7 &= 0x3u << X86_DEBUG_BP2_SIZE_SHIFT;
dr7 >>= X86_DEBUG_BP2_SIZE_SHIFT;
break;
default:
assert(bp_num == 3);
type = dr7 & (0x3u << X86_DEBUG_BP3_TYPE_SHIFT);
type >>= X86_DEBUG_BP3_TYPE_SHIFT;
dr7 &= 0x3u << X86_DEBUG_BP3_SIZE_SHIFT;
dr7 >>= X86_DEBUG_BP3_SIZE_SHIFT;
}
if (type == X86_DEBUG_BP_TYPE_INSTR) {
return 0;
}
switch (dr7) {
case X86_DEBUG_BP_SIZE_1B:
return 1;
case X86_DEBUG_BP_SIZE_2B:
return 2;
case X86_DEBUG_BP_SIZE_8B:
return 8;
default:
assert(dr7 == X86_DEBUG_BP_SIZE_4B);
return 4;
}
}
static void
enableBreakpoint(arch_tcb_t *uds, uint16_t bp_num)
{
word_t enable_bit;
assert(uds != NULL);
assert(bp_num < X86_DEBUG_BP_N_REGS);
switch (bp_num) {
case 0:
enable_bit = X86_DEBUG_BP0_ENABLE_BIT;
break;
case 1:
enable_bit = X86_DEBUG_BP1_ENABLE_BIT;
break;
case 2:
enable_bit = X86_DEBUG_BP2_ENABLE_BIT;
break;
default:
enable_bit = X86_DEBUG_BP3_ENABLE_BIT;
break;
}
bitwiseOrDr7Context(uds, enable_bit);
}
static void
disableBreakpoint(arch_tcb_t *uds, uint16_t bp_num)
{
word_t disable_mask;
assert(uds != NULL);
assert(bp_num < X86_DEBUG_BP_N_REGS);
switch (bp_num) {
case 0:
disable_mask = ~X86_DEBUG_BP0_ENABLE_BIT;
break;
case 1:
disable_mask = ~X86_DEBUG_BP1_ENABLE_BIT;
break;
case 2:
disable_mask = ~X86_DEBUG_BP2_ENABLE_BIT;
break;
default:
disable_mask = ~X86_DEBUG_BP3_ENABLE_BIT;
break;
}
bitwiseAndDr7Context(uds, disable_mask);
}
static bool_t
breakpointIsEnabled(arch_tcb_t *uds, uint16_t bp_num)
{
word_t dr7;
assert(uds != NULL);
assert(bp_num < X86_DEBUG_BP_N_REGS);
dr7 = readDr7Context(uds);
switch (bp_num) {
case 0:
return !!(dr7 & X86_DEBUG_BP0_ENABLE_BIT);
case 1:
return !!(dr7 & X86_DEBUG_BP1_ENABLE_BIT);
case 2:
return !!(dr7 & X86_DEBUG_BP2_ENABLE_BIT);
default:
return !!(dr7 & X86_DEBUG_BP3_ENABLE_BIT);
}
}
static void
setBpVaddrContext(user_breakpoint_state_t *uds, uint16_t bp_num, word_t vaddr)
{
assert(uds != NULL);
switch (bp_num) {
case 0:
uds->dr[0] = vaddr;
break;
case 1:
uds->dr[1] = vaddr;
break;
case 2:
uds->dr[2] = vaddr;
break;
default:
assert(bp_num == 3);
uds->dr[3] = vaddr;
break;
}
return;
}
void
setBreakpoint(arch_tcb_t *uds,
uint16_t bp_num, word_t vaddr, word_t types, word_t size, word_t rw)
{
word_t dr7val;
assert(uds != NULL);
dr7val = convertTypeAndAccessToArch(bp_num, types, rw);
dr7val |= convertSizeToArch(bp_num, types, size);
setBpVaddrContext(&uds->tcbContext.breakpointState, bp_num, vaddr);
unsetDr7BitsFor(uds, bp_num);
bitwiseOrDr7Context(uds, dr7val);
enableBreakpoint(uds, bp_num);
}
static word_t
getBpVaddrContext(user_breakpoint_state_t *uds, uint16_t bp_num)
{
assert(uds != NULL);
switch (bp_num) {
case 0:
return uds->dr[0];
case 1:
return uds->dr[1];
case 2:
return uds->dr[2];
default:
assert(bp_num == 3);
return uds->dr[3];
}
}
getBreakpoint_t
getBreakpoint(arch_tcb_t *uds, uint16_t bp_num)
{
word_t dr7val;
getBreakpoint_t ret;
convertedTypeAndAccess_t res;
dr7val = readDr7Context(uds);
ret.vaddr = getBpVaddrContext(&uds->tcbContext.breakpointState, bp_num);
ret.size = convertArchToSize(dr7val, bp_num);
res = convertArchToTypeAndAccess(dr7val, bp_num);
ret.type = res.type;
ret.rw = res.rw;
ret.is_enabled = breakpointIsEnabled(uds, bp_num);
return ret;
}
void
unsetBreakpoint(arch_tcb_t *uds, uint16_t bp_num)
{
disableBreakpoint(uds, bp_num);
unsetDr7BitsFor(uds, bp_num);
setBpVaddrContext(&uds->tcbContext.breakpointState, bp_num, 0);
}
typedef struct {
bool_t ret;
word_t instr_vaddr;
} testAndResetSingleStepException_t;
static testAndResetSingleStepException_t
testAndResetSingleStepException(arch_tcb_t *uc)
{
testAndResetSingleStepException_t ret;
word_t dr6;
dr6 = readDr6Reg();
if (!(dr6 & X86_DEBUG_DR6_SINGLE_STEP_FLAG)) {
ret.ret = false;
return ret;
}
ret.ret = true;
ret.instr_vaddr = uc->tcbContext.registers[FaultIP];
bitwiseAndDr6Reg(~X86_DEBUG_DR6_SINGLE_STEP_FLAG);
uc->tcbContext.registers[FLAGS] |= X86_DEBUG_EFLAGS_RESUME_FLAG;
return ret;
}
bool_t
configureSingleStepping(arch_tcb_t *uc, uint16_t bp_num, word_t n_instr,
UNUSED bool_t is_reply)
{
if (n_instr == 0) {
uc->tcbContext.breakpointState.single_step_enabled = false;
uc->tcbContext.registers[FLAGS] &= ~X86_DEBUG_EFLAGS_TRAP_FLAG;
} else {
uc->tcbContext.breakpointState.single_step_enabled = true;
}
uc->tcbContext.breakpointState.n_instructions = n_instr;
return false;
}
typedef struct {
int bp_num;
word_t vaddr, reason;
} getAndResetActiveBreakpoint_t;
static getAndResetActiveBreakpoint_t
getAndResetActiveBreakpoint(arch_tcb_t *at)
{
convertedTypeAndAccess_t tmp;
getAndResetActiveBreakpoint_t ret;
word_t dr6 = readDr6Reg();
if (dr6 & BIT(0)) {
ret.bp_num = 0;
} else if (dr6 & BIT(1)) {
ret.bp_num = 1;
} else if (dr6 & BIT(2)) {
ret.bp_num = 2;
} else if (dr6 & BIT(3)) {
ret.bp_num = 3;
} else {
ret.bp_num = -1;
return ret;
}
tmp = convertArchToTypeAndAccess(readDr7Context(at), ret.bp_num);
ret.vaddr = getBpVaddrContext(&at->tcbContext.breakpointState, ret.bp_num);
ret.reason = tmp.type;
bitwiseAndDr6Reg(~BIT(ret.bp_num));
return ret;
}
exception_t
handleUserLevelDebugException(int int_vector)
{
arch_tcb_t *context;
getAndResetActiveBreakpoint_t active_bp;
testAndResetSingleStepException_t single_step_info;
#if defined(DEBUG) || defined(CONFIG_BENCHMARK_TRACK_KERNEL_ENTRIES)
ksKernelEntry.path = Entry_UserLevelFault;
ksKernelEntry.word = int_vector;
#else
(void)int_vector;
#endif
#ifdef CONFIG_BENCHMARK_TRACK_KERNEL_ENTRIES
benchmark_track_start();
#endif
context = &NODE_STATE(ksCurThread)->tcbArch;
if (int_vector == int_software_break_request) {
current_fault = seL4_Fault_DebugException_new(getRestartPC(NODE_STATE(ksCurThread)),
0, seL4_SoftwareBreakRequest);
} else {
active_bp = getAndResetActiveBreakpoint(context);
if (active_bp.bp_num >= 0) {
current_fault = seL4_Fault_DebugException_new(active_bp.vaddr,
active_bp.bp_num,
active_bp.reason);
} else {
single_step_info = testAndResetSingleStepException(context);
if (single_step_info.ret == true) {
if (singleStepFaultCounterReady(context) == false) {
return EXCEPTION_NONE;
}
current_fault = seL4_Fault_DebugException_new(single_step_info.instr_vaddr,
0, seL4_SingleStep);
} else {
return EXCEPTION_SYSCALL_ERROR;
}
}
}
handleFault(NODE_STATE(ksCurThread));
schedule();
activateThread();
return EXCEPTION_NONE;
}
BOOT_CODE bool_t
Arch_initHardwareBreakpoints(void)
{
x86_cpu_identity_t *modelinfo;
modelinfo = x86_cpuid_get_model_info();
if (modelinfo->family == 15) {
if (modelinfo->model == 3 || modelinfo->model == 4
|| modelinfo->model == 6) {
byte8_bps_supported = true;
}
}
if (modelinfo->family == 6) {
if (modelinfo->model == 15 || modelinfo->model == 23
|| modelinfo->model == 0x1C) {
byte8_bps_supported = true;
}
}
return true;
}
void
Arch_initBreakpointContext(user_breakpoint_state_t *uds)
{
memset(uds, 0, sizeof(*uds));
uds->dr[4] = readDr6Reg() &
~(BIT(0)
| BIT(1)
| BIT(2)
| BIT(3)
| X86_DEBUG_DR6_SINGLE_STEP_FLAG);
uds->dr[5] = readDr7Reg() &
~(X86_DEBUG_BP0_ENABLE_BIT | X86_DEBUG_BP1_ENABLE_BIT
| X86_DEBUG_BP2_ENABLE_BIT
| X86_DEBUG_BP3_ENABLE_BIT);
}
#endif