/*
 * 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 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/mdb_modapi.h>
#include <mdb/mdb_ks.h>
#include <sys/async.h>		/* ecc_flt for pci_ecc.h */
#include <sys/ddi_subrdefs.h>
#include <sys/pci/pci_obj.h>
#include "niumx_var.h"
#include "px_obj.h"

static int intr_pci_walk_step(mdb_walk_state_t *);
static int intr_px_walk_step(mdb_walk_state_t *);
static int intr_niumx_walk_step(mdb_walk_state_t *);
static void intr_pci_print_items(mdb_walk_state_t *);
static void intr_px_print_items(mdb_walk_state_t *);
static char *intr_get_intr_type(uint16_t type);
static void intr_print_banner(void);

typedef struct intr_info {
	uint32_t	cpuid;
	uint32_t	inum;
	uint32_t	num;
	uint32_t	pil;
	uint16_t	intr_type;
	uint16_t	mondo;
	uint8_t		ino_ino;
	uint_t		intr_state;
	int		instance;
	int		shared;
	char		driver_name[12];
	char		pathname[MAXNAMELEN];
}
intr_info_t;

#define	PX_MAX_ENTRIES		32

static void intr_print_elements(intr_info_t);
static int detailed = 0; /* Print detailed view */


static int
intr_walk_init(mdb_walk_state_t *wsp)
{
	wsp->walk_addr = NULL;

	return (WALK_NEXT);
}

static int
intr_walk_step(mdb_walk_state_t *wsp)
{
	pci_t		*pci_per_p;
	px_t		*px_state_p;
	niumx_devstate_t *niumx_state_p;

	/* read globally declared structures in the pci driver */
	if (mdb_readvar(&pci_per_p, "per_pci_state") != -1) {
		wsp->walk_addr = (uintptr_t)pci_per_p;
		intr_pci_walk_step(wsp);
	}

	/* read globally declared structures in the px driver */
	if (mdb_readvar(&px_state_p, "px_state_p") != -1) {
		wsp->walk_addr = (uintptr_t)px_state_p;
		intr_px_walk_step(wsp);
	}

	/* read globally declared structures in the niumx driver */
	if (mdb_readvar(&niumx_state_p, "niumx_state") != -1) {
		wsp->walk_addr = (uintptr_t)niumx_state_p;
		intr_niumx_walk_step(wsp);
	}

	return (WALK_DONE);
}

static int
intr_pci_walk_step(mdb_walk_state_t *wsp)
{
	pci_t		*pci_per_p;
	pci_t		pci_per;
	uintptr_t	start_addr;

	/* Read start of state structure array */
	if (mdb_vread(&pci_per_p, sizeof (uintptr_t),
	    (uintptr_t)wsp->walk_addr) == -1) {
		mdb_warn("intr: failed to read the initial pci_per_p "
		    "structure\n");
		return (WALK_ERR);
	}

	/* Figure out how many items are here */
	start_addr = (uintptr_t)pci_per_p;

	intr_print_banner();

	while (mdb_vread(&pci_per_p, sizeof (uintptr_t),
	    (uintptr_t)start_addr) != -1) {
		/* Read until nothing is left */
		if (mdb_vread(&pci_per, sizeof (pci_t),
		    (uintptr_t)pci_per_p) == -1) {
			return (WALK_DONE);
		}

		wsp->walk_addr = (uintptr_t)pci_per.pci_ib_p;
		intr_pci_print_items(wsp);

		start_addr += sizeof (uintptr_t);
	}

	return (WALK_DONE);
}

static int
intr_px_walk_step(mdb_walk_state_t *wsp)
{
	px_t		*px_state_p;
	px_t		px_state;
	uintptr_t	start_addr;
	int		x;

	/* Read start of state structure array */
	if (mdb_vread(&px_state_p, sizeof (uintptr_t),
	    (uintptr_t)wsp->walk_addr) == -1) {
		mdb_warn("intr: failed to read the initial px_per_p "
		    "structure\n");
		return (WALK_ERR);
	}

	/* Figure out how many items are here */
	start_addr = (uintptr_t)px_state_p;

	intr_print_banner();

	for (x = 0; x < PX_MAX_ENTRIES; x++) {
		(void) mdb_vread(&px_state_p, sizeof (uintptr_t),
		    (uintptr_t)start_addr);

		start_addr += sizeof (uintptr_t);

		/* Read if anything is there */
		if (mdb_vread(&px_state, sizeof (px_t),
		    (uintptr_t)px_state_p) == -1) {
			continue;
		}

		wsp->walk_addr = (uintptr_t)px_state.px_ib_p;
		intr_px_print_items(wsp);
	}

	return (WALK_DONE);
}

static int
intr_niumx_walk_step(mdb_walk_state_t *wsp)
{
	niumx_devstate_t *niumx_state_p;
	niumx_devstate_t niumx_state;
	uintptr_t	start_addr;
	char		name[MODMAXNAMELEN + 1];
	struct dev_info	dev;
	intr_info_t	info;
	int		i;

	/* Read start of state structure array */
	if (mdb_vread(&niumx_state_p, sizeof (uintptr_t),
	    (uintptr_t)wsp->walk_addr) == -1) {
		mdb_warn("intr: failed to read the initial niumx_state_p "
		    "structure\n");
		return (WALK_ERR);
	}

	/* Figure out how many items are here */
	start_addr = (uintptr_t)niumx_state_p;

	while (mdb_vread(&niumx_state_p, sizeof (uintptr_t),
	    (uintptr_t)start_addr) >= 0) {

		start_addr += sizeof (uintptr_t);

		/* Read if anything is there */
		if (mdb_vread(&niumx_state, sizeof (niumx_devstate_t),
		    (uintptr_t)niumx_state_p) == -1) {
			return (WALK_DONE);
		}

		for (i = 0; i < NIUMX_MAX_INTRS; i++) {
			if (niumx_state.niumx_ihtable[i].ih_sysino == 0)
				continue;

			if (niumx_state.niumx_ihtable[i].ih_dip == 0)
				continue;

			bzero((void *)&info, sizeof (intr_info_t));

			info.shared = 0;

			(void) mdb_devinfo2driver(
			    (uintptr_t)niumx_state.niumx_ihtable[i].ih_dip,
			    name, sizeof (name));

			(void) mdb_ddi_pathname(
			    (uintptr_t)niumx_state.niumx_ihtable[i].ih_dip,
			    info.pathname, sizeof (info.pathname));

			/* Get instance */
			if (mdb_vread(&dev, sizeof (struct dev_info),
			    (uintptr_t)niumx_state.niumx_ihtable[i].ih_dip) ==
			    -1) {
				mdb_warn("intr: failed to read DIP "
				    "structure\n");

				return (WALK_DONE);
			}

			/* Make sure the name doesn't over run */
			(void) mdb_snprintf(info.driver_name,
			    sizeof (info.driver_name), "%s", name);

			info.instance = dev.devi_instance;
			info.inum = niumx_state.niumx_ihtable[i].ih_inum;
			info.intr_type = DDI_INTR_TYPE_FIXED;
			info.num = 0;
			info.intr_state = niumx_state.niumx_ihtable[i].ih_state;
			info.ino_ino = i;
			info.mondo = niumx_state.niumx_ihtable[i].ih_sysino;
			info.pil = niumx_state.niumx_ihtable[i].ih_pri;
			info.cpuid = niumx_state.niumx_ihtable[i].ih_cpuid;

			intr_print_elements(info);
		}
	}

	return (WALK_DONE);
}

static void
intr_pci_print_items(mdb_walk_state_t *wsp)
{
	ib_t			ib;
	ib_ino_info_t		ino;
	ib_ino_pil_t		ipil;
	ih_t			ih;
	int			count;
	char			name[MODMAXNAMELEN + 1];
	struct dev_info		dev;
	intr_info_t		info;

	if (mdb_vread(&ib, sizeof (ib_t),
	    (uintptr_t)wsp->walk_addr) == -1) {
		mdb_warn("intr: failed to read pci interrupt block "
		    "structure\n");
		return;
	}

	/* Read in ib_ino_info_t structure at address */
	if (mdb_vread(&ino, sizeof (ib_ino_info_t),
	    (uintptr_t)ib.ib_ino_lst) == -1) {
		/* Nothing here to read from */
		return;
	}

	do {
		if (mdb_vread(&ipil, sizeof (ib_ino_pil_t),
		    (uintptr_t)ino.ino_ipil_p) == -1) {
			mdb_warn("intr: failed to read pci interrupt "
			    "ib_ino_pil_t structure\n");
			return;
		}

		do {
			if (mdb_vread(&ih, sizeof (ih_t),
			    (uintptr_t)ipil.ipil_ih_start) == -1) {
				mdb_warn("intr: failed to read pci interrupt "
				    "ih_t structure\n");
				return;
			}

			count = 0;

			do {
				bzero((void *)&info, sizeof (intr_info_t));

				if ((ino.ino_ipil_size > 1) ||
				    (ipil.ipil_ih_size > 1)) {
					info.shared = 1;
				}

				(void) mdb_devinfo2driver((uintptr_t)ih.ih_dip,
				    name, sizeof (name));

				(void) mdb_ddi_pathname((uintptr_t)ih.ih_dip,
				    info.pathname, sizeof (info.pathname));

				/* Get instance */
				if (mdb_vread(&dev, sizeof (struct dev_info),
				    (uintptr_t)ih.ih_dip) == -1) {
					mdb_warn("intr: failed to read DIP "
					    "structure\n");
					return;
				}

				/* Make sure the name doesn't over run */
				(void) mdb_snprintf(info.driver_name,
				    sizeof (info.driver_name), "%s", name);

				info.instance = dev.devi_instance;
				info.inum = ih.ih_inum;
				info.intr_type = DDI_INTR_TYPE_FIXED;
				info.num = 0;
				info.intr_state = ih.ih_intr_state;
				info.ino_ino = ino.ino_ino;
				info.mondo = ino.ino_mondo;
				info.pil = ipil.ipil_pil;
				info.cpuid = ino.ino_cpuid;

				intr_print_elements(info);
				count++;

				(void) mdb_vread(&ih, sizeof (ih_t),
				    (uintptr_t)ih.ih_next);

			} while (count < ipil.ipil_ih_size);

		} while (mdb_vread(&ipil, sizeof (ib_ino_pil_t),
		    (uintptr_t)ipil.ipil_next_p) != -1);

	} while (mdb_vread(&ino, sizeof (ib_ino_info_t),
	    (uintptr_t)ino.ino_next_p) != -1);
}

static void
intr_px_print_items(mdb_walk_state_t *wsp)
{
	px_ib_t		ib;
	px_ino_t	ino;
	px_ino_pil_t	ipil;
	px_ih_t		ih;
	int		count;
	char		name[MODMAXNAMELEN + 1];
	struct dev_info	dev;
	intr_info_t	info;
	devinfo_intr_t	intr_p;

	if (mdb_vread(&ib, sizeof (px_ib_t), wsp->walk_addr) == -1) {
		return;
	}

	/* Read in px_ino_t structure at address */
	if (mdb_vread(&ino, sizeof (px_ino_t),
	    (uintptr_t)ib.ib_ino_lst) == -1) {
		/* Nothing here to read from */
		return;
	}

	do { /* ino_next_p loop */
		if (mdb_vread(&ipil, sizeof (px_ino_pil_t),
		    (uintptr_t)ino.ino_ipil_p) == -1) {
			continue;
		}

		do { /* ipil_next_p loop */
			if (mdb_vread(&ih, sizeof (px_ih_t),
			    (uintptr_t)ipil.ipil_ih_start) == -1) {
				continue;
			}

			count = 0;

			do { /* ipil_ih_size loop */
				bzero((void *)&info, sizeof (intr_info_t));

				(void) mdb_devinfo2driver((uintptr_t)ih.ih_dip,
				    name, sizeof (name));

				(void) mdb_ddi_pathname((uintptr_t)ih.ih_dip,
				    info.pathname, sizeof (info.pathname));

				/* Get instance */
				if (mdb_vread(&dev, sizeof (struct dev_info),
				    (uintptr_t)ih.ih_dip) == -1) {
					mdb_warn("intr: failed to read DIP "
					    "structure\n");
					return;
				}

				/* Make sure the name doesn't over run */
				(void) mdb_snprintf(info.driver_name,
				    sizeof (info.driver_name), "%s", name);

				info.instance = dev.devi_instance;
				info.inum = ih.ih_inum;

				/*
				 * Read the type used, keep PCIe messages
				 * separate.
				 */
				(void) mdb_vread(&intr_p,
				    sizeof (devinfo_intr_t),
				    (uintptr_t)dev.devi_intr_p);

				if (ih.ih_rec_type != MSG_REC) {
					info.intr_type =
					    intr_p.devi_intr_curr_type;
				}

				if ((ino.ino_ipil_size > 1) ||
				    (ipil.ipil_ih_size > 1)) {
					info.shared = 1;
				}

				info.num = ih.ih_msg_code;
				info.intr_state = ih.ih_intr_state;
				info.ino_ino = ino.ino_ino;
				info.mondo = ino.ino_sysino;
				info.pil = ipil.ipil_pil;
				info.cpuid = ino.ino_cpuid;

				intr_print_elements(info);
				count++;

				(void) mdb_vread(&ih, sizeof (px_ih_t),
				    (uintptr_t)ih.ih_next);

			} while (count < ipil.ipil_ih_size);

		} while ((ipil.ipil_next_p != NULL) &&
		    (mdb_vread(&ipil, sizeof (px_ino_pil_t),
		    (uintptr_t)ipil.ipil_next_p) != -1));

	} while ((ino.ino_next_p != NULL) && (mdb_vread(&ino, sizeof (px_ino_t),
	    (uintptr_t)ino.ino_next_p) != -1));
}

static char *
intr_get_intr_type(uint16_t type)
{
	switch (type) {
		case	DDI_INTR_TYPE_FIXED:
			return ("Fixed");
		case	DDI_INTR_TYPE_MSI:
			return ("MSI");
		case	DDI_INTR_TYPE_MSIX:
			return ("MSI-X");
		default:
			return ("PCIe");
	}
}

static void
intr_print_banner(void)
{
	if (!detailed) {
		mdb_printf("\n%<u>\tDevice\t"
		    " Type\t"
		    " MSG #\t"
		    " State\t"
		    " INO\t"
		    " Mondo\t"
		    " Shared\t"
		    "  Pil\t"
		    " CPU   %</u>"
		    "\n");
	}
}

static void
intr_print_elements(intr_info_t info)
{
	if (!detailed) {
		mdb_printf(" %11s#%d\t", info.driver_name, info.instance);
		mdb_printf(" %s\t", intr_get_intr_type(info.intr_type));
		if (info.intr_type == DDI_INTR_TYPE_FIXED) {
			mdb_printf("  --- \t");
		} else {
			mdb_printf(" %4d\t", info.num);
		}
		mdb_printf(" %2s\t",
		    info.intr_state ? "enbl" : "disbl");
		mdb_printf(" 0x%x\t", info.ino_ino);
		mdb_printf(" 0x%x\t", info.mondo);
		mdb_printf(" %5s\t",
		    info.shared ? "yes" : "no");
		mdb_printf(" %4d\t", info.pil);
		mdb_printf(" %3d \n", info.cpuid);
	} else {
		mdb_printf("\n-------------------------------------------\n");
		mdb_printf("Device:\t\t%s\n", info.driver_name);
		mdb_printf("Instance:\t%d\n", info.instance);
		mdb_printf("Path:\t\t%s\n", info.pathname);
		mdb_printf("Inum:\t\t%d\n", info.inum);
		mdb_printf("Interrupt Type:\t%s\n",
		    intr_get_intr_type(info.intr_type));
		if (info.intr_type == DDI_INTR_TYPE_MSI) {
			mdb_printf("MSI Number:\t%d\n", info.num);
		} else if (info.intr_type == DDI_INTR_TYPE_MSIX) {
			mdb_printf("MSI-X Number:\t%d\n", info.num);
		} else if (!info.intr_type) {
			mdb_printf("PCIe Message #:\t%d\n", info.num);
		}

		mdb_printf("Shared Intr:\t%s\n",
		    info.shared ? "yes" : "no");
		mdb_printf("State:\t\t%d (%s)\n", info.intr_state,
		    info.intr_state ? "Enabled" : "Disabled");
		mdb_printf("INO:\t\t0x%x\n", info.ino_ino);
		mdb_printf("Mondo:\t\t0x%x\n", info.mondo);
		mdb_printf("Pil:\t\t%d\n", info.pil);
		mdb_printf("CPU:\t\t%d\n", info.cpuid);
	}
}

/*ARGSUSED*/
static void
intr_walk_fini(mdb_walk_state_t *wsp)
{
	/* Nothing to do here */
}

/*ARGSUSED*/
static int
intr_intr(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
	detailed = 0;

	if (mdb_getopts(argc, argv, 'd', MDB_OPT_SETBITS, TRUE, &detailed,
	    NULL) != argc)
		return (DCMD_USAGE);

	if (!(flags & DCMD_ADDRSPEC)) {
		if (mdb_walk_dcmd("interrupts", "interrupts", argc, argv)
		    == -1) {
			mdb_warn("can't walk pci/px buffer entries\n");
			return (DCMD_ERR);
		}
		return (DCMD_OK);
	}

	return (DCMD_OK);
}

/*
 * MDB module linkage information:
 */

static const mdb_dcmd_t dcmds[] = {
	{ "interrupts", "[-d]", "display the interrupt info registered with "
	    "the PCI/PX nexus drivers", intr_intr },
	{ NULL }
};

static const mdb_walker_t walkers[] = {
	{ "interrupts", "walk PCI/PX interrupt structures",
		intr_walk_init, intr_walk_step, intr_walk_fini },
	{ NULL }
};

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

const mdb_modinfo_t *
_mdb_init(void)
{
	return (&modinfo);
}