/*
 * USC-ACSC UNIX Monitor: $Header:mouse.c 12.1$
 * Copyright University of Southern California, 1988
 */

#include <doscalls.h>
#include <dos.h>
#include <subcalls.h>
#include "pctype.h"
#include "rb.h"
#include "os2ioctl.h"
#include "dosfile.h"
#include "pcparam.h"
#include "os2data.h"
#include "thread.h"
#include "sem.h"

long mouse_sem;			/* semaphore for mouse thread */
u_int mouse_tid;		/* mouse thread id */


#define MSCMD_SWITCH  0x00
#define MSOPT_ENABLE  0x01
#define MSOPT_DISABLE 0x00

#define MSCMD_RESET   0x01

#define MSCMD_SETRATE 0x02
#define MSOPT_10RPS   0x00
#define MSOPT_20RPS   0x01
#define MSOPT_40RPS   0x02
#define MSOPT_60RPS   0x03
#define MSOPT_80RPS   0x04
#define MSOPT_100RPS  0x05
#define MSOPT_200RPS  0x06

#define MSCMD_SETRES  0x03
#define MSOPT_1CMP    0x00
#define MSOPT_2CMP    0x01
#define MSOPT_4CMP    0x02
#define MSOPT_8CMP    0x03

#define MSCMD_GETDEV  0x04

#define MSCMD_SETDPS  0x05
#define MSOPT_1BDPS   0x01
#define MSOPT_2BDPS   0x02
#define MSOPT_3BDPS   0x03
#define MSOPT_4BDPS   0x04
#define MSOPT_5BDPS   0x05
#define MSOPT_6BDPS   0x06
#define MSOPT_7BDPS   0x07
#define MSOPT_8BDPS   0x08

#define MSCMD_EXTEND  0x06
#define MSOPT_STATUS  0x00
#define MSOPT_1TO1    0x01
#define MSOPT_2TO1    0x02

#define MSCMD_FARCALL 0x07

static u_int	mh;		/* handle to mouse */
static u_int	mouse_open = 0; /* flag */
struct msqueue  msqueue = {0};
static u_short	msstatus, msx, msy;
static u_int	scaling = 2;	/* 2:1 */
static u_int	cur_res = 0x00;	/* 1 count/mm */
static u_int	cur_sr  = 0x02;	/* 40 reports/s */
static u_int	mouse_in_use = 0;

static void
msforward(mssrcp)
struct msdata  *mssrcp;

{
	register struct msdata *msdestp = &msdata;

	memcpy((char *) msdestp, (char *) mssrcp, sizeof(struct msdata));

#ifdef NOTHING
	printf("msforward: s %x t %x ex %x er %x r0 %x r1 %x r2 %x\n",
		(u_short)msdata.status,
		(u_short)msdata.type,
		(u_short)msdata.exit_stat,
		(u_short)msdata.error,
		(u_short)msdata.report[0],
		(u_short)msdata.report[1],
		(u_short)msdata.report[2]);
#endif
	rompint3(FAKEPOLL(MSIRQ));

}


static void
msdqueue(mssrcp)
	struct msdata  *mssrcp;
{
	register struct msdata *msdestp;
	register struct msqueue *msq = &msqueue;
	register int    i;

	i = msq->tail;
	msdestp = &msq->buff[i];;
	memcpy((char *) msdestp, (char *) mssrcp, sizeof(struct msdata));
	i = (i + 1) % MAXMSBUFF;
	if (i != msq->head)
		msq->tail = i;

}

/*
 *        This is the interface used
 *        by the Unix mouse driver to send commands to the
 *        PS/2 code. Commands are acknowledged to Unix by
 *        setting the msdata.type field to 1.
 *
 *   Paramaters:
 *       cmdresp - Indicates where called from:
 *                       0 - Called from interrupt level
 *                       1 - Called from mscmd()
 *
 *       rtncode - Only valid when cmdresp == 1
 *                 Contains BIOS return code for operation.
 *
 */

static int
mouseint(cmdresp, rtncode)
	u_short         cmdresp;
	u_short         rtncode;
{
	struct msdata   msbuff;
	register struct msdata *ms = &msbuff;
	register char  *msreport = ms->report;
	struct msqueue *msqp = &msqueue;
	int             i = 0;



	if (cmdresp)
	{
		ms->type = 1;
		ms->exit_stat = (rtncode >> 15);
		ms->error = LOBYTE(rtncode);
	} else
		ms->type = 0;

	*msreport++ = LOBYTE(msstatus);
	*msreport++ = LOBYTE(msx);
	*msreport = LOBYTE(msy);
	ms->status = 1;

	if (msdata.status)
	{
		msdqueue(ms);
		return;
	}
	if (msqp->head != msqp->tail)
	{
		msdqueue(ms);
		ENTER_CS();
		i = msqp->head;
		ms = &msqp->buff[i];
		i = (i + 1) % MAXMSBUFF;
		msqp->head = i;
		EXIT_CS();
	}
	msforward(ms);
}

/* This function is called to send on pending mouse events */

static void
forward_pending_ms_events()

{	int i = 0;
	struct msqueue *msqp = &msqueue;
	struct msdata   msbuff;
	register struct msdata *ms = &msbuff;

	if (msdata.status == 0 && (msqp->head != msqp->tail)) {
		ENTER_CS();
		i = msqp->head;
		ms = &msqp->buff[i];
		i = (i + 1) % MAXMSBUFF;
		msqp->head = i;
		EXIT_CS();
		msforward(ms);
	}
}

/* Function to close the mouse device. Would be used normally
 * only on shutdown */

int
close_mouse()

{
	MOUCLOSE(mh);
	mouse_open = 0;
}


int
mouse_init()

{
	int	rc;
	struct ScaleFact sf;

	u_int	status;

	if ( !mouse_open ) {

		/* mouse must be open BEFORE VIOSETMODE */

		if ( rc = MOUOPEN(0L,(unsigned far *)&mh) ){
			printf("mouse_init: MOUOPEN returned %d\n",rc);
			return -1;
		}
#ifndef POLL_MOUSE
		start_mouse_thread();
#endif /* POLL_MOUSE */
	}
	/* status must be set BEFORE VIOSETMODE */

	status =0x0300;	/* no draw/mickeys */
	if ( rc = MOUSETDEVSTATUS((unsigned far *)&status,mh) ){
		printf("mouse_init: MOUSETDEVSTATUS returned %d\n",rc);
		close_mouse();
		return -1;
	}
	/* set the scale factor to 1:2 */
	/* mouse moves too slow 
	sf.RowScale = 8;
	sf.ColScale = 16;
	 */
	 
	sf.RowScale = 1;
	sf.ColScale = 2;
	if ( rc = MOUSETSCALEFACT((struct ScaleFact far *)&sf,mh) ){
		printf("mouse_init: MOUSETSCALEFACT returned %d\n",rc);
		close_mouse();
		return -1;
	}
	set_current_vga_mode();
	mouse_open = 1;
	return 0;
}



/* This function makes a non blocking call to read mouse data.
 * It returns a negative error code if error, else 0 or > 0 for
 * data present or not present.
 */

static int
get_mouse_data(eip)
struct EventInfo *eip;		/* struct for mouse events */

{	int rc;
#ifdef POLL_MOUSE
	u_int	rtyp = 0;	/* non-blocking */
#else
	u_int	rtyp = 1;	/* blocking */
#endif /* POLL_MOUSE */
	
	if ( rc = MOUREADEVENTQUE((struct EventInfo far *)eip,
			(unsigned far *)&rtyp,mh) ) {
		printf("read event q returned %d\n",rc);
		return -rc;
	}
	return ( eip->Time ? 1 : 0 );
}

/* flush the mouse queue */

int
reset_msq()

{	int rval;
	register struct msqueue *msq = &msqueue;

	if ( mouse_in_use ) {
		if ( rval = MOUFLUSHQUE(mh) ) {
			printf("reset_msq: MOUFLUSHQUE %d\n",rval);
			return -1;
		}
	}
	msq->head = 0;
	msq->tail = 0;
	return 0;
}

/* this function converts os/2's button mask into something unix can
 * understand.
 */

static void
convert_data(eip)
struct EventInfo *eip;

{	u_char	status = 0x08;	/* bit 3 = 1 */
	u_int	mask = eip->Mask;
	int	x = eip->Col;
	int	y = eip->Row;

	if ( mask & 0x06 )	/* button one */ 
		status |= 0x01;	/* left button */
	if ( mask & 0x18 )	/* button two */
		status |= 0x02;	/* right button */
	if ( x < 0 ) {
		status |= 0x10;	/* neg x */
	}
	y = -y;			/* we go the wrong way! Hope no overflows! */
	if ( y < 0 ) {
		status |= 0x20;	/* neg y */
	}
	if ( (u_int) x > 0xff )
		status |= 0x40;	/* x overflow */

	if ( (u_int) y > 0xff )	/* y overflow */
		status |= 0x80;

	msstatus = status;
	msx = LOBYTE(x);	/* Assume no overflow when assigning a 2's */
	msy = LOBYTE(y);	/* complement int to a char		   */
}

int
send_mouse_events()

{	struct EventInfo ei;		/* struct for mouse events */
	int rc;

	if ( mouse_in_use ) {
		if ( rc = get_mouse_data(&ei) ) {
			if ( rc < 0 ) {
				/* error */
			}
			else {
				convert_data(&ei);
				mouseint(0,0);	/* send it off */
				return 1;
			}
		}
	}
	/* this will ensure that the mouse queue is flushed even
	 * after the mouse is disabled or an error has occured
	 */
	forward_pending_ms_events();
	return 0;
}

static u_char
fake_status()

{	u_char c = 0x40; /* stream mode */

	if ( mouse_in_use )
		c |= 0x20;
	if ( scaling == 2 )
		c |= 0x10;
	return c;
}

/* This function disables the mouse. This is for the vga driver which has
 * to disable the mouse in text mode.
 */

int
disable_mouse()

{
	mouse_in_use = 0;
}

static int
mousecmd(cmd, opt)
u_short         cmd;
u_short         opt;

{
	u_int             rval = 0;

#ifdef MOUSE_DEBUG
	printf("mousecmd: cmd = %x, opt = %x\n", cmd, opt);
#endif

	switch (cmd)
	{
	case MSCMD_SWITCH:
#ifdef MOUSE_DEBUG
		printf("ms_switch entered\n");
#endif
		if ( opt == MSOPT_ENABLE ) {
			mouse_in_use = 1;
			if ( reset_msq() < 0 ) {
				rval = 0x8003;
			}
			DOSSEMCLEAR(RAM_SEM(mouse_sem));
		}
		else if ( opt == MSOPT_DISABLE ) {
			mouse_in_use = 0;
		}
		else {
			printf("mscmd: MSCMD_SWITCH unknown option %d\n",opt);
			rval = 0x8003;
		}
		break;

	case MSCMD_RESET:
#ifdef MOUSE_DEBUG
		printf("ms_reset entered\n");
#endif
		mouse_in_use = 0;
		cur_sr = 0x00;
		cur_res = 0x02;
		scaling = 2;
		msstatus = 0;
		msx = 0;

		/* - disabled
		 * - sample rate 100
		 * - res 4
		 * - scaling 1:1
		 * - dps unchanged
		 */
		 break;

	case MSCMD_SETRATE:
#ifdef MOUSE_DEBUG
		printf("ms_setrate entered\n");
#endif
		cur_sr = opt;	/* Whatever! */
		break;

	case MSCMD_SETRES:
#ifdef MOUSE_DEBUG
		printf("ms_setres entered\n");
#endif
		cur_res = opt;	/* !! */
		break;

	case MSCMD_GETDEV:
#ifdef MOUSE_DEBUG
		printf("ms getdev entered\n");
#endif
		if ( mouse_open == 0 )
			rval = 0x8003;
		msstatus = 0;
		break;
	case MSCMD_SETDPS:
#ifdef MOUSE_DEBUG
		printf("ms_setdps entered \n");
#endif
		break;

	case MSCMD_EXTEND:
#ifdef MOUSE_DEBUG
		printf("ms_extend entered\n");
#endif
		if ( opt == MSOPT_STATUS ) {
			/* return status */
			msstatus = fake_status();
			msx = cur_res;
			msy = cur_sr;
		}
		else if ( opt == MSOPT_2TO1 ) {
			/* set two to 1 scaling */
		}
		else if ( opt == MSOPT_1TO1 ) {
			/* set linear scaling */
		}
		else 
			printf("mscmd: MSCMD_EXTEND unknown option %d\n",opt);
		break;

	case MSCMD_FARCALL:
#ifdef MOUSE_DEBUG
		printf("ms_farcall entered\n");
#endif
		break;

	default:
		printf("mscmd: unknown command (%d %d)\n",cmd,opt);
		break;
	}
#ifdef MOUSE_DEBUG
	printf("mscmd: rval = %x\n",rval);
#endif
	return (rval);
}

/* This function reads in the mouse parameters from Unix and executes the
 * commands.
 */

int
mouse_command(unixaddr)
u_long unixaddr;

{
	struct pcms_cmd mscmd;
	register struct pcms_cmd *mspl = &mscmd;
	char far       *pcaddr;
	int             rc;

	memset((char *)mspl, 0, sizeof(struct pcms_cmd));
	/*
	 * First read the pcms_cmd structure down from Unix 
	 */

	pcaddr = (char far *) mspl;

	rc = movein(unixaddr, pcaddr, sizeof(struct pcms_cmd));

	if (rc < 0)
	{
		printf("mouse_command: Can't read cmd structure from Unix\n");
		return (rc);
	}

	rc = mousecmd(mspl->cmd, mspl->param);

	mouseint(1, rc);

}

int
fake_ms_reply(cmd)

{
	msstatus = cmd;
	msx = 0;
	mouseint(1,cmd);
}

#ifndef POLL_MOUSE
/*
 * wait for mouse events; first we wait on the mouse semaphore 
 * for the mouse to be opened. Then we collect a mouse event and
 * send it to the romp.
 */
static void far
mouse_thread()
{
	SET_THREAD_PRIORITY(PRI_MOUSE_THREAD);
	for (;;) 
	{
		if (mouse_in_use)
			send_mouse_events();
		else
			DOSSEMSETWAIT(RAM_SEM(mouse_sem), (long) 10000);
	}
}

/*
 * start the mouse thread
 */
static int
start_mouse_thread()

{
	char	*stack;
	char	far *thstk;
	int	rc;
	char	*malloc();

	if ((stack = malloc(512)) == 0)
		fatal("No memory for mouse thread\n");
	stack += 512;
	thstk = (char far *)stack;
	if (rc = DOSCREATETHREAD(mouse_thread,(u_int far *)&mouse_tid,thstk)) {
		printf("Unable to create mouse thread: %d\n",rc);
		exit(0);
	}
}
#endif /* POLL_MOUSE */
