Path: blob/main/sys/cddl/dev/dtrace/aarch64/dtrace_subr.c
48375 views
/*1* CDDL HEADER START2*3* The contents of this file are subject to the terms of the4* Common Development and Distribution License, Version 1.0 only5* (the "License"). You may not use this file except in compliance6* with the License.7*8* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE9* or http://www.opensolaris.org/os/licensing.10* See the License for the specific language governing permissions11* and limitations under the License.12*13* When distributing Covered Code, include this CDDL HEADER in each14* file and include the License file at usr/src/OPENSOLARIS.LICENSE.15* If applicable, add the following below this CDDL HEADER, with the16* fields enclosed by brackets "[]" replaced with your own identifying17* information: Portions Copyright [yyyy] [name of copyright owner]18*19* CDDL HEADER END20*21*/22/*23* Copyright 2005 Sun Microsystems, Inc. All rights reserved.24* Use is subject to license terms.25*/2627#include <sys/param.h>28#include <sys/systm.h>29#include <sys/kernel.h>30#include <sys/malloc.h>31#include <sys/kmem.h>32#include <sys/proc.h>33#include <sys/smp.h>34#include <sys/dtrace_impl.h>35#include <sys/dtrace_bsd.h>36#include <cddl/dev/dtrace/dtrace_cddl.h>37#include <machine/armreg.h>38#include <machine/clock.h>39#include <machine/frame.h>40#include <machine/trap.h>41#include <machine/vmparam.h>42#include <vm/pmap.h>4344extern dtrace_id_t dtrace_probeid_error;45extern int (*dtrace_invop_jump_addr)(struct trapframe *);46extern void dtrace_getnanotime(struct timespec *tsp);47extern void dtrace_getnanouptime(struct timespec *tsp);4849int dtrace_invop(uintptr_t, struct trapframe *, uintptr_t);50void dtrace_invop_init(void);51void dtrace_invop_uninit(void);5253typedef struct dtrace_invop_hdlr {54int (*dtih_func)(uintptr_t, struct trapframe *, uintptr_t);55struct dtrace_invop_hdlr *dtih_next;56} dtrace_invop_hdlr_t;5758dtrace_invop_hdlr_t *dtrace_invop_hdlr;5960int61dtrace_invop(uintptr_t addr, struct trapframe *frame, uintptr_t eax)62{63struct thread *td;64dtrace_invop_hdlr_t *hdlr;65int rval;6667rval = 0;68td = curthread;69td->t_dtrace_trapframe = frame;70for (hdlr = dtrace_invop_hdlr; hdlr != NULL; hdlr = hdlr->dtih_next)71if ((rval = hdlr->dtih_func(addr, frame, eax)) != 0)72break;73td->t_dtrace_trapframe = NULL;74return (rval);75}7677void78dtrace_invop_add(int (*func)(uintptr_t, struct trapframe *, uintptr_t))79{80dtrace_invop_hdlr_t *hdlr;8182hdlr = kmem_alloc(sizeof (dtrace_invop_hdlr_t), KM_SLEEP);83hdlr->dtih_func = func;84hdlr->dtih_next = dtrace_invop_hdlr;85dtrace_invop_hdlr = hdlr;86}8788void89dtrace_invop_remove(int (*func)(uintptr_t, struct trapframe *, uintptr_t))90{91dtrace_invop_hdlr_t *hdlr, *prev;9293hdlr = dtrace_invop_hdlr;94prev = NULL;9596for (;;) {97if (hdlr == NULL)98panic("attempt to remove non-existent invop handler");99100if (hdlr->dtih_func == func)101break;102103prev = hdlr;104hdlr = hdlr->dtih_next;105}106107if (prev == NULL) {108ASSERT(dtrace_invop_hdlr == hdlr);109dtrace_invop_hdlr = hdlr->dtih_next;110} else {111ASSERT(dtrace_invop_hdlr != hdlr);112prev->dtih_next = hdlr->dtih_next;113}114115kmem_free(hdlr, 0);116}117118/*ARGSUSED*/119void120dtrace_toxic_ranges(void (*func)(uintptr_t base, uintptr_t limit))121{122123(*func)(0, (uintptr_t)VM_MIN_KERNEL_ADDRESS);124}125126static uint64_t nsec_scale;127128#define SCALE_SHIFT 25129130/*131* Choose scaling factors which let us convert a cntvct_el0 value to nanoseconds132* without overflow, as in the amd64 implementation.133*134* Documentation for the ARM generic timer states that typical counter135* frequencies are in the range 1Mhz-50Mhz; in ARMv9 the frequency is fixed at136* 1GHz. The lower bound of 1MHz forces the shift to be at most 25 bits. At137* that frequency, the calculation (hi * scale) << (32 - shift) will not138* overflow for over 100 years, assuming that the counter value starts at 0 upon139* boot.140*/141static void142dtrace_gethrtime_init(void *arg __unused)143{144uint64_t freq;145146freq = READ_SPECIALREG(cntfrq_el0);147nsec_scale = ((uint64_t)NANOSEC << SCALE_SHIFT) / freq;148}149SYSINIT(dtrace_gethrtime_init, SI_SUB_DTRACE, SI_ORDER_ANY,150dtrace_gethrtime_init, NULL);151152/*153* DTrace needs a high resolution time function which can be called from a154* probe context and guaranteed not to have instrumented with probes itself.155*156* Returns nanoseconds since some arbitrary point in time (likely SoC reset?).157*/158uint64_t159dtrace_gethrtime(void)160{161uint64_t count, freq;162uint32_t lo, hi;163164count = READ_SPECIALREG(cntvct_el0);165lo = count;166hi = count >> 32;167return (((lo * nsec_scale) >> SCALE_SHIFT) +168((hi * nsec_scale) << (32 - SCALE_SHIFT)));169}170171/*172* Return a much lower resolution wallclock time based on the system clock173* updated by the timer. If needed, we could add a version interpolated from174* the system clock as is the case with dtrace_gethrtime().175*/176uint64_t177dtrace_gethrestime(void)178{179struct timespec current_time;180181dtrace_getnanotime(¤t_time);182183return (current_time.tv_sec * 1000000000UL + current_time.tv_nsec);184}185186/* Function to handle DTrace traps during probes. See arm64/arm64/trap.c */187int188dtrace_trap(struct trapframe *frame, u_int type)189{190/*191* A trap can occur while DTrace executes a probe. Before192* executing the probe, DTrace blocks re-scheduling and sets193* a flag in its per-cpu flags to indicate that it doesn't194* want to fault. On returning from the probe, the no-fault195* flag is cleared and finally re-scheduling is enabled.196*197* Check if DTrace has enabled 'no-fault' mode:198*199*/200201if ((cpu_core[curcpu].cpuc_dtrace_flags & CPU_DTRACE_NOFAULT) != 0) {202/*203* There are only a couple of trap types that are expected.204* All the rest will be handled in the usual way.205*/206switch (type) {207case EXCP_DATA_ABORT:208/* Flag a bad address. */209cpu_core[curcpu].cpuc_dtrace_flags |= CPU_DTRACE_BADADDR;210cpu_core[curcpu].cpuc_dtrace_illval = frame->tf_far;211212/*213* Offset the instruction pointer to the instruction214* following the one causing the fault.215*/216frame->tf_elr += 4;217return (1);218default:219/* Handle all other traps in the usual way. */220break;221}222}223224/* Handle the trap in the usual way. */225return (0);226}227228void229dtrace_probe_error(dtrace_state_t *state, dtrace_epid_t epid, int which,230int fault, int fltoffs, uintptr_t illval)231{232233dtrace_probe(dtrace_probeid_error, (uint64_t)(uintptr_t)state,234(uintptr_t)epid,235(uintptr_t)which, (uintptr_t)fault, (uintptr_t)fltoffs);236}237238static void239dtrace_load64(uint64_t *addr, struct trapframe *frame, u_int reg)240{241242KASSERT(reg <= 31, ("dtrace_load64: Invalid register %u", reg));243if (reg < nitems(frame->tf_x))244frame->tf_x[reg] = *addr;245else if (reg == 30) /* lr */246frame->tf_lr = *addr;247/* Nothing to do for load to xzr */248}249250static void251dtrace_store64(uint64_t *addr, struct trapframe *frame, u_int reg)252{253254KASSERT(reg <= 31, ("dtrace_store64: Invalid register %u", reg));255if (reg < nitems(frame->tf_x))256*addr = frame->tf_x[reg];257else if (reg == 30) /* lr */258*addr = frame->tf_lr;259else if (reg == 31) /* xzr */260*addr = 0;261}262263static int264dtrace_invop_start(struct trapframe *frame)265{266int data, invop, tmp;267268invop = dtrace_invop(frame->tf_elr, frame, frame->tf_x[0]);269270tmp = (invop & LDP_STP_MASK);271if (tmp == STP_64 || tmp == LDP_64) {272register_t arg1, arg2, *sp;273int offs;274275sp = (register_t *)frame->tf_sp;276data = invop;277arg1 = (data >> ARG1_SHIFT) & ARG1_MASK;278arg2 = (data >> ARG2_SHIFT) & ARG2_MASK;279280offs = (data >> OFFSET_SHIFT) & OFFSET_MASK;281282switch (tmp) {283case STP_64:284if (offs >> (OFFSET_SIZE - 1))285sp -= (~offs & OFFSET_MASK) + 1;286else287sp += (offs);288dtrace_store64(sp + 0, frame, arg1);289dtrace_store64(sp + 1, frame, arg2);290break;291case LDP_64:292dtrace_load64(sp + 0, frame, arg1);293dtrace_load64(sp + 1, frame, arg2);294if (offs >> (OFFSET_SIZE - 1))295sp -= (~offs & OFFSET_MASK) + 1;296else297sp += (offs);298break;299default:300break;301}302303/* Update the stack pointer and program counter to continue */304frame->tf_sp = (register_t)sp;305frame->tf_elr += INSN_SIZE;306return (0);307}308309if ((invop & SUB_MASK) == SUB_INSTR) {310frame->tf_sp -= (invop >> SUB_IMM_SHIFT) & SUB_IMM_MASK;311frame->tf_elr += INSN_SIZE;312return (0);313}314315if (invop == NOP_INSTR) {316frame->tf_elr += INSN_SIZE;317return (0);318}319320if ((invop & B_MASK) == B_INSTR) {321data = (invop & B_DATA_MASK);322/* The data is the number of 4-byte words to change the pc */323data *= 4;324frame->tf_elr += data;325return (0);326}327328if (invop == RET_INSTR) {329frame->tf_elr = frame->tf_lr;330return (0);331}332333return (-1);334}335336void337dtrace_invop_init(void)338{339340dtrace_invop_jump_addr = dtrace_invop_start;341}342343void344dtrace_invop_uninit(void)345{346347dtrace_invop_jump_addr = 0;348}349350351