/*
 * Sample QNX4 Device Driver
 *
 * The purpose of this file is to provide a working QNX4
 * device driver which can be used as the basis for developing
 * "real" drivers. It is assumed that a "real" driver would be
 * interfacing to some "real" hardware somewhere, rather than
 * using stdin/stdout.
 *
 * One point worth mentioning is that "most" hardware drivers
 * will provide input into the Device subsystem directly from
 * the input interrupt handler (see Dev.ser source for an
 * example of this). This sample driver shows how to input
 * data at "process time".
 *
 * On the other hand, it is NOT uncommon for a "real" driver to
 * output data at process time as is the case in this example.
 * (for example Dev.con, Dev.win, Dev.par all output data at
 * process time).
 */


/*
 * This sample driver will read from stdin and write to stderr.
 * if 'DEBUGMODE' is defined. Comment out the next line to use your code
 */
#define DEBUGMODE

#include <sys/types.h>
#include <sys/sched.h>
#include <sys/kernel.h>
#include <sys/proxy.h>
#include <sys/psinfo.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>

#include <sys/_devdrvr.h>

#ifdef DEBUGMODE
#include <sys/dev.h>
unsigned old_mode;
#endif

extern int far Kick (struct output_buffer far *obuf);
extern int far Stty (struct termios far *tios); 
extern int far Ctrl (union ioctl_entry far *ioc); 

extern int sys_msg( void * );

union _private_msg {
	msg_t		type;
	msg_t		status;
	char		filer[100];
	} msg;

int idle = 1;
int more = 0;

pid_t out_proxy;
pid_t in_proxy;
pid_t proxy;

unsigned isize = 2048;
unsigned osize = 2048;
unsigned csize = 256;

#define MAX_DEVICES 10

struct driver_ctrl drv_ctrl;
struct device_ctrl ctrl[MAX_DEVICES];
int dev_handle;
int num_devices = 1;
unsigned driver_options = 0;  /* found in <sys/_devdrvr.h> */

char *prefix = "tst";

main(argc, argv)
int argc; char *argv[];
	{
	unsigned c;
	int i;
	pid_t pid;
	int t;
	int nbytes;
	extern void terminate();

	setscheduler(0, SCHED_FIFO, 15);

	/*
	 * Allow only SIGTERM, which we catch and cause
	 * graceful termination, all others are ignored
	 */
	for( i = _SIGMIN; i <= _SIGMAX; ++i)
		if( i != SIGTERM)
			signal( i, SIG_IGN );

	signal( SIGTERM, &terminate );

	/*
	 * Scan arguments
	 */
	while ((c=getopt(argc,argv,"C:I:O:N:n:")) != -1) {
		switch (c) {

		case 'C':
			csize = strtol(optarg,NULL,0);
			break;

		case 'I':
			isize = strtol(optarg,NULL,0);
			break;

		case 'O':
			osize = strtol(optarg,NULL,0);
			break;

		case 'N':
			prefix = optarg;
			break;

		case 'n':
			num_devices = strtol(optarg,NULL,0);
			if(num_devices > MAX_DEVICES)
				num_devices = MAX_DEVICES;
			break;

			}
		}

	/*
	 * Register ourselves with the DEVICE Manager
	 */

	if((dev_handle = dev_drvr_register("custom", prefix, num_devices,
					&Kick, &Stty, &Ctrl, &drv_ctrl, driver_options)) == -1) {
		exit(ENFILE);
		}

	/*
	 * Allocate the resources for this device
	 */

	for(i = 0; i < num_devices; ++i) {
		ctrl[i].isize = isize;
		ctrl[i].osize = osize;
		ctrl[i].csize = csize;
		}

	if(dev_drvr_mount(dev_handle, drv_ctrl.base_tty, num_devices, &ctrl[0])) {
		dev_drvr_shutdown( dev_handle );
		exit(ENOMEM);
		}

	out_proxy = qnx_proxy_attach(0, 0, 0, -1);
	in_proxy = qnx_proxy_attach(0, 0, 0, -1);

#ifndef DEBUGMODE
	/*
	 * Close off stdin,stdout,stderr since this process
	 * is usually started in sysinit as a background process,
	 * by the shell, which typically sets stdin to /dev/null
	 * (on the machine we booted from), which ties up resources
	 */
	close(0);
	close(1);
	close(2);
#endif

	/*
	 * Tell Proc that we will respond to "sin ver" messages
	 */
	qnx_pflags( _PPF_SERVER, _PPF_SERVER, 0, 0 );

	/*
	 * Initialize termios and queue parameters
	 * to whatever defaults make sense for this class of device
	 */
	
	for( i = 0; i < num_devices; ++i ) {
		/* struct input_buffer far *ibuf; */
		struct output_buffer far *obuf;
		struct termios far *tios;

		obuf = ctrl[i].obuf;
		obuf->mode |= OUTPUT_XNL;

		tios = ctrl[i].termios;

		tios->c_ispeed = tios->c_ospeed = 9600;

		tios->c_cflag = (CREAD);
		tios->c_oflag = (OPOST);
		tios->c_iflag = (ICRNL|IXON|BRKINT);
		tios->c_lflag = (ICANON|ISIG|ECHO|ECHOE|ECHOK|IEXTEN);

		tios->c_cc[VEOF]	= 0x04;
		tios->c_cc[VEOL]	= 0x00;
		tios->c_cc[VERASE]	= 0x7F;
		tios->c_cc[VINTR]	= 0x03;
		tios->c_cc[VKILL]	= 0x18;
		tios->c_cc[VQUIT]	= 0x00;
		tios->c_cc[VSUSP]	= 0x00;
		tios->c_cc[VSTART]	= 0x11;
		tios->c_cc[VSTOP]	= 0x13;

		tios->c_cc[VMIN]	= 0x01;
		tios->c_cc[VTIME]	= 0x00;
		}

	/*
	 * Initialize hardware
	 */

	init_driver();


	/*
	 * Let DEV call us now
	 */
	for(i = 0; i < num_devices; ++i) {
		if(dev_drvr_arm(ctrl[i].tty)) {
			dev_drvr_shutdown( dev_handle );
			exit(ENODEV);
			}
		}


	/*
	 * Main loop
	 */
	
	for(;;) {
	
		pid = Receive( 0, &msg, sizeof(msg) );

		if(pid == out_proxy) {
			idle = 1;
			/*
			 * Output data now available
			 */
			flush_output();
			continue;
			}

		if(pid == in_proxy) {
			/*
			 * Input data is available
			 * NOTE: that this would "normally" be taken care of
			 *       by an interrupt handler
			 */
			input_ready();
			continue;
			}

		/*
		 * Otherwise, process a message from a process
		 */

		t = msg.type;
		msg.status = ENOSYS;
		nbytes = sizeof(msg.status);

		switch(t) {

		case 0:
			nbytes = sys_msg(&msg);
			break;

		default:
			break;
			}

		if(nbytes)
			Reply(pid, &msg, nbytes);

		continue;		
		}
	}


void terminate( int signal )
	{
	if(signal) {
		/*
		 * Shutdown by signal
		 */
		dev_drvr_shutdown( dev_handle );
		/*
		 * We should give back any GDT's, etc. that
		 * the system doesn't release for us
		 */
		exit_driver();
		}
	exit(0);
	}


/*
 * Process all input data in all devices
 * Only return when all input is exhausted.
 * Rely on a subsequent Interrupt (which triggers 'in_proxy') to wake us up.
 */

int input_ready() {
	pid_t proxy;
	int i, n;
	char buf[100];

	proxy = 0;

	/*
	 * Scan all devices looking for available data
	 */
	for( i = 0; i < num_devices; ++i ) {
		while( (n = get_data(i, &buf[0], sizeof(buf))) > 0 ) {
			if(drv_ctrl.ttim_offset != 0) {
				/*
				 * Use multiple character input routine if supported by Dev
				 */
				pid_t (far *ttimcall) (int tty, int len, char far *data, unsigned ds);
				ttimcall = MK_FP( FP_SEG(drv_ctrl.tti), drv_ctrl.ttim_offset );
				proxy |= (*ttimcall) (ctrl[i].tty, n, &buf[0], drv_ctrl.tti_ds);
				}
			else {
				/*
				 * Must inject character at a time for older Dev's
				 */
				int j;
				for(j = 0; j < n; ++j)
					proxy |= (*drv_ctrl.tti) (buf[j], ctrl[i].tty, drv_ctrl.tti_ds);
				}
			}
		}

	/*
	 * "wake up" Dev if ANYTHING of interest happened
	 */
	if(proxy)
		Trigger(proxy);

	return(0);
	}

/*
 * This function will print forever, only returning
 * when all output buffers are drained
 * Rely on a subsequent Kick() call to wake us up again
 *
 * NOTE: that the memory semaphores 'idle' and 'more' are updated
 *       by the far-call stubs (ie: Kick) to make this work efficiently
 */

flush_output() {
	struct output_buffer far *obuf;
	char far *tail;
	char far *end;
	char far *begin;
	int i, n, size;
	int nothing, unit;
	int expand_newlines;

	for(;;) {
		/*
		 * Scan every output queue, looking for something to do
		 */
		nothing = 0;
		more = 0;
		for(unit = 0; unit < num_devices; ++unit) {

			obuf = ctrl[unit].obuf;

			/*
			 * Process flow control output functions first
			 */

			if(obuf->mode & OUTPUT_HFLOW_OFF) {
				obuf->mode &= ~OUTPUT_HFLOW_OFF;
				drop_hw(unit);
				}
	
			if(obuf->mode & OUTPUT_HFLOW_ON) {
				obuf->mode &= ~OUTPUT_HFLOW_ON;
				raise_hw(unit);
				}

			if(obuf->mode & OUTPUT_XOFF) {
				obuf->mode &= ~OUTPUT_XOFF;
				/*
				 * Disable input by sending XOFF
		   	 	 */
				put_data(unit, (char far *) "\023", 1, 0);
				}
	
			if(obuf->mode & OUTPUT_XON) {
				obuf->mode &= ~OUTPUT_XON;
				/*
				 * Re-enable input by sending XON
			 	 */
				put_data(unit, (char far *) "\021", 1, 0);
				}
	
			/*
			 * Skip if no data characters available
			 */

			_disable();
			if(obuf->size == 0  ||
					(obuf->mode & (OUTPUT_STOPPED|OUTPUT_STOPPED_HW)) != 0) {
				_enable();
				++nothing;
				continue;
				}
			_enable();
	
			if(obuf->discard) {
				/*
				 * Discard some (or all) data instead of displaying it
				 */
				n = obuf->discard;
				_disable();
				obuf->discard -= n;
				obuf->size -= n;
				_enable();
				i = &obuf->buffer[obuf->length] - obuf->tail;
				if(i > n)
					obuf->tail += n;
				else
					obuf->tail = &obuf->buffer[n-i];
				}

			/*
			 * Extract the data from the buffer
			 */

			tail = obuf->tail;
			end = &obuf->buffer[obuf->length];
			begin = &obuf->buffer[0];
			size = obuf->size;

			expand_newlines = obuf->mode & OUTPUT_XNL;

			/*
			 * Calculate number of chars before wrap-around point
			 */
			i = end - tail;
			if(i >= size) { /* No wrap around */
				n = put_data(unit, tail, size, expand_newlines);
				}
			else {
				n = put_data(unit, tail, i, expand_newlines);
				n += put_data(unit, begin, size - i, expand_newlines);
				}

			/*
			 * Update the Queue pointers, now that the
			 * 'n' bytes of data has been extracted
			 */

			_disable();

			if(i > n) /* No wrap around */
				obuf->tail = tail + n;
			else
				obuf->tail = begin + (n-i);
			obuf->size -= n;
			/*
			 * Compensate for any flushed data which
			 * was in the process of being transmitted
			 */
			if(obuf->discard > n)
				obuf->discard -= n;
			else
				obuf->discard = 0;

			_enable();
	
			/*
			 * If size of buffer drops below notification threshold,
			 * inform DEV of the new information
			 */
			_disable();
			if(obuf->size < obuf->bcount) {
				obuf->bcount = 0;
				obuf->action |= ACT_OUTPUT_READY;
				_enable();
				Trigger(drv_ctrl.hw_pid);
				}
			else
				_enable();
			}
	
		/*
		 */
		_disable();
		if(nothing >= num_devices  &&  more == 0) {
			idle = 1;
			_enable();
			return;
			}
		else
			_enable();
		}
	}



/*******************************************************************

 Hardware specific functions

 [add "useful" stuff here]

 ******************************************************************/


int init_driver()
	{
#ifdef DEBUGMODE
	old_mode = dev_mode( 0, 0, _DEV_MODES);
	dev_arm( 0, in_proxy, _DEV_EVENT_RXRDY );
#else
	/* Your stuff here */
#endif
	return(0);
	}



int exit_driver()
	{
#ifdef DEBUGMODE
	dev_mode( 0, old_mode, _DEV_MODES);
#else
	/* Your stuff here */
#endif
	return(0);
	}



get_data(int unit, char *buf, int nbytes)
	{
	/*
	 * Get (at most) 'nbytes' of data from 'unit' (into 'buf').
	 * Return #bytes read.
	 */
	int n = 0;

	unit = unit; /* keep compiler happy */

#ifdef DEBUGMODE
	n = dev_read(0, buf, nbytes, 0, 0, 0, 0, 0);
	dev_arm( 0, in_proxy, _DEV_EVENT_RXRDY );
#else
	/* Your stuff here */
#endif

	return(n);
	}



put_data(int unit, char far *buf, int nbytes, int expand_newlines)
	{
	/*
	 * Output 'nbytes' of data (in 'buf') to 'unit'.
	 * Return #bytes written (before expansion).
	 */
#ifdef DEBUGMODE
	int i;
	char c;

	unit = unit; /* keep compiler happy */

	for(i = 0; i < nbytes; ++i) {
		c = *buf++;
		if(expand_newlines  &&  c == '\n')
			write(1, "\r", 1);
		write(1, &c, 1);
		}
#else
	/* Your stuff here */
#endif
	return(nbytes);
	}



void drop_hw(int unit) {
	/*
	 * Disable input by dropping Hardware handshaking
 	 */
	unit = unit; /* keep compiler happy */
#ifndef DEBUGMODE
	/* Your stuff here */
#endif
	}



void raise_hw(int unit) {
	/*
	 * Re-enable input by raising Hardware handshaking
 	 */
	unit = unit; /* keep compiler happy */
#ifndef DEBUGMODE
	/* Your stuff here */
#endif
	}


#if 0

/************************************************************************
 * Sample interrupt handler                                             *
 ************************************************************************/

#include "../_devdrvr.h"
#include <sys/trace.h>
#include <sys/tracecod.h>

/*
 * Stack checking MUST be disabled in irq handlers
 */

#pragma off (check_stack);

pid_t async_int(int irq) {
	extern pid_t (far *tti)(int, int, unsigned); /* from dev_drvr_register */
	extern unsigned tti_ds;						 /* from dev_drvr_register */
	pid_t status = 0;
	unsigned char *data;
	int nbytes = 0;
	int irq_type = 0;
	unsigned short c;
	unsigned iobase = 0;

	switch(irq_type) {

	case 0:	/* Good data received */
		/*
		 * receive 'nbytes' of data ponted to by 'data'
		 */
		while(nbytes--) {
			c = (unsigned short) *data++;
			status |= (*tti) (c, dev->tty, tti_ds);
			}
		break;

	case 1: /* Bad data - parity error */
		status |= (*tti) (*data | RCV_PARITY, dev->tty, tti_ds);
		Trace1( (long)_TRACE_DEV_SER_PARITY, _TRACE_TRANSIENT, iobase );
		break;

	case 2: /* Bad data - framing error */
		status |= (*tti) (*data | RCV_FRAME, dev->tty, tti_ds);
		Trace1( (long)_TRACE_DEV_SER_FRAME, _TRACE_TRANSIENT, iobase );
		break;

	case 3: /* Bad data - overrun */
		status |= (*tti) (*data | RCV_OVERRUN, dev->tty, tti_ds);
		Trace1( (long)_TRACE_DEV_SER_OVERRUN, _TRACE_TRANSIENT, iobase );
		break;

	case 4: /* Break detected */
		status |= (*tti) (*data | RCV_BREAK, dev->tty, tti_ds);
		break;

	case 5: /* Raised Carrier */
		status |= (*tti) (RCV_CARRIER, dev->tty, tti_ds);
		Trace1( _TRACE_DEV_SER_CARRIER, _TRACE_EXPECTED, dev->iobase );
		break;

	case 6: /* Lost Carrier */
		status |= (*tti) (RCV_HNGUP, dev->tty, tti_ds);
		Trace1( _TRACE_DEV_SER_HUP, _TRACE_EXPECTED, dev->iobase );
		break;

	case 7: /* Resume Transmission (hardware handshaking) */
		status |= (*tti) (RCV_HW_ON, dev->tty, tti_ds);
		break;

	case 8: /* Stop Transmission (hardware handshaking) */
		status |= (*tti) (RCV_HW_OFF, dev->tty, tti_ds);
		break;
		}

	return(status);
	}

#pragma on (check_stack);

#endif
