#include <sys/param.h>
#include <sys/types.h>
#include <sys/lwp.h>
#include <sys/pool.h>
#include <x86/cpufunc.h>
#include <x86/dbregs.h>
#include <uvm/uvm_prot.h>
#include <uvm/uvm_pmap.h>
#include <machine/pmap.h>
struct pool x86_dbregspl;
static struct dbreg initdbstate;
#define X86_BREAKPOINT_CONDITION_DETECTED ( \
X86_DR6_DR0_BREAKPOINT_CONDITION_DETECTED | \
X86_DR6_DR1_BREAKPOINT_CONDITION_DETECTED | \
X86_DR6_DR2_BREAKPOINT_CONDITION_DETECTED | \
X86_DR6_DR3_BREAKPOINT_CONDITION_DETECTED )
#define X86_GLOBAL_BREAKPOINT ( \
X86_DR7_GLOBAL_DR0_BREAKPOINT | \
X86_DR7_GLOBAL_DR1_BREAKPOINT | \
X86_DR7_GLOBAL_DR2_BREAKPOINT | \
X86_DR7_GLOBAL_DR3_BREAKPOINT )
void
x86_dbregs_init(void)
{
initdbstate.dr[0] = rdr0();
initdbstate.dr[1] = rdr1();
initdbstate.dr[2] = rdr2();
initdbstate.dr[3] = rdr3();
initdbstate.dr[6] = rdr6();
initdbstate.dr[7] = rdr7();
initdbstate.dr[6] &= ~X86_BREAKPOINT_CONDITION_DETECTED;
initdbstate.dr[7] &= ~X86_DR7_GENERAL_DETECT_ENABLE;
pool_init(&x86_dbregspl, sizeof(struct dbreg), 16, 0, 0, "dbregs",
NULL, IPL_NONE);
}
static void
x86_dbregs_reset(void)
{
ldr7(0);
ldr6(rdr6() & ~X86_BREAKPOINT_CONDITION_DETECTED);
}
void
x86_dbregs_clear(struct lwp *l)
{
struct pcb *pcb = lwp_getpcb(l);
struct dbreg *dbregs;
KASSERT(l == curlwp);
if (__predict_true(pcb->pcb_dbregs == NULL)) {
KASSERT((pcb->pcb_flags & PCB_DBREGS) == 0);
return;
}
dbregs = pcb->pcb_dbregs;
kpreempt_disable();
pcb->pcb_dbregs = NULL;
pcb->pcb_flags &= ~PCB_DBREGS;
x86_dbregs_reset();
kpreempt_enable();
pool_put(&x86_dbregspl, dbregs);
}
void
x86_dbregs_abandon(struct lwp *l)
{
struct pcb *pcb = lwp_getpcb(l);
kpreempt_disable();
pcb->pcb_flags &= ~PCB_DBREGS;
x86_dbregs_reset();
kpreempt_enable();
}
void
x86_dbregs_read(struct lwp *l, struct dbreg *regs)
{
struct pcb *pcb = lwp_getpcb(l);
if (pcb->pcb_dbregs == NULL) {
pcb->pcb_dbregs = pool_get(&x86_dbregspl, PR_WAITOK);
memcpy(pcb->pcb_dbregs, &initdbstate, sizeof(initdbstate));
pcb->pcb_flags |= PCB_DBREGS;
}
memcpy(regs, pcb->pcb_dbregs, sizeof(*regs));
}
void
x86_dbregs_save(struct lwp *l)
{
struct pcb *pcb = lwp_getpcb(l);
if (!(pcb->pcb_flags & PCB_DBREGS)) {
return;
}
KASSERT(pcb->pcb_dbregs != NULL);
pcb->pcb_dbregs->dr[0] = rdr0();
pcb->pcb_dbregs->dr[1] = rdr1();
pcb->pcb_dbregs->dr[2] = rdr2();
pcb->pcb_dbregs->dr[3] = rdr3();
pcb->pcb_dbregs->dr[6] = rdr6();
pcb->pcb_dbregs->dr[7] = rdr7();
}
void
x86_dbregs_restore(struct lwp *l)
{
struct pcb *pcb = lwp_getpcb(l);
if (!(pcb->pcb_flags & PCB_DBREGS)) {
return;
}
KASSERT(pcb->pcb_dbregs != NULL);
ldr0(pcb->pcb_dbregs->dr[0]);
ldr1(pcb->pcb_dbregs->dr[1]);
ldr2(pcb->pcb_dbregs->dr[2]);
ldr3(pcb->pcb_dbregs->dr[3]);
ldr6(pcb->pcb_dbregs->dr[6]);
ldr7(pcb->pcb_dbregs->dr[7]);
}
void
x86_dbregs_store_dr6(struct lwp *l)
{
struct pcb *pcb = lwp_getpcb(l);
KASSERT(l == curlwp);
KASSERT(pcb->pcb_dbregs != NULL);
pcb->pcb_dbregs->dr[6] = rdr6();
}
int
x86_dbregs_user_trap(void)
{
register_t dr7, dr6;
register_t bp;
dr7 = rdr7();
if ((dr7 & X86_GLOBAL_BREAKPOINT) == 0) {
return 0;
}
dr6 = rdr6();
bp = dr6 & X86_BREAKPOINT_CONDITION_DETECTED;
if (!bp) {
return 0;
}
if (bp & X86_DR6_DR0_BREAKPOINT_CONDITION_DETECTED)
if (rdr0() < (vaddr_t)VM_MAXUSER_ADDRESS)
return 1;
if (bp & X86_DR6_DR1_BREAKPOINT_CONDITION_DETECTED)
if (rdr1() < (vaddr_t)VM_MAXUSER_ADDRESS)
return 1;
if (bp & X86_DR6_DR2_BREAKPOINT_CONDITION_DETECTED)
if (rdr2() < (vaddr_t)VM_MAXUSER_ADDRESS)
return 1;
if (bp & X86_DR6_DR3_BREAKPOINT_CONDITION_DETECTED)
if (rdr3() < (vaddr_t)VM_MAXUSER_ADDRESS)
return 1;
return 0;
}
int
x86_dbregs_validate(const struct dbreg *regs)
{
size_t i;
for (i = 0; i < X86_DBREGS; i++) {
if (regs->dr[i] >= (vaddr_t)VM_MAXUSER_ADDRESS)
return EINVAL;
}
#ifndef i386
if (regs->dr[6] & X86_DR6_MBZ) {
return EINVAL;
}
if (regs->dr[7] & X86_DR7_MBZ) {
return EINVAL;
}
#endif
if (regs->dr[7] & X86_DR7_GENERAL_DETECT_ENABLE) {
return EINVAL;
}
return 0;
}
void
x86_dbregs_write(struct lwp *l, const struct dbreg *regs)
{
struct pcb *pcb = lwp_getpcb(l);
if (pcb->pcb_dbregs == NULL) {
pcb->pcb_dbregs = pool_get(&x86_dbregspl, PR_WAITOK);
}
memcpy(pcb->pcb_dbregs, regs, sizeof(*regs));
pcb->pcb_flags |= PCB_DBREGS;
}
void
x86_dbregs_switch(struct lwp *oldlwp, struct lwp *newlwp)
{
struct pcb *oldpcb, *newpcb;
bool olddb, newdb;
oldpcb = lwp_getpcb(oldlwp);
newpcb = lwp_getpcb(newlwp);
olddb = (oldpcb->pcb_flags & PCB_DBREGS) != 0;
newdb = (newpcb->pcb_flags & PCB_DBREGS) != 0;
if (__predict_true(!olddb && !newdb)) {
return;
}
if (olddb) {
x86_dbregs_save(oldlwp);
}
if (newdb) {
x86_dbregs_restore(newlwp);
} else if (olddb) {
x86_dbregs_reset();
}
}