blob: d3ba783a55f81693f453a9b45a7aa0e1e73e3bdc [file] [log] [blame]
/* SPDX-License-Identifier: GPL-2.0-only */
#include <arch/registers.h>
#include <arch/breakpoint.h>
#include <console/console.h>
#include <types.h>
#define DEBUG_REGISTER_COUNT 4
/* Each enable field is 2 bits and starts at bit 0 */
#define DEBUG_CTRL_ENABLE_SHIFT(index) (2 * (index))
#define DEBUG_CTRL_ENABLE_MASK(index) (0x3 << DEBUG_CTRL_ENABLE_SHIFT(index))
#define DEBUG_CTRL_ENABLE(index, enable) ((enable) << DEBUG_CTRL_ENABLE_SHIFT(index))
/* Each breakpoint has a length and type, each is two bits and start at bit 16 */
#define DEBUG_CTRL_LT_SHIFT(index) (4 * (index) + 16)
#define DEBUG_CTRL_LT_MASK(index) (0xf << DEBUG_CTRL_LT_SHIFT(index))
#define DEBUG_CTRL_LT(index, len, type) ((((len) << 2 | (type))) << DEBUG_CTRL_LT_SHIFT(index))
/* Each field is one bit, starting at bit 0 */
#define DEBUG_STATUS_BP_HIT_MASK(index) (1 << (index))
#define DEBUG_STATUS_GET_BP_HIT(index, value) \
(((value) & DEBUG_STATUS_BP_HIT_MASK(index)) >> (index))
/* Breakpoint lengths values */
#define DEBUG_CTRL_LEN_1 0x0
#define DEBUG_CTRL_LEN_2 0x1
#define DEBUG_CTRL_LEN_8 0x2
#define DEBUG_CTRL_LEN_4 0x3
/* Breakpoint enable values */
#define DEBUG_CTRL_ENABLE_LOCAL 0x1
#define DEBUG_CTRL_ENABLE_GLOBAL 0x2
/* eflags/rflags bit to continue execution after hitting an instruction breakpoint */
#define FLAGS_RESUME (1 << 16)
struct breakpoint {
bool allocated;
enum breakpoint_type type;
breakpoint_handler handler;
};
static struct breakpoint breakpoints[DEBUG_REGISTER_COUNT];
static inline bool debug_write_addr_reg(int index, uintptr_t value)
{
switch (index) {
case 0:
asm("mov %0, %%dr0" ::"r"(value));
break;
case 1:
asm("mov %0, %%dr1" ::"r"(value));
break;
case 2:
asm("mov %0, %%dr2" ::"r"(value));
break;
case 3:
asm("mov %0, %%dr3" ::"r"(value));
break;
default:
return false;
}
return true;
}
static inline uintptr_t debug_read_status(void)
{
uintptr_t ret = 0;
asm("mov %%dr6, %0" : "=r"(ret));
return ret;
}
static inline void debug_write_status(uintptr_t value)
{
asm("mov %0, %%dr6" ::"r"(value));
}
static inline uintptr_t debug_read_control(void)
{
uintptr_t ret = 0;
asm("mov %%dr7, %0" : "=r"(ret));
return ret;
}
static inline void debug_write_control(uintptr_t value)
{
asm("mov %0, %%dr7" ::"r"(value));
}
static enum breakpoint_result allocate_breakpoint(struct breakpoint_handle *out_handle,
enum breakpoint_type type)
{
for (int i = 0; i < DEBUG_REGISTER_COUNT; i++) {
if (breakpoints[i].allocated)
continue;
breakpoints[i].allocated = true;
breakpoints[i].handler = NULL;
breakpoints[i].type = type;
out_handle->bp = i;
return BREAKPOINT_RES_OK;
}
return BREAKPOINT_RES_NONE_AVAILABLE;
}
static enum breakpoint_result validate_handle(struct breakpoint_handle handle)
{
int bp = handle.bp;
if (bp < 0 || bp >= DEBUG_REGISTER_COUNT || !breakpoints[bp].allocated)
return BREAKPOINT_RES_INVALID_HANDLE;
return BREAKPOINT_RES_OK;
}
enum breakpoint_result breakpoint_create_instruction(struct breakpoint_handle *out_handle,
void *virt_addr)
{
enum breakpoint_result res =
allocate_breakpoint(out_handle, BREAKPOINT_TYPE_INSTRUCTION);
if (res != BREAKPOINT_RES_OK)
return res;
int bp = out_handle->bp;
if (!debug_write_addr_reg(bp, (uintptr_t)virt_addr))
return BREAKPOINT_RES_INVALID_HANDLE;
uintptr_t control = debug_read_control();
control &= ~DEBUG_CTRL_LT_MASK(bp);
control |= DEBUG_CTRL_LT(bp, DEBUG_CTRL_LEN_1, BREAKPOINT_TYPE_INSTRUCTION);
debug_write_control(control);
return BREAKPOINT_RES_OK;
}
enum breakpoint_result breakpoint_create_data(struct breakpoint_handle *out_handle,
void *virt_addr, size_t len, bool write_only)
{
uintptr_t len_value = 0;
switch (len) {
case 1:
len_value = DEBUG_CTRL_LEN_1;
break;
case 2:
len_value = DEBUG_CTRL_LEN_2;
break;
case 4:
len_value = DEBUG_CTRL_LEN_4;
break;
case 8:
/* Only supported on 64-bit CPUs */
if (!ENV_X86_64)
return BREAKPOINT_RES_INVALID_LENGTH;
len_value = DEBUG_CTRL_LEN_8;
break;
default:
return BREAKPOINT_RES_INVALID_LENGTH;
}
enum breakpoint_type type =
write_only ? BREAKPOINT_TYPE_DATA_WRITE : BREAKPOINT_TYPE_DATA_RW;
enum breakpoint_result res = allocate_breakpoint(out_handle, type);
if (res != BREAKPOINT_RES_OK)
return res;
int bp = out_handle->bp;
if (!debug_write_addr_reg(bp, (uintptr_t)virt_addr))
return BREAKPOINT_RES_INVALID_HANDLE;
uintptr_t control = debug_read_control();
control &= ~DEBUG_CTRL_LT_MASK(bp);
control |= DEBUG_CTRL_LT(bp, len_value, type);
debug_write_control(control);
return BREAKPOINT_RES_OK;
}
enum breakpoint_result breakpoint_remove(struct breakpoint_handle handle)
{
enum breakpoint_result res = validate_handle(handle);
if (res != BREAKPOINT_RES_OK)
return res;
breakpoint_enable(handle, false);
int bp = handle.bp;
breakpoints[bp].allocated = false;
return BREAKPOINT_RES_OK;
}
enum breakpoint_result breakpoint_enable(struct breakpoint_handle handle, bool enabled)
{
enum breakpoint_result res = validate_handle(handle);
if (res != BREAKPOINT_RES_OK)
return res;
uintptr_t control = debug_read_control();
int bp = handle.bp;
control &= ~DEBUG_CTRL_ENABLE_MASK(bp);
if (enabled)
control |= DEBUG_CTRL_ENABLE(bp, DEBUG_CTRL_ENABLE_GLOBAL);
debug_write_control(control);
return BREAKPOINT_RES_OK;
}
enum breakpoint_result breakpoint_get_type(struct breakpoint_handle handle,
enum breakpoint_type *type)
{
enum breakpoint_result res = validate_handle(handle);
if (res != BREAKPOINT_RES_OK)
return res;
*type = breakpoints[handle.bp].type;
return BREAKPOINT_RES_OK;
}
enum breakpoint_result breakpoint_set_handler(struct breakpoint_handle handle,
breakpoint_handler handler)
{
enum breakpoint_result res = validate_handle(handle);
if (res != BREAKPOINT_RES_OK)
return res;
breakpoints[handle.bp].handler = handler;
return BREAKPOINT_RES_OK;
}
static enum breakpoint_result is_breakpoint_hit(struct breakpoint_handle handle, bool *out_hit)
{
enum breakpoint_result res = validate_handle(handle);
if (res != BREAKPOINT_RES_OK)
return res;
uintptr_t status = debug_read_status();
*out_hit = DEBUG_STATUS_GET_BP_HIT(handle.bp, status);
return BREAKPOINT_RES_OK;
}
int breakpoint_dispatch_handler(struct eregs *info)
{
bool instr_bp_hit = 0;
for (int i = 0; i < DEBUG_REGISTER_COUNT; i++) {
struct breakpoint_handle handle = { i };
bool hit = false;
enum breakpoint_type type;
if (is_breakpoint_hit(handle, &hit) != BREAKPOINT_RES_OK || !hit)
continue;
if (breakpoint_get_type(handle, &type) != BREAKPOINT_RES_OK)
continue;
instr_bp_hit |= type == BREAKPOINT_TYPE_INSTRUCTION;
/* Call the breakpoint handler. */
if (breakpoints[handle.bp].handler) {
int ret = breakpoints[handle.bp].handler(handle, info);
/* A non-zero return value indicates a fatal error. */
if (ret)
return ret;
}
}
/* Clear hit breakpoints. */
uintptr_t status = debug_read_status();
for (int i = 0; i < DEBUG_REGISTER_COUNT; i++) {
status &= ~DEBUG_STATUS_BP_HIT_MASK(i);
}
debug_write_status(status);
if (instr_bp_hit) {
/* Set the resume flag so the same breakpoint won't be hit immediately. */
#if ENV_X86_64
info->rflags |= FLAGS_RESUME;
#else
info->eflags |= FLAGS_RESUME;
#endif
}
return 0;
}