#include "opt_ddb.h"
#include <sys/types.h>
#include <sys/kdb.h>
#include <sys/pcpu.h>
#include <sys/reg.h>
#include <sys/smp.h>
#include <sys/systm.h>
#include <machine/frame.h>
#include <machine/kdb.h>
#include <machine/md_var.h>
#include <ddb/ddb.h>
#include <ddb/db_sym.h>
#define NDBREGS 4
#ifdef __amd64__
#define MAXWATCHSIZE 8
#else
#define MAXWATCHSIZE 4
#endif
static void
dbreg_set_watchreg(int watchnum, vm_offset_t watchaddr, vm_size_t size,
int access, struct dbreg *d)
{
int len;
MPASS(watchnum >= 0 && watchnum < NDBREGS);
if (access == DBREG_DR7_EXEC)
size = 1;
switch (size) {
case 1:
len = DBREG_DR7_LEN_1;
break;
case 2:
len = DBREG_DR7_LEN_2;
break;
case 4:
len = DBREG_DR7_LEN_4;
break;
#if MAXWATCHSIZE >= 8
case 8:
len = DBREG_DR7_LEN_8;
break;
#endif
default:
return;
}
d->dr[7] &= ~DBREG_DR7_MASK(watchnum);
DBREG_DRX(d, watchnum) = watchaddr;
d->dr[7] |= DBREG_DR7_SET(watchnum, len, access,
DBREG_DR7_GLOBAL_ENABLE);
}
static void
dbreg_clr_watchreg(int watchnum, struct dbreg *d)
{
MPASS(watchnum >= 0 && watchnum < NDBREGS);
d->dr[7] &= ~DBREG_DR7_MASK(watchnum);
DBREG_DRX(d, watchnum) = 0;
}
static void
dbreg_sync(struct dbreg *dp)
{
#ifdef __amd64__
struct pcpu *pc;
int cpu, c;
cpu = PCPU_GET(cpuid);
CPU_FOREACH(c) {
if (c == cpu)
continue;
pc = pcpu_find(c);
memcpy(pc->pc_dbreg, dp, sizeof(*dp));
pc->pc_dbreg_cmd = PC_DBREG_CMD_LOAD;
}
#endif
}
int
dbreg_set_watchpoint(vm_offset_t addr, vm_size_t size, int access)
{
struct dbreg *d;
int avail, i, wsize;
#ifdef __amd64__
d = (struct dbreg *)PCPU_PTR(dbreg);
#else
struct dbreg d_temp;
d = &d_temp;
#endif
if (access != DBREG_DR7_EXEC && access != DBREG_DR7_WRONLY &&
access != DBREG_DR7_RDWR)
return (EINVAL);
fill_dbregs(NULL, d);
avail = 0;
for (i = 0; i < NDBREGS; i++) {
if (!DBREG_DR7_ENABLED(d->dr[7], i))
avail++;
}
if (avail * MAXWATCHSIZE < size)
return (EBUSY);
for (i = 0; i < NDBREGS && size > 0; i++) {
if (!DBREG_DR7_ENABLED(d->dr[7], i)) {
if ((size >= 8 || (avail == 1 && size > 4)) &&
MAXWATCHSIZE == 8)
wsize = 8;
else if (size > 2)
wsize = 4;
else
wsize = size;
dbreg_set_watchreg(i, addr, wsize, access, d);
addr += wsize;
size -= wsize;
avail--;
}
}
set_dbregs(NULL, d);
dbreg_sync(d);
return (0);
}
int
dbreg_clr_watchpoint(vm_offset_t addr, vm_size_t size)
{
struct dbreg *d;
int i;
#ifdef __amd64__
d = (struct dbreg *)PCPU_PTR(dbreg);
#else
struct dbreg d_temp;
d = &d_temp;
#endif
fill_dbregs(NULL, d);
for (i = 0; i < NDBREGS; i++) {
if (DBREG_DR7_ENABLED(d->dr[7], i)) {
if (DBREG_DRX((d), i) >= addr &&
DBREG_DRX((d), i) < addr + size)
dbreg_clr_watchreg(i, d);
}
}
set_dbregs(NULL, d);
dbreg_sync(d);
return (0);
}
#ifdef DDB
static const char *
watchtype_str(int type)
{
switch (type) {
case DBREG_DR7_EXEC:
return ("execute");
case DBREG_DR7_RDWR:
return ("read/write");
case DBREG_DR7_WRONLY:
return ("write");
default:
return ("invalid");
}
}
void
dbreg_list_watchpoints(void)
{
struct dbreg d;
int i, len, type;
fill_dbregs(NULL, &d);
db_printf("\nhardware watchpoints:\n");
db_printf(" watch status type len address\n");
db_printf(" ----- -------- ---------- --- ----------\n");
for (i = 0; i < NDBREGS; i++) {
if (DBREG_DR7_ENABLED(d.dr[7], i)) {
type = DBREG_DR7_ACCESS(d.dr[7], i);
len = DBREG_DR7_LEN(d.dr[7], i);
db_printf(" %-5d %-8s %10s %3d ",
i, "enabled", watchtype_str(type), len + 1);
db_printsym((db_addr_t)DBREG_DRX(&d, i), DB_STGY_ANY);
db_printf("\n");
} else {
db_printf(" %-5d disabled\n", i);
}
}
}
#endif
#ifdef __amd64__
void
amd64_db_resume_dbreg(void)
{
struct dbreg *d;
switch (PCPU_GET(dbreg_cmd)) {
case PC_DBREG_CMD_LOAD:
d = (struct dbreg *)PCPU_PTR(dbreg);
set_dbregs(NULL, d);
PCPU_SET(dbreg_cmd, PC_DBREG_CMD_NONE);
break;
}
}
#endif
int
kdb_cpu_set_watchpoint(vm_offset_t addr, vm_size_t size, int access)
{
switch (access) {
case KDB_DBG_ACCESS_W:
access = DBREG_DR7_WRONLY;
break;
case KDB_DBG_ACCESS_RW:
access = DBREG_DR7_RDWR;
break;
case KDB_DBG_ACCESS_R:
default:
return (EINVAL);
}
return (dbreg_set_watchpoint(addr, size, access));
}
int
kdb_cpu_clr_watchpoint(vm_offset_t addr, vm_size_t size)
{
return (dbreg_clr_watchpoint(addr, size));
}