/* $NetBSD: db_machdep.c,v 1.45 2022/10/26 23:38:05 riastradh Exp $ */ /*- * Copyright (c) 2014 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Matt Thomas of 3am Software Foundry. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include __KERNEL_RCSID(0, "$NetBSD: db_machdep.c,v 1.45 2022/10/26 23:38:05 riastradh Exp $"); #ifdef _KERNEL_OPT #include "opt_compat_netbsd32.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include void db_md_cpuinfo_cmd(db_expr_t, bool, db_expr_t, const char *); void db_md_frame_cmd(db_expr_t, bool, db_expr_t, const char *); void db_md_lwp_cmd(db_expr_t, bool, db_expr_t, const char *); void db_md_pte_cmd(db_expr_t, bool, db_expr_t, const char *); void db_md_reset_cmd(db_expr_t, bool, db_expr_t, const char *); void db_md_tlbi_cmd(db_expr_t, bool, db_expr_t, const char *); void db_md_ttbr_cmd(db_expr_t, bool, db_expr_t, const char *); void db_md_sysreg_cmd(db_expr_t, bool, db_expr_t, const char *); void db_md_break_cmd(db_expr_t, bool, db_expr_t, const char *); void db_md_watch_cmd(db_expr_t, bool, db_expr_t, const char *); #if defined(_KERNEL) && defined(MULTIPROCESSOR) void db_md_switch_cpu_cmd(db_expr_t, bool, db_expr_t, const char *); #endif #if defined(_KERNEL) static void db_md_meminfo_cmd(db_expr_t, bool, db_expr_t, const char *); #endif #ifdef _KERNEL #define MAX_BREAKPOINT 15 #define MAX_WATCHPOINT 15 /* The number varies depending on the CPU core (e.g. big.LITTLE) */ static int max_breakpoint = MAX_BREAKPOINT; static int max_watchpoint = MAX_WATCHPOINT; struct breakpoint_info { db_addr_t addr; }; static struct breakpoint_info breakpoint_buf[MAX_BREAKPOINT + 1]; struct watchpoint_info { db_addr_t addr; int size; int accesstype; }; static struct watchpoint_info watchpoint_buf[MAX_WATCHPOINT + 1]; #endif const struct db_command db_machine_command_table[] = { #if defined(_KERNEL) && defined(MULTIPROCESSOR) { DDB_ADD_CMD( "cpu", db_md_switch_cpu_cmd, 0, "switch to a different cpu", NULL, NULL) }, #endif #if defined(_KERNEL) { DDB_ADD_CMD( "break", db_md_break_cmd, 0, "set or clear breakpoint", "[address|#]", "\taddress: breakpoint address to set\n" "\t#: breakpoint number to remove\n") }, { DDB_ADD_CMD( "cpuinfo", db_md_cpuinfo_cmd, 0, "Displays the current cpuinfo", NULL, NULL) }, { DDB_ADD_CMD( "frame", db_md_frame_cmd, 0, "Displays the contents of a trapframe", "address", "\taddress:\taddress of trapframe to display") }, { DDB_ADD_CMD( "lwp", db_md_lwp_cmd, 0, "Displays the lwp", "address", "\taddress:\taddress of lwp to display") }, { DDB_ADD_CMD( "pte", db_md_pte_cmd, 0, "Display information of pte", "address", "\taddress:\tvirtual address of page") }, { DDB_ADD_CMD( "reset", db_md_reset_cmd, 0, "Reset the system", NULL, NULL) }, { DDB_ADD_CMD( "sysreg", db_md_sysreg_cmd, 0, "Displays system registers", NULL, NULL) }, { DDB_ADD_CMD( "tlbi", db_md_tlbi_cmd, 0, "flush tlb", NULL, NULL) }, { DDB_ADD_CMD( "ttbr", db_md_ttbr_cmd, 0, "Dump or count TTBR table", "[/apc] address | pid", "\taddress:\taddress of pmap to display\n" "\tpid:\t\tpid of pmap to display") }, { DDB_ADD_CMD( "watch", db_md_watch_cmd, 0, "set or clear watchpoint", "[/rwbhlq] [address|#]", "\taddress: watchpoint address to set\n" "\t#: watchpoint number to remove\n" "\t/rw: read or write access\n" "\t/bhlq: size of access\n") }, { DDB_ADD_CMD( "meminfo", db_md_meminfo_cmd, 0, "Dump info about memory ranges", NULL, NULL) }, #endif { DDB_END_CMD }, }; const struct db_variable db_regs[] = { { "x0", (long *) &ddb_regs.tf_reg[0], FCN_NULL, NULL }, { "x1", (long *) &ddb_regs.tf_reg[1], FCN_NULL, NULL }, { "x2", (long *) &ddb_regs.tf_reg[2], FCN_NULL, NULL }, { "x3", (long *) &ddb_regs.tf_reg[3], FCN_NULL, NULL }, { "x4", (long *) &ddb_regs.tf_reg[4], FCN_NULL, NULL }, { "x5", (long *) &ddb_regs.tf_reg[5], FCN_NULL, NULL }, { "x6", (long *) &ddb_regs.tf_reg[6], FCN_NULL, NULL }, { "x7", (long *) &ddb_regs.tf_reg[7], FCN_NULL, NULL }, { "x8", (long *) &ddb_regs.tf_reg[8], FCN_NULL, NULL }, { "x9", (long *) &ddb_regs.tf_reg[9], FCN_NULL, NULL }, { "x10", (long *) &ddb_regs.tf_reg[10], FCN_NULL, NULL }, { "x11", (long *) &ddb_regs.tf_reg[11], FCN_NULL, NULL }, { "x12", (long *) &ddb_regs.tf_reg[12], FCN_NULL, NULL }, { "x13", (long *) &ddb_regs.tf_reg[13], FCN_NULL, NULL }, { "x14", (long *) &ddb_regs.tf_reg[14], FCN_NULL, NULL }, { "x15", (long *) &ddb_regs.tf_reg[15], FCN_NULL, NULL }, { "x16", (long *) &ddb_regs.tf_reg[16], FCN_NULL, NULL }, { "x17", (long *) &ddb_regs.tf_reg[17], FCN_NULL, NULL }, { "x18", (long *) &ddb_regs.tf_reg[18], FCN_NULL, NULL }, { "x19", (long *) &ddb_regs.tf_reg[19], FCN_NULL, NULL }, { "x20", (long *) &ddb_regs.tf_reg[20], FCN_NULL, NULL }, { "x21", (long *) &ddb_regs.tf_reg[21], FCN_NULL, NULL }, { "x22", (long *) &ddb_regs.tf_reg[22], FCN_NULL, NULL }, { "x23", (long *) &ddb_regs.tf_reg[23], FCN_NULL, NULL }, { "x24", (long *) &ddb_regs.tf_reg[24], FCN_NULL, NULL }, { "x25", (long *) &ddb_regs.tf_reg[25], FCN_NULL, NULL }, { "x26", (long *) &ddb_regs.tf_reg[26], FCN_NULL, NULL }, { "x27", (long *) &ddb_regs.tf_reg[27], FCN_NULL, NULL }, { "x28", (long *) &ddb_regs.tf_reg[28], FCN_NULL, NULL }, { "x29", (long *) &ddb_regs.tf_reg[29], FCN_NULL, NULL }, { "x30", (long *) &ddb_regs.tf_reg[30], FCN_NULL, NULL }, { "sp", (long *) &ddb_regs.tf_sp, FCN_NULL, NULL }, { "pc", (long *) &ddb_regs.tf_pc, FCN_NULL, NULL }, { "spsr", (long *) &ddb_regs.tf_spsr, FCN_NULL, NULL } }; const struct db_variable * const db_eregs = db_regs + __arraycount(db_regs); int db_active; void dump_trapframe(struct trapframe *tf, void (*pr)(const char *, ...)) { struct trapframe tf_buf; db_read_bytes((db_addr_t)tf, sizeof(tf_buf), (char *)&tf_buf); tf = &tf_buf; #ifdef COMPAT_NETBSD32 if (tf->tf_spsr & SPSR_A32) { (*pr)(" pc=%016"PRIxREGISTER", spsr=%016"PRIxREGISTER " (AArch32)\n", tf->tf_pc, tf->tf_spsr); (*pr)(" esr=%016"PRIxREGISTER", far=%016"PRIxREGISTER"\n", tf->tf_esr, tf->tf_far); (*pr)(" r0=%016"PRIxREGISTER", r1=%016"PRIxREGISTER"\n", tf->tf_reg[0], tf->tf_reg[1]); (*pr)(" r2=%016"PRIxREGISTER", r3=%016"PRIxREGISTER"\n", tf->tf_reg[2], tf->tf_reg[3]); (*pr)(" r4=%016"PRIxREGISTER", r5=%016"PRIxREGISTER"\n", tf->tf_reg[4], tf->tf_reg[5]); (*pr)(" r6=%016"PRIxREGISTER", r7=%016"PRIxREGISTER"\n", tf->tf_reg[6], tf->tf_reg[7]); (*pr)(" r8=%016"PRIxREGISTER", r9=%016"PRIxREGISTER"\n", tf->tf_reg[8], tf->tf_reg[9]); (*pr)(" r10=%016"PRIxREGISTER", r11=%016"PRIxREGISTER"\n", tf->tf_reg[10], tf->tf_reg[11]); (*pr)(" r12=%016"PRIxREGISTER", sp=r13=%016"PRIxREGISTER"\n", tf->tf_reg[12], tf->tf_reg[13]); (*pr)("lr=r14=%016"PRIxREGISTER", pc=r15=%016"PRIxREGISTER"\n", tf->tf_reg[14], tf->tf_pc); return; } #endif (*pr)(" pc=%016"PRIxREGISTER", spsr=%016"PRIxREGISTER"\n", tf->tf_pc, tf->tf_spsr); (*pr)(" esr=%016"PRIxREGISTER", far=%016"PRIxREGISTER"\n", tf->tf_esr, tf->tf_far); (*pr)(" x0=%016"PRIxREGISTER", x1=%016"PRIxREGISTER"\n", tf->tf_reg[0], tf->tf_reg[1]); (*pr)(" x2=%016"PRIxREGISTER", x3=%016"PRIxREGISTER"\n", tf->tf_reg[2], tf->tf_reg[3]); (*pr)(" x4=%016"PRIxREGISTER", x5=%016"PRIxREGISTER"\n", tf->tf_reg[4], tf->tf_reg[5]); (*pr)(" x6=%016"PRIxREGISTER", x7=%016"PRIxREGISTER"\n", tf->tf_reg[6], tf->tf_reg[7]); (*pr)(" x8=%016"PRIxREGISTER", x9=%016"PRIxREGISTER"\n", tf->tf_reg[8], tf->tf_reg[9]); (*pr)(" x10=%016"PRIxREGISTER", x11=%016"PRIxREGISTER"\n", tf->tf_reg[10], tf->tf_reg[11]); (*pr)(" x12=%016"PRIxREGISTER", x13=%016"PRIxREGISTER"\n", tf->tf_reg[12], tf->tf_reg[13]); (*pr)(" x14=%016"PRIxREGISTER", x15=%016"PRIxREGISTER"\n", tf->tf_reg[14], tf->tf_reg[15]); (*pr)(" x16=%016"PRIxREGISTER", x17=%016"PRIxREGISTER"\n", tf->tf_reg[16], tf->tf_reg[17]); (*pr)(" x18=%016"PRIxREGISTER", x19=%016"PRIxREGISTER"\n", tf->tf_reg[18], tf->tf_reg[19]); (*pr)(" x20=%016"PRIxREGISTER", x21=%016"PRIxREGISTER"\n", tf->tf_reg[20], tf->tf_reg[21]); (*pr)(" x22=%016"PRIxREGISTER", x23=%016"PRIxREGISTER"\n", tf->tf_reg[22], tf->tf_reg[23]); (*pr)(" x24=%016"PRIxREGISTER", x25=%016"PRIxREGISTER"\n", tf->tf_reg[24], tf->tf_reg[25]); (*pr)(" x26=%016"PRIxREGISTER", x27=%016"PRIxREGISTER"\n", tf->tf_reg[26], tf->tf_reg[27]); (*pr)(" x28=%016"PRIxREGISTER", fp=x29=%016"PRIxREGISTER"\n", tf->tf_reg[28], tf->tf_reg[29]); (*pr)("lr=x30=%016"PRIxREGISTER", sp=%016"PRIxREGISTER"\n", tf->tf_reg[30], tf->tf_sp); } void dump_switchframe(struct trapframe *tf, void (*pr)(const char *, ...)) { struct trapframe tf_buf; db_read_bytes((db_addr_t)tf, sizeof(tf_buf), (char *)&tf_buf); tf = &tf_buf; (*pr)(" x19=%016"PRIxREGISTER", x20=%016"PRIxREGISTER"\n", tf->tf_reg[19], tf->tf_reg[20]); (*pr)(" x21=%016"PRIxREGISTER", x22=%016"PRIxREGISTER"\n", tf->tf_reg[21], tf->tf_reg[22]); (*pr)(" x23=%016"PRIxREGISTER", x24=%016"PRIxREGISTER"\n", tf->tf_reg[23], tf->tf_reg[24]); (*pr)(" x25=%016"PRIxREGISTER", x26=%016"PRIxREGISTER"\n", tf->tf_reg[25], tf->tf_reg[26]); (*pr)(" x27=%016"PRIxREGISTER", x28=%016"PRIxREGISTER"\n", tf->tf_reg[27], tf->tf_reg[28]); (*pr)("fp=x29=%016"PRIxREGISTER", lr=x30=%016"PRIxREGISTER"\n", tf->tf_reg[29], tf->tf_reg[30]); } #if defined(_KERNEL) static void show_cpuinfo(struct cpu_info *ci) { struct cpu_info cpuinfobuf; u_int cpuidx; int i; db_read_bytes((db_addr_t)ci, sizeof(cpuinfobuf), (char *)&cpuinfobuf); cpuidx = cpu_index(&cpuinfobuf); db_printf("cpu_info=%p, cpu_name=%s\n", ci, cpuinfobuf.ci_cpuname); db_printf("%p cpu[%u].ci_cpuid = 0x%lx\n", &ci->ci_cpuid, cpuidx, cpuinfobuf.ci_cpuid); db_printf("%p cpu[%u].ci_curlwp = %p\n", &ci->ci_curlwp, cpuidx, cpuinfobuf.ci_curlwp); db_printf("%p cpu[%u].ci_onproc = %p\n", &ci->ci_onproc, cpuidx, cpuinfobuf.ci_onproc); for (i = 0; i < SOFTINT_COUNT; i++) { db_printf("%p cpu[%u].ci_softlwps[%d] = %p\n", &ci->ci_softlwps[i], cpuidx, i, cpuinfobuf.ci_softlwps[i]); } db_printf("%p cpu[%u].ci_lastintr = %" PRIu64 "\n", &ci->ci_lastintr, cpuidx, cpuinfobuf.ci_lastintr); db_printf("%p cpu[%u].ci_want_resched = %d\n", &ci->ci_want_resched, cpuidx, cpuinfobuf.ci_want_resched); db_printf("%p cpu[%u].ci_cpl = %d\n", &ci->ci_cpl, cpuidx, cpuinfobuf.ci_cpl); db_printf("%p cpu[%u].ci_softints = 0x%08x\n", &ci->ci_softints, cpuidx, cpuinfobuf.ci_softints); db_printf("%p cpu[%u].ci_intr_depth = %u\n", &ci->ci_intr_depth, cpuidx, cpuinfobuf.ci_intr_depth); db_printf("%p cpu[%u].ci_biglock_count = %u\n", &ci->ci_biglock_count, cpuidx, cpuinfobuf.ci_biglock_count); } void db_md_cpuinfo_cmd(db_expr_t addr, bool have_addr, db_expr_t count, const char *modif) { #ifdef MULTIPROCESSOR CPU_INFO_ITERATOR cii; struct cpu_info *ci; bool showall = false; if (modif != NULL) { for (; *modif != '\0'; modif++) { switch (*modif) { case 'a': showall = true; break; } } } if (showall) { for (CPU_INFO_FOREACH(cii, ci)) { show_cpuinfo(ci); } } else #endif /* MULTIPROCESSOR */ show_cpuinfo(curcpu()); } void db_md_frame_cmd(db_expr_t addr, bool have_addr, db_expr_t count, const char *modif) { struct trapframe *tf; if (!have_addr) { db_printf("frame address must be specified\n"); return; } tf = (struct trapframe *)addr; dump_trapframe(tf, db_printf); } void db_md_lwp_cmd(db_expr_t addr, bool have_addr, db_expr_t count, const char *modif) { lwp_t *l, lwp_buf; struct pcb *pcb, pcb_buf; if (!have_addr) { db_printf("lwp address must be specified\n"); return; } db_read_bytes(addr, sizeof(lwp_buf), (char *)&lwp_buf); l = &lwp_buf; #define SAFESTRPTR(p) (((p) == NULL) ? "NULL" : (p)) db_printf("lwp=%p\n", (void *)addr); db_printf("\tlwp_getpcb(l) =%p\n", lwp_getpcb(l)); db_printf("\tl->l_md.md_onfault=%p\n", l->l_md.md_onfault); db_printf("\tl->l_md.md_utf =%p\n", l->l_md.md_utf); dump_trapframe(l->l_md.md_utf, db_printf); db_read_bytes((db_addr_t)l->l_addr, sizeof(pcb_buf), (char *)&pcb_buf); pcb = &pcb_buf; db_printf("\tl->l_addr.pcb_tf =%p\n", pcb->pcb_tf); if (pcb->pcb_tf != l->l_md.md_utf) dump_switchframe(pcb->pcb_tf, db_printf); db_printf("\tl->l_md.md_cpacr =%016" PRIx64 "\n", l->l_md.md_cpacr); db_printf("\tl->l_md.md_flags =%08x\n", l->l_md.md_flags); db_printf("\tl->l_cpu =%p\n", l->l_cpu); db_printf("\tl->l_proc =%p\n", l->l_proc); db_printf("\tl->l_private =%p\n", l->l_private); db_printf("\tl->l_name =%s\n", SAFESTRPTR(l->l_name)); db_printf("\tl->l_wmesg =%s\n", SAFESTRPTR(l->l_wmesg)); } static void db_par_print(uint64_t par, vaddr_t va) { paddr_t pa = (__SHIFTOUT(par, PAR_PA) << PAR_PA_SHIFT) + (va & __BITS(PAR_PA_SHIFT - 1, 0)); if (__SHIFTOUT(par, PAR_F) == 0) { db_printf("%016" PRIx64 ": ATTR=0x%02" __PRIxBITS ", NS=%" __PRIuBITS ", SH=%" __PRIuBITS ", PA=%016" PRIxPADDR " (no fault)\n", par, __SHIFTOUT(par, PAR_ATTR), __SHIFTOUT(par, PAR_NS), __SHIFTOUT(par, PAR_SH), pa); } else { db_printf("%016" PRIx64 ", S=%" __PRIuBITS ", PTW=%" __PRIuBITS ", FST=%" __PRIuBITS " (fault)\n", par, __SHIFTOUT(par, PAR_S), __SHIFTOUT(par, PAR_PTW), __SHIFTOUT(par, PAR_FST)); } } void db_md_pte_cmd(db_expr_t addr, bool have_addr, db_expr_t count, const char *modif) { uint64_t par; if (!have_addr) { db_printf("pte address must be specified\n"); return; } reg_s1e0r_write(addr); isb(); par = reg_par_el1_read(); db_printf("Stage1 EL0 translation %016llx -> PAR_EL1 = ", addr); db_par_print(par, addr); reg_s1e1r_write(addr); isb(); par = reg_par_el1_read(); db_printf("Stage1 EL1 translation %016llx -> PAR_EL1 = ", addr); db_par_print(par, addr); db_pteinfo(addr, db_printf); } void db_md_reset_cmd(db_expr_t addr, bool have_addr, db_expr_t count, const char *modif) { if (cpu_reset_address == NULL) { db_printf("cpu_reset_address is not set\n"); return; } cpu_reset_address(); } void db_md_tlbi_cmd(db_expr_t addr, bool have_addr, db_expr_t count, const char *modif) { aarch64_tlbi_all(); } void db_md_ttbr_cmd(db_expr_t addr, bool have_addr, db_expr_t count, const char *modif) { bool countmode = false, by_pid = true; if (!have_addr) { db_printf("usage: machine ttbr [/a] [/p] [/c] address|pid\n"); db_printf("\t/a == argument is an address of any pmap_t\n"); db_printf("\t/p == argument is a pid [default]\n"); db_printf("\t/c == count TLB entries\n"); return; } if (modif != NULL) { for (; *modif != '\0'; modif++) { switch (*modif) { case 'c': countmode = true; break; case 'a': by_pid = false; break; case 'p': by_pid = true; break; } } } if (by_pid) { proc_t *p = db_proc_find((pid_t)addr); if (p == NULL) { db_printf("bad address\n"); return; } addr = (db_addr_t)p->p_vmspace->vm_map.pmap; } db_ttbrdump(countmode, addr, db_printf); } void db_md_sysreg_cmd(db_expr_t addr, bool have_addr, db_expr_t count, const char *modif) { #define SHOW_ARMREG(x) \ db_printf("%-16s = %016" PRIx64 "\n", #x, reg_ ## x ## _read()) // SHOW_ARMREG(cbar_el1); /* Cortex */ SHOW_ARMREG(ccsidr_el1); SHOW_ARMREG(clidr_el1); SHOW_ARMREG(cntfrq_el0); SHOW_ARMREG(cntkctl_el1); SHOW_ARMREG(cntp_ctl_el0); SHOW_ARMREG(cntp_cval_el0); SHOW_ARMREG(cntp_tval_el0); SHOW_ARMREG(cntpct_el0); // SHOW_ARMREG(cntps_ctl_el1); /* need secure state */ // SHOW_ARMREG(cntps_cval_el1); /* need secure state */ // SHOW_ARMREG(cntps_tval_el1); /* need secure state */ SHOW_ARMREG(cntv_ctl_el0); SHOW_ARMREG(cntv_ctl_el0); SHOW_ARMREG(cntv_cval_el0); SHOW_ARMREG(cntv_tval_el0); SHOW_ARMREG(cntv_tval_el0); SHOW_ARMREG(cntvct_el0); SHOW_ARMREG(cpacr_el1); SHOW_ARMREG(csselr_el1); SHOW_ARMREG(ctr_el0); SHOW_ARMREG(currentel); SHOW_ARMREG(daif); SHOW_ARMREG(dczid_el0); SHOW_ARMREG(elr_el1); SHOW_ARMREG(esr_el1); SHOW_ARMREG(far_el1); // SHOW_ARMREG(fpcr); /* FP trap */ // SHOW_ARMREG(fpsr); /* FP trap */ SHOW_ARMREG(id_aa64afr0_el1); SHOW_ARMREG(id_aa64afr1_el1); SHOW_ARMREG(id_aa64dfr0_el1); SHOW_ARMREG(id_aa64dfr1_el1); SHOW_ARMREG(id_aa64isar0_el1); SHOW_ARMREG(id_aa64isar1_el1); SHOW_ARMREG(id_aa64mmfr0_el1); SHOW_ARMREG(id_aa64mmfr1_el1); SHOW_ARMREG(id_aa64pfr0_el1); SHOW_ARMREG(id_aa64pfr1_el1); SHOW_ARMREG(isr_el1); // SHOW_ARMREG(l2ctlr_el1); /* Cortex */ SHOW_ARMREG(mair_el1); SHOW_ARMREG(mdscr_el1); SHOW_ARMREG(midr_el1); SHOW_ARMREG(mpidr_el1); SHOW_ARMREG(mvfr0_el1); SHOW_ARMREG(mvfr1_el1); SHOW_ARMREG(mvfr2_el1); SHOW_ARMREG(nzcv); SHOW_ARMREG(par_el1); SHOW_ARMREG(pmccfiltr_el0); SHOW_ARMREG(pmccntr_el0); SHOW_ARMREG(revidr_el1); // SHOW_ARMREG(rmr_el1); /* unknown reason trap */ // SHOW_ARMREG(rvbar_el1); SHOW_ARMREG(sctlr_el1); SHOW_ARMREG(spsel); SHOW_ARMREG(spsr_el1); SHOW_ARMREG(tcr_el1); SHOW_ARMREG(tpidr_el0); SHOW_ARMREG(tpidrro_el0); SHOW_ARMREG(tpidr_el1); SHOW_ARMREG(ttbr0_el1); SHOW_ARMREG(ttbr1_el1); SHOW_ARMREG(vbar_el1); } /* * hardware breakpoint/watchpoint command */ static void aarch64_set_bcr_bvr(int n, uint64_t bcr, uint64_t bvr) { #define DBG_BCR_BVR_SET(regno, bcr, bvr) \ do { \ reg_dbgbcr ## regno ## _el1_write(bcr); \ reg_dbgbvr ## regno ## _el1_write(bvr); \ } while (0 /* CONSTCOND */) switch (n) { case 0: DBG_BCR_BVR_SET(0, bcr, bvr); break; case 1: DBG_BCR_BVR_SET(1, bcr, bvr); break; case 2: DBG_BCR_BVR_SET(2, bcr, bvr); break; case 3: DBG_BCR_BVR_SET(3, bcr, bvr); break; case 4: DBG_BCR_BVR_SET(4, bcr, bvr); break; case 5: DBG_BCR_BVR_SET(5, bcr, bvr); break; case 6: DBG_BCR_BVR_SET(6, bcr, bvr); break; case 7: DBG_BCR_BVR_SET(7, bcr, bvr); break; case 8: DBG_BCR_BVR_SET(8, bcr, bvr); break; case 9: DBG_BCR_BVR_SET(9, bcr, bvr); break; case 10: DBG_BCR_BVR_SET(10, bcr, bvr); break; case 11: DBG_BCR_BVR_SET(11, bcr, bvr); break; case 12: DBG_BCR_BVR_SET(12, bcr, bvr); break; case 13: DBG_BCR_BVR_SET(13, bcr, bvr); break; case 14: DBG_BCR_BVR_SET(14, bcr, bvr); break; case 15: DBG_BCR_BVR_SET(15, bcr, bvr); break; } } static void aarch64_set_wcr_wvr(int n, uint64_t wcr, uint64_t wvr) { #define DBG_WCR_WVR_SET(regno, wcr, wvr) \ do { \ reg_dbgwcr ## regno ## _el1_write(wcr); \ reg_dbgwvr ## regno ## _el1_write(wvr); \ } while (0 /* CONSTCOND */) switch (n) { case 0: DBG_WCR_WVR_SET(0, wcr, wvr); break; case 1: DBG_WCR_WVR_SET(1, wcr, wvr); break; case 2: DBG_WCR_WVR_SET(2, wcr, wvr); break; case 3: DBG_WCR_WVR_SET(3, wcr, wvr); break; case 4: DBG_WCR_WVR_SET(4, wcr, wvr); break; case 5: DBG_WCR_WVR_SET(5, wcr, wvr); break; case 6: DBG_WCR_WVR_SET(6, wcr, wvr); break; case 7: DBG_WCR_WVR_SET(7, wcr, wvr); break; case 8: DBG_WCR_WVR_SET(8, wcr, wvr); break; case 9: DBG_WCR_WVR_SET(9, wcr, wvr); break; case 10: DBG_WCR_WVR_SET(10, wcr, wvr); break; case 11: DBG_WCR_WVR_SET(11, wcr, wvr); break; case 12: DBG_WCR_WVR_SET(12, wcr, wvr); break; case 13: DBG_WCR_WVR_SET(13, wcr, wvr); break; case 14: DBG_WCR_WVR_SET(14, wcr, wvr); break; case 15: DBG_WCR_WVR_SET(15, wcr, wvr); break; } } void aarch64_breakpoint_set(int n, vaddr_t addr) { uint64_t bcr, bvr; if (addr == 0) { bvr = 0; bcr = 0; } else { bvr = addr & DBGBVR_MASK; bcr = __SHIFTIN(0, DBGBCR_BT) | __SHIFTIN(0, DBGBCR_LBN) | __SHIFTIN(0, DBGBCR_SSC) | __SHIFTIN(0, DBGBCR_HMC) | __SHIFTIN(15, DBGBCR_BAS) | __SHIFTIN(3, DBGBCR_PMC) | __SHIFTIN(1, DBGBCR_E); } aarch64_set_bcr_bvr(n, bcr, bvr); } void aarch64_watchpoint_set(int n, vaddr_t addr, u_int size, u_int accesstype) { uint64_t wvr, wcr; uint32_t matchbytebit; KASSERT(size <= 8); if (size > 8) size = 8; /* * It is always watched in 8byte units, and * BAS is a bit field of byte offset in 8byte units. */ matchbytebit = 0xff >> (8 - size); matchbytebit <<= (addr & 7); addr &= ~7UL; /* load, store, or both */ accesstype &= WATCHPOINT_ACCESS_MASK; if (accesstype == 0) accesstype = WATCHPOINT_ACCESS_LOADSTORE; if (addr == 0) { wvr = 0; wcr = 0; } else { wvr = addr; wcr = __SHIFTIN(0, DBGWCR_MASK) | /* MASK: no mask */ __SHIFTIN(0, DBGWCR_WT) | /* WT: 0 */ __SHIFTIN(0, DBGWCR_LBN) | /* LBN: 0 */ __SHIFTIN(0, DBGWCR_SSC) | /* SSC: 00 */ __SHIFTIN(0, DBGWCR_HMC) | /* HMC: 0 */ __SHIFTIN(matchbytebit, DBGWCR_BAS) | /* BAS: 0-8byte */ __SHIFTIN(accesstype, DBGWCR_LSC) | /* LSC: Load/Store */ __SHIFTIN(3, DBGWCR_PAC) | /* PAC: 11 */ __SHIFTIN(1, DBGWCR_E); /* Enable */ } aarch64_set_wcr_wvr(n, wcr, wvr); } static int db_md_breakpoint_set(int n, vaddr_t addr) { if (n >= __arraycount(breakpoint_buf)) return -1; if ((addr & 3) != 0) { db_printf("address must be 4bytes aligned\n"); return -1; } breakpoint_buf[n].addr = addr; return 0; } static int db_md_watchpoint_set(int n, vaddr_t addr, u_int size, u_int accesstype) { if (n >= __arraycount(watchpoint_buf)) return -1; if (size != 0 && ((addr) & ~7UL) != ((addr + size - 1) & ~7UL)) { db_printf( "address and size must fit within a block of 8bytes\n"); return -1; } watchpoint_buf[n].addr = addr; watchpoint_buf[n].size = size; watchpoint_buf[n].accesstype = accesstype; return 0; } static void db_md_breakwatchpoints_clear(void) { int i; for (i = 0; i <= max_breakpoint; i++) aarch64_breakpoint_set(i, 0); for (i = 0; i <= max_watchpoint; i++) aarch64_watchpoint_set(i, 0, 0, 0); } static void db_md_breakwatchpoints_reload(void) { int i; for (i = 0; i <= max_breakpoint; i++) { aarch64_breakpoint_set(i, breakpoint_buf[i].addr); } for (i = 0; i <= max_watchpoint; i++) { aarch64_watchpoint_set(i, watchpoint_buf[i].addr, watchpoint_buf[i].size, watchpoint_buf[i].accesstype); } } void db_machdep_cpu_init(void) { uint64_t dfr, mdscr; int i, cpu_max_breakpoint, cpu_max_watchpoint; dfr = reg_id_aa64dfr0_el1_read(); cpu_max_breakpoint = __SHIFTOUT(dfr, ID_AA64DFR0_EL1_BRPS); cpu_max_watchpoint = __SHIFTOUT(dfr, ID_AA64DFR0_EL1_WRPS); for (i = 0; i <= cpu_max_breakpoint; i++) { /* clear all breakpoints */ aarch64_breakpoint_set(i, 0); } for (i = 0; i <= cpu_max_watchpoint; i++) { /* clear all watchpoints */ aarch64_watchpoint_set(i, 0, 0, 0); } /* enable watchpoint and breakpoint */ mdscr = reg_mdscr_el1_read(); mdscr |= MDSCR_MDE | MDSCR_KDE; reg_mdscr_el1_write(mdscr); reg_oslar_el1_write(0); } void db_machdep_init(struct cpu_info * const ci) { struct aarch64_sysctl_cpu_id * const id = &ci->ci_id; const uint64_t dfr = id->ac_aa64dfr0; const u_int cpu_max_breakpoint = __SHIFTOUT(dfr, ID_AA64DFR0_EL1_BRPS); const u_int cpu_max_watchpoint = __SHIFTOUT(dfr, ID_AA64DFR0_EL1_WRPS); /* * num of {watch,break}point may be different depending on the * core. */ if (max_breakpoint > cpu_max_breakpoint) max_breakpoint = cpu_max_breakpoint; if (max_watchpoint > cpu_max_watchpoint) max_watchpoint = cpu_max_watchpoint; } static void show_breakpoints(void) { uint64_t addr; unsigned int i, nused; for (nused = 0, i = 0; i <= max_breakpoint; i++) { addr = breakpoint_buf[i].addr; if (addr == 0) { db_printf("%d: disabled\n", i); } else { db_printf("%d: breakpoint %016" PRIx64 " (", i, addr); db_printsym(addr, DB_STGY_ANY, db_printf); db_printf(")\n"); nused++; } } db_printf("breakpoint used %d/%d\n", nused, max_breakpoint + 1); } static void show_watchpoints(void) { uint64_t addr; unsigned int i, nused; for (nused = 0, i = 0; i <= max_watchpoint; i++) { addr = watchpoint_buf[i].addr; if (addr == 0) { db_printf("%d: disabled\n", i); } else { db_printf("%d: watching %016" PRIx64 " (", i, addr); db_printsym(addr, DB_STGY_ANY, db_printf); db_printf("), %d bytes", watchpoint_buf[i].size); switch (watchpoint_buf[i].accesstype) { case WATCHPOINT_ACCESS_LOAD: db_printf(", load"); break; case WATCHPOINT_ACCESS_STORE: db_printf(", store"); break; case WATCHPOINT_ACCESS_LOADSTORE: db_printf(", load/store"); break; } db_printf("\n"); nused++; } } db_printf("watchpoint used %d/%d\n", nused, max_watchpoint + 1); } void db_md_break_cmd(db_expr_t addr, bool have_addr, db_expr_t count, const char *modif) { int i, rc; int added, cleared; if (!have_addr) { show_breakpoints(); return; } added = -1; cleared = -1; if (0 <= addr && addr <= max_breakpoint) { i = addr; if (breakpoint_buf[i].addr != 0) { db_md_breakpoint_set(i, 0); cleared = i; } } else { for (i = 0; i <= max_breakpoint; i++) { if (breakpoint_buf[i].addr == addr) { db_md_breakpoint_set(i, 0); cleared = i; } } if (cleared == -1) { for (i = 0; i <= max_breakpoint; i++) { if (breakpoint_buf[i].addr == 0) { rc = db_md_breakpoint_set(i, addr); if (rc != 0) return; added = i; break; } } if (i > max_breakpoint) { db_printf("no more available breakpoint\n"); } } } if (added >= 0) db_printf("add breakpoint %d as %016"DDB_EXPR_FMT"x\n", added, addr); if (cleared >= 0) db_printf("clear breakpoint %d\n", cleared); show_breakpoints(); } void db_md_watch_cmd(db_expr_t addr, bool have_addr, db_expr_t count, const char *modif) { int i, rc; int added, cleared; u_int accesstype, watchsize; if (!have_addr) { show_watchpoints(); return; } accesstype = watchsize = 0; if ((modif != NULL) && (*modif != '\0')) { int ch; for (; *modif != '\0'; modif++) { ch = *modif; switch (ch) { case 'b': watchsize = 1; break; case 'h': watchsize = 2; break; case 'l': watchsize = 4; break; case 'q': watchsize = 8; break; case 'r': accesstype |= WATCHPOINT_ACCESS_LOAD; break; case 'w': accesstype |= WATCHPOINT_ACCESS_STORE; break; } } } if (watchsize == 0) watchsize = 4; /* default: 4byte */ if (accesstype == 0) accesstype = WATCHPOINT_ACCESS_LOADSTORE; /* default */ added = -1; cleared = -1; if (0 <= addr && addr <= max_watchpoint) { i = addr; if (watchpoint_buf[i].addr != 0) { db_md_watchpoint_set(i, 0, 0, 0); cleared = i; } } else { for (i = 0; i <= max_watchpoint; i++) { if (watchpoint_buf[i].addr == addr) { db_md_watchpoint_set(i, 0, 0, 0); cleared = i; } } if (cleared == -1) { for (i = 0; i <= max_watchpoint; i++) { if (watchpoint_buf[i].addr == 0) { rc = db_md_watchpoint_set(i, addr, watchsize, accesstype); if (rc != 0) return; added = i; break; } } if (i > max_watchpoint) { db_printf("no more available watchpoint\n"); } } } if (added >= 0) db_printf("add watchpoint %d as %016"DDB_EXPR_FMT"x\n", added, addr); if (cleared >= 0) db_printf("clear watchpoint %d\n", cleared); show_watchpoints(); } #endif #ifdef MULTIPROCESSOR volatile struct cpu_info *db_trigger; volatile struct cpu_info *db_onproc; volatile struct cpu_info *db_newcpu; volatile struct trapframe *db_readytoswitch[MAXCPUS]; #ifdef _KERNEL void db_md_switch_cpu_cmd(db_expr_t addr, bool have_addr, db_expr_t count, const char *modif) { struct cpu_info *new_ci = NULL; u_int cpuno = (u_int)addr; int i; membar_consumer(); if (!have_addr) { for (i = 0; i < ncpu; i++) { if (db_readytoswitch[i] != NULL) { db_printf("cpu%d: ready. tf=%p, pc=%016lx ", i, db_readytoswitch[i], db_readytoswitch[i]->tf_pc); db_printsym(db_readytoswitch[i]->tf_pc, DB_STGY_ANY, db_printf); db_printf("\n"); } else { db_printf("cpu%d: not responding\n", i); } } return; } if (cpuno < ncpu) new_ci = cpu_lookup(cpuno); if (new_ci == NULL) { db_printf("cpu %u does not exist", cpuno); return; } if (db_readytoswitch[new_ci->ci_index] == 0) { db_printf("cpu %u is not responding", cpuno); return; } if (new_ci == curcpu()) return; db_newcpu = new_ci; db_continue_cmd(0, false, 0, ""); } #endif /* _KERNEL */ #endif /* MULTIPROCESSOR */ #ifdef DDB int kdb_trap(int type, struct trapframe *tf) { #ifdef MULTIPROCESSOR struct cpu_info * const ci = curcpu(); bool static_brk = false; #endif int s; bool restore_hw_watchpoints = true; switch (type) { case DB_TRAP_WATCHPOINT: case DB_TRAP_BREAKPOINT: /* * In the case of a hardware watchpoint or breakpoint, * even if cpu return from ddb as is, it will be trapped again, * so clear it all once. * * breakpoint and watchpoint will be restored at the end of * next DB_TRAP_BKPT_INSN (ddb's STEP_INVISIBLE mode). */ db_md_breakwatchpoints_clear(); restore_hw_watchpoints = false; break; case DB_TRAP_BKPT_INSN: #ifdef MULTIPROCESSOR /* brk #0xffff in cpu_Debugger() ? */ if (__SHIFTOUT(tf->tf_esr, ESR_ISS) == 0xffff) static_brk = true; /* FALLTHRU */ #endif case DB_TRAP_SW_STEP: case DB_TRAP_UNKNOWN: case -1: /* from pic_ipi_ddb() */ break; default: if (db_recover != 0) { db_error("Faulted in DDB: continuing...\n"); /* NOTREACHED */ } break; } #ifdef MULTIPROCESSOR if (ncpu > 1) { /* * Try to take ownership of DDB. * If we do, tell all other CPUs to enter DDB too. */ if (atomic_cas_ptr(&db_onproc, NULL, ci) == NULL) { intr_ipi_send(NULL, IPI_DDB); db_trigger = ci; } else { /* * If multiple CPUs catch kdb_trap() that is not IPI_DDB * derived at the same time, only the CPU that was able * to get db_onproc first will execute db_trap. * The CPU that could not get db_onproc will be set to * type = -1 once, and kdb_trap will be called again * with the correct type after kdb_trap returns. */ type = -1; restore_hw_watchpoints = true; } } db_readytoswitch[ci->ci_index] = tf; #endif for (;;) { #ifdef MULTIPROCESSOR if (ncpu > 1) { /* waiting my turn, or exit */ dsb(ishld); while (db_onproc != ci) { __asm __volatile ("wfe"); dsb(ishld); if (db_onproc == NULL) goto kdb_trap_done; } /* It's my turn! */ } #endif /* MULTIPROCESSOR */ /* Should switch to kdb`s own stack here. */ ddb_regs = *tf; s = splhigh(); db_active++; cnpollc(true); db_trap(type, 0/*code*/); cnpollc(false); db_active--; splx(s); *tf = ddb_regs; #ifdef MULTIPROCESSOR if (ncpu < 2) break; if (db_newcpu == NULL && db_onproc != db_trigger) { /* * If the "machine cpu" switches CPUs after entering * ddb from a breakpoint or watchpoint, it will return * control to the CPU that triggered the ddb in the * first place in order to correctly reset the * breakpoint or watchpoint. * If db_trap() returns further from here, * watchpoints and breakpoints will be reset. * (db_run_mode = STEP_INVISIBLE) */ db_newcpu = db_trigger; } if (db_newcpu != NULL) { /* XXX: override sys/ddb/db_run.c:db_run_mode */ db_continue_cmd(0, false, 0, ""); /* * When static BRK instruction (cpu_Debugger()), * db_trap() advance the $PC to the next instruction of * BRK. If db_trap() will be called twice by * "machine cpu" command, the $PC will be advanced to * the after the next instruction. * To avoid this, change 'type' so that the second call * to db_trap() will not change the PC. */ if (static_brk) type = -1; db_onproc = db_newcpu; db_newcpu = NULL; dsb(ishst); /* waking up the CPU waiting for its turn to db_trap */ sev(); continue; /* go to waiting my turn */ } #endif /* MULTIPROCESSOR */ break; } #ifdef MULTIPROCESSOR if (ncpu > 1 && db_onproc == ci) { db_onproc = NULL; dsb(ishst); /* waking up the CPU waiting for its turn to exit */ sev(); db_readytoswitch[cpu_index(ci)] = NULL; /* wait for all other CPUs are ready to exit */ for (;;) { int i; dsb(ishld); for (i = 0; i < ncpu; i++) { if (db_readytoswitch[i] != NULL) break; } if (i == ncpu) break; } db_trigger = NULL; sev(); } else { kdb_trap_done: db_readytoswitch[cpu_index(ci)] = NULL; dsb(ishst); __asm __volatile ("wfe"); } #endif if (restore_hw_watchpoints) db_md_breakwatchpoints_reload(); return 1; } #endif #if defined(_KERNEL) static void db_md_meminfo_cmd(db_expr_t addr, bool have_addr, db_expr_t count, const char *modif) { unsigned blk; for (blk = 0; blk < bootconfig.dramblocks; blk++) { db_printf("blk[%u]: start %lx end %lx (pages %x)\n", blk, bootconfig.dram[blk].address, bootconfig.dram[blk].address + (uint64_t)bootconfig.dram[blk].pages * PAGE_SIZE, bootconfig.dram[blk].pages); } } #endif