/* $NetBSD: db_interface.c,v 1.64 2008/05/23 17:01:32 tsutsui Exp $ */ /* * Mach Operating System * Copyright (c) 1991,1990 Carnegie Mellon University * All Rights Reserved. * * Permission to use, copy, modify and distribute this software and its * documentation is hereby granted, provided that both the copyright * notice and this permission notice appear in all copies of the * software, derivative works or modified versions, and any portions * thereof, and that both notices appear in supporting documentation. * * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. * * Carnegie Mellon requests users of this software to return to * * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU * School of Computer Science * Carnegie Mellon University * Pittsburgh PA 15213-3890 * * any improvements or extensions that they make and grant Carnegie Mellon * the rights to redistribute these changes. */ #include <sys/cdefs.h> __KERNEL_RCSID(0, "$NetBSD: db_interface.c,v 1.64 2008/05/23 17:01:32 tsutsui Exp $"); #include "opt_cputype.h" /* which mips CPUs do we support? */ #include "opt_ddb.h" #include "opt_kgdb.h" #include <sys/types.h> #include <sys/systm.h> #include <sys/param.h> #include <sys/proc.h> #include <sys/user.h> #include <sys/reboot.h> #include <uvm/uvm_extern.h> #include <mips/cache.h> #include <mips/pte.h> #include <mips/cpu.h> #include <mips/locore.h> #include <mips/mips_opcode.h> #include <dev/cons.h> #include <machine/int_fmtio.h> #include <machine/db_machdep.h> #include <ddb/db_access.h> #ifndef KGDB #include <ddb/db_command.h> #include <ddb/db_output.h> #include <ddb/db_sym.h> #include <ddb/db_extern.h> #include <ddb/db_interface.h> #endif int db_active = 0; db_regs_t ddb_regs; mips_reg_t kdbaux[11]; /* XXX struct switchframe: better inside curpcb? XXX */ void db_tlbdump_cmd(db_expr_t, bool, db_expr_t, const char *); void db_kvtophys_cmd(db_expr_t, bool, db_expr_t, const char *); void db_cp0dump_cmd(db_expr_t, bool, db_expr_t, const char *); static void kdbpoke_4(vaddr_t addr, int newval); static void kdbpoke_2(vaddr_t addr, short newval); static void kdbpoke_1(vaddr_t addr, char newval); static short kdbpeek_2(vaddr_t addr); static char kdbpeek_1(vaddr_t addr); vaddr_t MachEmulateBranch(struct frame *, vaddr_t, unsigned, int); paddr_t kvtophys(vaddr_t); #ifdef DDB_TRACE int kdbpeek(vaddr_t addr) { if (addr == 0 || (addr & 3)) return 0; return *(int *)addr; } #endif static short kdbpeek_2(vaddr_t addr) { return *(short *)addr; } static char kdbpeek_1(vaddr_t addr) { return *(char *)addr; } /* * kdbpoke -- write a value to a kernel virtual address. * XXX should handle KSEG2 addresses and check for unmapped pages. * XXX user-space addresess? */ static void kdbpoke_4(vaddr_t addr, int newval) { *(int*) addr = newval; wbflush(); } static void kdbpoke_2(vaddr_t addr, short newval) { *(short*) addr = newval; wbflush(); } static void kdbpoke_1(vaddr_t addr, char newval) { *(char*) addr = newval; wbflush(); } #if 0 /* UNUSED */ /* * Received keyboard interrupt sequence. */ void kdb_kbd_trap(int *tf) { if (db_active == 0 && (boothowto & RB_KDB)) { printf("\n\nkernel: keyboard interrupt\n"); ddb_trap(-1, tf); } } #endif #ifndef KGDB int kdb_trap(int type, mips_reg_t /* struct trapframe */ *tfp) { struct frame *f = (struct frame *)&ddb_regs; #ifdef notyet switch (type) { case T_BREAK: /* breakpoint */ case -1: /* keyboard interrupt */ break; default: printf("kernel: %s trap", trap_type[type & 0xff]); if (db_recover != 0) { db_error("Faulted in DDB; continuing...\n"); /*NOTREACHED*/ } break; } #endif /* Should switch to kdb`s own stack here. */ db_set_ddb_regs(type, tfp); db_active++; cnpollc(1); db_trap(type & ~T_USER, 0 /*code*/); cnpollc(0); db_active--; if (type & T_USER) *(struct frame *)curlwp->l_md.md_regs = *f; else { /* Synthetic full scale register context when trap happens */ tfp[TF_AST] = f->f_regs[_R_AST]; tfp[TF_V0] = f->f_regs[_R_V0]; tfp[TF_V1] = f->f_regs[_R_V1]; tfp[TF_A0] = f->f_regs[_R_A0]; tfp[TF_A1] = f->f_regs[_R_A1]; tfp[TF_A2] = f->f_regs[_R_A2]; tfp[TF_A3] = f->f_regs[_R_A3]; tfp[TF_T0] = f->f_regs[_R_T0]; tfp[TF_T1] = f->f_regs[_R_T1]; tfp[TF_T2] = f->f_regs[_R_T2]; tfp[TF_T3] = f->f_regs[_R_T3]; tfp[TF_TA0] = f->f_regs[_R_TA0]; tfp[TF_TA1] = f->f_regs[_R_TA1]; tfp[TF_TA2] = f->f_regs[_R_TA2]; tfp[TF_TA3] = f->f_regs[_R_TA3]; tfp[TF_T8] = f->f_regs[_R_T8]; tfp[TF_T9] = f->f_regs[_R_T9]; tfp[TF_RA] = f->f_regs[_R_RA]; tfp[TF_SR] = f->f_regs[_R_SR]; tfp[TF_MULLO] = f->f_regs[_R_MULLO]; tfp[TF_MULHI] = f->f_regs[_R_MULHI]; tfp[TF_EPC] = f->f_regs[_R_PC]; kdbaux[0] = f->f_regs[_R_S0]; kdbaux[1] = f->f_regs[_R_S1]; kdbaux[2] = f->f_regs[_R_S2]; kdbaux[3] = f->f_regs[_R_S3]; kdbaux[4] = f->f_regs[_R_S4]; kdbaux[5] = f->f_regs[_R_S5]; kdbaux[6] = f->f_regs[_R_S6]; kdbaux[7] = f->f_regs[_R_S7]; kdbaux[8] = f->f_regs[_R_SP]; kdbaux[9] = f->f_regs[_R_S8]; kdbaux[10] = f->f_regs[_R_GP]; } return (1); } void cpu_Debugger(void) { __asm("break"); } #endif /* !KGDB */ void db_set_ddb_regs(int type, mips_reg_t *tfp) { struct frame *f = (struct frame *)&ddb_regs; /* Should switch to kdb`s own stack here. */ if (type & T_USER) *f = *(struct frame *)curlwp->l_md.md_regs; else { /* Synthetic full scale register context when trap happens */ f->f_regs[_R_AST] = tfp[TF_AST]; f->f_regs[_R_V0] = tfp[TF_V0]; f->f_regs[_R_V1] = tfp[TF_V1]; f->f_regs[_R_A0] = tfp[TF_A0]; f->f_regs[_R_A1] = tfp[TF_A1]; f->f_regs[_R_A2] = tfp[TF_A2]; f->f_regs[_R_A3] = tfp[TF_A3]; f->f_regs[_R_T0] = tfp[TF_T0]; f->f_regs[_R_T1] = tfp[TF_T1]; f->f_regs[_R_T2] = tfp[TF_T2]; f->f_regs[_R_T3] = tfp[TF_T3]; f->f_regs[_R_TA0] = tfp[TF_TA0]; f->f_regs[_R_TA1] = tfp[TF_TA1]; f->f_regs[_R_TA2] = tfp[TF_TA2]; f->f_regs[_R_TA3] = tfp[TF_TA3]; f->f_regs[_R_T8] = tfp[TF_T8]; f->f_regs[_R_T9] = tfp[TF_T9]; f->f_regs[_R_RA] = tfp[TF_RA]; f->f_regs[_R_SR] = tfp[TF_SR]; f->f_regs[_R_MULLO] = tfp[TF_MULLO]; f->f_regs[_R_MULHI] = tfp[TF_MULHI]; f->f_regs[_R_PC] = tfp[TF_EPC]; f->f_regs[_R_S0] = kdbaux[0]; f->f_regs[_R_S1] = kdbaux[1]; f->f_regs[_R_S2] = kdbaux[2]; f->f_regs[_R_S3] = kdbaux[3]; f->f_regs[_R_S4] = kdbaux[4]; f->f_regs[_R_S5] = kdbaux[5]; f->f_regs[_R_S6] = kdbaux[6]; f->f_regs[_R_S7] = kdbaux[7]; f->f_regs[_R_SP] = kdbaux[8]; f->f_regs[_R_S8] = kdbaux[9]; f->f_regs[_R_GP] = kdbaux[10]; } } /* * Read bytes from kernel address space for debugger. */ void db_read_bytes(vaddr_t addr, size_t size, char *data) { int *ip; short *sp; while (size >= 4) ip = (int*)data, *ip = kdbpeek(addr), data += 4, addr += 4, size -= 4; while (size >= 2) sp = (short *)data, *sp = kdbpeek_2(addr), data += 2, addr += 2, size -= 2; if (size == 1) *data = kdbpeek_1(addr); } /* * Write bytes to kernel address space for debugger. */ void db_write_bytes(vaddr_t addr, size_t size, const char *data) { vaddr_t p = addr; size_t n = size; #ifdef DEBUG_DDB printf("db_write_bytes(%lx, %d, %p, val %x)\n", addr, size, data, (addr &3 ) == 0? *(u_int*)addr: -1); #endif while (n >= 4) { kdbpoke_4(p, *(const int *)data); p += 4; data += 4; n -= 4; } if (n >= 2) { kdbpoke_2(p, *(const short *)data); p += 2; data += 2; n -= 2; } if (n == 1) { kdbpoke_1(p, *(const char *)data); } mips_icache_sync_range((vaddr_t) addr, size); } #ifndef KGDB void db_tlbdump_cmd(db_expr_t addr, bool have_addr, db_expr_t count, const char *modif) { #ifdef MIPS1 if (!MIPS_HAS_R4K_MMU) { struct mips1_tlb { u_int32_t tlb_hi; u_int32_t tlb_lo; } tlb; int i; void mips1_TLBRead(int, struct mips1_tlb *); for (i = 0; i < mips_num_tlb_entries; i++) { mips1_TLBRead(i, &tlb); db_printf("TLB%c%2d Hi 0x%08x Lo 0x%08x", (tlb.tlb_lo & MIPS1_PG_V) ? ' ' : '*', i, tlb.tlb_hi, tlb.tlb_lo & MIPS1_PG_FRAME); db_printf(" %c%c%c\n", (tlb.tlb_lo & MIPS1_PG_D) ? 'D' : ' ', (tlb.tlb_lo & MIPS1_PG_G) ? 'G' : ' ', (tlb.tlb_lo & MIPS1_PG_N) ? 'N' : ' '); } } #endif #ifdef MIPS3_PLUS if (MIPS_HAS_R4K_MMU) { struct tlb tlb; int i; for (i = 0; i < mips_num_tlb_entries; i++) { #if defined(MIPS3) #if defined(MIPS3_5900) mips5900_TLBRead(i, &tlb); #else mips3_TLBRead(i, &tlb); #endif #elif defined(MIPS32) mips32_TLBRead(i, &tlb); #elif defined(MIPS64) mips64_TLBRead(i, &tlb); #endif db_printf("TLB%c%2d Hi 0x%08x ", (tlb.tlb_lo0 | tlb.tlb_lo1) & MIPS3_PG_V ? ' ' : '*', i, tlb.tlb_hi); db_printf("Lo0=0x%09" PRIx64 " %c%c attr %x ", (uint64_t)mips_tlbpfn_to_paddr(tlb.tlb_lo0), (tlb.tlb_lo0 & MIPS3_PG_D) ? 'D' : ' ', (tlb.tlb_lo0 & MIPS3_PG_G) ? 'G' : ' ', (tlb.tlb_lo0 >> 3) & 7); db_printf("Lo1=0x%09" PRIx64 " %c%c attr %x sz=%x\n", (uint64_t)mips_tlbpfn_to_paddr(tlb.tlb_lo1), (tlb.tlb_lo1 & MIPS3_PG_D) ? 'D' : ' ', (tlb.tlb_lo1 & MIPS3_PG_G) ? 'G' : ' ', (tlb.tlb_lo1 >> 3) & 7, tlb.tlb_mask); } } #endif } void db_kvtophys_cmd(db_expr_t addr, bool have_addr, db_expr_t count, const char *modif) { if (!have_addr) return; if (MIPS_KSEG2_START <= addr) { /* * Cast the physical address -- some platforms, while * being ILP32, may be using 64-bit paddr_t's. */ db_printf("0x%lx -> 0x%" PRIx64 "\n", addr, (uint64_t) kvtophys(addr)); } else printf("not a kernel virtual address\n"); } #define FLDWIDTH 10 #define SHOW32(reg, name) \ do { \ uint32_t __val; \ \ __asm volatile("mfc0 %0,$" ___STRING(reg) : "=r"(__val)); \ printf(" %s:%*s %#x\n", name, FLDWIDTH - (int) strlen(name), \ "", __val); \ } while (0) /* XXX not 64-bit ABI safe! */ #define SHOW64(reg, name) \ do { \ uint64_t __val; \ \ __asm volatile( \ ".set push \n\t" \ ".set mips3 \n\t" \ ".set noat \n\t" \ "dmfc0 $1,$" ___STRING(reg) " \n\t" \ "dsll %L0,$1,32 \n\t" \ "dsrl %L0,%L0,32 \n\t" \ "dsrl %M0,$1,32 \n\t" \ ".set pop" \ : "=r"(__val)); \ printf(" %s:%*s %#"PRIx64"x\n", name, FLDWIDTH - (int) strlen(name), \ "", __val); \ } while (0) void db_cp0dump_cmd(db_expr_t addr, bool have_addr, db_expr_t count, const char *modif) { SHOW32(MIPS_COP_0_TLB_INDEX, "index"); SHOW32(MIPS_COP_0_TLB_RANDOM, "random"); if (!MIPS_HAS_R4K_MMU) { SHOW32(MIPS_COP_0_TLB_LOW, "entrylow"); } else { if (CPUIS64BITS) { SHOW64(MIPS_COP_0_TLB_LO0, "entrylo0"); SHOW64(MIPS_COP_0_TLB_LO1, "entrylo1"); } else { SHOW32(MIPS_COP_0_TLB_LO0, "entrylo0"); SHOW32(MIPS_COP_0_TLB_LO1, "entrylo1"); } } if (CPUIS64BITS) { SHOW64(MIPS_COP_0_TLB_CONTEXT, "context"); } else { SHOW32(MIPS_COP_0_TLB_CONTEXT, "context"); } if (MIPS_HAS_R4K_MMU) { SHOW32(MIPS_COP_0_TLB_PG_MASK, "pagemask"); SHOW32(MIPS_COP_0_TLB_WIRED, "wired"); } if (CPUIS64BITS) { SHOW64(MIPS_COP_0_BAD_VADDR, "badvaddr"); } else { SHOW32(MIPS_COP_0_BAD_VADDR, "badvaddr"); } if (cpu_arch >= CPU_ARCH_MIPS3) { SHOW32(MIPS_COP_0_COUNT, "count"); } if (CPUIS64BITS) { SHOW64(MIPS_COP_0_TLB_HI, "entryhi"); } else { SHOW32(MIPS_COP_0_TLB_HI, "entryhi"); } if (cpu_arch >= CPU_ARCH_MIPS3) { SHOW32(MIPS_COP_0_COMPARE, "compare"); } SHOW32(MIPS_COP_0_STATUS, "status"); SHOW32(MIPS_COP_0_CAUSE, "cause"); if (CPUIS64BITS) { SHOW64(MIPS_COP_0_EXC_PC, "epc"); } else { SHOW32(MIPS_COP_0_EXC_PC, "epc"); } SHOW32(MIPS_COP_0_PRID, "prid"); SHOW32(MIPS_COP_0_CONFIG, "config"); #if defined(MIPS32) || defined(MIPS64) if (CPUISMIPSNN) { uint32_t val; val = mipsNN_cp0_config1_read(); printf(" config1: %#x\n", val); } #endif if (MIPS_HAS_LLSC) { if (CPUISMIPS64) { SHOW64(MIPS_COP_0_LLADDR, "lladdr"); SHOW64(MIPS_COP_0_WATCH_LO, "watchlo"); } else { SHOW32(MIPS_COP_0_LLADDR, "lladdr"); SHOW32(MIPS_COP_0_WATCH_LO, "watchlo"); } SHOW32(MIPS_COP_0_WATCH_HI, "watchhi"); if (CPUIS64BITS) { SHOW64(MIPS_COP_0_TLB_XCONTEXT, "xcontext"); } if (CPUISMIPSNN) { if (CPUISMIPS64) { SHOW64(MIPS_COP_0_PERFCNT, "perfcnt"); } else { SHOW32(MIPS_COP_0_PERFCNT, "perfcnt"); } } SHOW32(MIPS_COP_0_ECC, "ecc"); SHOW32(MIPS_COP_0_CACHE_ERR, "cacherr"); SHOW32(MIPS_COP_0_TAG_LO, "cachelo"); SHOW32(MIPS_COP_0_TAG_HI, "cachehi"); if (CPUIS64BITS) { SHOW64(MIPS_COP_0_ERROR_PC, "errorpc"); } else { SHOW32(MIPS_COP_0_ERROR_PC, "errorpc"); } } } const struct db_command db_machine_command_table[] = { { DDB_ADD_CMD("cp0", db_cp0dump_cmd, 0, "Dump CP0 registers.", NULL, NULL) }, { DDB_ADD_CMD("kvtop", db_kvtophys_cmd, 0, "Print the physical address for a given kernel virtual address", "address", " address:\tvirtual address to look up") }, { DDB_ADD_CMD("tlb", db_tlbdump_cmd, 0, "Print out TLB entries. (only works with options DEBUG)", NULL, NULL) }, { DDB_ADD_CMD(NULL, NULL, 0, NULL,NULL,NULL) } }; #endif /* !KGDB */ /* * Determine whether the instruction involves a delay slot. */ bool inst_branch(int inst) { InstFmt i; int delslt; i.word = inst; delslt = 0; switch (i.JType.op) { case OP_BCOND: case OP_J: case OP_JAL: case OP_BEQ: case OP_BNE: case OP_BLEZ: case OP_BGTZ: case OP_BEQL: case OP_BNEL: case OP_BLEZL: case OP_BGTZL: delslt = 1; break; case OP_COP0: case OP_COP1: switch (i.RType.rs) { case OP_BCx: case OP_BCy: delslt = 1; } break; case OP_SPECIAL: if (i.RType.op == OP_JR || i.RType.op == OP_JALR) delslt = 1; break; } return delslt; } /* * Determine whether the instruction calls a function. */ bool inst_call(int inst) { bool call; InstFmt i; i.word = inst; if (i.JType.op == OP_SPECIAL && ((i.RType.func == OP_JR && i.RType.rs != 31) || i.RType.func == OP_JALR)) call = 1; else if (i.JType.op == OP_JAL) call = 1; else call = 0; return call; } /* * Determine whether the instruction returns from a function (j ra). The * compiler can use this construct for other jumps, but usually will not. * This lets the ddb "next" command to work (also need inst_trap_return()). */ bool inst_return(int inst) { InstFmt i; i.word = inst; return (i.JType.op == OP_SPECIAL && i.RType.func == OP_JR && i.RType.rs == 31); } /* * Determine whether the instruction makes a jump. */ bool inst_unconditional_flow_transfer(int inst) { InstFmt i; bool jump; i.word = inst; jump = (i.JType.op == OP_J) || (i.JType.op == OP_SPECIAL && i.RType.func == OP_JR); return jump; } /* * Determine whether the instruction is a load/store as appropriate. */ bool inst_load(int inst) { InstFmt i; i.word = inst; switch (i.JType.op) { case OP_LWC1: case OP_LB: case OP_LH: case OP_LW: case OP_LD: case OP_LBU: case OP_LHU: case OP_LWU: case OP_LDL: case OP_LDR: case OP_LWL: case OP_LWR: case OP_LL: return 1; default: return 0; } } bool inst_store(int inst) { InstFmt i; i.word = inst; switch (i.JType.op) { case OP_SWC1: case OP_SB: case OP_SH: case OP_SW: case OP_SD: case OP_SDL: case OP_SDR: case OP_SWL: case OP_SWR: case OP_SCD: return 1; default: return 0; } } /* * Return the next pc if the given branch is taken. * MachEmulateBranch() runs analysis for branch delay slot. */ db_addr_t branch_taken(int inst, db_addr_t pc, db_regs_t *regs) { vaddr_t ra; unsigned fpucsr; fpucsr = curlwp ? PCB_FSR(&curlwp->l_addr->u_pcb) : 0; ra = MachEmulateBranch((struct frame *)regs, pc, fpucsr, 0); return ra; } /* * Return the next pc of an arbitrary instruction. */ db_addr_t next_instr_address(db_addr_t pc, bool bd) { unsigned ins; if (bd == false) return (pc + 4); if (pc < MIPS_KSEG0_START) ins = fuiword((void *)pc); else ins = *(unsigned *)pc; if (inst_branch(ins) || inst_call(ins) || inst_return(ins)) return (pc + 4); return (pc); }