/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

#ident	"$Header: kp.c 1.13 89/08/07 $"

/* $Log:	kp.c,v $
 */

/*
 * kp.c - kernel profiler device driver
 *
 * In the autoconfig scheme of things, the kp device is integral to the SEC
 * device, and it is only necessary to define KERNEL_PROFILING in the system
 * configuration file.
 */

#include "sys/types.h"
#include "sys/devsw.h"
#include "sys/param.h"
#include "sys/debug.h"
#include "sys/vm.h"
#include "sys/mutex.h"
#include "sys/dir.h"
#include "sys/signal.h"
#include "sys/user.h"
#include "sys/conf.h"
#include "sys/buf.h"
#include "sys/uio.h"
#include "sys/systm.h"
#include "sys/ioctl.h"

#include "sys/cfg.h"
#include "sys/slic.h"

#include "sys/hwparam.h"
#include "sys/pte.h"
#include "sys/plocal.h"
#include "sys/intctl.h"
#include "sys/reg.h"
#include "sys/psl.h"
#include "sys/mftpr.h"
#include "sys/errno.h"
#include "sys/sysmacros.h"
#include "sys/cmn_err.h"

#include "sys/consmemioctl.h"
#include "sys/sec.h"
#include "sys/sec_modes.h"
#include "sys/kp.h"

/*
 * Global data structures.
 */
struct kp *kp;
extern unchar kp_chan;
struct sec_dev sec_kp = {
	/* flags, bin, req_size, doneq_size, sec_idx, target, unit, ... */
	      0,    4,        3,          3,      -1,     -1,   0,
};
ulong *kp_nmis;			/* ptr to # of NMI's recv'd per processor */
struct pc_mode *kp_pc_m;	/* ptr to pc/mode pairs */

static unsigned kp_buf[512];
static int kp_state;		/* local copy of profiler state */
static int kp_c_size;		/* Size of counters data area */
static ulong *kp_counters;	/* ptr to profiling counters */
static int kp_bins;		/* number of bins used in profiling */
static unsigned kp_b_text;	/* ptr to 1st text position: */

#ifdef DEBUG
static int kp_debug = 0;	/* kp debug flag */
#endif /* DEBUG */

extern int kp_binshift;

extern int kpprobe(),  kpboot();

extern unsigned Nengine;	/* # of engines -- used to allocate storage */


extern struct dev kpdev;	/* dev structure, only one */
extern struct devops kpdevops;	/* devops for the xt driver */
extern int kp_c_major;		/* major number of the xt device */
/*
 * kpprobe() - probe procedure 
 *	struct sec_dev *sec_cfg;
 *
 * The device is present.
 */
kpprobe(sec)
struct sec_desc *sec;
{
	struct sec_gmode *tio;

	/*
	 * Probe by attempting to read the state of the profiler
	 */
	tio = (struct sec_gmode *) kp_buf;
	tio->gm_status = 0;

	SEC_startio(SINST_GETMODE, (int *)tio, 4, kp_chan,
		&sec->sec_powerup->pu_cib,
		(unchar) sec->sec_slicaddr);

	if (tio->gm_status != SINST_INSDONE) {
		return(0);
	} else {
		return(1);
	}
}

/*
 * kpboot - initialize this device driver.
 *
 * Called once after all probing has been done.
 *
 * This procedure initializes and allocates all device driver data
 * structures based off of the configuration information passed in from
 * autoconfig() and from the device drivers binary configuration tables.
 */

/*ARGSUSED*/
kpboot(dev)
struct sec_dev *dev;
{
	extern etext;
#ifdef DEBUG
	if (kp_debug)
		CPRINTF("Kp_init: sec_dev structure at 0x%x\n", dev);
#endif /* DEBUG */

	kp = (struct kp *) calloc(sizeof(struct kp));

	/*
	 * initialize the kp structure
	 */
	kp->kp_stat = 0;

	/*
	 * Init semas ...
	 */
	init_sema(&kp->kp_sema, 1);

	/*
	 * Allocate space
	 */

	/*
	 * Now set up one dev, since there is only one minor device.
	 */
	kpdev.d_dev = makedev(kp_c_major, 0);
	kpdev.d_refcnt = 1;
	kpdev.d_flags = 0;
	kpdev.d_ops = &kpdevops;
	if (devsw_add(&kpdev) < 0) {
		CPRINTF("kp: problem adding dev 0x%x to devsw\n", kpdev.d_dev);
		return;
	}

	/* calc # of bins: + 2 is for truncation in shift and for user mode */
	kp_nmis = (ulong *) calloc((int)(Nengine * sizeof(ulong)));
	kp_b_text = 9 * sizeof (long);	  /* sizeof exec_data in kern_exec.c */
	kp_bins = (((unsigned) &etext - kp_b_text) >> kp_binshift) + 2;
	kp_pc_m = (struct pc_mode *) calloc((int)(Nengine * sizeof(struct pc_mode)));
	kp_c_size = sizeof(unsigned) * kp_bins;
	kp_counters = (ulong *) calloc(kp_c_size);
	kp->kp_desc = dev;
#ifdef DEBUG
	if (kp_debug)
		CPRINTF("pc_m: 0x%x counters: 0x%x b_text: 0x%x etext: 0x%x bins: %d\n",
		kp_pc_m, kp_counters, kp_b_text, &etext, kp_bins);
#endif /* DEBUG */
	CPRINTF("\tkp found  # allocated %d KB's\n",
	    (kp_c_size + 1023) / 1024);
}
/*
 * kpopen	Open the device
 */
kpopen(dp)
struct dev *dp;
{
	register unit;
	dev_t dev = dp->d_dev;

	unit = minor(dev);

	if ( !sec_kp.sd_alive )
		return(ENXIO);

	if ((1 << unit) & SECvec)
		return(0);

	return(ENXIO);
}

/*
 * kpread	Read procedure.
 */
/*ARGSUSED*/
kpread(dp, uio)
struct dev *dp;
struct uio *uio;
{
	return(uiomove((caddr_t)kp_counters, kp_c_size, UIO_READ, uio));
}



/*
 * kpioctl - asynchronous io control requests of the driver.
 *
 * tmp concerns - uses kp_sema to assure that no one else is doing
 * anything to the device.
 */
/*ARGSUSED*/
kpioctl(dp, cmd, addr)
struct dev *dp;
int	cmd;
caddr_t	addr;
{
	register struct kp_modes *mp;
	register struct kp_status *stat;
	struct kp_status data;
	struct sec_gmode *gmp;
	struct sec_smode *smp;
	int error;
	extern etext;

	error = 0;

	(void)p_sema(&kp->kp_sema, PZERO-1);

	switch (cmd) {

	case KP_GETSTATE:
		stat = &data;
		gmp = (struct sec_gmode *) kp_buf;
		gmp->gm_status = 0;
		mp = (struct kp_modes *) &gmp->gm_un.gm_kp;
		sec_startio(SINST_GETMODE, (int *)gmp, kp->kp_desc);
		if (gmp->gm_status != SINST_INSDONE) {
			error = EIO;
			break;
		}
		stat->kps_interval = mp->kpm_interval;
		stat->kps_reload = mp->kpm_reload;
		stat->kps_state = mp->kpm_state;
		stat->kps_binshift = mp->kpm_binshift;
		stat->kps_bins = mp->kpm_bins;
		stat->kps_engines = mp->kpm_engines;
		stat->kps_b_text = kp_b_text;
		stat->kps_e_text = (unsigned) &etext;
		stat->kps_sced_nmis = mp->kpm_sced_nmis;
		if (copyout((caddr_t)&data, (caddr_t)addr,
			    sizeof(struct kp_status)))
			return(EFAULT);
		break;

	case KP_SETSTATE:
		if (copyin((caddr_t)addr, (caddr_t)&data,
			   sizeof(struct kp_status)))
			return(EFAULT);
		stat = &data;
		if ((stat->kps_interval <= 0) || (stat->kps_reload < 0)) {
			error = EINVAL;
			break;
		}
		kp_state = stat->kps_state;
		smp = (struct sec_smode *) kp_buf;
		smp->sm_status = 0;
		mp = (struct kp_modes *) &smp->sm_un.sm_kp;

		mp->kpm_interval = stat->kps_interval;
		mp->kpm_reload = stat->kps_reload;
		mp->kpm_state = stat->kps_state;
		/* Take care of things the user can't */
		mp->kpm_b_text = kp_b_text;
		mp->kpm_binshift = kp_binshift;
		mp->kpm_bins = kp_bins;
		mp->kpm_engines = Nengine;
		mp->kpm_pc = kp_pc_m;
		mp->kpm_cntrs = kp_counters;
		sec_startio(SINST_SETMODE, (int *)smp, kp->kp_desc);
		if (smp->sm_status != SINST_INSDONE) {
			error = EIO;
			break;
		}
		break;

	case KP_RESET:
		/* zero the counters */
		bzero((caddr_t)kp_counters, (unsigned)kp_c_size);
		break;

	/*
	 * Unknown command.
	 */
	default:
		error = ENXIO;
		break;
	}

	v_sema(&kp->kp_sema);
	return(error);
}
