/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include "intr_common.h"

static struct av_head	avec_tbl[APIC_MAX_VECTOR+1];
static uint16_t		shared_tbl[MAX_ISA_IRQ + 1];

static char *
interrupt_print_bus(uintptr_t dip_addr)
{
	char		bind_name[MODMAXNAMELEN + 1];
	struct dev_info	dev_info;

	if (mdb_vread(&dev_info, sizeof (dev_info), dip_addr) == -1) {
		mdb_warn("failed to read child dip");
		return ("-");
	}

	while (dev_info.devi_parent != 0) {
		if (mdb_vread(&dev_info, sizeof (dev_info),
		    (uintptr_t)dev_info.devi_parent) == -1)
			break;

		(void) mdb_readstr(bind_name, sizeof (bind_name),
		    (uintptr_t)dev_info.devi_binding_name);
		if (strcmp(bind_name, "isa") == 0)
			return ("ISA");
		else if (strcmp(bind_name, "pci") == 0 ||
		    strcmp(bind_name, "npe") == 0)
			return ("PCI");
	}
	return ("-");
}


/*
 * uppc_interrupt_dump:
 *	Dump uppc(7d) interrupt information.
 */
/* ARGSUSED */
int
uppc_interrupt_dump(uintptr_t addr, uint_t flags, int argc,
    const mdb_arg_t *argv)
{
	int		i, j;
	boolean_t	found = B_FALSE;
	struct autovec	avhp;

	option_flags = 0;
	if (mdb_getopts(argc, argv,
	    'd', MDB_OPT_SETBITS, INTR_DISPLAY_DRVR_INST, &option_flags,
	    'i', MDB_OPT_SETBITS, INTR_DISPLAY_INTRSTAT, &option_flags,
	    NULL) != argc)
		return (DCMD_USAGE);

	if (mdb_readvar(&avec_tbl, "autovect") == -1) {
		mdb_warn("failed to read autovect");
		return (DCMD_ERR);
	}

	if (mdb_readvar(&shared_tbl, "uppc_irq_shared_table") == -1) {
		mdb_warn("failed to read uppc_irq_shared_table");
		return (DCMD_ERR);
	}

	/*
	 * By default, on all x86 systems ::interrupts from uppc(7d) gets
	 * loaded first. For APIC systems the ::interrupts from pcplusmp(7d)
	 * ought to be executed. Confusion stems as both modules export the
	 * same dcmd.
	 */
	for (i = 0; i < MAX_ISA_IRQ + 1; i++)
		if (shared_tbl[i]) {
			found = B_TRUE;
			break;
		}

	if (found == B_FALSE) {
		if (mdb_lookup_by_obj("pcplusmp", "apic_irq_table",
		    NULL) == 0) {
			return (mdb_call_dcmd("pcplusmp`interrupts",
			    addr, flags, argc, argv));
		}
	}

	/* Print the header first */
	if (option_flags & INTR_DISPLAY_INTRSTAT)
		mdb_printf("%<u>CPU\t ");
	else
		mdb_printf("%<u>IRQ  Vector IPL(lo/hi) Bus Share ");
	mdb_printf("%s %</u>\n", option_flags & INTR_DISPLAY_DRVR_INST ?
	    "Driver Name(s)" : "ISR(s)");

	/* Walk all the entries */
	for (i = 0; i < MAX_ISA_IRQ + 1; i++) {
		/* Read the entry, if invalid continue */
		if (mdb_vread(&avhp, sizeof (struct autovec),
		    (uintptr_t)avec_tbl[i].avh_link) == -1)
			continue;

		/* Print each interrupt entry */
		if (option_flags & INTR_DISPLAY_INTRSTAT)
			mdb_printf("cpu0\t");
		else
			mdb_printf("%-3d   0x%2x   %4d/%-2d   %-4s %-3d  ",
			    i, i + PIC_VECTBASE, avec_tbl[i].avh_lo_pri,
			    avec_tbl[i].avh_hi_pri, avhp.av_dip ?
			    interrupt_print_bus((uintptr_t)avhp.av_dip) : " - ",
			    shared_tbl[i]);

		if (shared_tbl[i])
			interrupt_print_isr((uintptr_t)avhp.av_vector,
			    (uintptr_t)avhp.av_intarg1, (uintptr_t)avhp.av_dip);

		for (j = 1; j < shared_tbl[i]; j++) {
			if (mdb_vread(&avhp, sizeof (struct autovec),
			    (uintptr_t)avhp.av_link) != -1)  {
				mdb_printf(", ");
				interrupt_print_isr((uintptr_t)avhp.av_vector,
				    (uintptr_t)avhp.av_intarg1,
				    (uintptr_t)avhp.av_dip);
			} else {
				break;
			}
		}
		mdb_printf("\n");
	}

	return (DCMD_OK);
}


/*
 * MDB module linkage information:
 */
static const mdb_dcmd_t dcmds[] = {
	{ "interrupts", "?[-di]", "print interrupts", uppc_interrupt_dump,
	    interrupt_help},
	{ "softint", "?[-d]", "print soft interrupts", soft_interrupt_dump,
	    soft_interrupt_help},
	{ NULL }
};

static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, NULL };

const mdb_modinfo_t *
_mdb_init(void)
{
	GElf_Sym	sym;

	if (mdb_lookup_by_name("gld_intr", &sym) != -1)
		if (GELF_ST_TYPE(sym.st_info) == STT_FUNC)
			gld_intr_addr = (uintptr_t)sym.st_value;

	return (&modinfo);
}