Minix1.5/kernel/rs232.c

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

/*==========================================================================*
 *		rs232.c - serial driver for 8250 and 16450 UARTs 	    *
 *==========================================================================*/

#include "kernel.h"
#include <sgtty.h>
#include "tty.h"

/* Switches.
 * #define C_RS232_INT_HANDLERS to use the interrupt handlers in this file.
 * #define NO_HANDSHAKE to avoid requiring CTS for output.
 */

/* 8250 constants. */
#define DEF_BAUD             1200	/* default baud rate */
#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_NO_PARITY            0
#define LC_DATA_BITS            3
#define LC_ODD_PARITY           8
#define LC_EVEN_PARITY       0x18
#define LC_ADDRESS_DIVISOR   0x80
#define LC_STOP_BITS_SHIFT      2

#define DATA_BITS_SHIFT         8	/* amount data bits shifted in mode */

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

/* Input buffer watermarks.
 * The external device is asked to stop sending when the buffer
 * exactly reaches high water, or when TTY requests it.
 * TTY is also notified directly (rather than at a later clock tick) when
 * this watermark is reached.
 * A lower threshold could be used, but the buffer size and wakeup intervals
 * are chosen so the watermark shouldn't be hit at reasonable baud rates,
 * so this is unnecessary - the throttle is applied when TTY's buffers
 * get too full.
 * The low watermark is invisibly 0 since the buffer is always emptied all
 * at once.
 */
#define RS_IHIGHWATER (3 * RS_IBUFSIZE / 4)

/* Macros to handle flow control.
 * Interrupts must be off when they are used.
 * Time is critical - already the function call for out_byte() is annoying.
 * If out_byte() 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) \
  (out_byte( (rs)->modem_ctl_port, MC_OUT2 | MC_RTS | MC_DTR), \
   (rs)->idevready = TRUE)
#define istop(rs) \
  (out_byte( (rs)->modem_ctl_port, MC_OUT2 | MC_DTR), (rs)->idevready = FALSE)

/* Macro to tell if device is ready.
 * Don't require DSR, since modems drop this to indicate the line is not
 * ready even when the modem itself is ready.
 * If NO_HANDSHAKE, read the status port to clear the interrupt, then force
 * the ready bit.
 */
#if NO_HANDSHAKE
# define devready(rs) (in_byte(rs->modem_status_port), MS_CTS)
#else
# define devready(rs) (in_byte(rs->modem_status_port) & MS_CTS)
#endif

/* Macro to tell if transmitter is ready. */
#define txready(rs) (in_byte(rs->line_status_port) & LS_TRANSMITTER_READY)

/* Types. */
typedef char bool_t;		/* boolean */
typedef unsigned port_t;	/* hardware port */

/* RS232 device structure, one per device. */
struct rs232_s {
  int minor;			/* minor number of this line (base 0) */

  bool_t idevready;		/* nonzero if we are ready to receive (RTS) */
  bool_t ittyready;		/* nonzero if TTY is ready to receive */
  char *ibuf;			/* start of input buffer */
  char *ibufend;		/* end of input buffer */
  char *ihighwater;		/* threshold in input buffer */
  char *iptr;			/* next free spot in input buffer */

  unsigned char ostate;		/* combination of flags: */
#define ODEVREADY MS_CTS	/* external device hardware ready (CTS) */
#define ODONE          1	/* output completed (< output enable bits) */
#define OQUEUED     0x20	/* output buffer not empty */
#define ORAW           2	/* raw mode for xoff disable (< enab. bits) */
#define OSWREADY    0x40	/* external device software ready (no xoff) */
#define OWAKEUP        4	/* tty_wakeup() pending (asm code only) */
#if (ODEVREADY | 0x63) == 0x63
#error				/* bits are not unique */
#endif
  unsigned char oxoff;		/* char to stop output */
  char *obufend;		/* end of output buffer */
  char *optr;			/* next char to output */

  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;

  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;

  char ibuf1[RS_IBUFSIZE + 1];	/* 1st input buffer, guard at end */
  char ibuf2[RS_IBUFSIZE + 1];	/* 2nd input buffer (for swapping) */
};

/* Table and macro to translate an RS232 minor device number to its
 * struct rs232_s pointer.
 */
struct rs232_s *p_rs_addr[NR_RS_LINES];

#define rs_addr(minor) (p_rs_addr[minor])

/* 8250 base addresses. */
PRIVATE port_t addr_8250[] = {
  0x3F8,			/* COM1: (line 0); COM3 might be at 0x3E8 */
  0x2F8,			/* COM2: (line 1); COM4 might be at 0x2E8 */
};

PUBLIC struct rs232_s rs_lines[NR_RS_LINES];

#if C_RS232_INT_HANDLERS
FORWARD void in_int();
FORWARD void line_int();
FORWARD void modem_int();

#endif
FORWARD void out_int();
FORWARD int rs_config();


/* High level routines (should only be called by TTY). */

/*==========================================================================*
 *				rs_config			 	    *
 *==========================================================================*/
PRIVATE int rs_config(minor, in_baud, out_baud, parity, stop_bits, data_bits,
		      mode)
int minor;			/* which rs line */
int in_baud;			/* input speed: 110, 300, 1200, etc */
int out_baud;			/* output speed: 110, 300, 1200, etc */
int parity;			/* LC_something */
int stop_bits;			/* 2 (110 baud) or 1 (other speeds) */
int data_bits;			/* 5, 6, 7, or 8 */
int mode;			/* sgtty.h sg_mode word */
{
/* 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, but we have propagated both speeds
 * anyway for the benefit of future UART chips.
 */

  int divisor;
  int line_controls;
  register struct rs232_s *rs;

  rs = rs_addr(minor);

  /* Precalculate divisor and line_controls for reduced latency. */
  if (in_baud < 50) in_baud = DEF_BAUD;	/* prevent divide overflow */
  if (out_baud < 50) out_baud = DEF_BAUD;	/* prevent divide overflow */
  divisor = (int) (UART_FREQ / in_baud);	/* 8250 can't hack 2 speeds */
  line_controls = parity | ((stop_bits - 1) << LC_STOP_BITS_SHIFT)
			 | (data_bits - 5);

  /* 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. */
  out_byte(rs->line_ctl_port, LC_ADDRESS_DIVISOR);
  out_byte(rs->div_low_port, divisor);
  out_byte(rs->div_hi_port, divisor >> 8);

  /* Change the line controls and reselect the usual registers. */
  out_byte(rs->line_ctl_port, line_controls);

  if (mode & RAW)
	rs->ostate |= ORAW;
  else
	rs->ostate &= ~ORAW;
  unlock();
  return( (out_baud / 100) << 8) | (in_baud / 100);
}


/*==========================================================================*
 *				rs_ioctl			 	    *
 *==========================================================================*/
PUBLIC int rs_ioctl(minor, mode, speeds)
int minor;			/* which rs line */
int mode;			/* sgtty.h sg_mode word */
int speeds;			/* low byte is input speed, next is output */
{
/* Set the UART parameters. */

  int data_bits;
  int in_baud;
  int out_baud;
  int parity;
  int stop_bits;

  in_baud = 100 * (speeds & BYTE);
  if (in_baud == 100) in_baud = 110;
  out_baud = 100 * ((speeds >> 8) & BYTE);
  if (out_baud == 100) out_baud = 110;
  parity = LC_NO_PARITY;
  if (mode & ODDP) parity = LC_ODD_PARITY;
  if (mode & EVENP) parity = LC_EVEN_PARITY;
  stop_bits = in_baud == 110 ? 2 : 1;	/* not quite cricket */
  data_bits = 5 + ((mode >> DATA_BITS_SHIFT) & LC_DATA_BITS);
  return(rs_config(minor, in_baud, out_baud, parity, stop_bits, data_bits,
		     mode));
}


/*==========================================================================*
 *				rs_inhibit				    *
 *==========================================================================*/
PUBLIC void rs_inhibit(minor, inhibit)
int minor;			/* which rs line */
bool_t inhibit;			/* nonzero to inhibit, zero to uninhibit */
{
/* Update inhibition state to keep in sync with TTY. */

  register struct rs232_s *rs;

  rs = rs_addr(minor);
  lock();
  if (inhibit)
	rs->ostate &= ~OSWREADY;
  else
	rs->ostate |= OSWREADY;
  unlock();
}


/*==========================================================================*
 *				rs_init					    *
 *==========================================================================*/
PUBLIC int rs_init(minor)
int minor;			/* which rs line */
{
/* Initialize RS232 for one line. */

  register struct rs232_s *rs;
  int speed;
  port_t this_8250;

  rs = rs_addr(minor) = &rs_lines[minor];

  /* Record minor number. */
  rs->minor = minor;

  /* Set up input queue. */
  rs->iptr = rs->ibuf = rs->ibuf1;
  rs->ibufend = rs->ibuf1 + RS_IBUFSIZE;
  rs->ihighwater = rs->ibuf1 + RS_IHIGHWATER;
  rs->ittyready = TRUE;		/* idevready set to TRUE by istart() */

  /* Precalculate port numbers for speed. Magic numbers in the code (once). */
  this_8250 = addr_8250[minor];
  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 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.
   *	o be careful about the divisor latch.  It may be enabled now, and
   *	  that used to cause trouble when interrupts were enabled too early
   *	  (see comment in rs_config()).  Call rs_config() early to avoid this.
   */
  istop(rs);			/* sets modem_ctl_port */
  out_byte(rs->int_enab_port, 0);
  speed = rs_config(minor, DEF_BAUD, DEF_BAUD, LC_NO_PARITY, 1, 8, RAW);

  /* 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.
   */
  in_byte(rs->line_status_port);
  in_byte(rs->recv_port);
  rs->ostate = devready(rs) | ORAW | OSWREADY;	/* reads modem_ctl_port */

  /* Enable interrupts for both interrupt controller and device. */
  if (minor & 1)		/* COM2 on IRQ3 */
	enable_irq(SECONDARY_IRQ);
  else				/* COM1 on IRQ4 */
	enable_irq(RS232_IRQ);
  out_byte(rs->int_enab_port, IE_LINE_STATUS_CHANGE | IE_MODEM_STATUS_CHANGE
				| IE_RECEIVER_READY | IE_TRANSMITTER_READY);

  /* Tell external device we are ready. */
  istart(rs);

  return(speed);
}


/*==========================================================================*
 *				rs_istop				    *
 *==========================================================================*/
PUBLIC void rs_istop(minor)
int minor;			/* which rs line */
{
/* TTY wants RS232 to stop input.
 * RS232 drops RTS but keeps accepting input until its buffer overflows.
 */

  register struct rs232_s *rs;

  rs = rs_addr(minor);
  lock();
  rs->ittyready = FALSE;
  istop(rs);
  unlock();
}


/*==========================================================================*
 *				rs_istart				    *
 *==========================================================================*/
PUBLIC void rs_istart(minor)
int minor;			/* which rs line */
{
/* TTY is ready for another buffer full of input from RS232.
 * RS232 raises RTS unless its own buffer is already too full.
 */

  register struct rs232_s *rs;

  rs = rs_addr(minor);
  lock();
  rs->ittyready = TRUE;
  if (rs->iptr < rs->ihighwater) istart(rs);
  unlock();
}


/*==========================================================================*
 *				rs_ocancel				    *
 *==========================================================================*/
PUBLIC void rs_ocancel(minor)
int minor;			/* which rs line */
{
/* Cancel pending output. */

  register struct rs232_s *rs;

  lock();
  rs = rs_addr(minor);
  if (rs->ostate & ODONE) tty_events -= EVENT_THRESHOLD;
  rs->ostate &= ~(ODONE | OQUEUED);
  unlock();
}


/*==========================================================================*
 *				rs_read					    *
 *==========================================================================*/
PUBLIC int rs_read(minor, bufindirect, odoneindirect)
int minor;			/* which rs line */
char **bufindirect;		/* where to return pointer to our buffer */
bool_t *odoneindirect;		/* where to return output-done status */
{
/* Swap the input buffers, giving the old one to TTY, and restart input. */

  register char *ibuf;
  int nread;
  register struct rs232_s *rs;

  rs = rs_addr(minor);
  *odoneindirect = rs->ostate & ODONE;
  if (rs->iptr == (ibuf = rs->ibuf)) return(0);
  *bufindirect = ibuf;
  lock();
  nread = rs->iptr - ibuf;
  tty_events -= nread;
  if (ibuf == rs->ibuf1)
	ibuf = rs->ibuf2;
  else
	ibuf = rs->ibuf1;
  rs->ibufend = ibuf + RS_IBUFSIZE;
  rs->ihighwater = ibuf + RS_IHIGHWATER;
  rs->iptr = ibuf;
  if (!rs->idevready && rs->ittyready) istart(rs);
  unlock();
  rs->ibuf = ibuf;
  return(nread);
}


/*==========================================================================*
 *				rs_setc					    *
 *==========================================================================*/
PUBLIC void rs_setc(minor, xoff)
int minor;			/* which rs line */
int xoff;			/* xoff character */
{
/* RS232 needs to know the xoff character. */

  rs_addr(minor)->oxoff = xoff;
}


/*==========================================================================*
 *				rs_write				    *
 *==========================================================================*/
PUBLIC void rs_write(minor, buf, nbytes)
int minor;			/* which rs line */
char *buf;			/* pointer to buffer to write */
int nbytes;			/* number of bytes to write */
{
/* Tell RS232 about the buffer to be written and start output.
 * Previous output must have completed.
 */

  register struct rs232_s *rs;

  rs = rs_addr(minor);
  lock();
  rs->obufend = (rs->optr = buf) + nbytes;
  rs->ostate |= OQUEUED;
  if (txready(rs)) out_int(rs);
  unlock();
}


/* Low level (interrupt) routines. */

#if C_RS232_INT_HANDLERS
/*==========================================================================*
 *				rs232_1handler				    *
 *==========================================================================*/
PUBLIC void rs232_1handler()
{
/* Interrupt hander for IRQ4.
 * Only 1 line (usually COM1) should use it.
 */

#if NR_RS_LINES > 0
  register struct rs232_s *rs;

  rs = &rs_lines[0];
  while (TRUE) {
	/* 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.
	 */
	switch (in_byte(rs->int_id_port)) {
	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;
  }
#endif
}


/*==========================================================================*
 *				rs232_2handler				    *
 *==========================================================================*/
PUBLIC void rs232_2handler()
{
/* Interrupt hander for IRQ3.
 * Only 1 line (usually COM2) should use it.
 */

#if NR_RS_LINES > 1
  register struct rs232_s *rs;

  rs = &rs_lines[1];
  while (TRUE) {
	switch (in_byte(rs->int_id_port)) {
	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;
  }
#endif
}


/*==========================================================================*
 *				in_int					    *
 *==========================================================================*/
PRIVATE void in_int(rs)
register struct rs232_s *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.
 */

  if (rs->ostate & ORAW)
	*rs->iptr = in_byte(rs->recv_port);
  else if ( (*rs->iptr = in_byte(rs->recv_port)) == rs->oxoff)
	rs->ostate &= ~OSWREADY;
  else if (!(rs->ostate & OSWREADY)) {
	rs->ostate |= OSWREADY;
	if (txready(rs)) out_int(rs);
  }
  if (rs->iptr < rs->ibufend) {
	++tty_events;
	if (++rs->iptr == rs->ihighwater) istop(rs);
  }
}


/*==========================================================================*
 *				line_int				    *
 *==========================================================================*/
PRIVATE void line_int(rs)
register struct rs232_s *rs;	/* line with line status interrupt */
{
/* Check for and record errors. */

  rs->lstatus = in_byte(rs->line_status_port);
  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(rs)
register struct rs232_s *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 (!devready(rs))
	rs->ostate &= ~ODEVREADY;
  else if (!(rs->ostate & ODEVREADY)) {
	rs->ostate |= ODEVREADY;
	if (txready(rs)) out_int(rs);
  }
}

#endif /* C_RS232_INT_HANDLERS (except out_int is used from high level) */


/*==========================================================================*
 *				out_int					    *
 *==========================================================================*/
PRIVATE void out_int(rs)
register struct rs232_s *rs;	/* line with output interrupt */
{
/* If there is output to do and everything is ready, do it (local device is
 * known ready).
 * Interrupt TTY to indicate completion.
 */

  if (rs->ostate >= (ODEVREADY | OQUEUED | OSWREADY)) {
	/* Bit test allows ORAW and requires the others. */
	out_byte(rs->xmit_port, *rs->optr);
	if (++rs->optr >= rs->obufend) {
		tty_events += EVENT_THRESHOLD;
		rs->ostate ^= (ODONE | OQUEUED);  /* ODONE on, OQUEUED off */
		unlock();	/* safe, for complicated reasons */
		tty_wakeup();
		lock();
	}
  }
}