Index: mips/atheros/apb.c =================================================================== --- mips/atheros/apb.c (revision 232764) +++ mips/atheros/apb.c (working copy) @@ -36,6 +36,10 @@ #include #include #include +#include +#include +#include +#include #include #include @@ -44,6 +48,8 @@ #include #include +#define APB_INTR_PMC 5 + #undef APB_DEBUG #ifdef APB_DEBUG #define dprintf printf @@ -63,7 +69,7 @@ static struct resource_list * apb_get_resource_list(device_t, device_t); static void apb_hinted_child(device_t, const char *, int); -static int apb_intr(void *); +static int apb_filter(void *); static int apb_probe(device_t); static int apb_release_resource(device_t, device_t, int, int, struct resource *); @@ -132,7 +138,7 @@ } if ((bus_setup_intr(dev, sc->sc_misc_irq, INTR_TYPE_MISC, - apb_intr, NULL, sc, &sc->sc_misc_ih))) { + apb_filter, NULL, sc, &sc->sc_misc_ih))) { device_printf(dev, "WARNING: unable to register interrupt handler\n"); return (ENXIO); @@ -142,6 +148,12 @@ bus_enumerate_hinted_children(dev); bus_generic_attach(dev); + /* + * Unmask performance counter IRQ + */ + apb_unmask_irq((void*)APB_INTR_PMC); + sc->sc_intr_counter[APB_INTR_PMC] = mips_intrcnt_create("apb irq5: pmc"); + return (0); } @@ -329,11 +341,12 @@ } static int -apb_intr(void *arg) +apb_filter(void *arg) { struct apb_softc *sc = arg; struct intr_event *event; uint32_t reg, irq; + struct thread *td; reg = ATH_READ_REG(AR71XX_MISC_INTR_STATUS); for (irq = 0; irq < APB_NIRQS; irq++) { @@ -354,14 +367,34 @@ event = sc->sc_eventstab[irq]; if (!event || TAILQ_EMPTY(&event->ie_handlers)) { + if (irq == APB_INTR_PMC) { + register_t s; + struct trapframe *tf = PCPU_GET(curthread)->td_intr_frame; + s = intr_disable(); + mips_intrcnt_inc(sc->sc_intr_counter[irq]); + + if (pmc_intr && (*pmc_intr)(PCPU_GET(cpuid), tf)) { + intr_restore(s); + continue; + } + + intr_restore(s); + td = PCPU_GET(curthread); + + if (pmc_hook && (td->td_pflags & TDP_CALLCHAIN)) + pmc_hook(PCPU_GET(curthread), + PMC_FN_USER_CALLCHAIN, tf); + + continue; + + } /* Ignore timer interrupts */ if (irq != 0) printf("Stray APB IRQ %d\n", irq); continue; } - /* TODO: frame instead of NULL? */ - intr_event_handle(event, NULL); + intr_event_handle(event, PCPU_GET(curthread)->td_intr_frame); mips_intrcnt_inc(sc->sc_intr_counter[irq]); } } Index: dev/hwpmc/hwpmc_mips.c =================================================================== --- dev/hwpmc/hwpmc_mips.c (revision 232764) +++ dev/hwpmc/hwpmc_mips.c (working copy) @@ -34,7 +34,274 @@ #include #include +#include +#include +#if defined(__mips_n64) +# define MIPS_IS_VALID_KERNELADDR(reg) ((((reg) & 3) == 0) && \ + ((vm_offset_t)(reg) >= MIPS_XKPHYS_START)) +#else +# define MIPS_IS_VALID_KERNELADDR(reg) ((((reg) & 3) == 0) && \ + ((vm_offset_t)(reg) >= MIPS_KSEG0_START)) +#endif + +/* + * Wee need some reasonable default to prevent backtrace code + * from wandering too far + */ +#define MAX_FUNCTION_SIZE 0x10000 +#define MAX_PROLOGUE_SIZE 0x100 + +static int +pmc_next_frame(register_t *pc, register_t *sp) +{ + InstFmt i; + uintptr_t va; + uint32_t instr, mask; + int more, stksize; + register_t ra = 0; + + /* Jump here after a nonstandard (interrupt handler) frame */ + stksize = 0; + + /* check for bad SP: could foul up next frame */ + if (!MIPS_IS_VALID_KERNELADDR(*sp)) { + goto error; + } + + /* check for bad PC */ + if (!MIPS_IS_VALID_KERNELADDR(*pc)) { + goto error; + } + + /* + * Find the beginning of the current subroutine by scanning + * backwards from the current PC for the end of the previous + * subroutine. + */ + va = *pc - sizeof(int); + while (1) { + instr = *((uint32_t *)va); + + /* [d]addiu sp,sp,-X */ + if (((instr & 0xffff8000) == 0x27bd8000) + || ((instr & 0xffff8000) == 0x67bd8000)) + break; + + /* jr ra */ + if (instr == 0x03e00008) { + /* skip over branch-delay slot instruction */ + va += 2 * sizeof(int); + break; + } + + va -= sizeof(int); + } + + /* skip over nulls which might separate .o files */ + while ((instr = *((uint32_t *)va)) == 0) + va += sizeof(int); + + /* scan forwards to find stack size and any saved registers */ + stksize = 0; + more = 3; + mask = 0; + for (; more; va += sizeof(int), + more = (more == 3) ? 3 : more - 1) { + /* stop if hit our current position */ + if (va >= *pc) + break; + instr = *((uint32_t *)va); + i.word = instr; + switch (i.JType.op) { + case OP_SPECIAL: + switch (i.RType.func) { + case OP_JR: + case OP_JALR: + more = 2; /* stop after next instruction */ + break; + + case OP_SYSCALL: + case OP_BREAK: + more = 1; /* stop now */ + }; + break; + + case OP_BCOND: + case OP_J: + case OP_JAL: + case OP_BEQ: + case OP_BNE: + case OP_BLEZ: + case OP_BGTZ: + more = 2; /* stop after next instruction */ + break; + + case OP_COP0: + case OP_COP1: + case OP_COP2: + case OP_COP3: + switch (i.RType.rs) { + case OP_BCx: + case OP_BCy: + more = 2; /* stop after next instruction */ + }; + break; + + case OP_SW: + case OP_SD: + /* look for saved registers on the stack */ + if (i.IType.rs != 29) + break; + /* only restore the first one */ + if (mask & (1 << i.IType.rt)) + break; + mask |= (1 << i.IType.rt); + if (i.IType.rt == 31) + ra = *((register_t *)(*sp + (short)i.IType.imm)); + break; + + case OP_ADDI: + case OP_ADDIU: + case OP_DADDI: + case OP_DADDIU: + /* look for stack pointer adjustment */ + if (i.IType.rs != 29 || i.IType.rt != 29) + break; + stksize = -((short)i.IType.imm); + } + } + + if (!MIPS_IS_VALID_KERNELADDR(ra)) + return (-1); + + *pc = ra; + *sp += stksize; + + return (0); + +error: + return (-1); +} + +static int +pmc_next_uframe(register_t *pc, register_t *sp, register_t *ra) +{ + int offset, registers_on_stack; + uint32_t opcode, mask; + register_t function_start; + int stksize; + InstFmt i; + + registers_on_stack = 0; + mask = 0; + function_start = 0; + offset = 0; + stksize = 0; + + while (offset < MAX_FUNCTION_SIZE) { + opcode = fuword32((void *)(*pc - offset)); + + /* [d]addiu sp, sp, -X*/ + if (((opcode & 0xffff8000) == 0x27bd8000) + || ((opcode & 0xffff8000) == 0x67bd8000)) { + function_start = *pc - offset; + registers_on_stack = 1; + break; + } + + /* lui gp, X */ + if ((opcode & 0xffff8000) == 0x3c1c0000) { + /* + * Function might start with this instruction + * Keep an eye on "jr ra" and sp correction + * with positive value further on + */ + function_start = *pc - offset; + } + + if (function_start) { + /* + * Stop looking further. Possible end of + * function instruction: it means there is no + * stack modifications, sp is unchanged + */ + + /* [d]addiu sp,sp,X */ + if (((opcode & 0xffff8000) == 0x27bd0000) + || ((opcode & 0xffff8000) == 0x67bd0000)) + break; + + if (opcode == 0x03e00008) + break; + } + + offset += sizeof(int); + } + + if (!function_start) + return (-1); + + if (registers_on_stack) { + offset = 0; + while ((offset < MAX_PROLOGUE_SIZE) + && ((function_start + offset) < *pc)) { + i.word = + fuword32((void *)(function_start + offset)); + switch (i.JType.op) { + case OP_SW: + /* look for saved registers on the stack */ + if (i.IType.rs != 29) + break; + /* only restore the first one */ + if (mask & (1 << i.IType.rt)) + break; + mask |= (1 << i.IType.rt); + if (i.IType.rt == 31) + *ra = fuword32((void *)(*sp + (short)i.IType.imm)); + break; + +#if defined(__mips_n64) + case OP_SD: + /* look for saved registers on the stack */ + if (i.IType.rs != 29) + break; + /* only restore the first one */ + if (mask & (1 << i.IType.rt)) + break; + mask |= (1 << i.IType.rt); + /* ra */ + if (i.IType.rt == 31) + *ra = fuword64((void *)(*sp + (short)i.IType.imm)); + break; +#endif + + case OP_ADDI: + case OP_ADDIU: + case OP_DADDI: + case OP_DADDIU: + /* look for stack pointer adjustment */ + if (i.IType.rs != 29 || i.IType.rt != 29) + break; + stksize = -((short)i.IType.imm); + } + + offset += sizeof(int); + } + } + + /* + * We reached the end of backtrace + */ + if (*pc == *ra) + return (-1); + + *pc = *ra; + *sp += stksize; + + return (0); +} + struct pmc_mdep * pmc_md_initialize() { @@ -55,21 +322,53 @@ } int -pmc_save_kernel_callchain(uintptr_t *cc, int maxsamples, +pmc_save_kernel_callchain(uintptr_t *cc, int nframes, struct trapframe *tf) { - (void) cc; - (void) maxsamples; - (void) tf; - return (0); + register_t pc, ra, sp; + int frames = 0; + + pc = (uint64_t)tf->pc; + sp = (uint64_t)tf->sp; + ra = (uint64_t)tf->ra; + + /* + * Unwind, and unwind, and unwind + */ + while (1) { + cc[frames++] = pc; + if (frames >= nframes) + break; + + if (pmc_next_frame(&pc, &sp) < 0) + break; + } + + return (frames); } int -pmc_save_user_callchain(uintptr_t *cc, int maxsamples, +pmc_save_user_callchain(uintptr_t *cc, int nframes, struct trapframe *tf) { - (void) cc; - (void) maxsamples; - (void) tf; - return (0); + register_t pc, ra, sp; + int frames = 0; + + pc = (uint64_t)tf->pc; + sp = (uint64_t)tf->sp; + ra = (uint64_t)tf->ra; + + /* + * Unwind, and unwind, and unwind + */ + while (1) { + cc[frames++] = pc; + if (frames >= nframes) + break; + + if (pmc_next_uframe(&pc, &sp, &ra) < 0) + break; + } + + return (frames); }