[comp.os.minix] Serial TTY driver

csrng@daisy.warwick.ac.uk (C S PCB Group) (12/14/87)

We use MINIX permanently through a serial tty driver, and we too had
the problem of hanging due to missed interrupts.

The problems are to do with the way MINIX deals with interrupts that cannot
be handled immediately. Basically when an interrupt goes off , if it cannot
be accepted by the relevant task then the message is remembered and sent
when that task becomes free. The first problem is that there is only one
pending message so further missed interupts overwrite previous ones, this
is usually the problem with the tty. If you type fast enough you will
overwrite an output interupt with an input one. Usually this will cause
the serial driver to hang up. If you cure this by having one interupt
slot for each task things improve, but if you wish for a task to handle
more than one interrupt, such as the tty driver with input and output,
perhaps to multiple lines, then you have to change the way messages are
queued. We use one slot for each task * maximum number of interrups each
task can accept, which is kind of wasteful, but I can't be bothered to
rewrite it since it works. Even with this done we have GREAT difficulty
handling any signifant load on the serial lines. We have found 2400 baud
to the highest we can cope with. I guess a PC AT may do a bit better, but
don't expect wonders

John.

---------------------------------
John Vaudin: arthur@uk.ac.warwick

paradis@encore.UUCP (Jim Paradis) (01/07/88)

Here's part 2 of my serial TTY driver for MINIX:

Shar and enjoy! :-)

--------cut here--------valuable coupon--------unpack with /bin/sh------
echo x - mpx88.newstf
gres '^X' '' > mpx88.newstf << '/'
X.globl _ser1_int, _ser2_int
X
X|*===========================================================================*
X|*				ser1_int				     *
X|*===========================================================================*
X_ser1_int:			| Interrupt routine for primary serial port
X	call save		| save the machine state
X	mov ax,#0
X	push ax
X	call _ser_int		| process a serial interrupt
X	jmp _restart		| continue execution
X
X|*===========================================================================*
X|*				ser2_int				     *
X|*===========================================================================*
X_ser2_int:			| Interrupt routine for secondary serial port
X	call save		| save the machine state
X	mov ax,#1
X	push ax
X	call _ser_int		| process a keyboard interrupt
X	jmp _restart		| continue execution
X
/
echo x - rs232.c
gres '^X' '' > rs232.c << '/'
X/**************************************************************************
X * File: rs232.c
X * Creation date: 11/09/87
X *
X * All original code is Copyright 1988, James R. Paradis.  Permission
X * granted to copy and redistribute for educational and non-commercial
X * use.  Commercial use requires permission of copyright holder.
X *
X * This file contains the low-level serial driver support routines 
X * for the MINIX tty driver.  These routines interface directly to
X * the IBM-PC hardware.
X *
X * Entry points:
X *
X *	ser_init(minor, dev_num)
X *      ser_putc(minor, char)
X *	ser_ioctl(minor, func, addr)
X *	ser_fc(minor)
X *	ser_nofc(minor)
X * 	ser_oflush(minor)
X *	ser_bufin(minor)
X *
X * Internal routines:
X *	ser_int(line)
X *
X **************************************************************************/
X
X#include "../h/type.h"
X#include "../h/const.h"
X#include "../h/com.h"
X#include "tty.h"
X#include "const.h"
X
X
X#define RS232_OBUFSZ	0x100
X#define RS_OBUFMASK	0x0ff
X#define CIBUFSZ		0x80
X#define CIBUFSZMASK	0x7f
X#define MAX_CHARS_PER_INT	3
X
X/* Minor-device table, to translate TTY minor devices to serial line
X * numbers.
X */
XPRIVATE int	ser_minor[NUM_SERIAL_DEV];
X
X/* Inverse mapping table, to translate minor device numbers to
X * serial line numbers.  Note that this table assumes that we'll
X * NEVER have a minor device number greater than NUM_CONSOLES +
X * NUM_SERIAL_DEVS!
X */
XPRIVATE	int	ser_lns[NUM_CONSOLES + NUM_SERIAL_DEV];
X#define	find_rsstruct(minor) (&(ser_lines[ser_lns[minor]]))
X
X/* RS232 device structure, one per device.  */
Xtypedef struct {
X    char	rs_obuf[RS232_OBUFSZ];	/* TTY output buffer */
X    int		rs_first;		/* Circular queue, first element */
X    int		rs_last;		/* Last element */
X    int		rs_outrdy;		/* Ready for immediate output */
X    int		rs_qsize;		/* Size of queue */
X    int		rs_line;		/* Line number */
X    int		rs_fc;			/* 1 if flow ctl on, 0 if off */
X    message	rs_ttymsg;		/* Message buffer */
X    int		rs_ibuf[CIBUFSZ];	/* Input buffer */
X    int		rs_ifirst;		/* Queue pointers */
X    int		rs_ilast;
X    int		rs_baud;		/* Baud rate divisor */
X    int		rs_cint_sent;
X} RS232_STRUCT;
X
XRS232_STRUCT	ser_lines[NUM_SERIAL_DEV];
X
X/* Port defines.  Note that these are macros, with the argument being
X * the line number.  Currently these macros work for lines 0 and 1.
X * Other lines will require different macros.
X */
X
X/* Table of 8250 base addresses.  */
XPRIVATE	int	addr_8250[] = {
X			0x3f8,		/* COM1: (line 0) */
X			0x2f8,		/* COM2: (line 1) */
X			0x3e8,		/* COM3: (line 2) */
X			0x2e8 };	/* COM4: (line 3) */
X
X#define	XMIT_REG(line)	addr_8250[line]
X#define RECV_REG(line)	addr_8250[line]
X#define DIV_LOW_REG(line)	addr_8250[line]
X#define INT_ENA_REG(line)	(addr_8250[line] + 1)
X#define DIV_HI_REG(line)	(addr_8250[line] + 1)
X#define INT_ID_REG(line)	(addr_8250[line] + 2)
X#define LINE_CTL_REG(line)	(addr_8250[line] + 3)
X#define MOD_CTL_REG(line)	(addr_8250[line] + 4)
X#define LINE_STATUS_REG(line)	(addr_8250[line] + 5)
X#define MOD_STATUS_REG(line)	(addr_8250[line] + 6)
X
X/* Value definitions for the II (interrtupt ID) register */
X#define II_MASK		7
X#define	II_LINE		6	/* Receiver line status */
X#define II_DATA_READY	4	/* Received data available */
X#define II_OUTPUT_READY 2	/* Transmit holding register empty */
X#define	II_MODEM	0	/* MODEM status available */
X#define II_INT_PENDING  1	/* This bit is ZERO if interrupt pending! */
X
X/* Bit defines for the IE (interrupt enable) register */
X#define	IE_ERBFI	1	/* Rec'd data available interrupt */
X#define IE_ETBEI	2	/* Trans hold reg empty interrupt */
X#define IE_ELSI		4	/* Line status interrupt */
X#define IE_EDSSI	8	/* Modem status interrupt */
X
X/* Bit defines for the LC (line control) register */
X#define LC_5BITS	0	/* Word length = 5 bits */
X#define LC_6BITS	1	/* Word length = 6 bits */
X#define LC_7BITS	2	/* Word length = 7 bits */
X#define LC_8BITS	3	/* Word length = 8 bits */
X#define LC_1STOP	0	/* One stop bit */
X#define LC_2STOP	4	/* Two stop bits */
X#define LC_PE		8	/* Parity enable */
X#define LC_EVENP	0x10	/* Even parity select */
X#define LC_STICKP	0x20	/* Stick parity */
X#define LC_SETBRK	0x40	/* Set (send) break */
X#define LC_DLAB		0x80	/* Divisor latch address bit */
X
X/* Bit defines for the MC (modem control) register */
X#define MC_DTR		1
X#define MC_RTS		2
X#define MC_OUT1		4
X#define MC_OUT2		8
X#define MC_LOOP		0x10
X
X/* Bit defines for the LS (line status) register */
X#define LS_DATA_READY	1
X#define LS_OVERRUN	2
X#define LS_PERROR	4	/* Parity error */
X#define LS_FERROR	8	/* Framing error */
X#define LS_BREAK	0x10	/* Break interrupt */
X#define LS_THRE		0x20	/* Transmit holding register empty */
X#define LS_TSRE		0x40	/* Transmit shift register empty */
X
X/* Bit defines for MS (modem status) register */
X#define MS_DCTS		1
X#define MS_DDSR		2
X#define MS_TERI		4
X#define MS_DSLSD	8
X#define MS_CTS		0x10
X#define MS_DSR		0x20
X#define MS_RING		0x40
X#define MS_RLSD		0x80
X
X/* Divisor defines for various baud rates */
X#define	DIV_110		1047		/* 110 baud */
X#define	DIV_150		768		/* 150 baud */
X#define DIV_300		384		/* 300 baud */
X#define DIV_600		192		/* 600 baud */
X#define DIV_1200	96		/* 1200 baud */
X#define DIV_2400	48		/* 2400 baud */
X#define DIV_4800	24		/* 4800 baud */
X#define DIV_9600	12		/* 9600 baud */
X
X/************************************************************************
X * Function:
X *	ser_init(minor, devnum)
X *
X * Description:
X * 	Initializes the serial layer
X *
X * Arguments:
X *	minor	- Minor device number for this device in the tty
X *		  driver.
X *
X *	devnum	- Hardware device number to associate with this
X *		  minor device number.
X *
X * Return Value:
X *	None.
X *
X *************************************************************************/
XPUBLIC ser_init(minor, devnum)
Xint minor;
Xint devnum;
X{
X
X    /* Record the minor device number in the table */
X    ser_minor[devnum] = minor;
X    ser_lns[minor] = devnum;
X
X    /* Set up the serial line output queue */
X    ser_lines[devnum].rs_first = 0;
X    ser_lines[devnum].rs_last = 0;
X    ser_lines[devnum].rs_outrdy = 1;
X    ser_lines[devnum].rs_qsize = 0;
X    ser_lines[devnum].rs_line = devnum;
X    ser_lines[devnum].rs_fc = 0;
X    ser_lines[devnum].rs_ifirst = 0;
X    ser_lines[devnum].rs_ilast = 0;
X    ser_lines[devnum].rs_baud = DIV_2400;
X    ser_lines[devnum].rs_cint_sent = 0;
X
X    /* Set us up initially for 2400 baud, no parity, 8 data bits,
X     * 1 stop bit
X     */
X    port_out(LINE_CTL_REG(devnum), LC_8BITS | LC_1STOP);
X    port_out(INT_ENA_REG(devnum), 0);
X    port_out(MOD_CTL_REG(devnum), MC_OUT2 | MC_RTS | MC_DTR);
X    port_out(LINE_CTL_REG(devnum), LC_DLAB);
X    port_out(DIV_HI_REG(devnum), DIV_2400 >> 8);
X    port_out(DIV_LOW_REG(devnum), DIV_2400 & 0xff);
X    port_out(LINE_CTL_REG(devnum), LC_8BITS | LC_1STOP );
X
X    /* Enable interrupts for data ready and transmit holding
X     * register empty
X     */
X    port_out(INT_ENA_REG(devnum), IE_ERBFI | IE_ETBEI);
X
X}
X
X
X/************************************************************************
X * Function:
X *	ser_putc(minor, ch)
X *
X * Description:
X *	Writes a character out to the serial line.
X *
X * Arguments:
X *	minor - Minor device number.  
X *
X *	ch -	character to write out.
X *
X * Return Value:
X *	None
X *
X *************************************************************************/
XPUBLIC ser_putc(minor, ch)
Xint minor;
Xchar ch;
X{
X    RS232_STRUCT *	rs;
X
X    rs = find_rsstruct(minor);
X
X    /* If the device is ready NOW, then set the device as
X     * not-ready and send the byte out immediately.
X     */
X    if(rs->rs_outrdy) {
X	rs->rs_outrdy = 0;
X	port_out(XMIT_REG(rs->rs_line), ch);
X    }
X    else if(rs->rs_qsize < RS232_OBUFSZ) {
X	/* If there's room, then drop the character on the
X	 * output queue.  Otherwise, we drop it on the floor.
X	 */
X	rs->rs_obuf[rs->rs_last] = ch;
X	rs->rs_last = (rs->rs_last + 1) % RS232_OBUFSZ;
X	rs->rs_qsize++;
X    }
X
X}
X
X/************************************************************************
X * Function:
X *	ser_ioctl(minor, func, addr)
X *
X * Description:
X *	Performs ioctl functions.
X * 
X * Arguments:
X *	minor -	Minor device number.  
X *
X *	func -	ioctl function to perform.
X *
X *	addr -	Address of ioctl buffer.
X *
X * Return Value:
X *	None.
X *************************************************************************/
XPUBLIC ser_ioctl(minor, func, addr)
Xint minor;
Xint func;
Xchar * addr;
X{
X    /* Device IOCTLs not yet supported... */
X
X}
X
X
X/************************************************************************
X * Function:
X *	ser_fc(minor)
X *
X * Description:
X *	Turns on flow control for the specified serial line.
X * 
X * Arguments:
X *	minor -	Minor device number.
X *
X * Return Value:
X *	None.
X *************************************************************************/
XPUBLIC ser_fc(minor)
Xint minor;
X{
X    RS232_STRUCT * rs;
X
X    rs = find_rsstruct(minor);
X    rs->rs_fc = 1;
X
X}
X
X
X/************************************************************************
X * Function:
X *	ser_nofc(minor)
X *
X * Description:
X *	Turns off flow control for the specified serial line.
X * 
X * Arguments:
X *	minor - minor device number.
X *
X * Return Value:
X *	None.
X *************************************************************************/
XPUBLIC ser_nofc(minor)
Xint minor;
X{
X    RS232_STRUCT * rs;
X
X    rs = find_rsstruct(minor);
X    rs->rs_fc = 0;
X
X}
X
X
X/************************************************************************
X * Function:
X *	ser_oflush(minor)
X *
X * Description:
X *	Flushes any pending output on the specified serial line.
X * 
X * Arguments:
X *	minor -	Minor device number.
X *
X * Return Value:
X *	None.
X *************************************************************************/
XPUBLIC ser_oflush(minor)
Xint minor;
X{
X    RS232_STRUCT *	rs;
X
X    rs = find_rsstruct(minor);
X
X    /* Wait for interrupt service to take all the characters... */
X    while(rs->rs_qsize) ;
X    return;
X
X    rs->rs_outrdy = 1;
X
X}
X
X/************************************************************************
X * Function:
X *	ser_int(irq)
X *
X * Description:
X *	Called whenever an interrupt on a serial device occurs
X * 
X * Arguments:
X *	irq - indicates which interrupt happened. 0 = IRQ4, 1 =
X *	      IRQ3.
X *
X * Return Value:
X *	None.
X *************************************************************************/
Xser_int(irq)
Xint irq;
X{
X    int		istat;
X    int		line;
X    int		int_processed = 0;
X
X    switch(irq) {
X	case 0:
X	    /* IRQ4 can be either COM1 or COM3.  Try COM1 first */
X#if (NUM_SERIAL_DEV > 0)
X	    port_in(INT_ID_REG(0), &istat);
X	    if((istat & II_INT_PENDING) == 0) {
X		ser_doint(0, istat);
X		int_processed = 1;
X	    }
X#endif
X
X#if (NUM_SERIAL_DEV > 2)
X	    port_in(INT_ID_REG(2), &istat);
X	    if((istat & II_INT_PENDING) == 0) {
X		ser_doint(2, istat);
X		int_processed = 1;
X	    }
X#endif
X	    break;
X	    
X	case 1:
X
X	    /* IRQ3 can be either COM2 or COM4.  Try COM2 first */
X#if (NUM_SERIAL_DEV > 1)
X	    port_in(INT_ID_REG(1), &istat);
X	    if((istat & II_INT_PENDING) == 0) {
X		ser_doint(1, istat);
X		int_processed = 1;
X	    }
X#endif
X
X#if (NUM_SERIAL_DEV > 3)
X	    port_in(INT_ID_REG(3), &istat);
X	    if((istat & II_INT_PENDING) == 0) {
X		ser_doint(3, istat);
X		int_processed = 3;
X	    }
X#endif
X	    break;
X    }
X
X    if(!int_processed) {
X	/* Something wrong... */
X	printf("TTY: Int %d received but no interrupt pending!\n", irq);
X	port_out(INT_CTL, ENABLE);
X    }
X}
X
X/************************************************************************
X * Function:
X *	ser_doint(line, istat)
X *
X * Description:
X *	Called whenever an interrupt on a serial device occurs
X * 
X * Arguments:
X *	line - LINE number of the line that the interrupt occurred on.
X *	istat - interrupt status register for the line that the
X *		interrupt occurred on.
X *
X * Return Value:
X *	None.
X *************************************************************************/
XPRIVATE ser_doint(line, istat)
Xint line;
Xint istat;
X{
X    RS232_STRUCT    *rs;
X    int		    int_id;
X    int		    pstat;
X    int		    idata;
X    int		    i;
X    int		    send_interrupt = 0;
X    int		    another;
X    int		    latency;
X    int		    nchars_recd = 0;
X
X    rs = &(ser_lines[line]);
X
X    if(istat == II_DATA_READY) {
X	    /* A byte of data just came in.  Grab it, then see if
X	     * there are any other bytes immediately following.
X	     * Grab as many as we can, then send them all up to the
X	     * TTY layer.
X	     */
X
X	    port_in(RECV_REG(line), &idata);
X	    /* Drop the char on the floor if the buffer is full... */
X	    for(;;) {
X		if((((rs->rs_ilast + 1) & CIBUFSZMASK) == rs->rs_ifirst) ||
X		   (++nchars_recd > MAX_CHARS_PER_INT)) { break; }
X		rs->rs_ibuf[rs->rs_ilast] = (idata & 0xff);
X		rs->rs_ilast = (rs->rs_ilast + 1) & CIBUFSZMASK;
X
X		/* Hang around a bit and see if there's another
X		 * character coming...
X		 */
X		another = 0;
X		for(latency = 0; latency < (rs->rs_baud << 1); latency++) {
X		    int		pstatus;
X		    port_in(LINE_STATUS_REG(line), &pstatus);
X		    if(pstatus & LS_DATA_READY) {
X		 	another = 1;
X			break;
X		    }
X		}
X		if(another) {
X	            port_in(RECV_REG(line), &idata);
X		}
X		else {
X		    break;
X		}
X	    }
X
X	    /* Prepare a message to send to the TTY layer */
X	    rs->rs_ttymsg.m_type = TTY_CHAR_INT;
X	    rs->rs_ttymsg.TTY_LINE = ser_minor[line];
X	    send_interrupt = 1;
X    }
X
X    else if(istat == II_OUTPUT_READY) {
X	    /* Ready to output a character.
X	     * Determine if there's anything to send.  If there
X	     * is, then send it.  Otherwise, mark this tty as
X	     * being ready for immediate output.
X	     */
X
X	    if(rs->rs_qsize > 0) {
X
X		port_out(XMIT_REG(line), rs->rs_obuf[rs->rs_first]);
X		rs->rs_first = (rs->rs_first + 1) % RS232_OBUFSZ;
X		rs->rs_qsize--;
X
X	    }
X	    else {
X		rs->rs_outrdy = 1;
X	    }
X
X    }
X    
X    /* EITHER enable interrupts OR send interrupt to TTY (since
X     * the interrupt() routine enables interrupts also...
X     */
X    if(send_interrupt && !(rs->rs_cint_sent)) {
X	rs->rs_cint_sent = 1;
X	interrupt(TTY, &(rs->rs_ttymsg));
X    }
X    else {
X        port_out(INT_CTL, ENABLE);
X    }
X}
X
X/************************************************************************
X * Function:
X *	ser_bufin(minor, bufptr, maxchars)
X *
X * Description:
X *	Called by the TTY layer to get whatever buffered input
X *	the device layer may have.
X * 
X * Arguments:
X *	minor	Minor device.
X *	bufptr	Address of buffer to put characters into.
X *	maxchars Maximum number of characters to get.
X *
X * Return value:
X *	Returns number of characters actually gotten.
X *
X *************************************************************************/
XPUBLIC int ser_bufin(minor, bufptr, maxchars)
Xint minor;
Xchar * bufptr;
Xint maxchars;
X{
X    RS232_STRUCT * rs;
X    int		nchars;
X    int		i;
X
X    rs = find_rsstruct(minor);
X
X
X    /* nchars = min(maxchars, con_size) */
X    nchars = rs->rs_ilast - rs->rs_ifirst;
X    if(nchars < 0) nchars += CIBUFSZ;
X    if(maxchars < nchars) nchars = maxchars;
X
X    for(i = 0; i < nchars; i++) {
X	bufptr[i] = rs->rs_ibuf[rs->rs_ifirst];
X	rs->rs_ifirst = (rs->rs_ifirst + 1) & CIBUFSZMASK;
X    }
X
X    rs->rs_cint_sent = 0;
X    return(nchars);
X}
X
X
/
echo x - t.c
gres '^X' '' > t.c << '/'
X#include <signal.h>
X#include <sgtty.h>
X#include <stdio.h>
X
Xmain()
X{
X   int	pid;
X
X   printf("Poor Man's Terminal Program, V0.0.\n");
X   printf("Hit F10 to return to MINIX\n\n");
X   
X   pid = fork();
X   if(pid < 0) {
X	perror("Cannot fork");
X	exit(0);
X   }
X   else if(pid == 0) {
X	/* Child path. */
X	indata();
X   }
X   else {
X	/* Parent path */
X	outdata();
X	/* Upon return, kill child proc */
X	kill(pid, 9);
X   }
X}
X
Xindata()
X{
X	int	fd;
X	char	buffer[128];
X	int	nread;
X	struct	sgttyb	st;
X
X	/* Open the terminal */
X	fd = open("/dev/tty1", 2);
X	if(fd < 0) {
X		perror("Cannot open /dev/tty1");
X		exit(0);
X	}
X
X	/* Set raw mode */
X	ioctl(fd, TIOCGETP, &st);
X	st.sg_flags &= ~(ECHO | RAW | CBREAK);
X	st.sg_flags |= RAW;
X	ioctl(fd, TIOCSETP, &st);
X
X	/* Loop forever, getting whatever we can from the
X	 * tty and writing it out to our terminal.
X	 */
X	for(;;) {
X
X	    nread = read(fd, buffer, 128);
X
X	    if(nread < 0) {
X		perror("Read error on /dev/tty1");
X	    }
X	    else {
X
X		write(1, buffer, nread);
X
X	    }
X	}
X}
X
Xoutdata()
X{
X	int	fd;
X	char	buffer[128];
X	int	nread;
X	struct sgttyb	st;
X	struct sgttyb	old_tty_st;
X
X	/* Open the terminal */
X	fd = open("/dev/tty1", 2);
X	if(fd < 0) {
X		perror("Cannot open /dev/tty1");
X		exit(0);
X	}
X
X	/* Set raw mode */
X	ioctl(fd, TIOCGETP, &st);
X	st.sg_flags &= ~(ECHO | RAW | CBREAK);
X	st.sg_flags |= RAW;
X	ioctl(fd, TIOCSETP, &st);
X
X	/* Set our own terminal for raw mode... */
X	ioctl(0, TIOCGETP, &st);
X	ioctl(0, TIOCGETP, &old_tty_st);
X	st.sg_flags &= ~(ECHO | RAW | CBREAK);
X	st.sg_flags |= RAW;
X	ioctl(0, TIOCSETP, &st);
X
X	/* Loop forever, getting whatever we can from our
X	 * console and writing it out to the tty.
X	 */
X	for(;;) {
X
X	    nread = read(0, buffer, 128);
X
X	    if(nread < 0) {
X		perror("Read error on console");
X	    }
X	    else if(buffer[0] == 27) {
X		if(buffer[1] == 'O' && buffer[2] == 'Y') {
X		    /* F10 hit */
X		    ioctl(0, TIOCSETP, &old_tty_st);
X		    return;
X		}
X		else {
X		    write(fd, buffer, nread);
X		}
X	    }
X	    else {
X		if(write(fd, buffer, nread) < 0) {
X		    perror("Write error on /dev/tty1");
X		}
X	    }
X	}
X}
X
X
X
X
X
X
X
X
X
X
/
echo x - tty.c
gres '^X' '' > tty.c << '/'
X/* All original code is Copyright 1988, James R. Paradis.  Permission
X * granted to copy and redistribute for educational and non-commercial
X * use.  Commercial use requires permission of copyright holder.
X *
X * This file contains the device-independent portion of the MINIX terminal
X * driver.  It has one entry point: tty_task().
X *
X * The valid messages and their parameters are:
X *
X *   TTY_CHAR_INT: a character has been typed on a terminal (input interrupt)
X *   TTY_READ:     a process wants to read from a terminal
X *   TTY_WRITE:    a process wants to write on a terminal
X *   TTY_IOCTL:    a process wants to change a terminal's parameters
X *   CANCEL:       terminate a previous incomplete system call immediately
X *
X *    m_type      TTY_LINE   PROC_NR    COUNT   TTY_SPEK  TTY_FLAGS  ADDRESS
X * ---------------------------------------------------------------------------
X * | TTY_CHAR_INT|minor dev|         |         |         |         |         |
X * |-------------+---------+---------+---------+---------+---------+---------|
X * | TTY_READ    |minor dev| proc nr |  count  |         |         | buf ptr |
X * |-------------+---------+---------+---------+---------+---------+---------|
X * | TTY_WRITE   |minor dev| proc nr |  count  |         |         | buf ptr |
X * |-------------+---------+---------+---------+---------+---------+---------|
X * | TTY_IOCTL   |minor dev| proc nr |func code|erase etc|  flags  |         |
X * |-------------+---------+---------+---------+---------+---------+---------|
X * | CANCEL      |minor dev| proc nr |         |         |         |         |
X * ---------------------------------------------------------------------------
X */
X
X#include "../h/const.h"
X#include "../h/type.h"
X#include "../h/callnr.h"
X#include "../h/com.h"
X#include "../h/error.h"
X#include "../h/sgtty.h"
X#include "../h/signal.h"
X#include "const.h"
X#include "type.h"
X#include "glo.h"
X#include "proc.h"
X#include "tty.h"
X
XPRIVATE TTY_STRUCT	tty_list[NUM_TTYS];
X
X/* Staging buffer for data copies */
XPRIVATE	char	tty_sbuf[TTY_IBUFSZ];
X
X
X/* External function declarations */
Xextern	void	con_init();
Xextern	void	con_putc();
Xextern	void	con_ioctl();
Xextern	void	con_fc();
Xextern	void	con_nofc();
Xextern	void	con_oflush();
Xextern  int	con_bufin();
X
Xextern	void	ser_init();
Xextern	void	ser_putc();
Xextern	void	ser_ioctl();
Xextern	void	ser_fc();
Xextern	void	ser_nofc();
Xextern	void	ser_oflush();
Xextern	int	ser_bufin();
X
X
XPRIVATE	char	dq_fifo_char();
XPRIVATE	char	dq_lifo_char();
X
Xextern	phys_bytes	umap();
X
XPRIVATE	message	recv_message;
XPRIVATE message reply_message;
XPRIVATE message *recv_ptr;
XPRIVATE message *reply_ptr;
X
X/***********************************************************************
X * Function:
X *	tty_task()
X *
X * Description:
X *	Main entry point for the TTY driver.  Initializes the tty
X *	driver and the low-level devices, then enters an endless
X *	loop waiting for messages and processing them.
X * 
X * Arguments:
X *	None
X *
X * Return Value:
X *	Never returns!
X *
X *************************************************************************/
X
XPUBLIC tty_task()
X{
X/* Main routine of the terminal task. */
X
X  register TTY_STRUCT *tp;
X  int	i, ttnum;
X
X  recv_ptr = &recv_message;
X  reply_ptr = &reply_message;
X
X  tty_init();			/* initialize */
X
X  while (TRUE) {
X	receive(ANY, recv_ptr);
X	tp = &tty_list[recv_ptr->TTY_LINE];
X
X	switch(recv_ptr->m_type) {
X	    case TTY_CHAR_INT:
X		ttnum = recv_ptr->TTY_LINE;
X		for(i = 0; i < NUM_TTYS; i++) {
X		    do_charint(&tty_list[ttnum]);
X		    ttnum = (ttnum + 1) % NUM_TTYS;
X		}
X		break;
X	    case TTY_READ:	do_read(tp, recv_ptr);		break;
X	    case TTY_WRITE:	do_write(tp, recv_ptr);	break;
X	    case TTY_IOCTL:	do_ioctl(tp, recv_ptr);	break;
X	    case CANCEL   :	do_cancel(tp, recv_ptr);	break;
X	    default:		
X		tty_reply(TASK_REPLY, recv_ptr->m_source, 
X				recv_ptr->PROC_NR, EINVAL, 0L, 0L);
X		break;
X	}
X  }
X}
X
X
X/************************************************************************
X * Function:
X *	tty_init()
X *
X * Description:
X *	Performs initialization functions for the TTY driver.
X *	Sets up the tty_list, then calls lower-level functions
X * 	to initialize the physical devices.
X *
X * Return Value:
X *	None
X *************************************************************************/
Xtty_init()
X{
X    int		i;
X    TTY_STRUCT	*tp;
X
X    /* First, set up those fields common to all TTYs */
X    for(i = 0; i < NUM_TTYS; i++) {
X	tp = &tty_list[i];
X
X	tp->tt_minor = i;
X	tp->tt_first = 0;
X	tp->tt_last = 0;
X	tp->tt_bufsz = 0;
X	tp->tt_last_nl = -1;
X	tp->tt_readsz = 0;
X	tp->tt_proc = 0;
X	tp->tt_usrbuf = (char *)0;
X	tp->tt_fc_on = TTY_FC_ON;
X	tp->tt_fc_off = TTY_FC_OFF;
X	tp->tt_erase = ERASE_CHAR;
X	tp->tt_kill = KILL_CHAR;
X	tp->tt_wera = WERA_CHAR;
X	tp->tt_intr = INTR_CHAR;
X	tp->tt_quit = QUIT_CHAR;
X	tp->tt_stop = STOP_CHAR;
X	tp->tt_susp = XOFF_CHAR;
X	tp->tt_resume = XON_CHAR;
X	tp->tt_rprnt = RDSP_CHAR;
X	tp->tt_lnext = LNXT_CHAR;
X	tp->tt_eot = EOT_CHAR;
X	tp->tt_uflags = CRMOD | ECHO;
X	tp->tt_iflags = 0;
X    }
X
X    /* Set up the function fields for the console */
X    tp = tty_list;
X    tp->tt_dev_init = con_init;
X    tp->tt_dev_putc = con_putc;
X    tp->tt_dev_ioctl = con_ioctl;
X    tp->tt_dev_fcon = con_fc;
X    tp->tt_dev_fcoff = con_nofc;
X    tp->tt_dev_oflush = con_oflush;	
X    tp->tt_dev_bufin = con_bufin;
X
X    /* Set up the function fields for the serial device(s) */
X
X    for(i = 1; i <= NUM_SERIAL_DEV; i++) {
X	tp = &tty_list[i];
X	tp->tt_dev_init = ser_init;
X	tp->tt_dev_putc = ser_putc;
X	tp->tt_dev_ioctl = ser_ioctl;
X	tp->tt_dev_fcon = ser_fc;
X	tp->tt_dev_fcoff = ser_nofc;
X	tp->tt_dev_oflush = ser_oflush;
X	tp->tt_dev_bufin = ser_bufin;
X    }
X
X
X    /* Call lower-level functions to initialize physical devices */
X    (tty_list[0].tt_dev_init)(0, 0);
X
X    for(i = 1; i <= NUM_SERIAL_DEV; i++) {
X        (tty_list[1].tt_dev_init)(i, i - 1);
X    }
X
X}
X
X
X/************************************************************************
X * Function:
X *	do_charint(tp, m_ptr)
X *
X * Description:
X *	Processes an incoming character from the specified serial
X *	device
X * 
X * Arguments:
X *	tp	Pointer to the TTY structure for the device
X *
X * Return Value:
X *	None
X *************************************************************************/
X
XPRIVATE do_charint(tp)
Xregister TTY_STRUCT * tp;
X{
X  char	cbuf[132];
X  char	nchars;
X  int	i;
X  char	ch;
X  char	tmp_ch;
X
X  /* Get the incoming character(s) */
X  nchars = (tp->tt_dev_bufin)(tp->tt_minor, cbuf, 132);
X  for(i = 0; i < nchars; i++) {
X    ch = cbuf[i];
X
X    /* If we're suspended, only accept resume char */
X    if(tp->tt_iflags & TT_SUSPENDED) {
X	if(ch == tp->tt_resume) {
X	    tp->tt_iflags &= ~TT_SUSPENDED;
X
X	    /* Do any blocked write */
X	    if(tp->tt_iflags & TT_WR_SUSP) {
X		int	result;
X
X		tp->tt_iflags &= ~TT_WR_SUSP;
X		result = tty_write(tp, tp->tt_wproc, tp->tt_waddr,
X			 tp->tt_wcount);
X		(tp->tt_dev_oflush)(tp->tt_minor);
X
X		tty_areply(REVIVE, (int)tp->tt_wcaller, 
X			    (int)tp->tt_wproc, (int)tp->tt_wrcount, 0L, 0L);
X	    }
X	}
X	/* Pass signal-generating characters through */
X#ifdef JOB_CONTROL
X	else if((ch != tp->tt_intr) && (ch != tp->tt_quit) &&
X		(ch != tp->tt_stop)) {
X#else
X	else if((ch != tp->tt_intr) && (ch != tp->tt_quit)) {
X#endif JOB_CONTROL
X	    return;
X	}
X    }
X
X    /* If we're in RAW mode or LITNEXT mode, then just take the
X     * character as input.
X     */
X    if((tp->tt_uflags & RAW) || (tp->tt_iflags & TT_LITNEXT)) {
X	enque_char(tp, ch);
X	if(tp->tt_uflags & ECHO) {
X	    output_char(tp, ch);
X	    (tp->tt_dev_oflush)(tp->tt_minor);
X	}
X	if(tp->tt_iflags & TT_LITNEXT) {
X	    tp->tt_iflags &= ~TT_LITNEXT;
X	}
X    }
X
X    /* Otherwise, see if the input character is a special char.
X     * If so, process it accordingly.  If not, enque it.  Note
X     * that if we're in CBREAK mode then we'll only process
X     * certain special chars:  \r, intr, quit, (stop), and xoff.
X     */
X    else {
X	if((ch == tp->tt_erase) && !(tp->tt_uflags & CBREAK)) {
X	    /* Erase the last character typed (if any) */
X	    if(tp->tt_bufsz > 0) {
X		/* But not if it's a newline */
X		if((tmp_ch = dq_lifo_char(tp)) == '\n') {
X		    enque_char(tp, '\n');
X		}
X		else {
X		    tty_do_bs(tp, tmp_ch);
X		    (tp->tt_dev_oflush)(tp->tt_minor);
X		}
X	    }
X	}
X	else if((ch == tp->tt_kill) && !(tp->tt_uflags & CBREAK)) {
X	    /* Erase everything up to the last newline OR to the
X	     * beginning of the buffer if none...
X	     */
X	    while(tp->tt_bufsz) {
X		if((tmp_ch = dq_lifo_char(tp)) == '\n') {
X		    enque_char(tp, '\n');
X		    break;
X		}
X		else {
X		    tty_do_bs(tp, tmp_ch);
X		}
X	    }
X	    (tp->tt_dev_oflush)(tp->tt_minor);
X	}
X	else if((ch == tp->tt_wera) && !(tp->tt_uflags & CBREAK)) {
X	    /* Erase everything up to the last newline OR whitespace OR
X	     * beginning of buffer.
X	     */
X	    while(tp->tt_bufsz) {
X
X		tmp_ch = dq_lifo_char(tp);
X		if (tmp_ch == '\n' || tmp_ch == ' ' || tmp_ch == '\t') {
X		    enque_char(tp, tmp_ch);
X		    break;
X		}
X
X		tty_do_bs(tp, tmp_ch);
X
X	    }
X
X	    /* Now chomp down any leading whitespace */
X
X	    while(tp->tt_bufsz) {
X
X		tmp_ch = dq_lifo_char(tp);
X		if(tmp_ch == ' ' || tmp_ch == '\t') {
X		    tty_do_bs(tp, tmp_ch);
X		}
X		else {
X		    enque_char(tp, tmp_ch);
X		    break;
X		}
X	    }
X	    (tp->tt_dev_oflush)(tp->tt_minor);
X	}
X	else if(ch == tp->tt_intr) {
X	    /* Send SIGINT to controlling process */
X	    tty_sig(tp, SIGINT);
X	}
X	else if(ch == tp->tt_quit) {
X	    /* Send SIGQUIT to controlling process */
X	    tty_sig(tp, SIGQUIT);
X	}
X#ifdef JOB_CONTROL
X	else if(ch == tp->tt_stop) {
X	    tty_sig(tp, SIGTSTP);
X	}
X#endif JOB_CONTROL
X	else if(ch == tp->tt_susp) {
X	    /* Suspend I/O on this device */
X	    tp->tt_iflags |= TT_SUSPENDED;
X	}
X	else if(ch == tp->tt_resume) {
X	    /* Do nothing with resume char */
X	}
X	else if(ch == tp->tt_rprnt && (tp->tt_uflags & ECHO)
X		&& !(tp->tt_uflags & CBREAK)) {
X	    /* Re-print the last line entered.  We'll do our own
X	     * queue traversal here, since it's rather weird and it's
X	     * a read-only operation on the queue.
X	     */
X	    int		i;
X
X	    /* Figure out where to start.  If there are newlines in the
X	     * buffer, then start after the last one.  Otherwise, start
X	     * at the beginning of the buffer.
X	     */
X	    if(tp->tt_last_nl < 0) {
X		i = tp->tt_first;
X	    }
X	    else {
X	        i = (tp->tt_last_nl + 1) % TTY_IBUFSZ;
X	    }
X
X	    /* Put out a newline first */
X	    (tp->tt_dev_putc)(tp->tt_minor, '\r');
X	    (tp->tt_dev_putc)(tp->tt_minor, '\n');
X
X	    /* Now put out each char in turn */
X	    while(i != tp->tt_last) {
X		if(tp->tt_inbuf[i] == '\n') {
X		    (tp->tt_dev_putc)(tp->tt_minor, '\r');
X		    (tp->tt_dev_putc)(tp->tt_minor, '\n');
X		}
X		else {
X		    output_char(tp, tp->tt_inbuf[i]);
X		}
X		i = (i + 1) % TTY_IBUFSZ;
X	    }
X	    (tp->tt_dev_oflush)(tp->tt_minor);
X	}
X	else if (ch == tp->tt_rprnt && !(tp->tt_uflags & CBREAK)) {
X	    /* Do nothing if rprnt typed but echo off */
X	}
X	else if (ch == tp->tt_lnext && !(tp->tt_uflags & CBREAK)) {
X	    /* Turn on 'literal-next' mode */
X	    tp->tt_iflags |= TT_LITNEXT;
X	}
X	else if ((ch == '\r') && (tp->tt_uflags & CRMOD)) {
X	    /* Map CR to newline */
X	    enque_char(tp, '\n');
X	    if(tp->tt_uflags & ECHO) {
X		(tp->tt_dev_putc)(tp->tt_minor, '\r');
X		(tp->tt_dev_putc)(tp->tt_minor, '\n');
X		(tp->tt_dev_oflush)(tp->tt_minor);
X	    }
X        }
X	else if(ch == '\n') {
X	    enque_char(tp, ch);
X	    if(tp->tt_uflags & ECHO) {
X		(tp->tt_dev_putc)(tp->tt_minor, ch);
X		(tp->tt_dev_oflush)(tp->tt_minor);
X	    }
X	}
X	else if(ch == tp->tt_eot) {
X	    /* if it's EOT, echo it only if it's LITNEXT, RAW, or CBREAK */
X	    enque_char(tp, ch);
X	    if((tp->tt_uflags & ECHO) && 
X	       ((tp->tt_uflags & (RAW | CBREAK)) || 
X		(tp->tt_iflags & TT_LITNEXT))) {
X		output_char(tp, ch);
X		(tp->tt_dev_oflush)(tp->tt_minor);
X	    }
X	}
X	else {
X	    /* It's a real character.  Enqueue it and echo if desired */
X	    enque_char(tp, ch);
X	    if(tp->tt_uflags & ECHO) {
X		output_char(tp, ch);
X		(tp->tt_dev_oflush)(tp->tt_minor);
X	    }
X	}
X    }
X  }
X
X  /* Determine if we can now satisfy any outstanding read requests.
X   * If we're in RAW or CBREAK mode, we can do so if there is at least
X   * one character in the buffer.  Otherwise, we can do so if there's
X   * a newline in the buffer OR if the buffer size is greater than the
X   * outstanding read size.
X   */
X  if((tp->tt_readsz > 0) && (tp->tt_bufsz > 0)) {
X    int	read_amt = 0;
X    if(tp->tt_uflags & (RAW | CBREAK)) {
X	read_amt = MIN(tp->tt_bufsz, tp->tt_readsz);
X    }
X    else if (tp->tt_last_nl > 0) {
X	int	i;
X
X	/* Calculate number of characters from beginning of
X	 * buffer to the first newline.  Include the newline
X	 * in the count! (EOT counts as newline since we're not
X	 * in RAW or CBREAK mode)
X	 */
X	i = tp->tt_first;
X	while((tp->tt_inbuf[i] != '\n') && 
X		  (tp->tt_inbuf[i] != MARKER)){
X	    read_amt++;
X	    i = (i + 1) % TTY_IBUFSZ;
X	}
X	/* Add one for the newline... */
X	read_amt++;
X
X	/* But never more than we asked for... */
X	if(read_amt > tp->tt_readsz) {
X	    read_amt = tp->tt_readsz;
X	}
X    }
X
X    /* If there's anything to copy out, do so here.
X     * Notify the calling process that a read request
X     * is being honored, then set the outstanding read
X     * request amount to zero.
X     */
X    if(read_amt > 0) {
X        /* copyout returns either the number of bytes read or
X         * E_BAD_ADDR
X         */
X
X        read_amt = copyout(tp, (char)tp->tt_caller, tp->tt_usrbuf, 
X			       read_amt);
X
X        tp->tt_readsz = 0;
X        tty_areply(REVIVE, tp->tt_proc, tp->tt_caller, read_amt, 
X			0L, 0L);
X    }
X  }
X}
X
Xtty_do_bs(tp, ch)
Xregister TTY_STRUCT *tp;
Xchar ch;			/* char we're backing up over */
X{
X    int		i, nbs;
X
X    nbs = (ch <= 27 && ch != 9) ? 2 : 1;
X
X    /* Put out ^H ^H (May want to make provision for
X     * non-crt terminals later)
X     */
X    if(tp->tt_uflags & ECHO) {
X	for(i = 0; i < nbs; i++) {
X		(tp->tt_dev_putc)(tp->tt_minor, 8);
X		(tp->tt_dev_putc)(tp->tt_minor, ' ');
X		(tp->tt_dev_putc)(tp->tt_minor, 8);
X	}
X    }
X}
X
X/************************************************************************
X * Function:
X *	do_read(tp, m_ptr)
X *
X * Description:
X *	Handle a read request.
X * 
X * Arguments:
X *	tp	Pointer to the TTY structure on which the read is
X *		requested.
X *	m_ptr	Pointer to the original message.
X *
X *************************************************************************/
XPRIVATE do_read(tp, m_ptr)
Xregister TTY_STRUCT *tp;		/* pointer to tty struct */
Xmessage *m_ptr;			/* pointer to message sent to the task */
X{
X    int		read_amt = 0;
X    int		i;
X
X
X    /* Never read anything bigger than your buffer... */
X    if(m_ptr->COUNT > TTY_IBUFSZ) {
X	tty_reply(TASK_REPLY, m_ptr->m_source, m_ptr->PROC_NR, E2BIG, 
X					0L, 0L);
X	return;
X    }
X
X    /* If someone else is hanging on this tty, give up */
X    if(tp->tt_readsz > 0) {
X	tty_reply(TASK_REPLY, m_ptr->m_source, m_ptr->PROC_NR,
X		  E_TRY_AGAIN, 0L, 0L);
X	return;
X    }
X
X    /* Determine if we can satisfy the request immediately. */
X
X    if(tp->tt_uflags & (RAW | CBREAK)) {
X
X	/* RAW and CBREAK mode will be satisfied with as little
X	 * as one character...
X	 */
X	read_amt = MIN(tp->tt_bufsz, m_ptr->COUNT);
X    }
X    else if (tp->tt_last_nl > 0) {
X
X	/* If we're in cooked mode, then a newline will
X	 * terminate the read.  Feed back whatever characters
X	 * we have up to the newline.  EOT counts as newline,
X	 * since we're not in RAW mode.
X	 */
X	i = tp->tt_first;
X        while((tp->tt_inbuf[i] != '\n') && 
X	      (tp->tt_inbuf[i] != MARKER)){
X	    read_amt++;
X	    i = (i + 1) % TTY_IBUFSZ;
X	}
X	/* Add one for the newline... */
X	read_amt++;
X
X	/* But don't EVER give back more than we asked for! */
X	if(read_amt > m_ptr->COUNT) {
X	    read_amt = m_ptr->COUNT;
X	}
X    }
X    else if (tp->tt_bufsz >= m_ptr->COUNT) {
X	/* No newlines in the buffer, but we have enough characters
X	 * to satisfy the read request, so let's do it!
X	 */
X	read_amt = m_ptr->COUNT;
X    }
X
X    /* If there's anything to copy out, do so here.
X     * Notify the calling process that a read request
X     * is being honored, then set the outstanding read
X     * request amount to zero.
X     */
X    if(read_amt > 0) {
X	/* copyout returns either the number of bytes read or E_BAD_ADDR */
X
X        read_amt = copyout(tp, (char)m_ptr->PROC_NR, 
X			   (char *)(m_ptr->ADDRESS), read_amt);
X
X	tp->tt_readsz = 0;
X	tty_reply(TASK_REPLY, m_ptr->m_source, m_ptr->PROC_NR, read_amt,
X			0L, 0L);
X    }
X    else {
X	/* We can't satisfy the read request right away, so we
X	 * record the parameters of this read request and tell
X	 * the caller to suspend itself...
X	 */
X	tp->tt_readsz = m_ptr->COUNT;
X	tp->tt_proc = m_ptr->m_source;
X	tp->tt_caller = m_ptr->PROC_NR;
X	tp->tt_usrbuf = (char *)(m_ptr->ADDRESS);
X	tty_reply(TASK_REPLY, m_ptr->m_source, m_ptr->PROC_NR,
X		  SUSPEND, 0L, 0L);
X    }
X
X}
X
X
X/************************************************************************
X * Function:
X *	copyout(tp, proc_nr, vaddr, nbytes)
X *
X * Description:
X *	Copy the specified number of bytes from the input queue
X *	to the specified user buffer.
X * 
X * Arguments:
X *	tp		Pointer to the TTY struct
X *	proc_nr		Proc number of the user process to copy to
X *	vaddr		Process virtual address of the destination
X *	nbytes		Number of bytes to copy
X *
X * Return Value:
X *	Number of bytes copied if successful
X *	0 if unsuccessful.
X *************************************************************************/
XPRIVATE copyout(tp, proc_nr, vaddr, nbytes)
XTTY_STRUCT * tp;
Xchar proc_nr;
Xchar * vaddr;
Xint nbytes;
X{
X    phys_bytes	user_phys, tty_phys;
X    int		i;
X    int		num_copied = 0;
X    char	ch;
X
X    user_phys = umap(proc_addr(proc_nr), D, (vir_bytes)vaddr, 
X			(vir_bytes)nbytes);
X    if(user_phys == 0L) { 
X	return (E_BAD_ADDR);
X    }
X
X    tty_phys = umap(proc_addr(TTY), D, (vir_bytes)tty_sbuf, 
X			(vir_bytes)nbytes);
X
X    /* Take the bytes off the queue and stuff them into the staging
X     * buffer.
X     */
X    for(i = 0; i < nbytes; i++) {
X	ch = dq_fifo_char(tp);
X	if(ch != MARKER || (tp->tt_uflags & (RAW | CBREAK))) {
X	    tty_sbuf[num_copied++] = ch;
X	}
X    }
X
X    phys_copy((long)tty_phys, (long)user_phys, (long)num_copied);
X    return(num_copied);
X}
X
X/************************************************************************
X * Function:
X *	do_write(tp, m_ptr)
X *
X * Description:
X *	Handle a write request.
X * 
X * Arguments:
X *	tp	Pointer to the TTY structure on which the write is
X *		requested.
X *	m_ptr	Pointer to the original message.
X *
X *************************************************************************/
XPRIVATE do_write(tp, m_ptr)
Xregister TTY_STRUCT *tp;		/* pointer to tty struct */
Xmessage *m_ptr;			/* pointer to message sent to the task */
X{
X
X    int	result;
X    int	address;
X    int	count;
X    int num_written;
X    int num_to_write;
X
X    /* If we're suspended, then suspend this write */
X    if(tp->tt_iflags & TT_SUSPENDED) {
X	tp->tt_iflags |= TT_WR_SUSP;
X	tp->tt_wproc = m_ptr->PROC_NR;
X	tp->tt_wcaller = m_ptr->m_source;
X	tp->tt_waddr = m_ptr->ADDRESS;
X	tp->tt_wcount = m_ptr->COUNT;
X	tp->tt_wrcount = m_ptr->COUNT;
X	tty_reply(TASK_REPLY, m_ptr->m_source, m_ptr->PROC_NR, SUSPEND,
X		  0L, 0L);
X	return;
X    }
X
X    /* Otherwise, do the write */
X    address = m_ptr->ADDRESS;
X    count = m_ptr->COUNT;
X    num_written = 0;
X    while(count > 0) {
X	num_to_write = (count > WRITE_CHUNK_SIZE) ? WRITE_CHUNK_SIZE : count;
X        result = tty_write(tp, (char)m_ptr->PROC_NR, 
X			   (vir_bytes)m_ptr->ADDRESS, (int)m_ptr->COUNT);
X	num_written += result;
X	count -= result;
X	address += result;
X
X        /* If we're suspended, then suspend this write */
X        if(tp->tt_iflags & TT_SUSPENDED) {
X	    tp->tt_iflags |= TT_WR_SUSP;
X	    tp->tt_wproc = m_ptr->PROC_NR;
X	    tp->tt_wcaller = m_ptr->m_source;
X	    tp->tt_waddr = address;
X	    tp->tt_wcount = count;
X	    tp->tt_wrcount = m_ptr->COUNT;
X	    tty_reply(TASK_REPLY, m_ptr->m_source, m_ptr->PROC_NR, SUSPEND,
X		  0L, 0L);
X	    return;
X	}
X    }
X	
X
X    /* Signal the calling process that we're done, THEN flush
X     * the data in the device layer.  This way, the caller doesn't
X     * get hung up waiting for the flush to complete.
X     */
X    tty_reply(TASK_REPLY, m_ptr->m_source, m_ptr->PROC_NR, result,
X		0L, 0L);
X
X    (tp->tt_dev_oflush)(tp->tt_minor);
X
X}
X
X/************************************************************************
X * Function:
X *	tty_write(tp, uproc, uaddr, ucount)
X *
X * Description:
X *	Actually do the writing.  This form of the function can be
X *	called from anywhere within the tty driver.
X * 
X * Arguments:
X *	tp	Pointer to the TTY structure on which the write is
X *		requested.
X *	uproc	Process number of the user process doing the write
X *	uaddr	Virtual address (in user space) of the data to write
X *	ucount	Number of bytes to write.
X *
X *************************************************************************/
XPRIVATE tty_write(tp, uproc, uaddr, ucount)
Xregister TTY_STRUCT *tp;		/* pointer to tty struct */
Xchar		uproc;
Xvir_bytes	uaddr;
Xint		ucount;
X{
X    phys_bytes	user_phys, tty_phys;
X    int	i, bytes_to_copy, bytes_written;
X
X    bytes_written = 0;
X
X    while(ucount > 0) {
X
X	bytes_to_copy = MIN(ucount, TTY_IBUFSZ);
X
X        /* Copy the data to be written into the TTY staging buffer */
X        user_phys = umap(proc_addr(uproc), D, 
X		     (vir_bytes)uaddr, (vir_bytes)bytes_to_copy);
X
X        if(user_phys == 0) {
X	    return(E_BAD_ADDR);
X        }
X
X        tty_phys = umap(proc_addr(TTY), D, (vir_bytes)tty_sbuf, 
X			(vir_bytes)bytes_to_copy);
X
X        /* Do the copy */
X
X        phys_copy((long)user_phys, (long)tty_phys, (long)bytes_to_copy);
X
X        for(i = 0; i < bytes_to_copy; i++) {
X	    /* Handle newlines if CRMOD set for this tty */
X	    if((tp->tt_uflags & CRMOD) && (tty_sbuf[i] == '\n')) {
X	        (tp->tt_dev_putc)(tp->tt_minor, '\r');
X	    }
X	    (tp->tt_dev_putc)(tp->tt_minor, tty_sbuf[i]);
X        }
X
X	ucount -= bytes_to_copy;
X	uaddr += bytes_to_copy;
X	bytes_written += bytes_to_copy;
X    }
X
X    return(bytes_written);
X}
X
X
X/************************************************************************
X * Function:
X *	do_ioctl(tp, m_ptr)
X *
X * Description:
X *	Perform IOCTL function.
X *	NOTE:  This version of do_ioctl is severly limited, for
X *	       compatibility with the existing filesystem interface.
X *	       The filesystem interface should be redesigned so as
X *	       to allow access to the full array of IOCTL functions.
X * 
X * Arguments:
X *	tp	Pointer to TTY struct to do ioctl on
X *	m_ptr	Pointer to original message.
X *
X *************************************************************************/
XPRIVATE do_ioctl(tp, m_ptr)
Xregister TTY_STRUCT *tp;		/* pointer to TTY struct */
Xmessage *m_ptr;			/* pointer to message sent to task */
X{
X    long	flags, erki, erase, kill, intr, quit, xon, xoff, eof;
X    int		r;
X
X    r = OK;
X    flags = 0;
X    erki = 0;
X
X
X    switch(m_ptr->TTY_REQUEST) {
X	case TIOCSETP:
X	    /* Set erase, kill, and flags */
X	    tp->tt_erase = (char)((m_ptr->TTY_SPEK >> 8) & BYTE);
X	    tp->tt_kill = (char)((m_ptr->TTY_SPEK >> 0) & BYTE);
X	    tp->tt_uflags = (int)m_ptr->TTY_FLAGS;
X
X	    /* GROSS HACK for plug-compatibility:  If we enter
X	     * CBREAK mode on the console (device 0) then turn OFF
X	     * line wrapping on the console.  Otherwise, turn it on.
X	     * We only do this for compatibility with the 1.1 version
X	     * of MINED.  If we fix MINED so it doesn't assume that
X	     * writing past the end of the screen won't wrap, then we
X	     * can pull this hack.
X	     */
X	    if(tp->tt_minor == 0) {
X		if(tp->tt_uflags & CBREAK) {
X		    connowrap();
X		}
X		else {
X		    conwrap();
X		}
X	    }
X	    /* END GROSS HACK */
X	    break;
X
X	case TIOCSETC:
X	    /* set, intr, quit, xon, xoff, eof */
X	    tp->tt_intr = (char)((m_ptr->TTY_SPEK >> 24) & BYTE);
X	    tp->tt_quit = (char)((m_ptr->TTY_SPEK >> 16) & BYTE);
X	    tp->tt_resume = (char)((m_ptr->TTY_SPEK >> 8) & BYTE);
X	    tp->tt_susp = (char)((m_ptr->TTY_SPEK >> 0) & BYTE);
X	    /* tp->tt_eof not used */
X	    break;
X
X	case TIOCGETP:
X	    /* Get erase, kill, and flags */
X	    erase = ((long) tp->tt_erase) & BYTE;
X	    kill = ((long) tp->tt_kill) & BYTE;
X	    erki = (erase << 8) | kill;
X	    flags = (long)tp->tt_uflags;
X	    break;
X
X	case TIOCGETC:
X	    /* Get intr, quit, xon, xoff, eof. */
X	    intr = ((long) tp->tt_intr) & BYTE;
X	    quit = ((long) tp->tt_quit) & BYTE;
X	    xon = ((long) tp->tt_resume) & BYTE;
X	    xoff = ((long) tp->tt_susp) & BYTE;
X	    eof = 4;
X	    erki = (intr << 24) | (quit << 16) | (xon << 8) | (xoff << 0);
X	    flags = (eof << 8);
X	    break;
X
X	default:
X	    r = EINVAL;
X	    break;
X    }
X
X    /* Send the reply */
X    tty_reply(TASK_REPLY, m_ptr->m_source, m_ptr->PROC_NR, r, flags, erki);
X}
X
X
X
X/************************************************************************
X * Function:
X *	do_cancel(tp, m_ptr)
X *
X * Description:
X *	Cancel any pending I/O on the terminal immediately.
X *	This is called when a process catches a signal.
X * 	Causes the pending I/O to return with EINTR.
X * 
X * Arguments:
X *	tp	Poiner to the TTY structure involved.
X *	m_ptr	Pointer to original message.
X *
X *************************************************************************/
X
XPRIVATE do_cancel(tp, m_ptr)
Xregister TTY_STRUCT *tp;		/* pointer to TTY struct */
Xmessage *m_ptr;			/* pointer to message sent to task */
X{
X    /* Reply only if this process indeed has I/O pending */
X    if(tp->tt_readsz == 0 && !(tp->tt_iflags & (TT_SUSPENDED | TT_WR_SUSP))) {
X	return;
X    }
X
X    /* Kill any pending input */
X    tp->tt_first = 0;
X    tp->tt_last = 0;
X    tp->tt_bufsz = 0;
X
X    /* And any pending output */
X    tp->tt_wcount = 0;
X    tp->tt_wrcount = 0;
X
X    /* Cancel any outstanding read request */
X    tp->tt_readsz = 0;
X
X    /* Turn off XOFF */
X    tp->tt_iflags &= ~(TT_SUSPENDED | TT_WR_SUSP);
X
X    /* Reply */
X    tty_reply(TASK_REPLY, m_ptr->m_source, m_ptr->PROC_NR, EINTR, 0L, 0L);
X}
X
X
X
X/************************************************************************
X * Function:
X *	tty_reply(code, replyee, proc_nr, status, extra, other)
X *
X * Description:
X *	Send a message in reply to the message we just processed.
X * 
X * Arguments:
X *	code	What kind of message to send, either TASK_REPLY or
X *		REVIVE
X *	replyee	Destination process for the reply (Usually FS)
X *	proc_nr	Originator process.
X *	status	Reply code, either number of bytes processed or
X *		some error code.
X *	extra	extra value (for IOCTL)
X *	other	Another extra value (for IOCTL)
X *
X *************************************************************************/
XPRIVATE tty_reply(code, replyee, proc_nr, status, extra, other)
Xint code;			/* TASK_REPLY or REVIVE */
Xint replyee;			/* destination address for the reply */
Xint proc_nr;			/* to whom should the reply go? */
Xint status;			/* reply code */
Xlong extra;			/* extra value */
Xlong other;			/* used for IOCTL replies */
X{
X/* Send a reply to a process that wanted to read or write data. */
X
X  reply_ptr->m_type = code;
X  reply_ptr->REP_PROC_NR = proc_nr;
X  reply_ptr->REP_STATUS = status;
X  reply_ptr->TTY_FLAGS = extra;	/* used by IOCTL for flags (mode) */
X  reply_ptr->TTY_SPEK = other;	/* used by IOCTL for erase and kill chars */
X
X  send(replyee, reply_ptr);
X
X}
X
X
XPRIVATE tty_areply(code, replyee, proc_nr, status, extra, other)
Xint code;			/* TASK_REPLY or REVIVE */
Xint replyee;			/* destination address for the reply */
Xint proc_nr;			/* to whom should the reply go? */
Xint status;			/* reply code */
Xlong extra;			/* extra value */
Xlong other;			/* used for IOCTL replies */
X{
X  /* Send an asynchronous reply to a process that wanted to read or write 
X   * data.  An asynchronous reply is one that is not directly in response
X   * to a request from the filesystem (e.g. reviving a process waiting for
X   * an I/O to complete).  Such replies are sent to another task to be
X   * forwarded to their destination.  This is to prevent deadlock conditions
X   * where the filesystem is sending us a message at the same time that
X   * we're sending a message for an asynchronous reply.
X   */
X
X
X  reply_ptr->m_type = code;
X  reply_ptr->REP_PROC_NR = proc_nr;
X  reply_ptr->REP_STATUS = status;
X  reply_ptr->TTY_FLAGS = extra;	/* used by IOCTL for flags (mode) */
X  reply_ptr->TTY_SPEK = other;	/* used by IOCTL for erase and kill chars */
X  reply_ptr->m2_i3 = replyee;
X
X  send(TTY_ASYNC, reply_ptr);
X
X}
X
Xtty_async_task()
X{
X    message	async_message;
X
X    /* This task is a simple message forwarder.  It waits for a message,
X     * which should contain the REAL destination for the message in the
X     * m2_i3 field.  It then sends the message to that destination.
X     */
X
X    while(TRUE) {
X	receive(TTY, &async_message);
X
X	send(async_message.m2_i3, &async_message);
X    }
X}
X
X/************************************************************************
X * Function:
X *	enque_char(tp, ch)
X *
X * Description:
X *	Enqueues a character on the specified device's input queue.
X * 	If the queue becomes too full, turn flow control on.
X * 
X * Arguments:
X *	tp	Pointer to TTY struct for this device
X *	ch	character to enqueue.
X *
X * Return Value:
X *	None
X *************************************************************************/
XPRIVATE enque_char(tp, ch)
XTTY_STRUCT * tp;
Xchar ch;
X{
X    /* If the queue is full, drop the character on the floor */
X    if(tp->tt_bufsz == TTY_IBUFSZ) { return; }
X
X    if(!((tp->tt_uflags & (RAW | CBREAK)) ||
X	 (tp->tt_iflags & TT_LITNEXT)) && (ch == tp->tt_eot)) {
X	tp->tt_inbuf[tp->tt_last] = MARKER;
X	/* EOT counts as a newline for purposes of denoting end-of-line */
X	tp->tt_last_nl = tp->tt_last;
X    }
X    else {
X        tp->tt_inbuf[tp->tt_last] = ch;
X    }
X
X    if (ch == '\n') { 
X	tp->tt_last_nl = tp->tt_last;
X    }
X
X    tp->tt_last = (tp->tt_last + 1) % TTY_IBUFSZ;
X    tp->tt_bufsz++;
X
X    /* If the queue got too full, turn on flow control */
X    if(tp->tt_bufsz >= tp->tt_fc_on) {
X	if(!(tp->tt_iflags & TT_FC_ON)) {
X	    tp->tt_iflags |= TT_FC_ON;
X	    tp->tt_dev_fcon(tp->tt_minor);
X	}
X    }
X}
X
X
X/************************************************************************
X * Function:
X *	char	dq_fifo_char(tp)
X *
X * Description:
X *	Dequeues a character from the device's character queue in a
X *	fifo fashion.  This is normally done when presenting characters
X *	to a read command.
X * 
X * Arguments:
X *	tp	Pointer to this terminal device
X *
X * Return Value:
X *	The character taken off the queue.
X *
X * Note:
X *	If the queue is empty, this routine will return immediately with
X *	an indeterminate value.  It is the responsibility of the CALLER
X *	to determine if there is something in the queue BEFORE calling
X *	this routine.
X *
X *	If flow control is on and this routine causes us to fall below
X *	the flow control threshold, then flow control is turned off.
X *************************************************************************/
XPRIVATE char dq_fifo_char(tp)
XTTY_STRUCT *	tp;
X{
X    char	ch;
X
X    /* If the queue is empty, don't bother. */
X    if(tp->tt_bufsz == 0) { return; }
X
X    ch = tp->tt_inbuf[tp->tt_first];
X
X    /* If we ate the last newline in the buffer, then note that
X     * we have none.
X     */
X    if(tp->tt_first == tp->tt_last_nl) { tp->tt_last_nl = -1; }
X    tp->tt_first = (tp->tt_first + 1) % TTY_IBUFSZ;
X    tp->tt_bufsz--;
X
X    /* See if we should turn off flow control */
X    if((tp->tt_iflags & TT_FC_ON) && (tp->tt_bufsz <= tp->tt_fc_off)) {
X        tp->tt_iflags &= ~TT_FC_ON;
X	tp->tt_dev_fcoff(tp->tt_minor);
X    }
X
X    return(ch);
X}
X
X
X/************************************************************************
X * Function:
X *	char	dq_lifo_char(tp)
X *
X * Description:
X *	Dequeues a character from the device's character queue in a
X *	lifo fashion.  This is normally done when editing the input line.
X * 
X * Arguments:
X *	tp	Pointer to this terminal device
X *
X * Return Value:
X *	The character taken off the queue.
X *
X * Note:
X *	If the queue is empty, this routine will return immediately with
X *	an indeterminate value.  It is the responsibility of the CALLER
X *	to determine if there is something in the queue BEFORE calling
X *	this routine.
X *
X *	If flow control is on and this routine causes us to fall below
X *	the flow control threshold, then flow control is turned off.
X *
X *	Note that this routine DOES NOT BOTHER to check and see if we
X *	should reset tt_last_nl.  This is because as of this writing
X *	ALL callers of this routine IMMEDIATELY put back any newlines
X *      that they remove; enque_char will thus handle this.
X *************************************************************************/
XPRIVATE char dq_lifo_char(tp)
XTTY_STRUCT *	tp;
X{
X    char	ch;
X
X    /* If the queue is empty, don't bother. */
X    if(tp->tt_bufsz == 0) { return; }
X
X    if(--tp->tt_last < 0) { tp->tt_last = TTY_IBUFSZ - 1; }
X    ch = tp->tt_inbuf[tp->tt_last];
X    tp->tt_bufsz--;
X
X    /* See if we should turn off flow control */
X    if((tp->tt_iflags & TT_FC_ON) && (tp->tt_bufsz <= tp->tt_fc_off)) {
X        tp->tt_iflags &= ~TT_FC_ON;
X	tp->tt_dev_fcoff(tp->tt_minor);
X    }
X
X    return(ch);
X}
X
X
X/************************************************************************
X * Function:
X *	output_char(tp, ch)
X *
X * Description:
X *	Outputs the specified character to the specified device.
X *	Performs certain mappings for non-printing characters.
X * 
X * Arguments:
X *	tp	Pointer to TTY struct
X *	ch	character to print out.
X *
X *************************************************************************/
XPRIVATE output_char(tp, ch)
XTTY_STRUCT * tp;
Xchar ch;
X{
X    if((ch > 27) || (ch == 9)) {
X	(tp->tt_dev_putc)(tp->tt_minor, ch);
X    }
X    else {
X	(tp->tt_dev_putc)(tp->tt_minor, '^');
X	(tp->tt_dev_putc)(tp->tt_minor, ch + '@');
X    }
X}
X
X/************************************************************************
X * Function:
X *	putc(c)
X *
X * Description:
X *	Kernel-internal version of 'putc'.  Writes the specified
X *	character to the system console.
X * 
X * Arguments:
X *	c	Character to write.
X *************************************************************************/
XPUBLIC putc(c)
Xchar c;
X{
X    if(c == '\n') { con_putc(0, '\r'); }
X    con_putc(0, c);
X    con_oflush(0);
X}
X
Xtty_sig(tp, sig)
XTTY_STRUCT * tp;
Xint sig;
X{
X    /* THIS IS A HACK for now... the PROPER thing to do is to go
X     * through the filesystem to find the process for which this
X     * is the control terminal.  NOTE:  If/when we ever do job control,
X     * we'll have to do some special-case stuff because we may not
X     * want to cancel pending I/O...
X     */
X    /* Kill any pending input */
X    tp->tt_first = 0;
X    tp->tt_last = 0;
X    tp->tt_bufsz = 0;
X
X    /* And any pending output */
X    tp->tt_wcount = 0;
X    tp->tt_wrcount = 0;
X
X    /* Send an EINTR message to any procs with I/O pending... */
X    if(tp->tt_readsz != 0) {
X	tty_areply(REVIVE, tp->tt_proc, tp->tt_caller, EINTR, 0L, 0L);
X	tp->tt_readsz = 0;
X    }
X    if(tp->tt_iflags & TT_WR_SUSP) {
X	tty_areply(REVIVE, tp->tt_wcaller, tp->tt_wproc, EINTR, 0L, 0L);
X    }
X
X    /* Turn off XOFF */
X    tp->tt_iflags &= ~(TT_SUSPENDED | TT_WR_SUSP);
X
X    cause_sig(LOW_USER + 1 + tp->tt_minor, sig);
X}
X
/
echo x - tty.h
gres '^X' '' > tty.h << '/'
X#define ERASE_CHAR      '\b'	/* default erase character */
X#define KILL_CHAR        '@'	/* default kill character */
X#define INTR_CHAR (char)0177	/* default interrupt character */
X#define QUIT_CHAR (char) 034	/* default quit character */
X#define XOFF_CHAR (char) 023	/* default x-off character (CTRL-S) */
X#define XON_CHAR  (char) 021	/* default x-on character (CTRL-Q) */
X#define EOT_CHAR  (char) 004	/* CTRL-D */
X#define MARKER	  (char) 000	/* Marker for EOT */
X#define WERA_CHAR (char) 027	/* default word-erase char (CTRL-W) */
X#define STOP_CHAR (char) 032	/* default stop char (CTRL-Z) */
X#define RDSP_CHAR (char) 022	/* default redisplay char (CTRL-R) */
X#define LNXT_CHAR	'\\'	/* default literal-next char */
X
X#define WRITE_CHUNK_SIZE	16
X
X/* The TTY structure, one per active TTY */
X
X#define TTY_IBUFSZ	1536
X
X/* Turn flow control on when buffer is 3/4 full */
X#define TTY_FC_ON	((TTY_IBUFSZ >> 1) + (TTY_IBUFSZ >> 2))
X
X/* Turn it back off when buffer is 5/8 full */
X#define TTY_FC_OFF	((TTY_IBUFSZ >> 1) + (TTY_IBUFSZ >> 3))
X
Xtypedef struct {
X	int	tt_minor;		/* Minor device number */
X	char	tt_inbuf[TTY_IBUFSZ];	/* Input buffer */
X	int	tt_first;		/* Index of first char in buf */
X	int	tt_last;		/* Index of next avail slot */
X	int	tt_bufsz;		/* Number of chars in buffer */
X	int	tt_last_nl;		/* Last newline in buffer. */
X	int	tt_readsz;		/* Size of outstanding read */
X	char	tt_proc;		/* Process waiting on read */
X	char	tt_caller;		/* Caller of blocking read (FS) */
X	char	*tt_usrbuf;		/* virtual addr of user buffer */
X	char	tt_wproc;		/* Proc waiting on write */
X	char	tt_wcaller;		/* Caller of blocking write (FS) */
X	char	*tt_waddr;		/* Address of waited write */
X	int	tt_wcount;		/* Count of waited write */
X	int	tt_wrcount;		/* Total count of waited write */
X	int	tt_fc_on;		/* Num chars to turn on flow ctl */
X	int	tt_fc_off;		/* Num chars to turn OFF flow ctl */
X	char	tt_erase;		/* Erase char */
X	char	tt_kill;		/* Kill char */
X	char	tt_wera;		/* Word-erase char */
X	char	tt_intr;		/* Interrupt char */
X	char	tt_quit;		/* Quit char */
X	char	tt_stop;		/* Stop char */
X	char	tt_susp;		/* XOFF char */
X	char	tt_resume;		/* XON char */
X	char	tt_rprnt;		/* Redisplay char */
X	char	tt_lnext;		/* Lit next char */
X	char	tt_eot;			/* EOT char */
X	char	tt_uflags;		/* User-visible flags */
X	char	tt_iflags;		/* Internal flags */
X
X	/* The following are the routine entry points into the
X	 * device layer for the device associated with this TTY.
X	 */
X
X	void	(*tt_dev_init)();	/* Device initialization */
X	void	(*tt_dev_putc)();	/* Write a char to device */
X	void	(*tt_dev_ioctl)();	/* perform device ioctl */
X	void	(*tt_dev_fcon)();	/* Turn flow control on */
X	void	(*tt_dev_fcoff)();	/* Turn flow control off */
X	void	(*tt_dev_oflush)();	/* Flush any pending output */
X	int	(*tt_dev_bufin)();	/* Get buffered input */
X} TTY_STRUCT;
X
X/* Number of console devices */
X#define	NUM_CONSOLES	1
X
X/* Number of serial devices supported.  This parameter is used both by
X * tty.c and rs232.c.  The current tty driver supports up to FOUR such
X * devices. (COM1: through COM4: in IBM-PC parlance)
X */
X#define NUM_SERIAL_DEV	1
X
X/* Total number of TTYs */
X#define NUM_TTYS	(NUM_CONSOLES + NUM_SERIAL_DEV)
X
X/* Internal flags definitions */
X#define	TT_LITNEXT	1
X#define TT_FC_ON	2
X#define TT_SUSPENDED	4
X#define TT_WR_SUSP	8
X
X
X
X
/