/*
 * 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.
 */

/*
 * PCI nexus driver general debug support
 */
#include <sys/sysmacros.h>
#include <sys/async.h>
#include <sys/sunddi.h>		/* dev_info_t */
#include <sys/ddi_impldefs.h>
#include <sys/disp.h>
#include <sys/archsystm.h>	/* getpil() */
#include "px_obj.h"

/*LINTLIBRARY*/

#ifdef	DEBUG
uint64_t px_debug_flags = 0;

static char *px_debug_sym [] = {	/* same sequence as px_debug_bit */
	/*  0 */ "attach",
	/*  1 */ "detach",
	/*  2 */ "map",
	/*  3 */ "nex-ctlops",

	/*  4 */ "introps",
	/*  5 */ "intx-add",
	/*  6 */ "intx-rem",
	/*  7 */ "intx-intr",

	/*  8 */ "msiq",
	/*  9 */ "msiq-intr",
	/* 10 */ "msg",
	/* 11 */ "msg-intr",

	/* 12 */ "msix-add",
	/* 13 */ "msix-rem",
	/* 14 */ "msix-intr",
	/* 15 */ "err",

	/* 16 */ "dma-alloc",
	/* 17 */ "dma-free",
	/* 18 */ "dma-bind",
	/* 19 */ "dma-unbind",

	/* 20 */ "chk-dma-mode",
	/* 21 */ "bypass-dma",
	/* 22 */ "fast-dvma",
	/* 23 */ "init_child",

	/* 24 */ "dma-map",
	/* 25 */ "dma-win",
	/* 26 */ "map-win",
	/* 27 */ "unmap-win",

	/* 28 */ "dma-ctl",
	/* 29 */ "dma-sync",
	/* 30 */ NULL,
	/* 31 */ NULL,

	/* 32 */ "ib",
	/* 33 */ "cb",
	/* 34 */ "dmc",
	/* 35 */ "pec",

	/* 36 */ "ilu",
	/* 37 */ "tlu",
	/* 38 */ "lpu",
	/* 39 */ "mmu",

	/* 40 */ "open",
	/* 41 */ "close",
	/* 42 */ "ioctl",
	/* 43 */ "pwr",

	/* 44 */ "lib-cfg",
	/* 45 */ "lib-intr",
	/* 46 */ "lib-dma",
	/* 47 */ "lib-msiq",

	/* 48 */ "lib-msi",
	/* 49 */ "lib-msg",
	/* 50 */ "NULL",
	/* 51 */ "NULL",

	/* 52 */ "tools",
	/* 53 */ "phys_acc",

	/* 54 */ "hotplug",
	/* LAST */ "unknown"
};

/* Tunables */
static int px_dbg_msg_size = 16;		/* # of Qs.  Must be ^2 */

/* Non-Tunables */
static int px_dbg_qmask = 0xFFFF;		/* Mask based on Q size */
static px_dbg_msg_t *px_dbg_msgq = NULL;	/* Debug Msg Queue */
static uint8_t px_dbg_reference = 0;		/* Reference Counter */
static kmutex_t px_dbg_mutex;			/* Mutex for dequeuing */
static uint8_t px_dbg_qtail = 0;		/* Pointer to q tail */
static uint8_t px_dbg_qhead = 0;		/* Pointer to q head */
static uint_t px_dbg_qsize = 0;			/* # of pending messages */
static uint_t px_dbg_failed = 0;		/* # of overflows */

/* Forward Declarations */
static void px_dbg_print(px_debug_bit_t bit, dev_info_t *dip, char *fmt,
    va_list args);
static void px_dbg_queue(px_debug_bit_t bit, dev_info_t *dip, char *fmt,
    va_list args);
static uint_t px_dbg_drain(caddr_t arg1, caddr_t arg2);

/*
 * Print function called either directly by px_dbg or through soft interrupt.
 * This function cannot be called directly in threads with PIL above clock.
 */
static void
px_dbg_print(px_debug_bit_t bit, dev_info_t *dip, char *fmt, va_list args)
{
	int cont = bit >> DBG_BITS;

	if (cont)
		goto body;

	if (dip)
		prom_printf("%s(%d): %s: ", ddi_driver_name(dip),
		    ddi_get_instance(dip), px_debug_sym[bit]);
	else
		prom_printf("px: %s: ", px_debug_sym[bit]);
body:
	if (args)
		prom_vprintf(fmt, args);
	else
		prom_printf(fmt);
}

/*
 * Queueing mechanism to log px_dbg messages if calling thread is running with a
 * PIL above clock. It's Multithreaded safe.
 */
static void
px_dbg_queue(px_debug_bit_t bit, dev_info_t *dip, char *fmt, va_list args)
{
	int		instance = DIP_TO_INST(dip);
	px_t		*px_p = INST_TO_STATE(instance);
	uint8_t		q_no;
	px_dbg_msg_t	*msg_p;

	/* Check to make sure the queue hasn't overflowed */
	if (atomic_inc_uint_nv(&px_dbg_qsize) >= px_dbg_msg_size) {
		px_dbg_failed++;
		atomic_dec_uint(&px_dbg_qsize);
		return;
	}

	/*
	 * Grab the next available queue bucket. Incrementing the tail here
	 * doesn't need to be protected, as it is guaranteed to not overflow.
	 */
	q_no = ++px_dbg_qtail & px_dbg_qmask;
	msg_p = &px_dbg_msgq[q_no];

	ASSERT(msg_p->active == B_FALSE);

	/* Print the message in the buffer */
	vsnprintf(msg_p->msg, DBG_MSG_SIZE, fmt, args);
	msg_p->bit = bit;
	msg_p->dip = dip;
	msg_p->active = B_TRUE;

	/* Trigger Soft Int */
	ddi_intr_trigger_softint(px_p->px_dbg_hdl, (caddr_t)NULL);
}

/*
 * Callback function for queuing px_dbg in high PIL by soft intr.  This code
 * assumes it will be called serially for every msg.
 */
static uint_t
px_dbg_drain(caddr_t arg1, caddr_t arg2) {
	uint8_t		q_no;
	px_dbg_msg_t	*msg_p;
	uint_t		ret = DDI_INTR_UNCLAIMED;

	mutex_enter(&px_dbg_mutex);
	while (px_dbg_qsize) {
		atomic_dec_uint(&px_dbg_qsize);
		if (px_dbg_failed) {
			cmn_err(CE_WARN, "%d msg(s) were lost",
			    px_dbg_failed);
			px_dbg_failed = 0;
		}

		q_no = ++px_dbg_qhead & px_dbg_qmask;
		msg_p = &px_dbg_msgq[q_no];

		if (msg_p->active) {
			px_dbg_print(msg_p->bit, msg_p->dip, msg_p->msg, NULL);
			msg_p->active = B_FALSE;
		}
		ret = DDI_INTR_CLAIMED;
	}

	mutex_exit(&px_dbg_mutex);
	return (ret);
}

void
px_dbg(px_debug_bit_t bit, dev_info_t *dip, char *fmt, ...)
{
	va_list ap;

	bit &= DBG_MASK;
	if (bit >= sizeof (px_debug_sym) / sizeof (char *))
		return;
	if (!(1ull << bit & px_debug_flags))
		return;

	va_start(ap, fmt);
	if (getpil() > LOCK_LEVEL)
		px_dbg_queue(bit, dip, fmt, ap);
	else
		px_dbg_print(bit, dip, fmt, ap);
	va_end(ap);
}
#endif	/* DEBUG */

void
px_dbg_attach(dev_info_t *dip, ddi_softint_handle_t *dbg_hdl)
{
#ifdef	DEBUG
	if (px_dbg_reference++ == 0) {
		int size = px_dbg_msg_size;

		/* Check if px_dbg_msg_size is ^2 */
		/*
		 * WARNING: The bellow statement makes no sense.  If size is
		 * not a power of 2, it will set size to zero.
		 */
		size = !ISP2(size) ? ((size | ~size) + 1) : size;
		px_dbg_msg_size = size;
		px_dbg_qmask = size - 1;
		px_dbg_msgq = kmem_zalloc(sizeof (px_dbg_msg_t) * size,
		    KM_SLEEP);

		mutex_init(&px_dbg_mutex, NULL, MUTEX_DRIVER, NULL);
	}

	if (ddi_intr_add_softint(dip, dbg_hdl,
		DDI_INTR_SOFTPRI_MAX, px_dbg_drain, NULL) != DDI_SUCCESS) {
		DBG(DBG_ATTACH, dip,
		    "Unable to allocate soft int for DBG printing.\n");
		dbg_hdl = NULL;
	}
#endif	/* DEBUG */
}

/* ARGSUSED */
void
px_dbg_detach(dev_info_t *dip, ddi_softint_handle_t *dbg_hdl)
{
#ifdef	DEBUG
	if (dbg_hdl != NULL)
		(void) ddi_intr_remove_softint(*dbg_hdl);

	if (--px_dbg_reference == 0) {
		if (px_dbg_msgq != NULL)
			kmem_free(px_dbg_msgq,
			    sizeof (px_dbg_msg_t) * px_dbg_msg_size);
		mutex_destroy(&px_dbg_mutex);
	}
#endif	/* DEBUG */
}