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


/*
 * VUIDMICE module:  put mouse events into vuid format
 */

#include <sys/param.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/strsun.h>
#include <sys/errno.h>
#include <sys/debug.h>
#include <sys/cmn_err.h>
#include <sys/sad.h>
#include <sys/vuid_event.h>
#include "vuidmice.h"
#include <sys/vuid_wheel.h>
#include <sys/msio.h>

#include <sys/conf.h>
#include <sys/modctl.h>

#include <sys/kmem.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>

static int vuidmice_open(queue_t *const, const dev_t *const,
	const int, const int, const cred_t *const);
static int vuidmice_close(queue_t *const, const int, const cred_t *const);
static int vuidmice_rput(queue_t *const, mblk_t *);
static int vuidmice_rsrv(queue_t *const);
static int vuidmice_wput(queue_t *const, mblk_t *);
static void vuidmice_miocdata(queue_t *const, mblk_t *);
static int vuidmice_handle_wheel_resolution_ioctl(queue_t *const,
	mblk_t *, int);

static int vuidmice_service_wheel_info(mblk_t *);
static int vuidmice_service_wheel_state(queue_t *, mblk_t *, uint_t);

void VUID_QUEUE(queue_t *const, mblk_t *);
int VUID_OPEN(queue_t *const);
void VUID_CLOSE(queue_t *const);

static kmutex_t vuidmice_lock;

static struct module_info vuidmice_iinfo = {
	0,
	VUID_NAME,
	0,
	INFPSZ,
	1000,
	100
};

static struct qinit vuidmice_rinit = {
	vuidmice_rput,
	vuidmice_rsrv,
	vuidmice_open,
	vuidmice_close,
	NULL,
	&vuidmice_iinfo,
	NULL
};

static struct module_info vuidmice_oinfo = {
	0,
	VUID_NAME,
	0,
	INFPSZ,
	1000,
	100
};

static struct qinit vuidmice_winit = {
	vuidmice_wput,
	NULL,
	NULL,
	NULL,
	NULL,
	&vuidmice_oinfo,
	NULL
};

struct streamtab vuidmice_info = {
	&vuidmice_rinit,
	&vuidmice_winit,
	NULL,
	NULL
};

/*
 * This is the loadable module wrapper.
 */

/*
 * D_MTQPAIR effectively makes the module single threaded.
 * There can be only one thread active in the module at any time.
 * It may be a read or write thread.
 */
#define	VUIDMICE_CONF_FLAG	(D_MP | D_MTQPAIR)

static struct fmodsw fsw = {
	VUID_NAME,
	&vuidmice_info,
	VUIDMICE_CONF_FLAG
};

static struct modlstrmod modlstrmod = {
	&mod_strmodops,
	"mouse events to vuid events",
	&fsw
};

/*
 * Module linkage information for the kernel.
 */
static struct modlinkage modlinkage = {
	MODREV_1,
	&modlstrmod,
	NULL
};

static int module_open = 0;	/* allow only one open of this module */

int
_init(void)
{
	register int rc;

	mutex_init(&vuidmice_lock, NULL, MUTEX_DEFAULT, NULL);
	if ((rc = mod_install(&modlinkage)) != 0) {
		mutex_destroy(&vuidmice_lock);
	}
	return (rc);
}

int
_fini(void)
{
	register int rc;

	if ((rc = mod_remove(&modlinkage)) == 0)
		mutex_destroy(&vuidmice_lock);
	return (rc);
}

int
_info(struct modinfo *modinfop)
{
	return (mod_info(&modlinkage, modinfop));
}


/* ARGSUSED1 */
static int
vuidmice_open(queue_t *const qp, const dev_t *const devp,
	const int oflag, const int sflag, const cred_t *const crp)
{
	if (qp->q_ptr != NULL)
		return (0);	 /* reopen */

	mutex_enter(&vuidmice_lock);

	/* Allow only 1 open of this module */
	if (module_open) {
		mutex_exit(&vuidmice_lock);
		return (EBUSY);
	}

	module_open++;
	mutex_exit(&vuidmice_lock);

	/*
	 * Both the read and write queues share the same state structures.
	 */
	qp->q_ptr = kmem_zalloc(sizeof (struct MouseStateInfo), KM_SLEEP);
	WR(qp)->q_ptr = qp->q_ptr;

	/* initialize state */
	STATEP->format = VUID_NATIVE;

	qprocson(qp);

#ifdef	VUID_OPEN
	if (VUID_OPEN(qp) != 0) {
		qprocsoff(qp);

		mutex_enter(&vuidmice_lock);
		module_open--;
		mutex_exit(&vuidmice_lock);
		kmem_free(qp->q_ptr, sizeof (struct MouseStateInfo));
		qp->q_ptr = NULL;
		return (ENXIO);
	}
#endif

	return (0);
}

/* ARGSUSED1 */
static int
vuidmice_close(queue_t *const qp, const int flag, const cred_t *const crp)
{
	ASSERT(qp != NULL);

	qprocsoff(qp);
	flushq(qp, FLUSHALL);
	flushq(OTHERQ(qp), FLUSHALL);

#ifdef	VUID_CLOSE
	VUID_CLOSE(qp);
#endif
	mutex_enter(&vuidmice_lock);
	module_open--;
	mutex_exit(&vuidmice_lock);
	kmem_free(qp->q_ptr, sizeof (struct MouseStateInfo));
	qp->q_ptr = NULL;

	return (0);
}

/*
 * Put procedure for input from driver end of stream (read queue).
 */
static int
vuidmice_rput(queue_t *const qp, mblk_t *mp)
{
	ASSERT(qp != NULL);
	ASSERT(mp != NULL);

	/*
	 * Handle all the related high priority messages here, hence
	 * should spend the least amount of time here.
	 */

	if (DB_TYPE(mp) == M_DATA) {
		if ((int)STATEP->format ==  VUID_FIRM_EVENT)
			return (putq(qp, mp));   /* queue message & return */
	} else if (DB_TYPE(mp) == M_FLUSH) {
			if (*mp->b_rptr & FLUSHR)
				flushq(qp, FLUSHALL);
	}

	putnext(qp, mp);	/* pass it on */
	return (0);
}

static int
vuidmice_rsrv(queue_t *const qp)
{
	register mblk_t *mp;

	ASSERT(qp != NULL);

	while ((mp = getq(qp)) != NULL) {
		ASSERT(DB_TYPE(mp) == M_DATA);

		if (!canputnext(qp))
			return (putbq(qp, mp)); /* read side is blocked */

		switch (DB_TYPE(mp)) {
		case M_DATA:
			if ((int)STATEP->format == VUID_FIRM_EVENT)
				(void) VUID_QUEUE(qp, mp);
			else
				(void) putnext(qp, mp);
			break;

		default:
			cmn_err(CE_WARN,
			    "vuidmice_rsrv: bad message type (0x%x)\n",
			    DB_TYPE(mp));

			(void) putnext(qp, mp);
			break;
		}
	}
	return (0);
}

/*
 * Put procedure for write from user end of stream (write queue).
 */
static int
vuidmice_wput(queue_t *const qp, mblk_t *mp)
{
	int	error = 0;

	ASSERT(qp != NULL);
	ASSERT(mp != NULL);

	/*
	 * Handle all the related high priority messages here, hence
	 * should spend the least amount of time here.
	 */
	switch (DB_TYPE(mp)) {	/* handle hi pri messages here */
	case M_FLUSH:
		if (*mp->b_rptr & FLUSHW)
			flushq(qp, FLUSHALL);
		putnext(qp, mp);			/* pass it on */
		return (0);

	case M_IOCTL: {
		struct iocblk *iocbp = (void *)mp->b_rptr;

		switch (iocbp->ioc_cmd) {
		case VUIDSFORMAT:

			/*
			 * VUIDSFORMAT is known to the stream head and thus
			 * is guaranteed to be an I_STR ioctl.
			 */
			if (iocbp->ioc_count == TRANSPARENT) {
				miocnak(qp, mp, 0, EINVAL);
				return (0);
			} else {
				int format_type;

				error = miocpullup(mp, sizeof (int));
				if (error != 0) {
					miocnak(qp, mp, 0, error);
					return (0);
				}

				format_type =
				    *(int *)(void *)mp->b_cont->b_rptr;
				STATEP->format = (uchar_t)format_type;
				iocbp->ioc_rval = 0;
				iocbp->ioc_count = 0;
				iocbp->ioc_error = 0;
				mp->b_datap->db_type = M_IOCACK;
			}

			/* return buffer to pool ASAP */
			if (mp->b_cont) {
				freemsg(mp->b_cont);
				mp->b_cont = NULL;
			}

			qreply(qp, mp);
			return (0);

		case VUIDGFORMAT:

			/* return buffer to pool ASAP */
			if (mp->b_cont) {
				freemsg(mp->b_cont); /* over written below */
				mp->b_cont = NULL;
			}

			/*
			 * VUIDGFORMAT is known to the stream head and thus
			 * is guaranteed to be an I_STR ioctl.
			 */
			if (iocbp->ioc_count == TRANSPARENT) {
				miocnak(qp, mp, 0, EINVAL);
				return (0);
			}

			mp->b_cont = allocb(sizeof (int), BPRI_MED);
			if (mp->b_cont == NULL) {
				miocnak(qp, mp, 0, EAGAIN);
				return (0);
			}

			*(int *)(void *)mp->b_cont->b_rptr =
			    (int)STATEP->format;
			mp->b_cont->b_wptr += sizeof (int);

			iocbp->ioc_count = sizeof (int);
			mp->b_datap->db_type = M_IOCACK;
			qreply(qp, mp);
			return (0);

		case VUID_NATIVE:
		case VUIDSADDR:
		case VUIDGADDR:
			miocnak(qp, mp, 0, ENOTTY);
			return (0);

		case MSIOBUTTONS:
			/* return buffer to pool ASAP */
			if (mp->b_cont) {
				freemsg(mp->b_cont); /* over written below */
				mp->b_cont = NULL;
			}

			/*
			 * MSIOBUTTONS is known to streamio.c and this
			 * is assume to be non-I_STR & non-TRANSPARENT ioctl
			 */

			if (iocbp->ioc_count == TRANSPARENT) {
				miocnak(qp, mp, 0, EINVAL);
				return (0);
			}

			if (STATEP->nbuttons == 0) {
				miocnak(qp, mp, 0, EINVAL);
				return (0);
			}

			mp->b_cont = allocb(sizeof (int), BPRI_MED);
			if (mp->b_cont == NULL) {
				miocnak(qp, mp, 0, EAGAIN);
				return (0);
			}

			*(int *)(void *)mp->b_cont->b_rptr =
			    (int)STATEP->nbuttons;
			mp->b_cont->b_wptr += sizeof (int);

			iocbp->ioc_count = sizeof (int);
			mp->b_datap->db_type = M_IOCACK;
			qreply(qp, mp);
			return (0);

		/*
		 * New IOCTL support. Since it's explicitly mentioned
		 * that you can't add more ioctls to stream head's
		 * hard coded list, we have to do the transparent
		 * ioctl processing which is not very exciting.
		 */
		case VUIDGWHEELCOUNT:
		case VUIDGWHEELINFO:
		case VUIDGWHEELSTATE:
		case VUIDSWHEELSTATE:
		case MSIOSRESOLUTION:
			error = vuidmice_handle_wheel_resolution_ioctl(qp,
			    mp, iocbp->ioc_cmd);
			if (!error) {
				return (0);
			} else {
				miocnak(qp, mp, 0, error);
				return (0);
			}
		default:
			putnext(qp, mp);	/* nothing to process here */

			return (0);
		}

	} /* End of case M_IOCTL */

	case M_IOCDATA:
		vuidmice_miocdata(qp, mp);

		return (0);
	default:
		putnext(qp, mp);		/* pass it on */
		return (0);
	}
	/*NOTREACHED*/
}

void
VUID_PUTNEXT(queue_t *const qp, uchar_t event_id, uchar_t event_pair_type,
	uchar_t event_pair, int event_value)
{
	int strikes = 1;
	mblk_t *bp;
	Firm_event *fep;

	/*
	 * Give this event 3 chances to allocate blocks,
	 * otherwise discard this mouse event.  3 Strikes and you're out.
	 */
	while ((bp = allocb((int)sizeof (Firm_event), BPRI_HI)) == NULL) {
		if (++strikes > 3)
			return;
		drv_usecwait(10);
	}

	fep = (void *)bp->b_wptr;
	fep->id = vuid_id_addr(VKEY_FIRST) | vuid_id_offset(event_id);

	fep->pair_type	= event_pair_type;
	fep->pair	= event_pair;
	fep->value	= event_value;
	uniqtime32(&fep->time);
	bp->b_wptr += sizeof (Firm_event);

	if (canput(qp->q_next))
		putnext(qp, bp);
	else
		(void) putbq(qp, bp); /* read side is blocked */
}


/*
 * vuidmice_miocdata
 *	M_IOCDATA processing for IOCTL's: VUIDGWHEELCOUNT, VUIDGWHEELINFO,
 *	VUIDGWHEELSTATE, VUIDSWHEELSTATE & MSIOSRESOLUTION.
 */
static void
vuidmice_miocdata(queue_t *qp, mblk_t  *mp)
{
	struct copyresp		*copyresp;
	struct iocblk		*iocbp;
	mblk_t			*ioctmp;
	mblk_t			*datap;
	Mouse_iocstate_t	*Mouseioc;
	size_t			size;
	int			err = 0;


	copyresp = (void *)mp->b_rptr;
	iocbp = (void *)mp->b_rptr;

	if (copyresp->cp_rval) {
		err = EAGAIN;

		goto err;
	}
	switch (copyresp->cp_cmd) {
	case VUIDGWHEELCOUNT:
		mp->b_datap->db_type = M_IOCACK;
		mp->b_wptr = mp->b_rptr + sizeof (struct iocblk);
		iocbp->ioc_error = 0;
		iocbp->ioc_count = 0;
		iocbp->ioc_rval = 0;
		if (mp->b_cont != NULL) {
			freemsg(mp->b_cont);
			mp->b_cont = NULL;
		}

		break;
	case VUIDGWHEELINFO:
	case VUIDGWHEELSTATE:
		ioctmp = copyresp->cp_private;
		Mouseioc = (void *)ioctmp->b_rptr;
		if (Mouseioc->ioc_state == GETSTRUCT) {
			if (mp->b_cont == NULL) {
				err = EINVAL;

				break;
			}
			datap = mp->b_cont;
			if (copyresp->cp_cmd == VUIDGWHEELSTATE) {
				err = vuidmice_service_wheel_state(qp, datap,
				    VUIDGWHEELSTATE);
			} else {
				err = vuidmice_service_wheel_info(datap);
			}
			if (err) {
				break;
			}

			if (copyresp->cp_cmd == VUIDGWHEELSTATE) {
				size = sizeof (wheel_state);
			} else {
				size = sizeof (wheel_info);
			}

			Mouseioc->ioc_state = GETRESULT;
			ASSERT(Mouseioc->u_addr != NULL);
			mcopyout(mp, ioctmp, size, Mouseioc->u_addr, NULL);
		} else if (Mouseioc->ioc_state == GETRESULT) {
			freemsg(ioctmp);
			mp->b_datap->db_type = M_IOCACK;
			mp->b_wptr = mp->b_rptr + sizeof (struct iocblk);
			iocbp->ioc_error = 0;
			iocbp->ioc_count = 0;
			iocbp->ioc_rval = 0;
			if (mp->b_cont != NULL) {
				freemsg(mp->b_cont);
				mp->b_cont = NULL;
			}
		}

		break;
	case VUIDSWHEELSTATE:
	case MSIOSRESOLUTION:
		ioctmp = copyresp->cp_private;
		Mouseioc = (void *)ioctmp->b_rptr;
		if (mp->b_cont == NULL) {
			err = EINVAL;

			break;
		}
		datap = mp->b_cont;

		if (copyresp->cp_cmd == VUIDSWHEELSTATE) {
			err = vuidmice_service_wheel_state(qp,
			    datap, VUIDSWHEELSTATE);
		}

		if (err) {
			break;
		}

		if (mp->b_cont) {
			freemsg(mp->b_cont);
			mp->b_cont = NULL;
		}
		freemsg(ioctmp);
		iocbp->ioc_count = 0;
		iocbp->ioc_error = 0;
		iocbp->ioc_rval = 0;
		mp->b_datap->db_type = M_IOCACK;

		break;
	default:
		err = EINVAL;

		break;
	}

err:
	if (err) {
		mp->b_datap->db_type = M_IOCNAK;
		if (mp->b_cont) {
			freemsg(mp->b_cont);
			mp->b_cont = NULL;
		}
		if (copyresp->cp_private) {
			freemsg(copyresp->cp_private);
			copyresp->cp_private = NULL;
		}
		iocbp->ioc_count = 0;
		iocbp->ioc_error = err;
	}
	qreply(qp, mp);
}


/*
 * vuidmice_handle_wheel_resolution_ioctl
 *	Handle wheel mouse and MSIOSRESOLUTION ioctls.
 *
 * Here we also support non-transparent way of these ioctls
 * just like usb mouse driver does, so the consms module is
 * very simple to deal with these ioctls.
 */
static int
vuidmice_handle_wheel_resolution_ioctl(queue_t *qp, mblk_t *mp, int cmd)
{
	int			err = 0;
	Mouse_iocstate_t	*Mouseioc;
	caddr_t			useraddr;
	size_t			size;
	mblk_t			*ioctmp;
	mblk_t			*datap;

	struct iocblk *iocbp = (void *)mp->b_rptr;

	if (iocbp->ioc_count == TRANSPARENT) {
		if (mp->b_cont == NULL)
			return (EINVAL);
		useraddr = *((caddr_t *)(void *)mp->b_cont->b_rptr);
		switch (cmd) {
		case VUIDGWHEELCOUNT:
			size = sizeof (int);
			if ((datap = allocb(sizeof (int), BPRI_HI)) == NULL)
				return (EAGAIN);
			*((int *)(void *)datap->b_wptr) =
			    STATEP->vuid_mouse_mode;
			mcopyout(mp, NULL, size, NULL, datap);
			qreply(qp, mp);

			return (err);
		case VUIDGWHEELINFO:
			size = sizeof (wheel_info);
			break;

		case VUIDSWHEELSTATE:
		case VUIDGWHEELSTATE:
			size = sizeof (wheel_state);
			break;

		case MSIOSRESOLUTION:
			size = sizeof (Ms_screen_resolution);
			break;
		}

		if ((ioctmp = allocb(sizeof (Mouse_iocstate_t),
		    BPRI_MED)) == NULL)
			return (EAGAIN);
		Mouseioc = (void *)ioctmp->b_rptr;
		Mouseioc->ioc_state = GETSTRUCT;
		Mouseioc->u_addr = useraddr;
		ioctmp->b_wptr = ioctmp->b_rptr + sizeof (Mouse_iocstate_t);
		mcopyin(mp, ioctmp, size, NULL);
		qreply(qp, mp);

		return (err);
	} else {
		switch (cmd) {
		case VUIDGWHEELCOUNT:
			if (mp->b_cont) {
				freemsg(mp->b_cont);
				mp->b_cont = NULL;
			}
			if ((datap = allocb(sizeof (int), BPRI_HI)) == NULL) {
				err = EAGAIN;
				break;
			}
			*((int *)(void *)datap->b_wptr) =
			    STATEP->vuid_mouse_mode;
			datap->b_wptr +=  sizeof (int);
			mp->b_cont = datap;
			break;

		case VUIDGWHEELINFO:
			if (mp->b_cont == NULL ||
			    iocbp->ioc_count != sizeof (wheel_info)) {
				err = EINVAL;
				break;
			}
			datap = mp->b_cont;
			err = vuidmice_service_wheel_info(datap);
			break;

		case VUIDSWHEELSTATE:
		case VUIDGWHEELSTATE:
			if (mp->b_cont == NULL ||
			    iocbp->ioc_count != sizeof (wheel_state)) {
				err = EINVAL;
				break;
			}
			datap = mp->b_cont;
			err = vuidmice_service_wheel_state(qp, datap, cmd);
			break;

		case MSIOSRESOLUTION:
			/*
			 * Now we just make Xserver and
			 * the virtual mouse happy. Of course,
			 * the screen resolution value may
			 * be used later for absolute PS/2 mouse.
			 */
			err = 0;
			break;
		}

		if (!err) {
			mp->b_datap->db_type = M_IOCACK;
			iocbp->ioc_rval = 0;
			iocbp->ioc_error = 0;
			qreply(qp, mp);
		}

		return (err);
	}
}

static int
vuidmice_service_wheel_info(register mblk_t *datap)
{
	wheel_info		*wi;
	int			err = 0;

	wi = (void *)datap->b_rptr;
	if (wi->vers != VUID_WHEEL_INFO_VERS) {
		err = EINVAL;
		return (err);
	}

	if (wi->id > (VUIDMICE_NUM_WHEELS - 1)) {
		err = EINVAL;
		return (err);
	}
	wi->format = (wi->id == VUIDMICE_VERTICAL_WHEEL_ID) ?
	    VUID_WHEEL_FORMAT_VERTICAL : VUID_WHEEL_FORMAT_HORIZONTAL;

	return (err);
}


static int
vuidmice_service_wheel_state(register queue_t	*qp,
			    register mblk_t	*datap,
			    register uint_t	cmd)
{
	wheel_state	*ws;
	uint_t		err = 0;

	ws = (void *)datap->b_rptr;
	if (ws->vers != VUID_WHEEL_STATE_VERS) {
		err = EINVAL;
		return (err);
	}

	if (ws->id > (VUIDMICE_NUM_WHEELS - 1)) {
		err = EINVAL;
		return (err);
	}

	switch (cmd) {
	case	VUIDGWHEELSTATE:
		ws->stateflags =
		    (STATEP->wheel_state_bf >> ws->id) & 1;

		break;
	case	VUIDSWHEELSTATE:
		STATEP->wheel_state_bf = (ws->stateflags << ws->id) |
		    (STATEP->wheel_state_bf & ~(1 << ws->id));

		break;
	default:
		err = EINVAL;

		return (err);
	}

	return (err);
}