
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/sched.h>
#include <sys/psinfo.h>
#include <sys/_devdrvr.h>
#include <sys/kernel.h>
#include <sys/ser_msg.h>
#include "struct.h"

#define PCMCIA

#ifdef PCMCIA
#include <sys/proxy.h>
#include <sys/pcmcia.h>

pcmcia_t pcmcia_handle;
pid_t pcmcia_proxy;
struct _pcmcia_io card_info;
#endif

/*
	declare the calls 'dev' will make into driver.
*/
int far	Kick(struct output_buffer far *obuf);
int far	Stty(struct termios far *tios); 
int far	Ctrl(union ioctl_entry far *ioc); 

#pragma aux (_dev_call) Kick;
#pragma aux (_dev_call) Stty;
#pragma aux (_dev_call) Ctrl;

/*
 * Receive message buffer
 */

union _async_msg {
	msg_t					type;
	msg_t					status;
	struct _ser_reset		reset;
	struct _ser_reset_reply	reset_reply;
	struct _ser_query		query;
	struct _ser_query_reply	query_reply;
	} msg;

/*
 * Request from user for an option to set the initial state of RTS/CTS
 * (for use with multi-drop lines)
 */

int num_asyncs = 0;
long divisor;			// baudrate divisor, assumes on clock for all chips

struct dev_entry dev_table[MAX_ASYNCS];
struct dev_entry *dev_list[MAX_IRQS];

struct device_ctrl ctrl[MAX_ASYNCS];
struct dev_mode dmode[MAX_ASYNCS];
struct driver_ctrl drv_ctrl;

unsigned isize, osize, csize, high, low, sflow, hflow, raw, mode;
int threshold = 0;
long baud;
int uses_pcmcia = 0;
pid_t pcmcia_proxy = -1;
char *prefix = "ser";

int dev_handle;

void terminate( int signal )
	{
	int index;

	switch(signal) {
	default:
	case SIGTERM:
		/*
		 * Shutdown by signal
		 */
		dev_drvr_shutdown( dev_handle );
		/*
		 * We should give back any GDT's, etc. that
		 * the system doesn't release for us
		 */
		break;

	case SIGPWR:
		for(index = 0; index < num_asyncs; ++index) {
			if(dev_table[index].mode & MODE_PWR) {
				shutdown_async(index);
				}
			}
		break;
		}
	exit(0);
	}

main(argc, argv)
int argc; char *argv[];
	{
	int tty, i;
	int index = 0;
	int arg;
	unsigned base, irq;
	unsigned slot;
	register char *p;
	char *p1;
	struct input_buffer far *ibuf;
	struct output_buffer far *obuf;
	struct termios far *tios;
	pid_t pid;
	msg_t t;
	int nbytes;
#ifdef PCMCIA
	struct dev_entry *dev;
#endif

	setscheduler(0, SCHED_RR, 20);

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

	signal( SIGTERM, &terminate );
	signal( SIGPWR, &terminate );

	init_tables();

	/*
	 * Setup Defaults
	 */

	base = 0;
	irq = 4;

	isize = 2048;
	osize = 2048;
	csize = 256;
	high = -1;
	low = -1;
	baud = 9600;
	raw = 1;
	hflow = 1;
	sflow = 0;
	mode = (MODE_DTR | MODE_RTS | MODE_SPLIT);
	divisor = 115200L; /* divisor for baud rate */

	/*
	 * Scan the arguments
	 */

	for(arg = 1; arg < argc; ++arg) {
		p = argv[arg];
		if(*p == 0)
			continue;
		if(*p == '-') {
			while(*++p) {
				switch(*p) {
				case 'b':
					baud =  atol(*(p+1) ? p+1 : argv[++arg]);
					break;
				case 'C':
					csize = atoi(*(p+1) ? p+1 : argv[++arg]);
					break;
				case 'd':
					mode |= MODE_DTR;
					continue;
				case 'D':
					mode &= ~MODE_DTR;
					continue;
				case 'e':
					raw = 0;
					continue;
				case 'E':
					raw = 1;
					continue;
				case 'f':
					hflow = 1;
					continue;
				case 'F':
					hflow = 0;
					continue;
				case 'h':
					high =  atoi(*(p+1) ? p+1 : argv[++arg]);
					break;
				case 'I':
					isize = atoi(*(p+1) ? p+1 : argv[++arg]);
					break;
				case 'm':
					mode |= MODE_NOHUP;
					continue;
				case 'M':
					mode &= ~MODE_NOHUP;
					continue;
				case 'N':
					prefix =     *(p+1) ? p+1 : argv[++arg];
					break;
				case 'O':
					osize = atoi(*(p+1) ? p+1 : argv[++arg]);
					break;
				case 'l':
					low =   atoi(*(p+1) ? p+1 : argv[++arg]);
					break;
				case 'p':
					mode |= MODE_PKT;
					continue;
				case 'P':
					mode &= ~MODE_PKT;
					continue;
				case 'r':
					mode |= MODE_RTS;
					continue;
				case 'R':
					mode &= ~MODE_RTS;
					continue;
				case 's':
					mode |= MODE_SPLIT;
					continue;
				case 'S':
					mode &= ~MODE_SPLIT;
					continue;
				case 't':
					threshold = atoi(*(p+1) ? p+1 : argv[++arg]);
					break;
				case 'w':
					mode |= MODE_PWR;
					continue;
				case 'W':
					mode &= ~MODE_PWR;
					continue;
				case 'x':
					sflow = 1;
					continue;
				case 'X':
					sflow = 0;
					continue;
				case 'y':
					divisor = atol(*(p+1) ? p+1 : argv[++arg]);
					break;

					} /* switch */

				} /* while */

			continue;
			} /* if */

		else {
    		/*
			 * Got a new ioport[,irq[,'p']] or 'p'n[,n[,...]]
			 */
			p1 = p;
			while(*p  &&  *p != ',') ++p;
			if(*p == ',')
				*p++ = 0;
			slot = 0;
			if(*p1 == 'p') {
				 ++p1;
				while(*p1) {
					i = atoi(p1);
					if(i >= 1  &&  i <= 16)
						slot |= 1 << (i - 1);
					p1 = p;
					while(*p  &&  *p != ',') ++p;
					if(*p == ',')
						*p++ = 0;
					}
				base = 0;
				irq = 0;
				}
			else {
				base = atoh(p1);
				irq = 4;
				if(*p) {
					p1 = p;
					while(*p  &&  *p != ',') ++p;
					if(*p == ',')
						*p++ = 0;
					irq = atoi(p1);
					if(*p == 'p')
						slot = -1;
					}
				}
			new_async(num_asyncs++, base, irq, slot);
			}

		} /* for */

	/*
	 * If no serial ports are given, then scan for all that exist
	 * with the last state of the global options
	 */
	if(num_asyncs == 0) {
		num_asyncs = find_asyncs();
		if(num_asyncs == 0)
			exit(0); /* terminate quietly if no serial ports */
		}

	if(num_asyncs <= 0  ||  num_asyncs >= MAX_ASYNCS) {
		exit(ERANGE);
		}

	/*
	 * Register ourselves with the DEVICE Manager
	 */

	if((dev_handle = dev_drvr_register("serial", prefix, num_asyncs,
					&Kick, &Stty, &Ctrl, &drv_ctrl, 0)) == -1) {
		exit(ENFILE);
		}

	tty = drv_ctrl.base_tty;

	/*
	 * Create each of the devices now
	 */

	for(index = 0; index < num_asyncs; ++index) {

		/*
		 * Get a tty number, and allocate input/output buffers
		 */

		if(dev_drvr_mount(dev_handle, tty, 1, &ctrl[index])) {
			dev_drvr_shutdown( dev_handle );
			exit(ENOMEM);
			}

		dev_table[index].tty = ctrl[index].tty;
		dev_table[index].obuf = ctrl[index].obuf;
	
		/*
		 * Setup TERMIOS defaults
		 */
		tios = ctrl[index].termios;
		tios->handle = index;
		if(dmode[index].raw) {
			tios->c_iflag		 = 0;
			tios->c_oflag		 = 0;
			tios->c_lflag		 = (IEXTEN);
			tios->c_cflag		 = (CREAD|CS8);
			tios->c_ispeed		 = dev_table[index].baud;
			tios->c_ospeed		 = dev_table[index].baud;
			tios->c_cc[VEOF]	 = 0x00;
			tios->c_cc[VEOL]	 = 0x00;
			tios->c_cc[VERASE]	 = 0x00;
			tios->c_cc[VINTR]	 = 0x00;
			tios->c_cc[VKILL]	 = 0x00;
			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;
			}
		else {
			tios->c_iflag		 = (BRKINT|IXON|ICRNL);
			tios->c_oflag		 = (OPOST);
			tios->c_lflag		 = (ISIG|ICANON|ECHO|ECHOK|ECHOE|IEXTEN);
			tios->c_cflag		 = (CREAD|CS8);
			tios->c_ispeed		 = dev_table[index].baud;
			tios->c_ospeed		 = dev_table[index].baud;
			tios->c_cc[VEOF]	 = 0x04;
			tios->c_cc[VEOL]	 = 0x00;
			tios->c_cc[VERASE]	 = 0x7F;
			tios->c_cc[VINTR]	 = 0x03;
			tios->c_cc[VKILL]	 = 0x15;
			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;
			}

		if(dmode[index].hflow) {
			tios->c_cflag |= (IHFLOW|OHFLOW);
			tios->c_qflag |= TC_PROTECT_HFLOW; /* Lock it on! */
			}

		if(dmode[index].sflow) {
			tios->c_iflag |= (IXON|IXOFF);
			tios->c_qflag |= TC_PROTECT_SFLOW; /* Lock it on! */
			}
	
		/*
		 * Setup Output port handle
		 */
		obuf = ctrl[index].obuf;
		obuf->handle = index;
		if(dmode[index].raw == 0)
			obuf->mode |= OUTPUT_XNL;
	
		/*
		 * Setup default control parameters
		 */
		ibuf = ctrl[index].ibuf;
		ibuf->handle = index;
		ibuf->sw_high_water = dmode[index].high;
		ibuf->sw_low_water = dmode[index].low;
		ibuf->hw_high_water = dmode[index].high+2;
		ibuf->hw_low_water = dmode[index].low-2;

		/*
		 * Initialize Device
		 */
		init(index, dev_table[index].iobase, dev_table[index].irq,
			dev_table[index].baud, dmode[index].mode);
	
		/*
		 * Start up the device
		 */
		if(dev_drvr_arm(tty)) {
			dev_drvr_shutdown( dev_handle );
			exit(ENODEV);
			}

		++tty;
		}

#ifdef PCMCIA		
	if(uses_pcmcia) {
		pcmcia_proxy = qnx_proxy_attach(0, 0, 0, -1);
		pcmcia_handle = qnx_pcmcia_io_attach(0);
		qnx_pcmcia_io_arm(pcmcia_handle, _PCMCIA_DEV_SERIAL, -pcmcia_proxy);
		/*
		 * Kick a proxy now so that main loop will wake up and
		 * scan for installed pcmcia cards right away
		 */
		Trigger(pcmcia_proxy); 
		}
#endif

	/*
	 * 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);

	qnx_pflags( _PPF_SERVER, _PPF_SERVER, 0, 0 );

	/*
	 * Wait for Control messages.
	 * The interrupt handlers will take care of ALL I/O
	 */

	for(;;) {
		if((pid = Receive(0, &msg, sizeof(msg))) == -1)
			continue;

#ifdef PCMCIA
		if(pid == pcmcia_proxy) {
			while(qnx_pcmcia_io_info(pcmcia_handle, _PCMCIA_DEV_SERIAL,
					&card_info) != -1) {

				slot = 1 << card_info.socket;
				base = card_info.base;
				irq = card_info.irq;
	
				if(card_info.flags & _PCMCIA_FLAG_CARD) {
	
					/*
					 * Scan for the device corresponding to this slot/port
					 */
					dev = &dev_table[0];
					for(index = 0; index < num_asyncs; ++index) {
						if((dev->slots & slot) != 0
								 && (dev->iobase == 0 || dev->iobase == base) )
							break;
						++dev;
						}
					if(index >= num_asyncs)
						continue;
	
					/*
					 * The card is now plugged in
					 * Reset the hardware registers to match the software state
					 * and link in to the appropriate interrupt chain.
					 */
					if(qnx_pcmcia_io_lock(pcmcia_handle, card_info.socket,
							card_info.index) != -1) {
						if(dev->slots != -1) {
							dev->iobase = base;
							dev->irq = irq;
							}
						dev->slot = card_info.socket + 1;
						reset_async( index, 1 );
						dev->mode |= MODE_AVAILABLE;
						/*
						 * Tell Dev that the output buffer has
						 * a size > 0 which will allow write() to succeed.
						 */
						ctrl[index].obuf->length = ctrl[index].osize;
						}
					}
				else {
					/*
					 * Scan for the device corresponding to this slot/port
					 */
					dev = &dev_table[0];
					for(index = 0; index < num_asyncs; ++index) {
						if((dev->slots & slot) != 0 && dev->iobase == base )
							break;
						++dev;
						}
					if(index >= num_asyncs)
						continue;
	
					/*
					 * A card is now unplugged
					 */
					if(dev->mode & MODE_AVAILABLE) {
						dev->mode &= ~MODE_AVAILABLE;

						disable_irq( index );

						if(dev->slots != -1) {
							dev->iobase = 0;
							dev->irq = 0;
							}
						dev->slot = 0;
						/*
						 * Temporarily tell Dev that the output buffer has
						 * a size of zero bytes, which means treat as NULL
						 * device on writes()
						 */
						ctrl[index].obuf->length = 0;

						qnx_pcmcia_io_unlock(pcmcia_handle, card_info.socket,
								card_info.index);
						}
					}
				continue;
				}
			}
#endif

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

		switch(t) {

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

		case _SER_RESET:
			index = msg.reset.unit;
			if(index < 0  ||  index >= num_asyncs) { 
				msg.status = EINVAL;
				}
			reset_async( index, 1 );
			memset( &msg, 0, sizeof(msg.reset_reply) );
			msg.reset_reply.status = EOK;
			break;

		case _SER_QUERY:
			index = msg.query.unit;
			memset( &msg, 0, sizeof(msg.query_reply) );
			if(index < 0  ||  index >= num_asyncs) { 
				msg.status = EINVAL;
				}
			else {
				struct dev_entry *dev;
				dev = &dev_table[index];
				msg.query_reply.status = EOK;
				msg.query_reply.iobase = dev->iobase;
				msg.query_reply.irq    = dev->irq;
				msg.query_reply.mode   = dev->mode;
				msg.query_reply.slots  = dev->slots;
				msg.query_reply.slot   = dev->slot;
// New in 4.23
				msg.query_reply.rx_count = dev->rx_count;
				msg.query_reply.tx_count = dev->tx_count;
				msg.query_reply.cur_state = (dev->modem_status & 0xF0)
											| (dev->modem_control & 0x03);
				}
			nbytes = sizeof(msg.query_reply);
			break;

		default:
			break;
			}

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

/*
 * Create a new device entry based on current values
 * in global variables
 */

new_async(index, iobase, irq, slots)
int index;
unsigned iobase, irq;
unsigned slots;
	{

	/*
	 * Limit values to reasonable range
	 */
	if(isize <  16)
		isize = 16;
	if(osize < 16)
		osize = 16;
	if(csize < 16)
		csize = 16;
	if(high == -1)
		high = isize / 4 * 3;
	if(high > (isize - 4))
		high = isize - 4;
	if(low == -1)
		low = isize / 4;
	if(low > (high - 4))
		low = high - 4;

	ctrl[index].isize = isize;
	ctrl[index].osize = osize;
	ctrl[index].csize = csize;

	dmode[index].raw = raw;
	dmode[index].hflow = hflow;
	dmode[index].sflow = sflow;
	dmode[index].mode = mode;
	dmode[index].high = high;
	dmode[index].low = low;

	dev_table[index].threshold = threshold;
	dev_table[index].iobase = iobase;
	dev_table[index].irq = irq;
	dev_table[index].baud = baud;
	dev_table[index].slots = slots;

	if(slots != 0)
		uses_pcmcia = 1;

	return(0);
	}
