Coherent4.2.10/io.386/hs.c

Compare this file to the similar file:
Show the results in this format:

/* $Header: /ker/io.386/RCS/hs.c,v 2.6 93/10/29 00:58:50 nigel Exp Locker: nigel $ */
/* (-lgl
 * 	COHERENT Driver Kit Version 1.1.0
 * 	Copyright (c) 1982, 1990 by Mark Williams Company.
 * 	All rights reserved. May not be copied without permission.
 -lgl) */
/*
 * Polled Serial Port Device Driver.
 * - supports version 7 compatible ioctl
 * $Log:	hs.c,v $
 * Revision 2.6  93/10/29  00:58:50  nigel
 * R98 (aka 4.2 Beta) prior to removing System Global memory
 * 
 * Revision 2.5  93/09/13  08:06:34  nigel
 * Changed to reflect the fact that entry points are 'void' once more
 * 
 * Revision 2.4  93/08/19  10:39:07  nigel
 * r83 ioctl (), corefile, new headers
 * 
 * Revision 2.3  93/08/19  04:02:48  nigel
 * Nigel's R83
 */

#include <sys/coherent.h>
#include <sys/ins8250.h>
#include <sys/stat.h>
#include <sys/proc.h>
#include <sys/tty.h>		/* indirectly includes sgtty.h */
#include <sys/con.h>
#include <sys/devices.h>
#include <sys/errno.h>
#include <sys/sched.h>		/* CVTTOUT, IVTTOUT, SVTTOUT */
#include <sys/poll_clk.h>

#define	EEBUSY	EDBUSY

/*
 * Definitions.
 *
 * HSBAUD is the highest baud rate supported by this driver
 * HS_HZ is the polling rate, i.e. the number of times per second
 *   at which all open ports are checked for input, output, and
 *   line status changes
 * MAX_HSNUM is the maximum number of devices that can be polled
 *   using this driver and can be revised up or down
 * PORT is a convenience macro for the base address of a port
 * port_config is the structure of the initial configuration for each
 *   polled port;  note that "speed" is NOT the actual baud rate, but
 *   the value of the symbol for that baud rate as defined in
 *   /usr/include/sgtty.h
 */
#define	HSBAUD	9600
#define	HS_HZ	(HSBAUD/6)
#define MAX_HSNUM	8
#define	PORT	((int)(tp->t_ddp))
struct port_config {
	int	addr;	/* base address of the 8250-family UART */
	int	speed;	/* B0..B19200 */
};

/*
 * Export Variables - these can be patched without recompiling and linking
 *
 * HSNUM is the actual number of polled serial ports, and should be
 *   less than or equal to MAX_HSNUM
 * HS_PORTS is an array of address/speed pairs, one for each port
 */
int	HSNUM = 4;
struct port_config HS_PORTS[MAX_HSNUM] = {
	{ 0x3F8, B9600 },
	{ 0x2F8, B9600 },
	{ 0x3E8, B9600 },
	{ 0x2E8, B9600 }
};


/*
 * Local variables.
 */

static TTY *hstty;
static silo_t *out_silos, *in_silos;
static TTY *hslimtty;
static TIM hstim;
static int poll_divisor;	/* used in hsclk() and set_poll_rate() */
static int iocbaud[MAX_HSNUM];
static char ioclcr[MAX_HSNUM];

/*
 * Time constant table.
 * Indexed by ioctl baud rate.
 */

extern int albaud[], alp_rate[];


/*
 * Clock Interrupt driven Polling routine.
 */

static void
hsintr()
{
	register TTY * tp = hstty;
	register int b;
	silo_t * out_silo = out_silos;
	silo_t * in_silo = in_silos;

	for (tp = hstty, in_silo = in_silos, out_silo = out_silos;
	  tp < hslimtty; tp++, in_silo++, out_silo++) {
		if (tp->t_open == 0)
			continue;

		/*
		 * Check modem status if modem control is enabled.
		 */
		if (tp->t_flags & T_MODC) {

			b = inb(PORT+MSR);

			if (b & (MS_DCTS|MS_DDSR)) {

				if (b & MS_DCTS) {
					if (b & MS_CTS)
						tp->t_flags &= ~T_STOP;
					else
						tp->t_flags |=  T_STOP;
				}
				if (b & MS_DDSR) {
					if (b & MS_DSR)
						tp->t_flags |=  T_CARR;
					else {
						tp->t_flags &= ~T_CARR;
						tthup(tp);
					}
				}
			}
		}

		b = inb(PORT+LSR);

		if ((b & LS_BREAK) && (tp->t_flags & T_CARR))
			ttsignal(tp, SIGINT);

		/*
		 * Receive ready.
		 */
		if (b & LS_RxRDY) {

			in_silo->si_buf[in_silo->si_ix] = inb(PORT+DREG);

			if (tp->t_flags & T_CARR) {

				if (++(in_silo->si_ix) >=
						sizeof(in_silo->si_buf))
					in_silo->si_ix = 0;
			}
		}

		/*
		 * Transmit ready and raw output data exists.
		 */
		if ((b & LS_TxRDY) && ((tp->t_flags & T_STOP) == 0)
		  && (out_silo->si_ix != out_silo->si_ox)) {

			outb(	PORT+DREG,
				out_silo->si_buf[ out_silo->si_ox ]);

			if (++(out_silo->si_ox) >=
					sizeof(out_silo->si_buf))
				out_silo->si_ox = 0;
		}

	}
}


/*
 * hsclk will be called every time T0 interrupts - if it returns 0,
 * the usual system timer interrupt stuff is done
 */

static int
hsclk()
{
	static int count;

	hsintr();
	count++;
	if (count >= poll_divisor)
		count = 0;
	return count;
}


/*
 * set_poll_rate is called when a port is opened or closed or changes speed
 * it sets the polling rate only as fast as needed, and shuts off polling
 * whenever possible
 */

static void
set_poll_rate()
{
	int port_num, max_rate, port_rate;

	/*
	 * If another driver has the polling clock, do nothing.
	 */
	if (poll_owner & ~ POLL_HS)
		return;

	/*
	 * find highest valid polling rate in units of HZ/10
	 */
	max_rate = 0;
	for (port_num = 0; port_num < HSNUM; port_num++) {
		if (hstty[port_num].t_open) {
		  port_rate = alp_rate[hstty[port_num].t_sgttyb.sg_ispeed];
		  if (max_rate < port_rate)
			max_rate = port_rate;
		}
	}
	/*
	 * if max_rate is not current rate, adjust the system clock
	 */
	if (max_rate != poll_rate) {
		poll_rate = max_rate;
		poll_divisor = poll_rate/HZ;  /* used in hsclk() */
		altclk_out();		/* stop previous polling */
		poll_owner &= ~POLL_HS;
		if (max_rate) {	/* resume polling at new rate if needed */
			altclk_in(poll_rate, hsclk);
			poll_owner |= POLL_HS;
		}
	}
}


/*
 * Set hardware parameters.
 */

static void
hsparam(tp)
register TTY * tp;
{
	int s;
	int hnum;
	int newbaud;
	char newlcr;
	int write_baud = 1, write_lcr = 1;

	newbaud = albaud[tp->t_sgttyb.sg_ospeed];

	switch (tp->t_sgttyb.sg_flags & (EVENP|ODDP|RAW)) {
	case ODDP:
		newlcr = LC_CS7|LC_PARENB;
		break;
	case EVENP:
		newlcr = LC_CS7|LC_PARENB|LC_PAREVEN;
		break;
	default:
		newlcr = LC_CS8;
		break;
	}
	
	hnum = tp - hstty;
	if (hnum >= 0 && hnum < HSNUM) {
		if (newbaud == iocbaud[hnum]) {
			write_baud = 0;
			if (newlcr == ioclcr[hnum]) {
				write_lcr = 0;
			}
		}
		iocbaud[hnum] = newbaud;
		ioclcr[hnum] = newlcr;
	}

	s = sphi();
	/*
	 * Assert required modem control lines (DTR, RTS).
	 */
	if (tp->t_sgttyb.sg_ospeed == B0) {
		outb(PORT+MCR, 0);
	} else {
		outb(PORT+MCR, MC_DTR | MC_RTS);
	}

	/*
	 * Program baud rate.
	 */
	if (write_baud) {
		outb(PORT+LCR, LC_DLAB);
		outb(PORT+DLL, newbaud);
		outb(PORT+DLH, newbaud >> 8);
	}

	/*
	 * Program character size, parity.
	 */
	if (write_lcr)
		outb(PORT+LCR, newlcr);

	/*
	 * Enable Transmit Buffer Empty Interrupts.
	 */
	outb(PORT+IER, IE_TxI);

	spl(s);
	set_poll_rate();
}


/*
 * Start Routine.
 */

static void
hsstart (tp)
register TTY * tp;
{
	register int s;
	int chan = tp - hstty;
	silo_t * out_silo = out_silos + chan;

	/*
	 * Transmit buffer is empty, and raw output buffer is not.
	 */
	s = sphi();
	if ((inb(PORT+LSR) & LS_TxRDY)
	  && (out_silo->si_ix != out_silo->si_ox)) {

		/*
		 * Send next char from raw output buffer.
		 */
		outb(PORT+DREG, out_silo->si_buf[ out_silo->si_ox ]);

		if (++out_silo->si_ox >= sizeof(out_silo->si_buf))
			out_silo->si_ox = 0;
	}
	spl(s);
}


/*
 * Load Routine.
 */

static void
hsload()
{
	register TTY * tp;
	register int port;
	int i, b;

	if ((in_silos = (silo_t *)kalloc(HSNUM*sizeof(silo_t))) == 0) {
		printf("hsload: can't allocate in_silos\n");
		return;
	}
	kclear(in_silos, HSNUM*sizeof(silo_t));

	if ((out_silos = (silo_t *)kalloc(HSNUM*sizeof(silo_t))) == 0) {
		printf("hsload: can't allocate out_silos\n");
		return;
	}
	kclear(out_silos, HSNUM*sizeof(silo_t));

	if ((hstty = (TTY *)kalloc(HSNUM*sizeof(TTY))) == 0) {
		printf("hsload: can't allocate tty's\n");
		return;
	}
	kclear(hstty, HSNUM*sizeof(TTY));

	for (i = 0; i < HSNUM; i++) {
		port = HS_PORTS[i].addr;
		tp = hstty + i;

		outb(port+MCR, 0);
		outb(port+IER, 0);

		tp->t_start   = hsstart;
		tp->t_param   = hsparam;
		tp->t_sgttyb.sg_ospeed = tp->t_sgttyb.sg_ispeed = 
		tp->t_dispeed = tp->t_dospeed = HS_PORTS[i].speed;
		tp->t_ddp     = port;

		b = albaud[ tp->t_sgttyb.sg_ospeed ];
		outb(port+LCR, LC_DLAB);
		outb(port+DLL, b);
		outb(port+DLH, b >> 8);
		outb(port+LCR, LC_CS8);

	}
	hslimtty = hstty + HSNUM;
}

static void
hsunload()
{
	if (hstty != (TTY *)0)
		kfree(hstty);
}


/*
 * Cyclic routine - invoked every clock tick to perform raw input/output.
 *
 *	Notes:	Invoked 10 times per second.
 */

static void
hscycle(tp)
register TTY * tp;
{
	register int resid;
	register int c;
	int chan = tp - hstty;
	silo_t * out_silo = out_silos + chan;
	silo_t * in_silo = in_silos + chan;

	/*
	 * Process rawin buf.
	 */
	while (in_silo->si_ix != in_silo->si_ox) {

		ttin(tp, in_silo->si_buf[ in_silo->si_ox ]);

		if (in_silo->si_ox >= sizeof(in_silo->si_buf) - 1)
			in_silo->si_ox = 0;
		else
			in_silo->si_ox++;
	}

	/*
	 * Calculate free output slot count.
	 */
	resid  = sizeof(out_silo->si_buf) - 1;
	resid += out_silo->si_ox - out_silo->si_ix;
	resid %= sizeof(out_silo->si_buf);

	/*
	 * Fill raw output buffer.
	 */
	while ((--resid >= 0) && ((c = ttout(tp)) >= 0)) {

		out_silo->si_buf[ out_silo->si_ix ] = c;

		if (out_silo->si_ix >= sizeof(out_silo->si_buf) - 1)
			out_silo->si_ix = 0;
		else
			out_silo->si_ix++;
	}

	/*
	 * (Re)start output, waking processes waiting to output, etc.
	 */
	ttstart(tp);

	/*
	 * Schedule next cycle.
	 */
	if (tp->t_open != 0)
		timeout(&tp->t_rawtim, HZ/10, hscycle, tp);
}


/*
 * Open Routine.
 */

static void
hsopen(dev, mode)
dev_t dev;
int mode;
{
	register TTY * tp = &hstty[ dev & 15 ];
	register int b;
	int s;

	/*
	 * Verify hardware exists.
	 */
	if (PORT == 0 || (inb (PORT + IER) & ~IE_TxI) != 0) {
		set_user_error (ENXIO);
		return;
	}

	/*
	 * Can't open if another driver is using polling
	 */
	if ((poll_owner & ~ POLL_HS) != 0) {
		set_user_error (EBUSY);
		return;
	}

	/*
	 * Initialize if not already open.
	 */
	if (++tp->t_open == 1) {
		ttopen(tp);

		if (dev & 0x80) {
			s = sphi();
			b = inb(PORT+MSR);
			tp->t_flags |= T_MODC + T_STOP;
			if (b & MS_CTS)
				tp->t_flags &= ~T_STOP;
			if (b & MS_DSR)
				tp->t_flags |=  T_CARR;
			spl(s);
		} else  {
			tp->t_flags &= ~T_MODC;
			tp->t_flags |=  T_CARR;
		}
		hscycle (tp);
	}
	ttsetgrp(tp, dev, mode);
	set_poll_rate();
}

/*
 * Close Routine.
 */

static void
hsclose(dev)
dev_t dev;
{
	short chan = dev & 15;
	register TTY * tp = hstty + chan;
	silo_t * out_silo = out_silos + chan;

	/*
	 * Reset if last close.
	 */
	if (tp->t_open == 1) {
		int state;

		ttclose(tp);
		/*
		 * ttclose() only emptied the output queue tp->t_oq;
		 * now wait 0.1 sec for the silo to empty
		 * and allow a delay for the UART on-chip xmit buffer to empty
		 *
		 * state 2: waiting for silo to empty
		 * state 1: stalling so UART can empty xmit buffer
		 * state 0: done!
		 */
		state = 2;
		while (state) {
			timeout(&hstim, 10, wakeup, (int)&hstim);
#ifdef _I386
			x_sleep((char *)&hstim,
			  pritty, slpriNoSig, "hsopen");
#else
			v_sleep((char *)&hstim,
			  CVTTOUT, IVTTOUT, SVTTOUT, "hsopen");
#endif
			if (out_silo->si_ix == out_silo->si_ox  && state)
				state--;
		}
	}

	--tp->t_open;
	set_poll_rate();
}


/*
 * Read Routine.
 */

static void
hsread(dev, iop)
dev_t dev;
register IO * iop;
{
	ttread(&hstty[ dev & 15 ], iop);
}


/*
 * Write Routine.
 */

static void
hswrite(dev, iop)
dev_t dev;
register IO * iop;
{
	ttwrite (& hstty [dev & 15], iop);
}


/*
 * Ioctl Routine.
 */

static void
hsioctl(dev, com, vec)
dev_t	dev;
int	com;
struct sgttyb *vec;
{
	ttioctl (& hstty [dev & 15], com, vec);
}

/*
 * Polling Routine.
 */

static int
hspoll(dev, ev, msec)
dev_t dev;
int ev;
int msec;
{
	return ttpoll(&hstty[ dev & 15 ], ev, msec);
}


/*
 * Configuration table.
 */

CON hscon = {
	DFCHR|DFPOL,			/* Flags */
	HS_MAJOR,			/* Major index */
	hsopen,				/* Open */
	hsclose,			/* Close */
	NULL,				/* Block */
	hsread,				/* Read */
	hswrite,			/* Write */
	hsioctl,			/* Ioctl */
	NULL,				/* Powerfail */
	NULL,				/* Timeout */
	hsload,				/* Load */
	hsunload,			/* Unload */
	hspoll				/* Poll */
};