1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26#include <mdb/mdb_modapi.h>
27#include <mdb/mdb_ks.h>
28#include <mdb/mdb_ctf.h>
29#include <sys/evtchn_impl.h>
30#include <errno.h>
31#include <sys/xc_levels.h>
32
33#include "intr_common.h"
34
35static shared_info_t shared_info;
36static int have_shared_info;
37static uintptr_t evtchn_cpus_addr;
38static struct av_head avec_tbl[NR_IRQS];
39static irq_info_t irq_tbl[NR_IRQS];
40static mec_info_t ipi_tbl[MAXIPL];
41static mec_info_t virq_tbl[NR_VIRQS];
42static short evtchn_tbl[NR_EVENT_CHANNELS];
43static apic_irq_t *apic_irq_tbl[APIC_MAX_VECTOR+1];
44static char level_tbl[APIC_MAX_VECTOR+1];
45
46static int
47update_tables(void)
48{
49	GElf_Sym sym;
50	uintptr_t shared_info_addr;
51
52	if (mdb_readvar(&irq_tbl, "irq_info") == -1) {
53		mdb_warn("failed to read irq_info");
54		return (0);
55	}
56
57	if (mdb_readvar(&ipi_tbl, "ipi_info") == -1) {
58		mdb_warn("failed to read ipi_info");
59		return (0);
60	}
61
62	if (mdb_readvar(&avec_tbl, "autovect") == -1) {
63		mdb_warn("failed to read autovect");
64		return (0);
65	}
66
67	if (mdb_readvar(&irq_tbl, "irq_info") == -1) {
68		mdb_warn("failed to read irq_info");
69		return (0);
70	}
71
72	if (mdb_readvar(&ipi_tbl, "ipi_info") == -1) {
73		mdb_warn("failed to read ipi_info");
74		return (0);
75	}
76
77	if (mdb_readvar(&virq_tbl, "virq_info") == -1) {
78		mdb_warn("failed to read virq_info");
79		return (0);
80	}
81
82	if (mdb_readvar(&evtchn_tbl, "evtchn_to_irq") == -1) {
83		mdb_warn("failed to read evtchn_to_irq");
84		return (0);
85	}
86
87	if (mdb_readvar(&apic_irq_tbl, "apic_irq_table") == -1) {
88		mdb_warn("failed to read apic_irq_table");
89		return (0);
90	}
91
92	if (mdb_readvar(&level_tbl, "apic_level_intr") == -1) {
93		mdb_warn("failed to read apic_level_intr");
94		return (0);
95	}
96
97	if (mdb_lookup_by_name("evtchn_cpus", &sym) == -1) {
98		mdb_warn("failed to lookup evtchn_cpus");
99		return (0);
100	}
101
102	evtchn_cpus_addr = sym.st_value;
103
104	if (mdb_readvar(&shared_info_addr, "HYPERVISOR_shared_info") == -1) {
105		mdb_warn("failed to read HYPERVISOR_shared_info");
106		return (0);
107	}
108
109	/*
110	 * It's normal for this to fail with a domain dump.
111	 */
112	if (mdb_ctf_vread(&shared_info, "shared_info_t",
113	    shared_info_addr, 0) != -1)
114		have_shared_info = 1;
115
116	return (1);
117}
118
119static const char *
120virq_type(int irq)
121{
122	int i;
123
124	for (i = 0; i < NR_VIRQS; i++) {
125		if (virq_tbl[i].mi_irq == irq)
126			break;
127	}
128
129	switch (i) {
130	case VIRQ_TIMER:
131		return ("virq:timer");
132	case VIRQ_DEBUG:
133		return ("virq:debug");
134	case VIRQ_CONSOLE:
135		return ("virq:console");
136	case VIRQ_DOM_EXC:
137		return ("virq:dom exc");
138	case VIRQ_DEBUGGER:
139		return ("virq:debugger");
140	case VIRQ_MCA:
141		return ("virq:mca");
142	default:
143		break;
144	}
145
146	return ("virq:?");
147}
148
149static const char *
150irq_type(int irq, int extended)
151{
152	switch (irq_tbl[irq].ii_type) {
153	case IRQT_UNBOUND:
154		return ("unset");
155	case IRQT_PIRQ:
156		return ("pirq");
157	case IRQT_VIRQ:
158		if (extended)
159			return (virq_type(irq));
160		return ("virq");
161	case IRQT_IPI:
162		return ("ipi");
163	case IRQT_EVTCHN:
164		return ("evtchn");
165	case IRQT_DEV_EVTCHN:
166		return ("device");
167	}
168
169	return ("?");
170}
171
172/*
173 * We need a non-trivial IPL lookup as the CPU poke's IRQ doesn't have ii_ipl
174 * set -- see evtchn.h.
175 */
176static int
177irq_ipl(int irq)
178{
179	int i;
180
181	if (irq_tbl[irq].ii_u2.ipl != 0)
182		return (irq_tbl[irq].ii_u2.ipl);
183
184	for (i = 0; i < MAXIPL; i++) {
185		if (ipi_tbl[i].mi_irq == irq) {
186			return (i);
187		}
188	}
189
190	return (0);
191}
192
193static void
194print_cpu(irq_info_t *irqp, int evtchn)
195{
196	size_t cpuset_size = BT_BITOUL(NCPU) * sizeof (ulong_t);
197	int cpu;
198
199	if (irqp != NULL) {
200		switch (irqp->ii_type) {
201		case IRQT_VIRQ:
202		case IRQT_IPI:
203			mdb_printf("all ");
204			return;
205
206		case IRQT_DEV_EVTCHN:
207			mdb_printf("0   ");
208			return;
209
210		default:
211			break;
212		}
213	}
214
215	if (evtchn >= NR_EVENT_CHANNELS || evtchn == 0) {
216		mdb_printf("-   ");
217		return;
218	}
219
220	cpu = mdb_cpuset_find(evtchn_cpus_addr +
221	    (cpuset_size * evtchn));
222
223	/*
224	 * XXPV: we should verify this against the CPU's mask and show
225	 * something if they don't match.
226	 */
227	mdb_printf("%-4d", cpu);
228}
229
230static void
231print_isr(int i)
232{
233	if (avec_tbl[i].avh_link != NULL) {
234		struct autovec avhp;
235
236		(void) mdb_vread(&avhp, sizeof (struct autovec),
237		    (uintptr_t)avec_tbl[i].avh_link);
238
239		interrupt_print_isr((uintptr_t)avhp.av_vector,
240		    (uintptr_t)avhp.av_intarg1, (uintptr_t)avhp.av_dip);
241	} else if (irq_ipl(i) == XC_CPUPOKE_PIL) {
242		mdb_printf("poke_cpu");
243	}
244}
245
246static int
247evtchn_masked(int i)
248{
249	return (!!TEST_EVTCHN_BIT(i, &shared_info.evtchn_mask[0]));
250}
251
252static int
253evtchn_pending(int i)
254{
255	return (!!TEST_EVTCHN_BIT(i, &shared_info.evtchn_pending[0]));
256}
257
258static void
259print_bus(int irq)
260{
261	char parent[7];
262	uintptr_t dip_addr;
263	struct dev_info	dev_info;
264	struct autovec avhp;
265
266	bzero(&avhp, sizeof (avhp));
267
268	if (mdb_ctf_vread(&avhp, "struct autovec",
269	    (uintptr_t)avec_tbl[irq].avh_link, 0) == -1)
270		goto fail;
271
272	dip_addr = (uintptr_t)avhp.av_dip;
273
274	if (dip_addr == NULL)
275		goto fail;
276
277	/*
278	 * Sigh.  As a result of the perennial confusion of how you do opaque
279	 * handles, dev_info_t has a funny old type, which means we can't use
280	 * mdb_ctf_vread() here.
281	 */
282
283	if (mdb_vread(&dev_info, sizeof (struct dev_info), dip_addr) == -1)
284		goto fail;
285
286	dip_addr = (uintptr_t)dev_info.devi_parent;
287
288	if (mdb_vread(&dev_info, sizeof (struct dev_info), dip_addr) == -1)
289		goto fail;
290
291	if (mdb_readstr(parent, 7, (uintptr_t)dev_info.devi_node_name) == -1)
292		goto fail;
293
294	mdb_printf("%-6s ", parent);
295	return;
296
297fail:
298	mdb_printf("-      ");
299}
300
301static void
302ec_interrupt_dump(int i)
303{
304	irq_info_t *irqp = &irq_tbl[i];
305	char evtchn[8];
306
307	if (irqp->ii_type == IRQT_UNBOUND)
308		return;
309
310	if (option_flags & INTR_DISPLAY_INTRSTAT) {
311		print_cpu(irqp, irqp->ii_u.evtchn);
312		print_isr(i);
313		mdb_printf("\n");
314		return;
315	}
316
317	switch (irqp->ii_type) {
318	case IRQT_EVTCHN:
319	case IRQT_VIRQ:
320		if (irqp->ii_u.index == VIRQ_TIMER) {
321			strcpy(evtchn, "T");
322		} else {
323			mdb_snprintf(evtchn, sizeof (evtchn), "%-7d",
324			    irqp->ii_u.evtchn);
325		}
326		break;
327	case IRQT_IPI:
328		strcpy(evtchn, "I");
329		break;
330	case IRQT_DEV_EVTCHN:
331		strcpy(evtchn, "D");
332		break;
333	}
334
335	/* IRQ */
336	mdb_printf("%3d  ", i);
337	/* Vector */
338	mdb_printf("-    ");
339	/* Evtchn */
340	mdb_printf("%-7s", evtchn);
341	/* IPL */
342	mdb_printf("%-4d", irq_ipl(i));
343	/* Bus */
344	print_bus(i);
345	/* Trigger */
346	mdb_printf("%-4s", "Edg");
347	/* Type */
348	mdb_printf("%-7s", irq_type(i, 0));
349	/* CPU */
350	print_cpu(irqp, irqp->ii_u.evtchn);
351	/* Share */
352	mdb_printf("-     ");
353	/* APIC/INT# */
354	mdb_printf("-         ");
355
356	print_isr(i);
357
358	mdb_printf("\n");
359}
360
361/* ARGSUSED */
362static int
363interrupts_dump(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
364{
365	int i;
366
367	option_flags = 0;
368	if (mdb_getopts(argc, argv,
369	    'd', MDB_OPT_SETBITS, INTR_DISPLAY_DRVR_INST, &option_flags,
370	    'i', MDB_OPT_SETBITS, INTR_DISPLAY_INTRSTAT, &option_flags,
371	    NULL) != argc)
372		return (DCMD_USAGE);
373
374	if (!update_tables())
375		return (DCMD_ERR);
376
377	if (option_flags & INTR_DISPLAY_INTRSTAT) {
378		mdb_printf("%<u>CPU ");
379	} else {
380		mdb_printf("%<u>IRQ  Vect Evtchn IPL Bus    Trg Type   "
381		    "CPU Share APIC/INT# ");
382	}
383	mdb_printf("%s %</u>\n", option_flags & INTR_DISPLAY_DRVR_INST ?
384	    "Driver Name(s)" : "ISR(s)");
385
386	for (i = 0; i < NR_IRQS; i++) {
387		if (irq_tbl[i].ii_type == IRQT_PIRQ) {
388			apic_irq_t airq;
389
390			if (irq_tbl[i].ii_u.evtchn == 0)
391				continue;
392
393			if (mdb_vread(&airq, sizeof (apic_irq_t),
394			    (uintptr_t)apic_irq_tbl[i]) == -1)
395				continue;
396
397			apic_interrupt_dump(&airq, &avec_tbl[i], i,
398			    &irq_tbl[i].ii_u.evtchn, level_tbl[i]);
399			continue;
400		}
401
402		ec_interrupt_dump(i);
403	}
404
405	return (DCMD_OK);
406}
407
408static void
409evtchn_dump(int i)
410{
411	int irq = evtchn_tbl[i];
412
413	if (irq == INVALID_IRQ) {
414		mdb_printf("%-14s%-7d%-4s%-4s", "unassigned", i, "-", "-");
415		print_cpu(NULL, i);
416		if (have_shared_info) {
417			mdb_printf("%-7d", evtchn_masked(i));
418			mdb_printf("%-8d", evtchn_pending(i));
419		}
420		mdb_printf("\n");
421		return;
422	}
423
424	/* Type */
425	mdb_printf("%-14s", irq_type(irq, 1));
426	/* Evtchn */
427	mdb_printf("%-7d", i);
428	/* IRQ */
429	mdb_printf("%-4d", irq);
430	/* IPL */
431	mdb_printf("%-4d", irq_ipl(irq));
432	/* CPU */
433	print_cpu(NULL, i);
434	if (have_shared_info) {
435		/* Masked/Pending */
436		mdb_printf("%-7d", evtchn_masked(i));
437		mdb_printf("%-8d", evtchn_pending(i));
438	}
439	/* ISR */
440	print_isr(irq);
441
442	mdb_printf("\n");
443}
444
445/* ARGSUSED */
446static int
447evtchns_dump(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
448{
449	int i;
450
451	option_flags = 0;
452	if (mdb_getopts(argc, argv,
453	    'd', MDB_OPT_SETBITS, INTR_DISPLAY_DRVR_INST, &option_flags,
454	    NULL) != argc)
455		return (DCMD_USAGE);
456
457	if (!update_tables())
458		return (DCMD_ERR);
459
460	if (flags & DCMD_ADDRSPEC) {
461		/*
462		 * Note: we allow the invalid evtchn 0, as it can help catch if
463		 * we incorrectly try to configure it.
464		 */
465		if ((int)addr >= NR_EVENT_CHANNELS) {
466			mdb_warn("Invalid event channel %d.\n", (int)addr);
467			return (DCMD_ERR);
468		}
469	}
470
471	mdb_printf("%<u>Type          Evtchn IRQ IPL CPU ");
472	if (have_shared_info)
473		mdb_printf("Masked Pending ");
474
475	mdb_printf("%s %</u>\n", option_flags & INTR_DISPLAY_DRVR_INST ?
476	    "Driver Name(s)" : "ISR(s)");
477
478	if (flags & DCMD_ADDRSPEC) {
479		evtchn_dump((int)addr);
480		return (DCMD_OK);
481	}
482
483	for (i = 0; i < NR_EVENT_CHANNELS; i++) {
484		if (evtchn_tbl[i] == INVALID_IRQ)
485			continue;
486
487		evtchn_dump(i);
488	}
489
490	return (DCMD_OK);
491}
492
493static void
494evtchns_help(void)
495{
496	mdb_printf("Print valid event channels\n"
497	    "If %<u>addr%</u> is given, interpret it as an evtchn to print "
498	    "details of.\n"
499	    "By default, only interrupt service routine names are printed.\n\n"
500	    "Switches:\n"
501	    "  -d   instead of ISR, print <driver_name><instance#>\n");
502}
503
504static const mdb_dcmd_t dcmds[] = {
505	{ "interrupts", "?[-di]", "print interrupts", interrupts_dump,
506	    interrupt_help },
507	{ "evtchns", "?[-d]", "print event channels", evtchns_dump,
508	    evtchns_help },
509	{ "softint", "?[-d]", "print soft interrupts", soft_interrupt_dump,
510	    soft_interrupt_help},
511	{ NULL }
512};
513
514static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, NULL };
515
516const mdb_modinfo_t *
517_mdb_init(void)
518{
519	GElf_Sym sym;
520
521	if (mdb_lookup_by_name("gld_intr", &sym) != -1)
522		if (GELF_ST_TYPE(sym.st_info) == STT_FUNC)
523			gld_intr_addr = (uintptr_t)sym.st_value;
524
525	return (&modinfo);
526}
527