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

/* UNIX Monitor VGA Terminal Driver.
 */

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

#ifdef PROFILING
#include "profile.h"
#endif

/* This is the shared data structure with unix */
extern struct vga_params *vga;

/* local variables */
static int	v_screen_mode;	/* text or graphics */
static	short	hot_key_mode = 0;	/* hot key usage indicator */
static struct CursorData saved_cursor;	/* saved cursor */
static cursor_saved = 0;		/* flag */
u_int vio_tid;
extern not_started;
extern sleep_tid;

/*
 * parameters to set the modes to
 */
struct vga_mode_param {
	u_char	type;
	u_char	color;
	u_int	col;
	u_int	row;
	u_int	hres;
	u_int	vres;
	u_long	screen_addr;
};

static struct vga_mode_param	vga_mode_param[] = {
	/* type  color  col  row   hres  vres  screeen_addr */
	{ 0x05,    4,   40,  25,   320,  200,  0xb8000L }, /* UNIX 001 (0) */
	{ 0x05,    4,   40,  25,   320,  350,  0xb8000L }, /* UNIX 002 (1) */
	{ 0x05,    4,   40,  25,   360,  400,  0xb8000L }, /* UNIX 003 (2) */
	{ 0x01,    4,   40,  25,   320,  200,  0xb8000L }, /* UNIX 011 (3) */
	{ 0x01,    4,   40,  25,   320,  350,  0xb8000L }, /* UNIX 012 (4) */
	{ 0x01,    4,   40,  25,   360,  400,  0xb8000L }, /* UNIX 013 (5) */
	{ 0x05,    4,   80,  25,   640,  200,  0xb8000L }, /* UNIX 021 (6) */
	{ 0x05,    4,   80,  25,   640,  350,  0xb8000L }, /* UNIX 022 (7) */
	{ 0x05,    4,   80,  25,   720,  400,  0xb8000L }, /* UNIX 023 (8) */
	{ 0x01,    4,   80,  25,   640,  200,  0xb8000L }, /* UNIX 033 (9) */
	{ 0x01,    4,   80,  25,   640,  350,  0xb8000L }, /* UNIX 032 (10) */
	{ 0x01,    4,   80,  25,   720,  400,  0xb8000L }, /* UNIX 039 (11) */
	{ 0x03,    2,   40,  25,   320,  200,  0xb8000L }, /* UNIX 041 (12) */
	{ 0x07,    2,   40,  25,   320,  200,  0xb8000L }, /* UNIX 051 (13) */
	{ 0x03,    1,   80,  25,   640,  200,  0xb8000L }, /* UNIX 061 (14) */
	{ 0x00,    0,   80,  25,   720,  350,  0xb0000L }, /* UNIX 072 (15) */
	{ 0x00,    0,   80,  25,   720,  400,  0xb0000L }, /* UNIX 073 (16) */
	{ 0x03,    4,   40,  25,   320,  200,  0xa0000L }, /* UNIX 0d1 (17) */
	{ 0x03,    4,   40,  25,   640,  200,  0xa0000L }, /* UNIX 0e1 (18) */
	{ 0x02,    0,   80,  25,   640,  200,  0xa0000L }, /* UNIX 0f2 (19) */
	{ 0x03,    4,   80,  25,   640,  350,  0xa0000L }, /* UNIX 102 (20) */
	{ 0x03,    1,   80,  25,   640,  480,  0xa0000L }, /* UNIX 114 (21) */
	{ 0x03,    4,   80,  25,   640,  480,  0xa0000L }, /* UNIX 124 (22) */
	{ 0x03,    8,   40,  25,   320,  200,  0xa0000L }, /* UNIX 132 (23) */
};

#define VGA_SCREEN_ADDR	((unsigned long)0xb8000)
#define SAVE_REDRAW	0
#define	POS_0		0x100
#define	POS_1		0x101
#define	POS_2		0x102
#define ADAPTER_SETUP	0x8
#define	SLOT_1		0x0
#define	SLOT_2		0x1
#define	SLOT_3		0x2
#define	SLOT_4		0x3
#define	SLOT_5		0x4
#define	SLOT_6		0x5
#define	SLOT_7		0x6
#define	SLOT_8		0x7
#define CARD_ENABLE	0x1
#define IBM8514_SLOT	SLOT_6
#define IBM8514_A	0xEF7F	/* ibm 8514 adapater id number */

static int
lock_screen()

{	int	rc;
	char	status;

	if ( rc = VIOSCRLOCK(0,(char far *)&status,0) ){
		printf("VIOSCRLOCK returned %d\n",rc);
		exit(1);
	}
	return (int) status;
}

#define unlock_screen()	VIOSCRUNLOCK(0)

/* function to save the state of the cursor on startup so that it can be
 * restored later.
 */

static void
save_cursor()

{
	int	rc;

	if ( cursor_saved ) return;	/* only once */
	if ( rc = VIOGETCURTYPE((struct CursorData far *)&saved_cursor,0) ){
		printf("save_cursor: VIOGETCURTYPE: %d\n",rc);
	}
	cursor_saved = 1;
	return;
}

/* this function returns the saved cursor */

int
restore_saved_cursor()

{	int rc;
	if ( !cursor_saved ) return -1;
	if ( rc = VIOSETCURTYPE((struct CursorData far *)&saved_cursor, 0) ){
		printf("restore_saved_cursor: VIOSETCURTYPE: %d\n",rc);
	}
	return 0;
}

unsigned	vga_row,vga_column;
/*
 * get the cursor location
 */
get_cur_pos()

{
	int	rc;

	if ( rc = VIOGETCURPOS(&vga_row,&vga_column,0) ){
		printf("save_cursor: VIOGETCURPOS: %d\n",rc);
	}
	return;
}

/*
 * set the cursor location
 */
set_cur_pos()

{
	int	rc;

	if ( rc = VIOSETCURPOS(vga_row,vga_column,0) ){
		printf("save_cursor: VIOGETCURPOS: %d\n",rc);
	}
	return;
}

static int
set_vga_mode(parms,reset_8514)
	register struct vga_mode_param *parms;
	int reset_8514;
{	int rc;
	struct ModeData vga_mode;	/* struct for setting mode */
	int	id_1,id_2;
	int	ibm8514_default;

	if (vga->write_status == exchw(VGA_WRITE_OK)) {
		vga_mode.length = 12; /* was sizeof(vga_mode) for MS C */
		vga_mode.type = parms->type;
		vga_mode.color = parms->color;
		vga_mode.col = parms->col;
		vga_mode.row = parms->row;
		vga_mode.hres = parms->hres;
		vga_mode.vres = parms->vres;

		if (!reset_8514) {
			/* disable 8514 */
			cli();
			outp(0x94,0xff); /* enable adapter POS setup */
			outp(0x96,(ADAPTER_SETUP|IBM8514_SLOT));
			id_1 = inp(POS_0);
			id_2 = inp(POS_1);
			if (((id_2 << 8) | (id_1 & 0xff)) == IBM8514_A) {
				ibm8514_default = inp(POS_2);
				/* disable the 8514 */
				outp(POS_2,ibm8514_default & ~CARD_ENABLE);
			}
			outp(0x96,0);		/* reset the POS setupt */
			sti();
		}
		rc = VIOSETMODE((struct ModeData far *)&vga_mode,0);
		if (!reset_8514) {
			/* enable 8514 */
			cli();
			outp(0x94,0xff);	/* enable adapter POS setup */
			outp(0x96,ADAPTER_SETUP|IBM8514_SLOT);
			if (((id_2 << 8) | (id_1 & 0xff)) == IBM8514_A) {
				/* enable the 8514 */
				outp(POS_2,ibm8514_default);
			}
			outp(0x96,0);		/* reset the POS setup */
			sti();
		}
		return rc;
	} else {
		return(0);
	}
}


static void
set_screen(mode,ibm8514_reset)
u_short	mode;
int	ibm8514_reset;

{
	int	rc;

	if (mode > VGA_MAX_MODES) {
		return;
	}

	if ( rc = set_vga_mode(&vga_mode_param[mode],ibm8514_reset) ){
		printf("set_screen_mode: Mode %d - error %d\n", mode,rc);
	}
	/*vga->u.vga_screen_addr = exchl(vga_mode_param[mode].screen_addr);*/
		vga->u.vga_screen_addr = exchl(VGA_SCREEN_ADDR);
	v_screen_mode = mode;
}

/* This is for shutdown time only */

int
restore_screen_for_exit()

{
	set_screen(MODE_039,1);
	VIOSETCURPOS(24,0,0);
	restore_saved_cursor();
}

/* This function restores the current vga state */

int
set_current_vga_mode()

{
	if (v_screen_mode != -1) {
		set_screen(v_screen_mode,0);
	}
}


/* save processing 
 * This is also called from the keyoard routine that handles the
 * abort key sequence.
 */

int
vga_save()

{	u_long count;
#ifdef PROFILING
	u_long vs_last_time;

	pt_vstsv_cnt++;
	vs_last_time = elapsed_time();
#endif

	/* set the write status (for use when hot key mode not used) */

	vga->write_status = exchw(VGA_WRITE_DISABLED);

	if ( !hot_key_mode )
		goto save_exit;

	count = 0xfffff;

	/* wait for opcode to clear */
	while (vga->pc_opcode && --count)
		;
	if ( count == 0 )
		printf("VGA_SAVE: opcode not clear\n");
	/* set the opcode */
	vga->pc_opcode = exchw(VGA_SAVE);
	/* wait for pc_opcode to clear before unlocking */
	count = 200;	/* try a lot of times */
	while ( --count ) {
		if (send_hot_key(VGA_SAVE,1)) { /* tell unix */
		/*
		 * the interrupt level is all jammed up, we need to
		 * give up so we don't hang the system.
		 */
			count = 0;
			break;
		}

		DOSSLEEP((long)100);
		if ( vga->pc_opcode == 0 )
			break;
	}
	if ( count <= 0 )
		printf("vga: screen save time out\n");

save_exit:
	;
	set_cur_pos();

#ifdef PROFILING
	pt_vstsv_time += elapsed_time() - vs_last_time;
#endif
}

/*
 * The restore stuff.
 * This is also called from the keyoard routine that handles the
 * abort key sequence.
 */
 
int
vga_restore()

{	u_long count;
#ifdef PROFILING
	u_long vr_last_time;

	pt_vstrdrw_cnt++;
	vr_last_time = elapsed_time();
#endif
	get_cur_pos();

	/*
	 * if we are waiting to start the romp, do it now
	 */
	if (not_started) {
		not_started = 0;
		DOSRESUMETHREAD(sleep_tid);
	}

	/* set the write status (for use when hot key mode not used) */
	vga->write_status = exchw(VGA_WRITE_OK);

	/* first restore our mode */
	set_current_vga_mode();

	if ( !hot_key_mode )  {
		send_hot_key(VGA_RESTORE,0);	/* tell unix */
		goto restore_exit;
	}

	count = 0xfffff;

	while (vga->pc_opcode && --count)
		;
	if ( count == 0 )
		printf("VGA_RESTORE: opcode not clear\n");

	vga->pc_opcode = exchw(VGA_RESTORE);

	count = 200;	/* try a lot of times */
	while ( --count ) {
		if (send_hot_key(VGA_RESTORE,1)) { /* tell unix */
		/*
		 * the interrupt level is all jammed up, we need to
		 * give up so we don't hang the system.
		 */
			count = 0;
			break;
		}

		DOSSLEEP((long)100);

		if ( vga->pc_opcode == 0 )
			break;
	}
	if ( count <= 0 )
		printf("vga: screen restore time out\n");

restore_exit:
	;

#ifdef PROFILING
	pt_vstrdrw_time += elapsed_time() - vr_last_time;
#endif
}

/* this thread informs unix when to save and restore.
 * Note only the sending of the hot key is done within a critical section.
 * This is because saving the screen (done in unix) may cause paging => disk
 * io to be handled.
 */

static void far
vio_sr_thread(void)

{
	u_int	i;
	int	rc;
#ifdef PROFILING
	u_long	vst_last_time;
#endif
        SET_THREAD_PRIORITY(PRI_VGA_THREAD);
	while(1) {
		if ( rc = VIOSAVREDRAWWAIT(SAVE_REDRAW,(u_int far *)&i,0) ){
			printf("VIOSAVREDRAWWAIT: %d\n", rc);
			continue;
		}
#ifdef PROFILING
		pt_vst_cnt++;
		vst_last_time = elapsed_time();
#endif
		lock_screen();
		if ( i ) {
			/* we have to restore */
			vga_restore();
		}
		else {
			/* save signalled. */
			vga_save();
		}
		unlock_screen();
		vga->pc_opcode = 0;	/* clear it */
#ifdef PROFILING
		pt_vst_time += elapsed_time() - vst_last_time;
#endif
	}
}

/* initialization routine for the screen.
 * It spawns off the thread to catch the viosaveredrawwait, and then
 * gets hold of the physical screen buffer and initializes globals.
 */

int
screen_init()

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

	vga = (struct vga_params *) align(vga, 4, int);
	save_cursor();
	get_cur_pos();

	if ( (stack = malloc(256)) == 0 ){
		printf("vga: unable to get stack\n");
		restore_saved_cursor();
		return -1;
	}
	stack += 255;	/* want the bottom */

	v_screen_mode = -1;
	if ( lock_screen() )  {
		/* we're in the background */
		vga->u.vga_screen_addr = exchl(VGA_SCREEN_ADDR);
		vga->write_status = exchw(VGA_WRITE_DISABLED);
	} else {
		/* we're in the foreground */
		unlock_screen();	/* don't leave it locked */
		vga->u.vga_screen_addr = exchl(VGA_SCREEN_ADDR);
		vga->write_status = exchw(VGA_WRITE_OK);
	}

	vga->pc_opcode = 0;
	vga->unix_opcode = 0;
	hot_key_mode = 0;	/* disable hot key sending */

	/* spawn the thread */
	if ( rc = DOSCREATETHREAD(vio_sr_thread,(unsigned far *) &vio_tid,stack) ){
		printf("screen_init: unable to create thread: %d\n",rc);
		restore_saved_cursor();
		return -1;
	}
	DOSSLEEP(500L);	/* wait for the thread */
	return 1;
}

/* function to execute parse and execute vga command */

int
process_vga_command()

{
	short	opcode;
#ifdef PROFILING
	u_long pvc_last_time;

	pt_viocmd_cnt++;
	pvc_last_time = elapsed_time();
#endif

	/*
	 * Change byte order of unix opcode.
	 */
	opcode = exchw(vga->unix_opcode);

	switch ( opcode ) {

	case	VGA_HKEY_REQ:
		hot_key_mode = 1;
		break;

	case	VGA_SET_MODE:
		set_screen(exchw(vga->mode),0);
		break;

	default:
		printf("vga: unsupported op code = %d\n",opcode);
		break;
	}

	vga->unix_opcode = 0;	/* reset */

#ifdef PROFILING
	pt_viocmd_time += elapsed_time() - pvc_last_time;
#endif
	return 1;
}

clear_screen()
{
	printf("\33[H\33[2J");
}
