minix/drivers/tty/rs232.c
David van Moolenbroek b4d909d415 Split block/character protocols and libdriver
This patch separates the character and block driver communication
protocols. The old character protocol remains the same, but a new
block protocol is introduced. The libdriver library is replaced by
two new libraries: libchardriver and libblockdriver. Their exposed
API, and drivers that use them, have been updated accordingly.
Together, libbdev and libblockdriver now completely abstract away
the message format used by the block protocol. As the memory driver
is both a character and a block device driver, it now implements its
own message loop.

The most important semantic change made to the block protocol is that
it is no longer possible to return both partial results and an error
for a single transfer. This simplifies the interaction between the
caller and the driver, as the I/O vector no longer needs to be copied
back. Also, drivers are now no longer supposed to decide based on the
layout of the I/O vector when a transfer should be cut short. Put
simply, transfers are now supposed to either succeed completely, or
result in an error.

After this patch, the state of the various pieces is as follows:
- block protocol: stable
- libbdev API: stable for synchronous communication
- libblockdriver API: needs slight revision (the drvlib/partition API
  in particular; the threading API will also change shortly)
- character protocol: needs cleanup
- libchardriver API: needs cleanup accordingly
- driver restarts: largely unsupported until endpoint changes are
  reintroduced

As a side effect, this patch eliminates several bugs, hacks, and gcc
-Wall and -W warnings all over the place. It probably introduces a
few new ones, too.

Update warning: this patch changes the protocol between MFS and disk
drivers, so in order to use old/new images, the MFS from the ramdisk
must be used to mount all file systems.
2011-11-23 14:06:37 +01:00

819 lines
25 KiB
C

#include <minix/config.h>
/*---------------------------------------------------------------------------*
* rs232.c - serial driver for 8250 and 16450 UARTs *
* Added support for Atari ST M68901 and YM-2149 --kub *
*---------------------------------------------------------------------------*/
#include <minix/drivers.h>
#include <termios.h>
#include <signal.h>
#include "tty.h"
#if NR_RS_LINES > 0
/* 8250 constants. */
#define UART_FREQ 115200L /* timer frequency */
/* Interrupt enable bits. */
#define IE_RECEIVER_READY 1
#define IE_TRANSMITTER_READY 2
#define IE_LINE_STATUS_CHANGE 4
#define IE_MODEM_STATUS_CHANGE 8
/* Interrupt status bits. */
#define IS_MODEM_STATUS_CHANGE 0
#define IS_TRANSMITTER_READY 2
#define IS_RECEIVER_READY 4
#define IS_LINE_STATUS_CHANGE 6
/* Line control bits. */
#define LC_2STOP_BITS 0x04
#define LC_PARITY 0x08
#define LC_PAREVEN 0x10
#define LC_BREAK 0x40
#define LC_ADDRESS_DIVISOR 0x80
/* Line status bits. */
#define LS_OVERRUN_ERR 2
#define LS_PARITY_ERR 4
#define LS_FRAMING_ERR 8
#define LS_BREAK_INTERRUPT 0x10
#define LS_TRANSMITTER_READY 0x20
/* Modem control bits. */
#define MC_DTR 1
#define MC_RTS 2
#define MC_OUT2 8 /* required for PC & AT interrupts */
/* Modem status bits. */
#define MS_CTS 0x10
#define MS_RLSD 0x80 /* Received Line Signal Detect */
#define MS_DRLSD 0x08 /* RLSD Delta */
#define DATA_BITS_SHIFT 8 /* amount data bits shifted in mode */
#define DEF_BAUD 1200 /* default baud rate */
#define RS_IBUFSIZE 1024 /* RS232 input buffer size */
#define RS_OBUFSIZE 1024 /* RS232 output buffer size */
/* Input buffer watermarks.
* The external device is asked to stop sending when the buffer
* exactly reaches high water, or when TTY requests it. Sending restarts
* when the input buffer empties below the low watermark.
*/
#define RS_ILOWWATER (1 * RS_IBUFSIZE / 4)
#define RS_IHIGHWATER (3 * RS_IBUFSIZE / 4)
/* Output buffer low watermark.
* TTY is notified when the output buffer empties below the low watermark, so
* it may continue filling the buffer if doing a large write.
*/
#define RS_OLOWWATER (1 * RS_OBUFSIZE / 4)
/* Macros to handle flow control.
* Interrupts must be off when they are used.
* Time is critical - already the function call for outb() is annoying.
* If outb() can be done in-line, tests to avoid it can be dropped.
* istart() tells external device we are ready by raising RTS.
* istop() tells external device we are not ready by dropping RTS.
* DTR is kept high all the time (it probably should be raised by open and
* dropped by close of the device).
* OUT2 is also kept high all the time.
*/
#define istart(rs) \
(sys_outb((rs)->modem_ctl_port, MC_OUT2 | MC_RTS | MC_DTR), \
(rs)->idevready = TRUE)
#define istop(rs) \
(sys_outb((rs)->modem_ctl_port, MC_OUT2 | MC_DTR), \
(rs)->idevready = FALSE)
/* Macro to tell if device is ready. The rs->cts field is set to MS_CTS if
* CLOCAL is in effect for a line without a CTS wire.
*/
#define devready(rs) ((my_inb(rs->modem_status_port) | rs->cts) & MS_CTS)
/* Macro to tell if transmitter is ready. */
#define txready(rs) (my_inb(rs->line_status_port) & LS_TRANSMITTER_READY)
/* Macro to tell if carrier has dropped.
* The RS232 Carrier Detect (CD) line is usually connected to the 8250
* Received Line Signal Detect pin, reflected by bit MS_RLSD in the Modem
* Status Register. The MS_DRLSD bit tells if MS_RLSD has just changed state.
* So if MS_DRLSD is set and MS_RLSD cleared, we know that carrier has just
* dropped.
*/
#define devhup(rs) \
((my_inb(rs->modem_status_port) & (MS_RLSD|MS_DRLSD)) == MS_DRLSD)
/* Types. */
typedef unsigned char bool_t; /* boolean */
/* RS232 device structure, one per device. */
typedef struct rs232 {
tty_t *tty; /* associated TTY structure */
int icount; /* number of bytes in the input buffer */
char *ihead; /* next free spot in input buffer */
char *itail; /* first byte to give to TTY */
bool_t idevready; /* nonzero if we are ready to receive (RTS) */
char cts; /* normally 0, but MS_CTS if CLOCAL is set */
unsigned char ostate; /* combination of flags: */
#define ODONE 1 /* output completed (< output enable bits) */
#define ORAW 2 /* raw mode for xoff disable (< enab. bits) */
#define OWAKEUP 4 /* tty_wakeup() pending (asm code only) */
#define ODEVREADY MS_CTS /* external device hardware ready (CTS) */
#define OQUEUED 0x20 /* output buffer not empty */
#define OSWREADY 0x40 /* external device software ready (no xoff) */
#define ODEVHUP MS_RLSD /* external device has dropped carrier */
#define OSOFTBITS (ODONE | ORAW | OWAKEUP | OQUEUED | OSWREADY)
/* user-defined bits */
#if (OSOFTBITS | ODEVREADY | ODEVHUP) == OSOFTBITS
/* a weak sanity check */
#error /* bits are not unique */
#endif
unsigned char oxoff; /* char to stop output */
bool_t inhibited; /* output inhibited? (follows tty_inhibited) */
bool_t drain; /* if set drain output and reconfigure line */
int ocount; /* number of bytes in the output buffer */
char *ohead; /* next free spot in output buffer */
char *otail; /* next char to output */
#if (MACHINE == IBM_PC)
port_t xmit_port; /* i/o ports */
port_t recv_port;
port_t div_low_port;
port_t div_hi_port;
port_t int_enab_port;
port_t int_id_port;
port_t line_ctl_port;
port_t modem_ctl_port;
port_t line_status_port;
port_t modem_status_port;
#endif
unsigned char lstatus; /* last line status */
unsigned char pad; /* ensure alignment for 16-bit ints */
unsigned framing_errors; /* error counts (no reporting yet) */
unsigned overrun_errors;
unsigned parity_errors;
unsigned break_interrupts;
int irq; /* irq for this line */
int irq_hook_id; /* interrupt hook */
char ibuf[RS_IBUFSIZE]; /* input buffer */
char obuf[RS_OBUFSIZE]; /* output buffer */
} rs232_t;
PRIVATE rs232_t rs_lines[NR_RS_LINES];
#if (MACHINE == IBM_PC)
/* 8250 base addresses. */
PRIVATE port_t addr_8250[] = {
0x3F8, /* COM1 */
0x2F8, /* COM2 */
0x3E8, /* COM3 */
0x2E8, /* COM4 */
};
#endif
FORWARD _PROTOTYPE( void in_int, (rs232_t *rs) );
FORWARD _PROTOTYPE( void line_int, (rs232_t *rs) );
FORWARD _PROTOTYPE( void modem_int, (rs232_t *rs) );
FORWARD _PROTOTYPE( int rs_write, (tty_t *tp, int try) );
FORWARD _PROTOTYPE( void rs_echo, (tty_t *tp, int c) );
FORWARD _PROTOTYPE( int rs_ioctl, (tty_t *tp, int try) );
FORWARD _PROTOTYPE( void rs_config, (rs232_t *rs) );
FORWARD _PROTOTYPE( int rs_read, (tty_t *tp, int try) );
FORWARD _PROTOTYPE( int rs_icancel, (tty_t *tp, int try) );
FORWARD _PROTOTYPE( int rs_ocancel, (tty_t *tp, int try) );
FORWARD _PROTOTYPE( void rs_ostart, (rs232_t *rs) );
FORWARD _PROTOTYPE( int rs_break, (tty_t *tp, int try) );
FORWARD _PROTOTYPE( int rs_close, (tty_t *tp, int try) );
FORWARD _PROTOTYPE( void out_int, (rs232_t *rs) );
FORWARD _PROTOTYPE( void rs232_handler, (rs232_t *rs) );
/* XXX */
PRIVATE void lock(void) {}
PRIVATE void unlock(void) {}
PRIVATE int my_inb(port_t port)
{
int r;
unsigned long v = 0;
r = sys_inb(port, &v);
if (r != OK)
printf("RS232 warning: failed inb 0x%x\n", port);
return (int) v;
}
/*===========================================================================*
* rs_write *
*===========================================================================*/
PRIVATE int rs_write(register tty_t *tp, int try)
{
/* (*devwrite)() routine for RS232. */
rs232_t *rs = tp->tty_priv;
int count, ocount;
if (rs->inhibited != tp->tty_inhibited) {
/* Inhibition state has changed. */
lock();
rs->ostate |= OSWREADY;
if (tp->tty_inhibited) rs->ostate &= ~OSWREADY;
unlock();
rs->inhibited = tp->tty_inhibited;
}
if (rs->drain) {
/* Wait for the line to drain then reconfigure and continue output. */
if (rs->ocount > 0) return 0;
rs->drain = FALSE;
rs_config(rs);
}
/* While there is something to do. */
for (;;) {
ocount = buflen(rs->obuf) - rs->ocount;
count = bufend(rs->obuf) - rs->ohead;
if (count > ocount) count = ocount;
if (count > tp->tty_outleft) count = tp->tty_outleft;
if (count == 0 || tp->tty_inhibited) {
if (try) return 0;
break;
}
if (try) return 1;
/* Copy from user space to the RS232 output buffer. */
sys_safecopyfrom(tp->tty_outcaller, tp->tty_outgrant,
tp->tty_outoffset, (vir_bytes) rs->ohead, count, D);
/* Perform output processing on the output buffer. */
out_process(tp, rs->obuf, rs->ohead, bufend(rs->obuf), &count, &ocount);
if (count == 0) break;
/* Assume echoing messed up by output. */
tp->tty_reprint = TRUE;
/* Bookkeeping. */
lock(); /* protect interrupt sensitive rs->ocount */
rs->ocount += ocount;
rs_ostart(rs);
unlock();
if ((rs->ohead += ocount) >= bufend(rs->obuf))
rs->ohead -= buflen(rs->obuf);
tp->tty_outoffset += count;
tp->tty_outcum += count;
if ((tp->tty_outleft -= count) == 0) {
/* Output is finished, reply to the writer. */
if(tp->tty_outrepcode == TTY_REVIVE) {
notify(tp->tty_outcaller);
tp->tty_outrevived = 1;
} else {
tty_reply(tp->tty_outrepcode, tp->tty_outcaller,
tp->tty_outproc, tp->tty_outcum);
tp->tty_outcum = 0;
}
}
}
if (tp->tty_outleft > 0 && tp->tty_termios.c_ospeed == B0) {
/* Oops, the line has hung up. */
if(tp->tty_outrepcode == TTY_REVIVE) {
notify(tp->tty_outcaller);
tp->tty_outrevived = 1;
} else {
tty_reply(tp->tty_outrepcode, tp->tty_outcaller,
tp->tty_outproc, EIO);
tp->tty_outleft = tp->tty_outcum = 0;
}
}
return 1;
}
/*===========================================================================*
* rs_echo *
*===========================================================================*/
PRIVATE void rs_echo(tp, c)
tty_t *tp; /* which TTY */
int c; /* character to echo */
{
/* Echo one character. (Like rs_write, but only one character, optionally.) */
rs232_t *rs = tp->tty_priv;
int count, ocount;
ocount = buflen(rs->obuf) - rs->ocount;
if (ocount == 0) return; /* output buffer full */
count = 1;
*rs->ohead = c; /* add one character */
out_process(tp, rs->obuf, rs->ohead, bufend(rs->obuf), &count, &ocount);
if (count == 0) return;
lock();
rs->ocount += ocount;
rs_ostart(rs);
unlock();
if ((rs->ohead += ocount) >= bufend(rs->obuf)) rs->ohead -= buflen(rs->obuf);
}
/*===========================================================================*
* rs_ioctl *
*===========================================================================*/
PRIVATE int rs_ioctl(tty_t *tp, int UNUSED(dummy))
/* tp; which TTY */
{
/* Reconfigure the line as soon as the output has drained. */
rs232_t *rs = tp->tty_priv;
rs->drain = TRUE;
return 0; /* dummy */
}
/*===========================================================================*
* rs_config *
*===========================================================================*/
PRIVATE void rs_config(rs232_t *rs)
/* rs which line */
{
/* Set various line control parameters for RS232 I/O.
* If DataBits == 5 and StopBits == 2, 8250 will generate 1.5 stop bits.
* The 8250 can't handle split speed, so we use the input speed.
*/
tty_t *tp = rs->tty;
int divisor;
int line_controls;
static struct speed2divisor {
speed_t speed;
int divisor;
} s2d[] = {
#if (MACHINE == IBM_PC)
{ B50, UART_FREQ / 50 },
#endif
{ B75, UART_FREQ / 75 },
{ B110, UART_FREQ / 110 },
{ B134, UART_FREQ * 10 / 1345 },
{ B150, UART_FREQ / 150 },
{ B200, UART_FREQ / 200 },
{ B300, UART_FREQ / 300 },
{ B600, UART_FREQ / 600 },
{ B1200, UART_FREQ / 1200 },
#if (MACHINE == IBM_PC)
{ B1800, UART_FREQ / 1800 },
#endif
{ B2400, UART_FREQ / 2400 },
{ B4800, UART_FREQ / 4800 },
{ B9600, UART_FREQ / 9600 },
{ B19200, UART_FREQ / 19200 },
#if (MACHINE == IBM_PC)
{ B38400, UART_FREQ / 38400 },
{ B57600, UART_FREQ / 57600 },
{ B115200, UART_FREQ / 115200L },
#endif
};
struct speed2divisor *s2dp;
/* RS232 needs to know the xoff character, and if CTS works. */
rs->oxoff = tp->tty_termios.c_cc[VSTOP];
rs->cts = (tp->tty_termios.c_cflag & CLOCAL) ? MS_CTS : 0;
/* Look up the 8250 rate divisor from the output speed. */
divisor = 0;
for (s2dp = s2d; s2dp < s2d + sizeof(s2d)/sizeof(s2d[0]); s2dp++) {
if (s2dp->speed == tp->tty_termios.c_ospeed) divisor = s2dp->divisor;
}
if (divisor == 0) return; /* B0? */
/* Compute line control flag bits. */
line_controls = 0;
if (tp->tty_termios.c_cflag & PARENB) {
line_controls |= LC_PARITY;
if (!(tp->tty_termios.c_cflag & PARODD)) line_controls |= LC_PAREVEN;
}
if (divisor >= (UART_FREQ / 110)) line_controls |= LC_2STOP_BITS;
line_controls |= (tp->tty_termios.c_cflag & CSIZE) >> 2;
/* Lock out interrupts while setting the speed. The receiver register is
* going to be hidden by the div_low register, but the input interrupt
* handler relies on reading it to clear the interrupt and avoid looping
* forever.
*/
lock();
/* Select the baud rate divisor registers and change the rate. */
sys_outb(rs->line_ctl_port, LC_ADDRESS_DIVISOR);
sys_outb(rs->div_low_port, divisor);
sys_outb(rs->div_hi_port, divisor >> 8);
/* Change the line controls and reselect the usual registers. */
sys_outb(rs->line_ctl_port, line_controls);
rs->ostate = devready(rs) | ORAW | OSWREADY; /* reads modem_ctl_port */
if ((tp->tty_termios.c_lflag & IXON) && rs->oxoff != _POSIX_VDISABLE)
rs->ostate &= ~ORAW;
unlock();
}
/*===========================================================================*
* rs_init *
*===========================================================================*/
PUBLIC void rs_init(tty_t *tp)
/* tp which TTY */
{
unsigned long dummy;
/* Initialize RS232 for one line. */
register rs232_t *rs;
int line;
port_t this_8250;
int irq;
char l[10];
/* Associate RS232 and TTY structures. */
line = tp - &tty_table[NR_CONS];
/* See if kernel debugging is enabled; if so, don't initialize this
* serial line, making tty not look at the irq and returning ENXIO
* for all requests on it from userland. (The kernel will use it.)
*/
if(env_get_param(SERVARNAME, l, sizeof(l)-1) == OK && atoi(l) == line) {
return;
}
rs = tp->tty_priv = &rs_lines[line];
rs->tty = tp;
/* Set up input queue. */
rs->ihead = rs->itail = rs->ibuf;
/* Precalculate port numbers for speed. Magic numbers in the code (once). */
this_8250 = addr_8250[line];
rs->xmit_port = this_8250 + 0;
rs->recv_port = this_8250 + 0;
rs->div_low_port = this_8250 + 0;
rs->div_hi_port = this_8250 + 1;
rs->int_enab_port = this_8250 + 1;
rs->int_id_port = this_8250 + 2;
rs->line_ctl_port = this_8250 + 3;
rs->modem_ctl_port = this_8250 + 4;
rs->line_status_port = this_8250 + 5;
rs->modem_status_port = this_8250 + 6;
/* Set up the hardware to a base state, in particular
* o turn off DTR (MC_DTR) to try to stop the external device.
* o be careful about the divisor latch. Some BIOS's leave it enabled
* here and that caused trouble (no interrupts) in version 1.5 by
* hiding the interrupt enable port in the next step, and worse trouble
* (continual interrupts) in an old version by hiding the receiver
* port in the first interrupt. Call rs_ioctl() early to avoid this.
* o disable interrupts at the chip level, to force an edge transition
* on the 8259 line when interrupts are next enabled and active.
* RS232 interrupts are guaranteed to be disabled now by the 8259
* mask, but there used to be trouble if the mask was set without
* handling a previous interrupt.
*/
istop(rs); /* sets modem_ctl_port */
rs_config(rs);
sys_outb(rs->int_enab_port, 0);
/* Clear any harmful leftover interrupts. An output interrupt is harmless
* and will occur when interrupts are enabled anyway. Set up the output
* queue using the status from clearing the modem status interrupt.
*/
sys_inb(rs->line_status_port, &dummy);
sys_inb(rs->recv_port, &dummy);
rs->ostate = devready(rs) | ORAW | OSWREADY; /* reads modem_ctl_port */
rs->ohead = rs->otail = rs->obuf;
/* Enable interrupts for both interrupt controller and device. */
irq = (line & 1) == 0 ? RS232_IRQ : SECONDARY_IRQ;
rs->irq = irq;
rs->irq_hook_id = rs->irq; /* call back with irq line number */
if (sys_irqsetpolicy(irq, IRQ_REENABLE, &rs->irq_hook_id) != OK) {
printf("RS232: Couldn't obtain hook for irq %d\n", irq);
} else {
if (sys_irqenable(&rs->irq_hook_id) != OK) {
printf("RS232: Couldn't enable irq %d (hooked)\n", irq);
}
}
rs_irq_set |= (1 << irq);
sys_outb(rs->int_enab_port, IE_LINE_STATUS_CHANGE | IE_MODEM_STATUS_CHANGE
| IE_RECEIVER_READY | IE_TRANSMITTER_READY);
/* Fill in TTY function hooks. */
tp->tty_devread = rs_read;
tp->tty_devwrite = rs_write;
tp->tty_echo = rs_echo;
tp->tty_icancel = rs_icancel;
tp->tty_ocancel = rs_ocancel;
tp->tty_ioctl = rs_ioctl;
tp->tty_break = rs_break;
tp->tty_close = rs_close;
/* Tell external device we are ready. */
istart(rs);
}
/*===========================================================================*
* rs_interrupt *
*===========================================================================*/
PUBLIC void rs_interrupt(message *m)
{
unsigned long irq_set;
int i;
rs232_t *rs;
irq_set= m->NOTIFY_ARG;
for (i= 0, rs = rs_lines; i<NR_RS_LINES; i++, rs++)
{
if (irq_set & (1 << rs->irq))
rs232_handler(rs);
}
}
/*===========================================================================*
* rs_icancel *
*===========================================================================*/
PRIVATE int rs_icancel(tty_t *tp, int UNUSED(dummy))
{
/* Cancel waiting input. */
rs232_t *rs = tp->tty_priv;
lock();
rs->icount = 0;
rs->itail = rs->ihead;
istart(rs);
unlock();
return 0; /* dummy */
}
/*===========================================================================*
* rs_ocancel *
*===========================================================================*/
PRIVATE int rs_ocancel(tty_t *tp, int UNUSED(dummy))
{
/* Cancel pending output. */
rs232_t *rs = tp->tty_priv;
lock();
rs->ostate &= ~(ODONE | OQUEUED);
rs->ocount = 0;
rs->otail = rs->ohead;
unlock();
return 0; /* dummy */
}
/*===========================================================================*
* rs_read *
*===========================================================================*/
PRIVATE int rs_read(tty_t *tp, int try)
{
/* Process characters from the circular input buffer. */
rs232_t *rs = tp->tty_priv;
int icount, count, ostate;
if (!(tp->tty_termios.c_cflag & CLOCAL)) {
if (try) return 1;
/* Send a SIGHUP if hangup detected. */
lock();
ostate = rs->ostate;
rs->ostate &= ~ODEVHUP; /* save ostate, clear DEVHUP */
unlock();
if (ostate & ODEVHUP) {
sigchar(tp, SIGHUP, 1);
tp->tty_termios.c_ospeed = B0; /* Disable further I/O. */
return 0;
}
}
if (try) {
if (rs->icount > 0)
return 1;
return 0;
}
while ((count = rs->icount) > 0) {
icount = bufend(rs->ibuf) - rs->itail;
if (count > icount) count = icount;
/* Perform input processing on (part of) the input buffer. */
if ((count = in_process(tp, rs->itail, count, -1)) == 0) break;
lock(); /* protect interrupt sensitive variables */
rs->icount -= count;
if (!rs->idevready && rs->icount < RS_ILOWWATER) istart(rs);
unlock();
if ((rs->itail += count) == bufend(rs->ibuf)) rs->itail = rs->ibuf;
}
return 0;
}
/*===========================================================================*
* rs_ostart *
*===========================================================================*/
PRIVATE void rs_ostart(rs232_t *rs)
{
/* Tell RS232 there is something waiting in the output buffer. */
rs->ostate |= OQUEUED;
if (txready(rs)) out_int(rs);
}
/*===========================================================================*
* rs_break *
*===========================================================================*/
PRIVATE int rs_break(tty_t *tp, int UNUSED(dummy))
{
/* Generate a break condition by setting the BREAK bit for 0.4 sec. */
rs232_t *rs = tp->tty_priv;
unsigned long line_controls;
sys_inb(rs->line_ctl_port, &line_controls);
sys_outb(rs->line_ctl_port, line_controls | LC_BREAK);
/* XXX */
/* milli_delay(400); */ /* ouch */
printf("RS232 break\n");
sys_outb(rs->line_ctl_port, line_controls);
return 0; /* dummy */
}
/*===========================================================================*
* rs_close *
*===========================================================================*/
PRIVATE int rs_close(tty_t *tp, int UNUSED(dummy))
{
/* The line is closed; optionally hang up. */
rs232_t *rs = tp->tty_priv;
if (tp->tty_termios.c_cflag & HUPCL) {
sys_outb(rs->modem_ctl_port, MC_OUT2 | MC_RTS);
}
return 0; /* dummy */
}
/* Low level (interrupt) routines. */
/*===========================================================================*
* rs232_handler *
*===========================================================================*/
PRIVATE void rs232_handler(struct rs232 *rs)
{
/* Interrupt hander for RS232. */
while (TRUE) {
unsigned long v;
/* Loop to pick up ALL pending interrupts for device.
* This usually just wastes time unless the hardware has a buffer
* (and then we have to worry about being stuck in the loop too long).
* Unfortunately, some serial cards lock up without this.
*/
sys_inb(rs->int_id_port, &v);
switch (v) {
case IS_RECEIVER_READY:
in_int(rs);
continue;
case IS_TRANSMITTER_READY:
out_int(rs);
continue;
case IS_MODEM_STATUS_CHANGE:
modem_int(rs);
continue;
case IS_LINE_STATUS_CHANGE:
line_int(rs);
continue;
}
return;
}
}
/*===========================================================================*
* in_int *
*===========================================================================*/
PRIVATE void in_int(register rs232_t *rs)
/* rs line with input interrupt */
{
/* Read the data which just arrived.
* If it is the oxoff char, clear OSWREADY, else if OSWREADY was clear, set
* it and restart output (any char does this, not just xon).
* Put data in the buffer if room, otherwise discard it.
* Set a flag for the clock interrupt handler to eventually notify TTY.
*/
unsigned long c;
#if 0 /* Enable this if you want serial input in the kernel */
return;
#endif
sys_inb(rs->recv_port, &c);
if (!(rs->ostate & ORAW)) {
if (c == rs->oxoff) {
rs->ostate &= ~OSWREADY;
} else
if (!(rs->ostate & OSWREADY)) {
rs->ostate |= OSWREADY;
if (txready(rs)) out_int(rs);
}
}
if (rs->icount == buflen(rs->ibuf))
{
printf("in_int: discarding byte\n");
return; /* input buffer full, discard */
}
if (++rs->icount == RS_IHIGHWATER && rs->idevready) istop(rs);
*rs->ihead = c;
if (++rs->ihead == bufend(rs->ibuf)) rs->ihead = rs->ibuf;
if (rs->icount == 1) {
rs->tty->tty_events = 1;
force_timeout();
}
else
printf("in_int: icount = %d\n", rs->icount);
}
/*===========================================================================*
* line_int *
*===========================================================================*/
PRIVATE void line_int(register rs232_t *rs)
/* rs line with line status interrupt */
{
/* Check for and record errors. */
unsigned long s;
sys_inb(rs->line_status_port, &s);
rs->lstatus = s;
if (rs->lstatus & LS_FRAMING_ERR) ++rs->framing_errors;
if (rs->lstatus & LS_OVERRUN_ERR) ++rs->overrun_errors;
if (rs->lstatus & LS_PARITY_ERR) ++rs->parity_errors;
if (rs->lstatus & LS_BREAK_INTERRUPT) ++rs->break_interrupts;
}
/*===========================================================================*
* modem_int *
*===========================================================================*/
PRIVATE void modem_int(register rs232_t *rs)
/* rs line with modem interrupt */
{
/* Get possibly new device-ready status, and clear ODEVREADY if necessary.
* If the device just became ready, restart output.
*/
if (devhup(rs)) {
rs->ostate |= ODEVHUP;
rs->tty->tty_events = 1;
force_timeout();
}
if (!devready(rs))
rs->ostate &= ~ODEVREADY;
else if (!(rs->ostate & ODEVREADY)) {
rs->ostate |= ODEVREADY;
if (txready(rs)) out_int(rs);
}
}
/*===========================================================================*
* out_int *
*===========================================================================*/
PRIVATE void out_int(register rs232_t *rs)
/* rs; line with output interrupt */
{
/* If there is output to do and everything is ready, do it (local device is
* known ready).
* Notify TTY when the buffer goes empty.
*/
if (rs->ostate >= (ODEVREADY | OQUEUED | OSWREADY)) {
/* Bit test allows ORAW and requires the others. */
sys_outb(rs->xmit_port, *rs->otail);
if (++rs->otail == bufend(rs->obuf)) rs->otail = rs->obuf;
if (--rs->ocount == 0) {
rs->ostate ^= (ODONE | OQUEUED); /* ODONE on, OQUEUED off */
rs->tty->tty_events = 1;
force_timeout();
} else
if (rs->ocount == RS_OLOWWATER) { /* running low? */
rs->tty->tty_events = 1;
force_timeout();
}
}
}
#endif /* NR_RS_LINES > 0 */