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

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

#include <sys/types.h>
#include <sys/time.h>
#include <sys/sysmacros.h>
#include <ctype.h>
#include <sys/mdb_modapi.h>
#include <sys/cpuvar.h>
#include <sys/machcpuvar.h>
#include <sys/error.h>


/*ARGSUSED*/
int
resumable(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
	uint_t verbose = 0;
	cpu_t cpu;
	uintptr_t current, first;

	if (flags & DCMD_ADDRSPEC)
		return (DCMD_USAGE);

	if (mdb_getopts(argc, argv,
	    'v', MDB_OPT_SETBITS, 1, &verbose, NULL) != argc)
		return (DCMD_USAGE);

	if (mdb_readvar(&first, "cpu_list") == -1) {
		mdb_warn("failed to read 'cpu_list'");
		return (DCMD_ERR);
	}

	if (verbose)
		mdb_printf("CPUID ADDRESS\n");

	current = first;
	do {
		if (mdb_vread(&cpu, sizeof (cpu), current) == -1) {
			mdb_warn("failed to read cpu at %p", current);
			return (DCMD_ERR);
		}

		if (verbose) {
			if (cpu.cpu_m.cpu_rq_lastre == 0)
				mdb_printf("%-5d empty\n", cpu.cpu_id);
			else
				mdb_printf("%-5d %lx\n", cpu.cpu_id,
				    cpu.cpu_m.cpu_rq_lastre);
		} else if (cpu.cpu_m.cpu_rq_lastre != 0)
			mdb_printf("%lx\n", cpu.cpu_m.cpu_rq_lastre);
	} while ((current = (uintptr_t)cpu.cpu_next) != first);

	return (DCMD_OK);
}

/*ARGSUSED*/
int
nonresumable(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
	uint_t verbose = 0;
	cpu_t cpu;
	uintptr_t current, first;

	if (flags & DCMD_ADDRSPEC)
		return (DCMD_USAGE);

	if (mdb_getopts(argc, argv,
	    'v', MDB_OPT_SETBITS, 1, &verbose, NULL) != argc)
		return (DCMD_USAGE);

	if (mdb_readvar(&first, "cpu_list") == -1) {
		mdb_warn("failed to read 'cpu_list'");
		return (DCMD_ERR);
	}

	if (verbose)
		mdb_printf("CPUID ADDRESS\n");

	current = first;
	do {
		if (mdb_vread(&cpu, sizeof (cpu), current) == -1) {
			mdb_warn("failed to read cpu at %p", current);
			return (DCMD_ERR);
		}

		if (verbose) {
			if (cpu.cpu_m.cpu_nrq_lastnre == 0)
				mdb_printf("%-5d empty\n", cpu.cpu_id);
			else
				mdb_printf("%-5d %lx\n", cpu.cpu_id,
				    cpu.cpu_m.cpu_nrq_lastnre);
		} else if (cpu.cpu_m.cpu_nrq_lastnre != 0)
			mdb_printf("%lx\n", cpu.cpu_m.cpu_nrq_lastnre);
	} while ((current = (uintptr_t)cpu.cpu_next) != first);

	return (DCMD_OK);
}

/*ARGSUSED*/
int
rqueue(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
	uint_t verbose = 0;
	cpu_t cpu;
	uintptr_t ao, lower, upper, current;

	if (!(flags & DCMD_ADDRSPEC))
		return (DCMD_USAGE);

	if (mdb_getopts(argc, argv,
	    'v', MDB_OPT_SETBITS, 1, &verbose, NULL) != argc)
		return (DCMD_USAGE);

	if (mdb_vread(&cpu, sizeof (cpu_t), addr) == -1) {
		mdb_warn("failed to find cpu at %p", addr);
		return (DCMD_ERR);
	}

	ao = (uintptr_t)cpu.cpu_m.cpu_rq_lastre;	/* beginning and end */
	lower = (uintptr_t)cpu.cpu_m.cpu_rq_va + CPU_RQ_SIZE;
	upper = lower + CPU_RQ_SIZE - Q_ENTRY_SIZE;

	if (ao < lower || upper < ao) {
		if (verbose)
			mdb_printf("empty\n");
		return (DCMD_OK);
	}

	for (current = ao; current >= lower; current -= Q_ENTRY_SIZE)
		mdb_printf("%lx\n", current);

	for (current = upper; current > ao; current -= Q_ENTRY_SIZE)
		mdb_printf("%lx\n", current);

	return (DCMD_OK);
}

/*ARGSUSED*/
int
nrqueue(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
	uint_t verbose = 0;
	cpu_t cpu;
	uintptr_t lower, ao, upper;
	uintptr_t current;

	if (!(flags & DCMD_ADDRSPEC))
		return (DCMD_USAGE);

	if (mdb_getopts(argc, argv,
	    'v', MDB_OPT_SETBITS, 1, &verbose, NULL) != argc)
		return (DCMD_USAGE);

	if (mdb_vread(&cpu, sizeof (cpu_t), addr) == -1) {
		mdb_warn("failed to find cpu at %p", addr);
		return (DCMD_ERR);
	}

	ao = (uintptr_t)cpu.cpu_m.cpu_nrq_lastnre;	/* beginning and end */
	lower = (uintptr_t)cpu.cpu_m.cpu_nrq_va + CPU_NRQ_SIZE;
	upper = lower + CPU_NRQ_SIZE - Q_ENTRY_SIZE;

	if (ao < lower || upper < ao) {
		if (verbose)
			mdb_printf("empty\n");
		return (DCMD_OK);
	}

	for (current = ao; current >= lower; current -= Q_ENTRY_SIZE)
		mdb_printf("%lx\n", current);

	for (current = upper; current > ao; current -= Q_ENTRY_SIZE)
		mdb_printf("%lx\n", current);

	return (DCMD_OK);
}

/*ARGSUSED*/
int
errh_prtaddr(uintptr_t addr, const void *data, void *private)
{
	mdb_printf("%lx\n", addr);
	return (WALK_NEXT);
}

/*ARGSUSED*/
int
rq_walk_init(mdb_walk_state_t *wsp)
{
	cpu_t cpu;
	uintptr_t *ao, *lower, *upper;

	if (wsp->walk_addr == NULL) {
		mdb_warn("address of struct cpu_t is required\n");
		return (WALK_ERR);
	}

	if (mdb_vread(&cpu, sizeof (cpu_t), wsp->walk_addr) == -1) {
		mdb_warn("failed to find cpu at %p", wsp->walk_addr);
		return (WALK_ERR);
	}

	wsp->walk_callback = (mdb_walk_cb_t)errh_prtaddr;
	wsp->walk_addr = (uintptr_t)cpu.cpu_m.cpu_rq_lastre;
	wsp->walk_data = mdb_alloc(sizeof (uintptr_t) * 3, UM_SLEEP);

	ao = lower = upper = wsp->walk_data;
	lower += 1;
	upper += 2;

	*ao = (uintptr_t)wsp->walk_addr;	/* beginning and end */
	*lower = (uintptr_t)cpu.cpu_m.cpu_rq_va + CPU_RQ_SIZE;
	*upper = (uintptr_t)*lower + CPU_RQ_SIZE - Q_ENTRY_SIZE;

	if (wsp->walk_addr < *lower || *upper < wsp->walk_addr) {
		mdb_free(wsp->walk_data, sizeof (uintptr_t) * 3);
		return (WALK_DONE);
	}

	return (WALK_NEXT);
}

/*ARGSUSED*/
int
nrq_walk_init(mdb_walk_state_t *wsp)
{
	cpu_t cpu;
	uintptr_t *ao, *lower, *upper;

	if (wsp->walk_addr == NULL) {
		mdb_warn("address of struct cpu_t is required\n");
		return (WALK_ERR);
	}

	if (mdb_vread(&cpu, sizeof (cpu_t), wsp->walk_addr) == -1) {
		mdb_warn("failed to find cpu at %p", wsp->walk_addr);
		return (WALK_ERR);
	}

	wsp->walk_callback = (mdb_walk_cb_t)errh_prtaddr;
	wsp->walk_addr = (uintptr_t)cpu.cpu_m.cpu_nrq_lastnre;
	wsp->walk_data = mdb_alloc(sizeof (uintptr_t) * 3, UM_SLEEP);

	ao = lower = upper = wsp->walk_data;
	lower += 1;
	upper += 2;

	*ao = (uintptr_t)wsp->walk_addr;	/* beginning and end */
	*lower = (uintptr_t)cpu.cpu_m.cpu_nrq_va + CPU_NRQ_SIZE;
	*upper = (uintptr_t)*lower + CPU_NRQ_SIZE - Q_ENTRY_SIZE;

	if (wsp->walk_addr < *lower || *upper < wsp->walk_addr) {
		mdb_free(wsp->walk_data, sizeof (uintptr_t) * 3);
		return (WALK_DONE);
	}

	return (WALK_NEXT);
}

/*ARGSUSED*/
int
errh_walk_step(mdb_walk_state_t *wsp)
{
	int status;
	uintptr_t *ao, *lower, *upper;

	if (wsp->walk_addr == NULL)
		return (WALK_DONE);

	status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data,
	    wsp->walk_cbdata);

	wsp->walk_addr -= Q_ENTRY_SIZE;

	ao = lower = upper = wsp->walk_data;
	lower += 1;
	upper += 2;

	if (wsp->walk_addr < *lower)
		wsp->walk_addr = *upper;		/* wrap around */
	else if (wsp->walk_addr == *ao)
		return (WALK_DONE);			/* end of loop */

	return (status);
}

void
errh_walk_fini(mdb_walk_state_t *wsp)
{
	mdb_free(wsp->walk_data, sizeof (uintptr_t) * 3);
}

/*
 * MDB module linkage information:
 *
 * Declare a list of structures describing dcmds, and a function
 * named _mdb_init to return a pointer to module information.
 */

static const mdb_dcmd_t dcmds[] = {
	{ "errhre", "[-v]", "addr of sun4v resumable error element",
	    resumable },
	{ "errhnre", "[-v]", "addr of sun4v nonresumable error element",
	    nonresumable },
	{ "errhrq", ":", "addr of sun4v resumable errors in RQ", rqueue },
	{ "errhnrq", ":", "addr of sun4v nonresumable errors in NRQ", nrqueue },
	{ NULL }
};

static const mdb_walker_t walkers[] = {
	{ "errhrq", "walk a cpu-specific sun4v resumble error queue",
	    rq_walk_init, errh_walk_step, errh_walk_fini, NULL },
	{ "errhnrq", "walk a cpu-specific sun4v nonresumble error queue",
	    nrq_walk_init, errh_walk_step, errh_walk_fini, NULL },
	{ NULL }
};

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

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