Coherent4.2.10/io.386/hs.c
/* $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 */
};