1214501Srpaulo/*	$NetBSD: fbt_isa.c,v 1.2 2023/05/22 15:12:54 riastradh Exp $	*/
2214501Srpaulo
3289549Srpaulo/*
4214501Srpaulo * CDDL HEADER START
5252726Srpaulo *
6252726Srpaulo * The contents of this file are subject to the terms of the
7214501Srpaulo * Common Development and Distribution License (the "License").
8214501Srpaulo * You may not use this file except in compliance with the License.
9214501Srpaulo *
10214501Srpaulo * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
11214501Srpaulo * or http://www.opensolaris.org/os/licensing.
12214501Srpaulo * See the License for the specific language governing permissions
13214501Srpaulo * and limitations under the License.
14214501Srpaulo *
15214501Srpaulo * When distributing Covered Code, include this CDDL HEADER in each
16214501Srpaulo * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
17214501Srpaulo * If applicable, add the following below this CDDL HEADER, with the
18214501Srpaulo * fields enclosed by brackets "[]" replaced with your own identifying
19214501Srpaulo * information: Portions Copyright [yyyy] [name of copyright owner]
20214501Srpaulo *
21214501Srpaulo * CDDL HEADER END
22214501Srpaulo *
23214501Srpaulo * Portions Copyright 2006-2008 John Birrell jb@freebsd.org
24214501Srpaulo * Portions Copyright 2013 Justin Hibbits jhibbits@freebsd.org
25214501Srpaulo * Portions Copyright 2013 Howard Su howardsu@freebsd.org
26214501Srpaulo *
27281806Srpaulo * $FreeBSD: head/sys/cddl/dev/fbt/arm/fbt_isa.c 312378 2017-01-18 13:27:24Z andrew $
28281806Srpaulo *
29281806Srpaulo */
30289549Srpaulo
31346981Scy/*
32214501Srpaulo * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
33214501Srpaulo * Use is subject to license terms.
34214501Srpaulo */
35214501Srpaulo
36214501Srpaulo#include <sys/cdefs.h>
37214501Srpaulo#include <sys/param.h>
38214501Srpaulo#include <sys/module.h>
39351611Scy#include <sys/kmem.h>
40351611Scy
41214501Srpaulo#include <sys/dtrace.h>
42351611Scy
43214501Srpaulo#include <machine/trap.h>
44214501Srpaulo#include <arm/cpufunc.h>
45252726Srpaulo#include <arm/armreg.h>
46252726Srpaulo#include <arm/frame.h>
47252726Srpaulo#include <uvm/uvm_extern.h>
48281806Srpaulo
49281806Srpaulo#include "fbt.h"
50214501Srpaulo
51214501Srpaulo#define	FBT_PUSHM		0xe92d0000
52214501Srpaulo#define	FBT_POPM		0xe8bd0000
53214501Srpaulo#define	FBT_JUMP		0xea000000
54214501Srpaulo#define	FBT_SUBSP		0xe24dd000
55214501Srpaulo
56281806Srpaulo#define	FBT_ENTRY	"entry"
57214501Srpaulo#define	FBT_RETURN	"return"
58214501Srpaulo
59214501Srpaulostatic uint32_t
60214501Srpauloldinstr(const uint32_t *instr)
61214501Srpaulo{
62214501Srpaulo#ifdef _ARM_ARCH_BE8		/* big-endian data, big-endian instructions */
63214501Srpaulo	return *instr;
64214501Srpaulo#else				/* little-endian instructions */
65214501Srpaulo	return le32toh(*instr);
66214501Srpaulo#endif
67214501Srpaulo}
68214501Srpaulo
69214501Srpaulostatic void
70214501Srpaulostinstr(uint32_t *instr, uint32_t val)
71214501Srpaulo{
72281806Srpaulo
73281806Srpaulo#ifdef _ARM_ARCH_BE8		/* big-endian data, big-endian instructions */
74214501Srpaulo	val = bswap32(val);
75214501Srpaulo#endif
76214501Srpaulo	ktext_write(instr, &val, sizeof(val)); /* write little-endian */
77214501Srpaulo}
78214501Srpaulo
79214501Srpauloint
80214501Srpaulofbt_invop(uintptr_t addr, struct trapframe *frame, uintptr_t rval)
81214501Srpaulo{
82214501Srpaulo	solaris_cpu_t *cpu = &solaris_cpu[cpu_number()];
83281806Srpaulo	fbt_probe_t *fbt = fbt_probetab[FBT_ADDR2NDX(addr)];
84281806Srpaulo	register_t fifthparam;
85281806Srpaulo
86214501Srpaulo	for (; fbt != NULL; fbt = fbt->fbtp_hashnext) {
87214501Srpaulo		if ((uintptr_t)fbt->fbtp_patchpoint == addr) {
88214501Srpaulo			if (fbt->fbtp_roffset == 0) {
89214501Srpaulo				/* Get 5th parameter from stack */
90214501Srpaulo				DTRACE_CPUFLAG_SET(CPU_DTRACE_NOFAULT);
91214501Srpaulo				fifthparam = *(register_t *)frame->tf_svc_sp;
92214501Srpaulo				DTRACE_CPUFLAG_CLEAR(CPU_DTRACE_NOFAULT |
93214501Srpaulo				    CPU_DTRACE_BADADDR);
94252726Srpaulo
95252726Srpaulo				cpu->cpu_dtrace_caller = frame->tf_svc_lr;
96252726Srpaulo				dtrace_probe(fbt->fbtp_id, frame->tf_r0,
97214501Srpaulo					     frame->tf_r1, frame->tf_r2,
98214501Srpaulo					     frame->tf_r3, fifthparam);
99281806Srpaulo			} else {
100281806Srpaulo				/* XXX set caller */
101214501Srpaulo				cpu->cpu_dtrace_caller = 0;
102289549Srpaulo				dtrace_probe(fbt->fbtp_id, fbt->fbtp_roffset,
103289549Srpaulo				    rval, 0, 0, 0);
104289549Srpaulo			}
105214501Srpaulo
106214501Srpaulo			cpu->cpu_dtrace_caller = 0;
107			return (fbt->fbtp_rval);
108		}
109	}
110
111	return (0);
112}
113
114
115void
116fbt_patch_tracepoint(fbt_probe_t *fbt, fbt_patchval_t val)
117{
118	dtrace_icookie_t c;
119
120	c = dtrace_interrupt_disable();
121	stinstr(fbt->fbtp_patchpoint, val);
122	dtrace_interrupt_enable(c);
123}
124
125#ifdef __FreeBSD__
126
127int
128fbt_provide_module_function(linker_file_t lf, int symindx,
129    linker_symval_t *symval, void *opaque)
130{
131	char *modname = opaque;
132	const char *name = symval->name;
133	fbt_probe_t *fbt, *retfbt;
134	uint32_t *instr, *limit;
135	int popm;
136
137	if (fbt_excluded(name))
138		return (0);
139
140	instr = (uint32_t *)symval->value;
141	limit = (uint32_t *)(symval->value + symval->size);
142
143	/*
144	 * va_arg functions has first instruction of
145	 * sub sp, sp, #?
146	 */
147	if ((ldinstr(instr) & 0xfffff000) == FBT_SUBSP)
148		instr++;
149
150	/*
151	 * check if insn is a pushm with LR
152	 */
153	if ((ldinstr(instr) & 0xffff0000) != FBT_PUSHM ||
154	    (ldinstr(instr) & (1 << LR)) == 0)
155		return (0);
156
157	fbt = kmem_zalloc(sizeof (fbt_probe_t), KM_SLEEP);
158	fbt->fbtp_name = name;
159	fbt->fbtp_id = dtrace_probe_create(fbt_id, modname,
160	    name, FBT_ENTRY, 5, fbt);
161	fbt->fbtp_patchpoint = instr;
162	fbt->fbtp_ctl = lf;
163	fbt->fbtp_loadcnt = lf->loadcnt;
164	fbt->fbtp_savedval = ldinstr(instr);
165	fbt->fbtp_patchval = FBT_BREAKPOINT;
166	fbt->fbtp_rval = DTRACE_INVOP_PUSHM;
167	fbt->fbtp_symindx = symindx;
168
169	fbt->fbtp_hashnext = fbt_probetab[FBT_ADDR2NDX(instr)];
170	fbt_probetab[FBT_ADDR2NDX(instr)] = fbt;
171
172	lf->fbt_nentries++;
173
174	popm = FBT_POPM | (ldinstr(instr) & 0x3FFF) | 0x8000;
175
176	retfbt = NULL;
177again:
178	for (; instr < limit; instr++) {
179		if (ldinstr(instr) == popm)
180			break;
181		else if ((ldinstr(instr) & 0xff000000) == FBT_JUMP) {
182			uint32_t *target, *start;
183			int offset;
184
185			offset = (ldinstr(instr) & 0xffffff);
186			offset <<= 8;
187			offset /= 64;
188			target = instr + (2 + offset);
189			start = (uint32_t *)symval->value;
190			if (target >= limit || target < start)
191				break;
192		}
193	}
194
195	if (instr >= limit)
196		return (0);
197
198	/*
199	 * We have a winner!
200	 */
201	fbt = kmem_zalloc(sizeof (fbt_probe_t), KM_SLEEP);
202	fbt->fbtp_name = name;
203	if (retfbt == NULL) {
204		fbt->fbtp_id = dtrace_probe_create(fbt_id, modname,
205		    name, FBT_RETURN, 5, fbt);
206	} else {
207		retfbt->fbtp_next = fbt;
208		fbt->fbtp_id = retfbt->fbtp_id;
209	}
210	retfbt = fbt;
211
212	fbt->fbtp_patchpoint = instr;
213	fbt->fbtp_ctl = lf;
214	fbt->fbtp_loadcnt = lf->loadcnt;
215	fbt->fbtp_symindx = symindx;
216	if ((ldinstr(instr) & 0xff000000) == FBT_JUMP)
217		fbt->fbtp_rval = DTRACE_INVOP_B;
218	else
219		fbt->fbtp_rval = DTRACE_INVOP_POPM;
220	fbt->fbtp_savedval = ldinstr(instr);
221	fbt->fbtp_patchval = FBT_BREAKPOINT;
222	fbt->fbtp_hashnext = fbt_probetab[FBT_ADDR2NDX(instr)];
223	fbt_probetab[FBT_ADDR2NDX(instr)] = fbt;
224
225	lf->fbt_nentries++;
226
227	instr++;
228	goto again;
229}
230
231#endif /* __FreeBSD_ */
232
233#ifdef __NetBSD__
234
235#define	FBT_PATCHVAL		DTRACE_BREAKPOINT
236
237/* entry and return */
238#define	FBT_BX_LR_P(insn)	(((insn) & ~INSN_COND_MASK) == 0x012fff1e)
239#define	FBT_B_LABEL_P(insn)	(((insn) & 0xff000000) == 0xea000000)
240/* entry */
241#define	FBT_MOV_IP_SP_P(insn)	((insn) == 0xe1a0c00d)
242/* index=1, add=1, wback=0 */
243#define	FBT_LDR_IMM_P(insn)	(((insn) & 0xfff00000) == 0xe5900000)
244#define	FBT_MOVW_P(insn)	(((insn) & 0xfff00000) == 0xe3000000)
245#define	FBT_MOV_IMM_P(insn)	(((insn) & 0xffff0000) == 0xe3a00000)
246#define	FBT_CMP_IMM_P(insn)	(((insn) & 0xfff00000) == 0xe3500000)
247#define	FBT_PUSH_P(insn)	(((insn) & 0xffff0000) == 0xe92d0000)
248/* return */
249/* cond=always, writeback=no, rn=sp and register_list includes pc */
250#define	FBT_LDM_P(insn)	(((insn) & 0x0fff8000) == 0x089d8000)
251#define	FBT_LDMIB_P(insn)	(((insn) & 0x0fff8000) == 0x099d8000)
252#define	FBT_MOV_PC_LR_P(insn)	(((insn) & ~INSN_COND_MASK) == 0x01a0f00e)
253/* cond=always, writeback=no, rn=sp and register_list includes lr, but not pc */
254#define	FBT_LDM_LR_P(insn)	(((insn) & 0xffffc000) == 0xe89d4000)
255#define	FBT_LDMIB_LR_P(insn)	(((insn) & 0xffffc000) == 0xe99d4000)
256
257/* rval = insn | invop_id (overwriting cond with invop ID) */
258#define	BUILD_RVAL(insn, id)	(((insn) & ~INSN_COND_MASK) | __SHIFTIN((id), INSN_COND_MASK))
259/* encode cond in the first byte */
260#define	PATCHVAL_ENCODE_COND(insn)	(FBT_PATCHVAL | __SHIFTOUT((insn), INSN_COND_MASK))
261
262int
263fbt_provide_module_cb(const char *name, int symindx, void *value,
264	uint32_t symsize, int type, void *opaque)
265{
266	fbt_probe_t *fbt, *retfbt;
267	uint32_t *instr, *limit;
268	bool was_ldm_lr = false;
269	int size;
270
271	struct fbt_ksyms_arg *fka = opaque;
272	modctl_t *mod = fka->fka_mod;
273	const char *modname = module_name(mod);
274
275
276	/* got a function? */
277	if (ELF_ST_TYPE(type) != STT_FUNC)
278		return 0;
279
280	if (fbt_excluded(name))
281		return (0);
282
283	/*
284	 * Exclude some more symbols which can be called from probe context.
285	 */
286	if (strncmp(name, "_spl", 4) == 0 ||
287	    strcmp(name, "binuptime") == 0 ||
288	    strcmp(name, "nanouptime") == 0 ||
289	    strcmp(name, "dosoftints") == 0 ||
290	    strcmp(name, "fbt_emulate") == 0 ||
291	    strcmp(name, "undefinedinstruction") == 0 ||
292	    strncmp(name, "dmt_", 4) == 0 /* omap */ ||
293	    strncmp(name, "mvsoctmr_", 9) == 0 /* marvell */ ) {
294		return 0;
295	}
296
297	instr = (uint32_t *) value;
298	limit = (uint32_t *)((uintptr_t)value + symsize);
299
300	if (!FBT_MOV_IP_SP_P(ldinstr(instr))
301	    && !FBT_BX_LR_P(ldinstr(instr))
302	    && !FBT_MOVW_P(ldinstr(instr))
303	    && !FBT_MOV_IMM_P(ldinstr(instr))
304	    && !FBT_B_LABEL_P(ldinstr(instr))
305	    && !FBT_LDR_IMM_P(ldinstr(instr))
306	    && !FBT_CMP_IMM_P(ldinstr(instr))
307	    && !FBT_PUSH_P(ldinstr(instr))
308	    ) {
309		return 0;
310	}
311
312	fbt = kmem_zalloc(sizeof (fbt_probe_t), KM_SLEEP);
313	fbt->fbtp_name = name;
314	fbt->fbtp_id = dtrace_probe_create(fbt_id, modname,
315	    name, FBT_ENTRY, 5, fbt);
316	fbt->fbtp_patchpoint = instr;
317	fbt->fbtp_ctl = mod;
318	/* fbt->fbtp_loadcnt = lf->loadcnt; */
319	if (FBT_MOV_IP_SP_P(ldinstr(instr))) {
320		fbt->fbtp_rval = BUILD_RVAL(ldinstr(instr),
321		    DTRACE_INVOP_MOV_IP_SP);
322	} else if (FBT_LDR_IMM_P(ldinstr(instr))) {
323		fbt->fbtp_rval = BUILD_RVAL(ldinstr(instr),
324		    DTRACE_INVOP_LDR_IMM);
325	} else if (FBT_MOVW_P(ldinstr(instr))) {
326		fbt->fbtp_rval = BUILD_RVAL(ldinstr(instr), DTRACE_INVOP_MOVW);
327	} else if (FBT_MOV_IMM_P(ldinstr(instr))) {
328		fbt->fbtp_rval = BUILD_RVAL(ldinstr(instr),
329		    DTRACE_INVOP_MOV_IMM);
330	} else if (FBT_CMP_IMM_P(ldinstr(instr))) {
331		fbt->fbtp_rval = BUILD_RVAL(ldinstr(instr),
332		    DTRACE_INVOP_CMP_IMM);
333	} else if (FBT_BX_LR_P(ldinstr(instr))) {
334		fbt->fbtp_rval = BUILD_RVAL(ldinstr(instr),
335		    DTRACE_INVOP_BX_LR);
336	} else if (FBT_PUSH_P(ldinstr(instr))) {
337		fbt->fbtp_rval = BUILD_RVAL(ldinstr(instr),
338		    DTRACE_INVOP_PUSHM);
339	} else if (FBT_B_LABEL_P(ldinstr(instr))) {
340		fbt->fbtp_rval = BUILD_RVAL(ldinstr(instr),
341		    DTRACE_INVOP_B);
342	} else {
343		KASSERT(0);
344	}
345
346	KASSERTMSG((fbt->fbtp_rval >> 28) != 0,
347		   "fbt %p insn 0x%x name %s rval 0x%08x",
348		   fbt, ldinstr(instr), name, fbt->fbtp_rval);
349
350	fbt->fbtp_patchval = PATCHVAL_ENCODE_COND(ldinstr(instr));
351	fbt->fbtp_savedval = ldinstr(instr);
352	fbt->fbtp_symindx = symindx;
353
354	fbt->fbtp_hashnext = fbt_probetab[FBT_ADDR2NDX(instr)];
355	fbt_probetab[FBT_ADDR2NDX(instr)] = fbt;
356
357	retfbt = NULL;
358
359	while (instr < limit) {
360		if (instr >= limit)
361			return (0);
362
363		size = 1;
364
365		if (!FBT_BX_LR_P(ldinstr(instr))
366		    && !FBT_MOV_PC_LR_P(ldinstr(instr))
367		    && !FBT_LDM_P(ldinstr(instr))
368		    && !FBT_LDMIB_P(ldinstr(instr))
369		    && !(was_ldm_lr && FBT_B_LABEL_P(ldinstr(instr)))
370		    ) {
371			if (FBT_LDM_LR_P(ldinstr(instr)) ||
372			    FBT_LDMIB_LR_P(ldinstr(instr)))
373				was_ldm_lr = true;
374			else
375				was_ldm_lr = false;
376			instr += size;
377			continue;
378		}
379
380		/*
381		 * We have a winner!
382		 */
383		fbt = kmem_zalloc(sizeof (fbt_probe_t), KM_SLEEP);
384		fbt->fbtp_name = name;
385
386		if (retfbt == NULL) {
387			fbt->fbtp_id = dtrace_probe_create(fbt_id, modname,
388			    name, FBT_RETURN, 5, fbt);
389		} else {
390			retfbt->fbtp_next = fbt;
391			fbt->fbtp_id = retfbt->fbtp_id;
392		}
393
394		retfbt = fbt;
395		fbt->fbtp_patchpoint = instr;
396		fbt->fbtp_ctl = mod;
397		/* fbt->fbtp_loadcnt = lf->loadcnt; */
398		fbt->fbtp_symindx = symindx;
399
400		if (FBT_BX_LR_P(ldinstr(instr))) {
401			fbt->fbtp_rval = BUILD_RVAL(ldinstr(instr),
402			    DTRACE_INVOP_BX_LR);
403		} else if (FBT_MOV_PC_LR_P(ldinstr(instr))) {
404			fbt->fbtp_rval = BUILD_RVAL(ldinstr(instr),
405			    DTRACE_INVOP_MOV_PC_LR);
406		} else if (FBT_LDM_P(ldinstr(instr))) {
407			fbt->fbtp_rval = BUILD_RVAL(ldinstr(instr),
408			    DTRACE_INVOP_LDM);
409		} else if (FBT_LDMIB_P(ldinstr(instr))) {
410			fbt->fbtp_rval = BUILD_RVAL(ldinstr(instr),
411			    DTRACE_INVOP_POPM);
412		} else if (FBT_B_LABEL_P(ldinstr(instr))) {
413			fbt->fbtp_rval = BUILD_RVAL(ldinstr(instr),
414			    DTRACE_INVOP_B);
415		} else {
416			KASSERT(0);
417		}
418
419		KASSERTMSG((fbt->fbtp_rval >> 28) != 0, "fbt %p name %s rval 0x%08x",
420			   fbt, name, fbt->fbtp_rval);
421
422		fbt->fbtp_roffset = (uintptr_t)(instr - (uint32_t *) value);
423		fbt->fbtp_patchval = PATCHVAL_ENCODE_COND(ldinstr(instr));
424
425		fbt->fbtp_savedval = ldinstr(instr);
426		fbt->fbtp_hashnext = fbt_probetab[FBT_ADDR2NDX(instr)];
427		fbt_probetab[FBT_ADDR2NDX(instr)] = fbt;
428
429		instr += size;
430		was_ldm_lr = false;
431	}
432
433	return 0;
434}
435
436#endif /* __NetBSD__ */
437