[comp.os.minix] HERE IT IS!!! New serial TTY driver!

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

Well, friends, here's what you've all been waiting for... a serial
TTY driver for MINIX!  This is a rewrite from the ground up, not
a modification of the existing driver, although this new driver
is pretty much plug-compatible with the old.  (You should still
read the installation instructions in README before proceeding,
though).

As this went to press (silicon?) there was one problem just discovered:
since the serial portion of this driver is interrupt-driven, if you're
running any code that does a lock() for any appreciable length of time,
you CAN drop characters.  I discovered this by accident one day when I
tried to do an 'lpr' in the background while running a terminal session.
That also explained why I was sometimes losing characters on emacs
sessions and the like -- if I made a mistake, emacs would beep at me.
And the beep() routine disables interrupts for the duration of the beep!
If anyone out there has some answers, I'd appreciate knowing them.

Enjoy!  Any problems/suggestions, send email, though I don't know how
quickly I'll be able to respond...

   +----------------+  Jim Paradis                  linus--+
+--+-------------+  |  Encore Computer Corp.       necntc--|
|  | E N C O R E |  |  257 Cedar Hill St.           ihnp4--+-encore!paradis
|  +-------------+--+  Marlboro MA 01752           decvax--|
+----------------+     (617) 460-0500             talcott--+
But our sales would PLUMMET!!

------------cut here--------------------unpack with /bin/sh--------------
echo x - MANIFEST
gres '^X' '' > MANIFEST << '/'
XAfter unpacking your TTY kit, you should have the following files:
X
X-rw-rw-rw-  1   root   14292 Jan  3 13:43 README
X-rw-r--r--  1   root   32926 Jan  3 13:30 console.c
X-rw-rw-rw-  1   root    4590 Jan  3 13:30 klib88.newstf
X-rw-rw-rw-  1   root     284 Jan  3 13:30 mansi.tc
X-rw-rw-rw-  1   root     791 Jan  3 13:41 mpx88.newstf
X-rw-r--r--  1   root   14761 Jan  3 13:30 rs232.c
X-rw-r--r--  1   root    2081 Jan  3 13:30 t.c
X-rw-r--r--  1   root   34687 Jan  3 13:30 tty.c
X-rw-r--r--  1   root    3321 Jan  3 13:30 tty.h
X
XFollowing is a brief description of each file:
X
XREADME		Read this file before doing anything else!
X
Xtty.c		The device-independent portion of the TTY driver
X
Xtty.h		Definitions used by the various components of the TTY
X		driver.
X
Xconsole.c	The device-specific portion of the driver for the IBM-PC
X		keyboard and display adapter.  This SHOULD work with most
X		CGA-compatible and mono display adapters, although it's only
X		received extensive testing with a CGA-clone.
X
Xrs232.c		The device-specific portion of the driver for serial
X		devices.
X
Xklib88.newstf	New routines to add to the "klib88.s" file.
X
Xmpx88.newstf	New routines to add to the "mpx88.s" file.
X
Xmansi.tc	A termcap entry corresponding to the ANSI capabilities
X		of the console driver.
X
Xt.c		Source for one of the world's cheapest & dirtiest terminal
X		programs.  What can I say?  It works!
/
echo x - README
gres '^X' '' > README << '/'
XBefore doing anything else, I suggest consulting the file named
XMANIFEST to verify that you have received all the files which
Xcomprise this kit.  After that, I suggest that you read this file
Xfrom top to bottom before you attempt to install your driver.
X
XThis kit contains Jim Paradis' new TTY driver for MINIX.  Although
Xpretty much plug-compatible with the stock TTY driver, my driver
Xis a complete rewrite from the ground up with the exception of some
Xkeyboard-handling stuff and a few other minor things which I lifted
Xfrom the original TTY driver (Acknowledgements and mucho thanks to 
XAndy Tanenbaum!!).  Among the features supported by the new driver are:
X
X	- Serial TTY support for up to four serial lines
X	  (As shipped, the driver is configured for one
X	  serial line, but you only have to change one
X	  #define to get the number you want, up to four.
X	  Each serial line chews up some memory for buffers
X	  and tables, so you should only configure those 
X	  you need)
X
X	- On the PC console, long lines now wrap instead of
X	  being truncated at the edge of the screen.
X
X	- Typing the KILL character now causes the line to
X	  be erased.
X
X	- Word-erase and redisplay are now supported.
X
X	- Control characters are echoed as "^X"
X
X	- This driver supports a sufficient subset of ANSI
X	  terminal escape sequences to satisfy most editors
X	  (Including ELLE)
X
X	- Debugging functions (e.g. proc dump, memory dump)
X	  are now done by hitting CTRL-ALT-Fkey rather than
X	  just Fkey.  This frees the Fkeys to be used by other
X	  things.
X
XFunction Keys:
X
X	This driver supports the function keys F1 through F12 in
Xfour different ways.  Just hitting a function key causes the following
Xescape sequences to be generated:
X
X	F1	Esc OP		F5	Esc OT		F9	Esc OX
X	F2	Esc OQ		F6	Esc OU		F10	Esc OY
X	F3	Esc OR		F7	Esc OV		F11	Esc OZ
X	F4	Esc OS		F8	Esc OW		F12	Esc O[
X
XHolding down CTRL while hitting a function key causes the following
Xescape sequences to be generated:
X
X	F1	Esc QP		F5	Esc QT		F9	Esc QX
X	F2	Esc QQ		F6	Esc QU		F10	Esc QY
X	F3	Esc QR		F7	Esc QV		F11	Esc QZ
X	F4	Esc QS		F8	Esc QW		F12	Esc Q[
X
XIn addition, this driver has hooks for implementing user-definable 
Xfunction keys, where up to sixteen characters can be stored for each
Xkey.  Holding down ALT while hitting a function key causes the associated
Xtext to be transmitted as though you had typed it at the console keyboard.
XUntil I get around to re-doing ioctl, though, there isn't any way to
Xtransmit function key text to the driver to be stored.  If you wish, though,
Xyou CAN hard-code function-key text into the driver, and I have provided
Xan example such that ALT-F1 is bound to the string "ls\r".  If you look at
Xthe beginning of con_init() in console.c, you'll see how it's done.
X
XFinally, hitting CTRL-ALT-Fkey causes various debug functions (e.g. proc_dmp())
Xto be invoked, much as they are by hitting just the function key in the old
Xtty driver.
X
XCharacter Defaults
X
X	I use the following control characters as defaults
Xin this driver.  They are different than the MINIX defaults.
XYou may wish to change them before installing the driver.  They
Xare defined in the first few lines of tty.h:
X
X	interrupt		^C
X	quit			^\
X	erase			^H
X	kill			^U
X	word-erase		^W
X	redisplay		^R
X	XOFF (suspend)		^S
X	XON (resume)		^Q
X	EOT			^D
X	literal-next		\
X
X
XInstalling the driver:
X
X	To install this driver, you must do the following:
X
X	(1) Move the following files from this distribution into
X	your kernel source directory:
X
X		tty.h
X		tty.c (you may want to back up the old tty.c first!)
X		console.c
X		rs232.c
X
X	(2) Edit h/const.h and bump NR_TASKS from 8 to 9.
X
X	(3) Edit h/com.h and add the following line:
X
X		#define	TTY_ASYNC -9
X
X	(4) Edit kernel/const.h and add the following lines:
X
X		#define SER1_VECTOR	12
X		#define SER2_VECTOR	11
X
X	(5) Edit kernel/main.c and add the following lines among
X	all the other set_vec calls:
X
X		set_vec(SER1_VECTOR, ser1_int, base_click);
X		set_vec(SER2_VECTOR, ser2_int, base_click);
X
X	    Also, add the following line to the declaration
X	section:
X
X		extern int ser1_int(), ser2_int();
X
X	(6) Edit kernel/dmp.c and add the entry "TTASYN" to the
X	front of the "naymes" array.
X
X	(7) Edit kernel/table.c and change the initialization of the
X	"task" array to the following:
X
X		int (*task[NR_TASKS+INIT_PROC_NR+1])() = {
X			 tty_async_task, printer_task, tty_task, 
X			 winchester_task, floppy_task, 
X			 mem_task, clock_task, sys_task, 0, 0, 0, 0
X		};
X
X	    Also, add the line:
X
X		extern int tty_async_task();
X
X	    to the declaration section.
X
X
X	(8) Edit your kernel makefile and add console.s and rs232.s to
X	the definition of "obj".  Also, add the following dependencies:
X
X		tty.s:	const.h type.h $h/const.h $h/type.h
X		tty.s:	$h/callnr.h
X		tty.s:	$h/com.h
X		tty.s:	$h/error.h
X		tty.s:	$h/sgtty.h
X		tty.s:	$h/signal.h
X		tty.s:	glo.h
X		tty.s:	proc.h
X		tty.s:	tty.h
X
X		console.s:	const.h
X		console.s:	$h/type.h
X		console.s:	$h/const.h
X		console.s:	$h/com.h
X
X		rs232.s:	const.h
X		rs232.s:	tty.h
X		rs232.s:	$h/const.h
X		rs232.s:	$h/type.h
X		rs232.s:	$h/com.h
X
X	(9) If you have more than one serial line that you wish to
X	support, then edit tty.h and change the line
X
X		#define NUM_SERIAL_DEV	1
X
X	to the number of serial devices that you wish to have.  Note
X	that if you have only one serial device connected to the system
X	but it's configured as COM2:, COM3:, or COM4:, then you still
X	will have to configure the driver for two, three, or four devices
X	respectively.  Of course, it might be preferable to just reconfigure
X	the device itself as COM1:.
X
X	(10) Incorporate the routines in the file "klib88.newstf" into your
X	klib88.s file.  Delete the routine _vid_copy: as well as all references
X	to the symbols "vid_copy" and "vid_mask".  Similarly, incorporate the 
X	routines in the file "mpx88.newstf" into your mpx88.s file.
X
X	(11) Build your kernel!
X
X	(12) If you have a termcap file, you may wish to incorporate
X	the contents of the file "mansi.tc" into it.  It defines
X	a terminal type "mansi", which corresponds to the ANSI support
X	provided for the console driver.
X
X	Once your TTY driver is installed, you'll probably want to try
X	it out.  The file "t.c" contains the source for a VERY cheap
X	terminal program (I knocked it together in about 10 minutes).
X	Cheap though it may be, I find that if I'm talking to a UNIX
X	system and I've uploaded my "mansi" termcap entry to that system,
X	then it's more than adequate for interactive use.  It doesn't do
X	file transfers at all, but I'll try porting some other program
X	for that.
X
XCheap vs. Good CGAs.
X
X	You may notice that the file console.c contains some code that is
Xconditionally compiled on the parameter CHEAP_CGA.  In this case, a cheap
XCGA is one that doesn't dual-port the video RAM properly, such that writing
Xto video RAM at any time other than during blanking intervals causes "snow" to
Xappear on the screen.  This driver comes with CHEAP_CGA defined so that
Xeveryone can use it right out of the box.  However, if you have an EGA or a
Xgood CGA that has properly dual-ported RAM, then you will want to #undef this
Xparameter (performance should improve significantly).
X
X
XTechnical notes on the driver:
X
X	Unfortunately, the present implementation of the driver only
Xallows for a single hard-coded baud rate on the serial lines (see
Xser_init() in rs232.c).  The reason for this is that the present
Xstructure of the ioctl() system call is too limited to support full-blown
Xioctls like in v7 or sysV.  Therefore, to keep with the design criterion
Xthat the new driver be as plug-compatible as possible with the old,
XI have omitted the feature of line control.  Don't worry, it will exist
Xin a future version of the driver (heck, I won't be able to port half the
Xsoftware I want to port if it doesn't exist!).
X
X	In order to cut down on interrupt and TTY_CHAR_INT message traffic
Xwhen characters come in thick and fast on the serial line, the serial
Xinterrupt handler waits for a short time after it receives a character to
Xsee if there is another character immediately following.  There is a
X#define near the beginning of rs232.c of a parameter called MAX_CHARS_PER_INT.
XThis is the maximum number of characters that will be received this way on
Xa single interrupt.  If this number is too low, then interrupt traffic will
Xbe needlessly increased.  If this number is too HIGH, then input will tend
Xto come in in "bursts" rather than smoothly.  This is especially noticeable
Xif you're doing, say, a "tip" over that serial line.  I found the supplied
Xvalue of 3 to be a nice compromise.  I may make this a special ioctl
Xvparameter in the future (so that you can set it higher for non-interactive
Xinput.  This would be especially useful if you're doing "packetized" I/O
Xsuch as SL/IP.  Setting this value to the packet size would result in taking
Xonly one interrupt per packet!)
X
X	Also, there's a bit of magick in the "additional character"
Xhandling itself.  Note the following line from rs232.c:
X
X	for(latency = 0; latency < (rs->rs_baud << 1); latency++) {
X
XThe parameter "rs->rs_baud" is a counter divisor that the serial
Xchip uses to determine the baud rate.  By empirical observation, I
Xdetermined that, on a 12-MHz AT, looping for TWICE this number of
Xiterations while waiting for another character yielded the best
Xresults.  However, if you loop TOO many times, you'll lose, as you
Xmay end up waiting around for nothing (i.e. the timeout will be too
Xlong) and you may actually end up DROPPING characters as a result.
XTherefore, if you're running a slow machine (i.e. a 4.77MHz true-blue PC),
Xyou may want to omit the "<< 1".  Alternately, if you're running a 20-MHz
X80386 machine, you may want to use "<< 2" instead.
X
XADDING YOUR OWN DEVICES:
X
X	I wrote this driver intending it to be relatively easy to add
Xadditional types of devices to it, and I've kept the interface between
Xthe "tty-driver" portion of the driver and the "device-driver" portion
Xas clean as possible.  In fact, the entire interface consists of seven
Xroutine entry points and one message protocol.  Following is a brief
Xdiscussion of the entry points:
X
Xvoid	(*tt_dev_init)(minor, devnum);
XThis routine is called when the TTY driver is initialized, and it
Xhas two functions: first, it performs any device-specific
Xinitializations necessary for the particular device, and second, it
Xassociates a minor device number with a particular hardware device.
XThe "devnum" parameter is provided for those device-specific components
Xwhich support multiple devices.  For example, the rs232 driver supplied
Xsupports up to four devices.  To the rs232 driver, these devices are numbered
X0 thru 3.  However, the TTY driver knows them as minor devices 1 thru 4,
Xsince 0 is already taken by the console.  Hence, the rs232 driver has
Xto take care of the mapping.
X
Xvoid	(*tt_dev_putc)(minor, c);
XThis routine outputs a character to the specified device.  Note that upon
Xreturning from this routine it is not guaranteed that the character has
Xactually made it out to the device; on a slow device it may well be the
Xcase that we queue up characters to be written, and the actual character
Xwrites take place upon receipt of a "device ready" interrupt.  This is of
Xno consequence to the TTY driver; when it wants to make SURE that characters
Xhave gotten out to where they're going, it will call the flush-output
Xroutine
X
Xvoid	(*tt_dev_ioctl)(minor, type, ctlp);
XThis routine handles device-specific ioctl requests.  Since this rev of
Xthe driver doesn't support such an animal, this interface remains unused
Xas of this writing.  However, to prevent a plethora of implementers from
Xdefining their own interfaces, I will SUGGEST that this routine take (1)
Xa minor device number, (2) an integer ioctl type, and (3) a pointer to a 
Xstructure containing the ioctl request to be performed.  The size and 
Xformat of the structure may very well be dependent on the request type.
XWatch this space for further details.
X
Xvoid	(*tt_dev_fcon)(minor);
XThis routine is called when the TTY driver's internal buffers are starting
Xto fill up.  If there is any way to flow-control a terminal (e.g. sending
XXOFF) this routine should perform that operation on the specified device.
XSome devices (such as the console) do not have nor require flow-control
Xcapabilities.  The present driver does very little with this flag.
X
Xvoid	(*tt_dev_fcoff)(minor);
XThis routine is called when the TTY driver's internal buffers drain after
Xan "fcon" has been performed.  It should re-enable the device (e.g. send XON)
X
Xvoid	(*tt_dev_oflush)(minor);
XThis routine flushes all output pending on the specified device.  Upon
Xreturning from this routine, one can be guaranteed that all characters
Xwritten to the device have indeed been written.
X
Xint	(*tt_dev_bufin)(minor, buf, maxchars);
XThis routine implements the receiving end of the character-input protocol.
XWhenever a character(s) is/are received by the device driver, the device
Xdriver should buffer the character(s), construct a TTY_CHAR_INT message
Xcontaining the minor device number of the serial line in the TTY_LINE
Xfield, and send this message to the TTY task.  The TTY task, in turn
Xwill then call this routine to get any characters buffered up to this
Xpoint.  The "buf" parameter is a pointer to a buffer to receive the
Xbuffered characters, and the "maxchars" parameter is the maximum number
Xof characters to receive.  This routine should return the number of
Xcharacters actually placed in the buffer.
X
XOne small note about the handling of TTY_CHAR_INT messages:  you may
Xnotice in tty_task() that whenever we take a TTY_CHAR_INT message, we
Xactually check ALL configured tty's for input, starting with the one
Xthat sent the message.  The reason for this is that if we have a number
Xof devices configured, each clamoring for attention, it's entirely
Xpossible that a message will get dropped.  The reason for this is that
Xsuch messages are sent from the TTY driver to itself, and if the TTY
Xdriver is busy handling another message at the time then the message
Xwaiting to get sent goes into the "pending-message" field of TTY's
Xproc structure.  If we take ANOTHER interrupt during this period, then
Xthis pending message might get overwritten with the next one that gets
Xsent to the TTY.  Essentially the problem is that the message queue from
XTTY to itself is only one message long, and we may very well try to send
Xmessages faster than TTY can receive them.  The approach of checking
Xeach TTY device whenever we get a TTY_CHAR_INT message is MUCH easier
Xthan implementing a full-blown queueing mechanism for messages, and the
Xcheck itself is fairly cheap.
X
/
echo x - console.c
gres '^X' '' > console.c << '/'
X/**************************************************************************
X * File: console.c
X * Creation date: 9/19/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 console driver support routines 
X * for the MINIX tty driver.  These routines interface directly to
X * the IBM-PC hardware.  This version of the driver supports the
X * IBM Color Graphics Adaptor.  Support for other display adapters
X * may be added later.
X *
X * Entry points:
X *
X *	con_init(minor, dev_num)
X *      con_putc(minor, char)
X *	con_ioctl(minor, func, addr)
X *	con_fc(minor)
X *	con_nofc(minor)
X * 	con_oflush(minor)
X *	con_bufin(minor)
X *
X * Internal routines:
X *	scr_set_cursor(row, col)
X *	scr_forward_scroll(nlines)
X *	scr_flush_output(option)
X *	scr_shortescape();
X *	scr_longescape()
X *	...
X *
X **************************************************************************/
X
X#include "../h/type.h"
X#include "../h/const.h"
X#include "../h/com.h"
X#include "const.h"
X
X/* If we're using a CGA that produces snow if we access video RAM
X * outside the blanking interval, then we define CHEAP_CGA.  Otherwise,
X * we can pull this define for faster performance
X */
X
X#define CHEAP_CGA
X
X/* Type declarations and global data */
X
X#ifdef CHEAP_CGA
X
X/* An entry in the output queue.  Since we can only write characters out
X * during the blanking interval, we queue up characters to be written until
X * we can write them out.  We implement this as a circular queue.
X */
Xtypedef struct {
X    int address;
X    union {
X	struct {
X            char ch;
X            char attrib;
X	} c;
X	int i;
X    } u; 
X} OQUE_ENTRY;
X
X#define CH u.c.ch
X#define ATTRIB u.c.attrib
X#define INTVAL u.i
X
X/* Size of the output queue.  We don't want to make this TOO big,
X * or else the output may start appearing in bursts rather than
X * smoothly.  This number may be one to experiment with.
X */
X#define NUM_OQUE_ENTRIES	80
X
XPRIVATE OQUE_ENTRY	oque[NUM_OQUE_ENTRIES];
XPRIVATE	int		oque_first;
XPRIVATE int		oque_last;
XPRIVATE	int		oque_size;
X
X#endif CHEAP_CGA
X
X/* Current cursor position.  We keep track of this so we know when to
X * do things like wrapping and the like.
X */
X#define	NROWS		25
X#define	NCOLS		80
X
XPRIVATE	int		crow, ccol;
X
X/* Character attribute definitions and current character attribute byte. */
X#define	A_CHBLUE	0x01
X#define	A_CHGREEN	0x02
X#define	A_CHRED		0x04
X#define A_CHWHITE	0x07
X#define A_INTENSE	0x08
X#define A_BGBLUE	0x10
X#define	A_BGGREEN	0x20
X#define A_BGRED		0x40
X#define A_BGWHITE	0x70
X#define A_BLINK		0x80
X
XPRIVATE	char		attrib;
X
X/* Hardware port and register definitions */
XPRIVATE	int	ADDR_PORT;
XPRIVATE	int	DATA_PORT;
XPRIVATE	int	MODE_PORT;
XPRIVATE	int	COLOR_PORT;
XPRIVATE	int	STATUS_PORT;
X
X#define	CGA_ADDR_PORT	0x3d4
X#define CGA_DATA_PORT	0x3d5
X#define CGA_MODE_PORT	0x3d8
X#define CGA_COLOR_PORT	0x3d9
X#define CGA_STATUS_PORT	0x3da
X
X#define	MONO_ADDR_PORT	0x3b4
X#define MONO_DATA_PORT	0x3b5
X#define MONO_MODE_PORT	0x3b8
X#define MONO_COLOR_PORT	0x3b9
X#define MONO_STATUS_PORT	0x3ba
X
X#define START_HI	12
X#define START_LO	13
X#define CURSOR_HI	14
X#define CURSOR_LO	15
X
X#define BLANKING_ON	1
X
X#define TIMER2		0x42
X#define TIMER3		0x43
X#define KEYBD		0x60
X#define PORT_B		0x61
X#define KBIT		0x80
X#define B_TIME		0x2000
X
X#define AT_SIGN		0220		/* Special value for ^@ */
X
X/* Current start and cursor values */
XPRIVATE	int		MAX_START;
XPRIVATE	int		SEG_MASK;
XPRIVATE	int		VIDEO_SEG;
X
X#define CGA_MAX_START	0x4000
X#define CGA_SEG_MASK	0x3fff
X#define CGA_VIDEO_SEG	0xb800
X
X#define MONO_MAX_START	0x1000;
X#define MONO_SEG_MASK	0x0fff;
X#define MONO_VIDEO_SEG	0xb000
X
XPRIVATE int	cur_start;
XPRIVATE int	cur_cursor;
X
Xextern	int	color;
X
X
X/* Table of recommended values to send to the 6845 on initialization */
XPRIVATE int	m6845_init_table[] = { 0x71, 0x50, 0x5a, 0x0a, 0x1f, 
X				     0x06, 0x19, 0x1c, 0x02, 0x07, 
X				     0x00, 0x1f, 0,    0,    0, 0 };
X
X/* State of escape processing.  This version of the tty driver will
X * support a subset of the ANSI escape sequences.  These sequences
X * are of two general types:
X *	ESC [ num ; num ; ... X
X *	ESC X
X * Where "X" is any non-numeric character.
X */
XPRIVATE int	escape_state = 0;
X
X/* Escape state defines */
X#define	ESC_INESCAPE	1	/* In escape sequence */
X#define	ESC_BRACKET	2	/* Bracket seen */
X#define ESC_FIRSTPARAM	4	/* First param seen */
X
X/* Number of escape parameters seen */
XPRIVATE	int	num_esc_params = 0;
XPRIVATE int	esc_params[16];
X
X/* A state variable to control wrapping at the end of a line.  When we
X * write a character INTO the last column of the screen, we set this
X * variable to 1 so that we know that the next write should wrap the
X * screen.  Any operation that moves the cursor FROM column 80 should
X * definitely CLEAR this variable! 
X */
XPRIVATE int	wrap_needed;
XPRIVATE int	wrap_enabled = 1;
X
XPRIVATE int	cint_sent;
X
X/* Definitions of special characters */
X#define CHAR_BELL	7
X#define	CHAR_BS		8
X#define	CHAR_CR		13
X#define CHAR_LF		10
X#define CHAR_ESC	27
X#define CHAR_TAB	9
X
X/* Tab table.  For now, tabs will be hard-coded at 8; however, we may
X * eventually wish to make this adjustable by a special ioctl.  For that
X * reason, we're using a table-driven algorithm.  The table is
X * terminated with -1.
X */
X
XPRIVATE int tab_table[] = { 9, 17, 25, 33, 41, 49, 57, 65, 73, 80, -1 };
X
X/* TRUE if flow control is enabled.  With FC enabled, inputs are
X * disallowed.
X */
XPRIVATE int	fc_on;
X
XPUBLIC scan_code;		/* scan code for '=' saved by bootstrap */
X
X
X/* Scan codes to ASCII for unshifted keys */
XPRIVATE char unPC[] = {
X 0,033,'1','2','3','4','5','6',        '7','8','9','0','-','=','\b','\t',
X 'q','w','e','r','t','y','u','i',      'o','p','[',']',015,0202,'a','s',
X 'd','f','g','h','j','k','l',';',      047,0140,0200,0134,'z','x','c','v',
X 'b','n','m',',','.','/',0201,'*',     0203,' ',0204,0241,0242,0243,0244,0245,
X 0246,0247,0250,0251,0252,0205,0210,0267,  0270,0271,0211,0264,0265,0266,0214
X,0261,0262,0263,'0',0177
X};
X
X/* Scan codes to ASCII for shifted keys */
XPRIVATE char shPC[] = {
X 0,033,'!','@','#','$','%','^',        '&','*','(',')','_','+','\b','\t',
X 'Q','W','E','R','T','Y','U','I',      'O','P','{','}',015,0202,'A','S',
X 'D','F','G','H','J','K','L',':',      042,'~',0200,'|','Z','X','C','V',
X 'B','N','M','<','>','?',0201,'*',    0203,' ',0204,0221,0222,0223,0224,0225,
X 0226,0227,0230,0231,0232,0204,0213,'7',  '8','9',0211,'4','5','6',0214,'1',
X '2','3','0','.'
X};
X
X
X/* Scan codes to ASCII for Olivetti M24 for unshifted keys. */
XPRIVATE char unm24[] = {
X 0,033,'1','2','3','4','5','6',        '7','8','9','0','-','^','\b','\t',
X 'q','w','e','r','t','y','u','i',      'o','p','@','[','\r',0202,'a','s',
X 'd','f','g','h','j','k','l',';',      ':',']',0200,'\\','z','x','c','v',
X 'b','n','m',',','.','/',0201,'*',     0203,' ',0204,0241,0242,0243,0244,0245,
X 0246,0247,0250,0251,0252,023,0210,0267,0270,0271,0211,0264,0265,0266,0214,0261,
X0262,0263,'0','.',' ',014,0212,'\r',   0264,0262,0266,0270,032,0213,' ','/',
X 0253,0254,0255,0256,0257,0215,0216,0217
X};
X
X/* Scan codes to ASCII for Olivetti M24 for shifted keys. */
XPRIVATE char m24[] = {
X 0,033,'!','"','#','$','%','&',        047,'(',')','_','=','~','\b','\t',
X 'Q','W','E','R' ,'T','Y','U','I',     'O','P',0140,'{','\r',0202,'A','S',
X 'D','F','G','H','J','K','L','+',      '*','}',0200,'|','Z','X','C','V',
X 'B','N','M','<','>','?',0201,'*',     0203,' ',0204,0221,0222,0223,0224,0225,
X 0226,0227,0230,0231,0232,0270,023,'7', '8','9',0211,'4','5','6',0214,'1',
X '2','3',0207,0177,0271,014,0272,'\r', '\b','\n','\f',036,032,0273,0274,'/',
X 0233,0234,0235,0236,0237,0275,0276,0277
X};
X
X/* Function key scan codes */
X#define IS_FKEY(n) ((n >= 0x3b && n <= 0x44) || (n >= 0x57 && n <= 0x58))
X#define SCAN_CODE_TO_FKEY(n) ((n < 0x45) ? (n - 58) : (n - 76))
X
X/* Misc. scan codes */
X#define DEL_CODE	(char)83
X#define TOP_ROW	14
X
X/* These point to whichever table to actually use */
XPRIVATE	char *	unsh;
XPRIVATE char *	sh;
X
X/* Variables to hold the shift-state of the keyboard */
XPRIVATE	int	shift1, shift2, control, alt, capslock, numlock;
XPRIVATE int	caps_off = 1;
XPRIVATE int	num_off = 1;
X
X/* This message is used to send data to the upper layer */
XPRIVATE message	keybd_mess;
X
X/* set in main.c to 0 if mono, 1 if color. */
XPUBLIC	int	color;
X
X/* A small, internal, circular buffer for received characters */
X#define CIBUFSZ	0x80
X#define CIBUFSZMASK 0x7f
XPRIVATE	char	con_ibuf[CIBUFSZ];
XPRIVATE	int	con_first, con_last, con_size;
XPRIVATE char	con_ilock;
X
X#define NUM_FKEYS	12
X#define	FKEY_TEXT_SIZE	16
X
X/* Definitions for programmable keys (ALT-Fx) */
Xtypedef struct {
X    int		fk_nchars;
X    char	fk_text[FKEY_TEXT_SIZE];
X} FKEY;
X
XPRIVATE	FKEY	fkey_defs[NUM_FKEYS];
X
X/************************************************************************
X *	con_init(minor, devnum)
X *************************************************************************/
XPUBLIC con_init(minor, devnum)
Xint minor;
Xint devnum;
X{
X    int	i;
X
X    /* Set up function key definitions.  For now, to test it out,
X     * set up fkey 1 as 'ls', fkey's 2-12 are undefined.
X     */
X    fkey_defs[0].fk_nchars = 3;
X    fkey_defs[0].fk_text[0] = 'l';
X    fkey_defs[0].fk_text[1] = 's';
X    fkey_defs[0].fk_text[2] = '\r';
X
X    for(i = 1; i < NUM_FKEYS; i++) {
X	fkey_defs[i].fk_nchars = 0;
X    }
X
X    /* Determine whether we have a color or mono display attached,
X     * and set up accordingly.
X     */
X    if(color) {
X	MAX_START = CGA_MAX_START;
X	SEG_MASK = CGA_SEG_MASK;
X	VIDEO_SEG = CGA_VIDEO_SEG;
X	ADDR_PORT = CGA_ADDR_PORT;
X	DATA_PORT = CGA_DATA_PORT;
X	MODE_PORT = CGA_MODE_PORT;
X	COLOR_PORT = CGA_COLOR_PORT;
X	STATUS_PORT = CGA_STATUS_PORT;
X    }
X    else {
X	MAX_START = MONO_MAX_START;
X	SEG_MASK = MONO_SEG_MASK;
X	VIDEO_SEG = MONO_VIDEO_SEG;
X	ADDR_PORT = MONO_ADDR_PORT;
X	DATA_PORT = MONO_DATA_PORT;
X	MODE_PORT = MONO_MODE_PORT;
X	COLOR_PORT = MONO_COLOR_PORT;
X	STATUS_PORT = MONO_STATUS_PORT;
X    }
X
X    /* Initialize the display adapter.  First, disable video while we
X     * muck about...
X     */
X    port_out(MODE_PORT, 0x21);
X
X    /* Initialize all of video memory.  */
X    vidfill(VIDEO_SEG, 0, MAX_START >> 1, 0x0700);
X
X    /* Initialize the 6845 */
X    for(i = 10; i < 16; i++) {
X	port_out(ADDR_PORT, i);
X	port_out(DATA_PORT, m6845_init_table[i]);
X    }
X
X    /* Select video mode, re-enable video output, no fancy colors */
X    port_out(MODE_PORT, 0x29);
X    port_out(COLOR_PORT, 0);
X
X#ifdef CHEAP_CGA
X    /* Initialize output queue */
X    oque_first = 0;
X    oque_last = 0;
X    oque_size = 0;
X#endif CHEAP_CGA
X
X    /* Initialize global data */
X    crow = 0;
X    ccol = 0;
X    attrib = A_CHWHITE;
X    cur_start = 0;
X    cur_cursor = 0;
X    escape_state = 0;
X
X    /* Flow control is OFF */
X    fc_on = 0;
X
X    /* Initialize input queue */
X    con_first = con_last = con_size = 0;
X    con_ilock = 0;
X
X    /* Select scan-code table to use.  If the scan code for '='
X     * is 12, use the Olivetti table, otherwise use the PC table
X     */
X    if(scan_code == 12) {
X	unsh = unm24;
X	sh = m24;
X    }
X    else {
X	unsh = unPC;
X	sh = shPC;
X    }
X
X    cint_sent = 0;
X}
X
X
X/************************************************************************
X *	con_putc(minor, ch)
X *************************************************************************/
XPUBLIC con_putc(minor, ch)
Xint minor;
Xchar ch;
X{
X    int	i;
X
X
X    /* If we are handling an escape sequence, then
X     * continue handling it.
X     */
X    if(escape_state & ESC_INESCAPE) {
X	if(escape_state & ESC_BRACKET) {
X	    /* We've already seen a bracket.  If the char is
X	     * a semicolon, go to the next param.  If it's a
X	     * digit, add it in.  Otherwise, execute the
X	     * sequence.
X	     */
X	    if(ch == ';') {
X		esc_params[++num_esc_params] = 0;
X	    }
X	    else if((ch >= '0') && (ch <= '9')) {
X		esc_params[num_esc_params] *= 10;
X		esc_params[num_esc_params] += (int)(ch - '0');
X	    }
X	    else {
X		scr_longescape(ch);
X		escape_state = 0;
X	    }
X	}
X	else if(ch == '[') {
X	    escape_state |= ESC_BRACKET;
X	}
X	else {
X	    scr_shortescape(ch);
X	    escape_state = 0;
X	}
X	return;
X    }
X
X    /* Handle special characters.  If it's not a special char,
X     * then drop it on the queue and flush it out if it's not
X     * too much trouble.  Also, handle line wrapping and screen
X     * scrolling as necessary!
X     */
X    switch(ch) {
X	case CHAR_ESC:
X	    /* Escape.  Put us into escape-processing mode */
X	    escape_state |= ESC_INESCAPE;
X	    num_esc_params = 0;
X	    esc_params[0] = 0;
X	    break;
X
X	case CHAR_TAB:
X	    /* Tab.  Go thru the tab table until we find a
X	     * tab stop after our current position.  If we find
X 	     * it, set our cursor there.
X	     */
X	    for(i = 0; tab_table[i] > 0; i++) {
X		if (tab_table[i] > (ccol + 1)) {
X		    scr_set_cursor(crow, (tab_table[i] - 1));
X		    break;
X		}
X	    }
X	    break;
X
X	case CHAR_BS:
X	    /* Backspace.  If we're not at the left edge of the
X	     * screen, then back our cursor up one notch.
X	     */
X	    if(ccol > 0) {
X		scr_set_cursor(crow, ccol - 1);
X	    }
X	    break;
X
X	case CHAR_CR:
X	    /* Carriage return.  Put the cursor at the beginning 
X	     * of the line
X	     */
X	    scr_set_cursor(crow, 0);
X	    break;
X
X	case CHAR_LF:
X	    /* Linefeed.  If we're on the bottom row of the screen,
X	     * we have to scroll the screen down a line first.  In
X	     * either case, position our cursor on the next line
X 	     * scr_forward_scroll automatically repositions the cursor
X	     * to reflect the fact that the screen scrolled up...
X	     */
X	    if(crow == (NROWS - 1)) {
X		scr_forward_scroll(1);
X	    }
X	    scr_set_cursor(crow+1, ccol);
X	    break;
X
X	case CHAR_BELL:
X	    beep(0x0533);
X	    break;
X
X	default:
X	    /* This is merely a character to be written out.
X	     * If it's a non-printing character, ignore it.  The
X	     * high-level TTY driver should have handled this
X	     * already.  Otherwise, check our position.  If we
X	     * are at the end of the line, then we have to wrap
X	     * to the beginning of the next line.  This may, in turn
X	     * require that we scroll the screen.
X	     */
X	    if(ch > CHAR_ESC) {
X		if((ccol == NCOLS - 1) && wrap_needed && wrap_enabled) {
X		    if(crow == NROWS - 1) {
X			scr_forward_scroll(1);
X		    }
X		    scr_set_cursor(crow+1, 0);
X		}
X
X#ifdef CHEAP_CGA
X		/* Now we're at the spot where we'd like the
X		 * character to go.  Make sure there's a clear
X		 * spot in the queue for us to drop a character
X		 * into...
X		 */
X		while(oque_size == NUM_OQUE_ENTRIES) {
X		    scr_flush_output(2);
X		}
X
X		oque[oque_last].address = cur_start +
X				( ((crow * NCOLS) + ccol) << 1);
X		oque[oque_last].ATTRIB = attrib;
X		oque[oque_last].CH = ch;
X		oque_last = (oque_last + 1) % NUM_OQUE_ENTRIES;
X		oque_size++;
X
X		/* Flush whatever we can... */
X		scr_flush_output(1);
X
X#else
X		put_byte(VIDEO_SEG,
X		    (int)(cur_start + (((crow * NCOLS) + ccol) << 1)),
X		    (int)ch);
X		put_byte(VIDEO_SEG,
X		    (int)(cur_start + (((crow * NCOLS) + ccol) << 1)) + 1,
X		    (int)attrib);
X#endif
X
X		/* Set our cursor to the next position */
X		if(ccol == NCOLS - 1) { 
X		    /* If we just wrote INTO the last column, then
X		     * set wrap_needed.  We don't need to reposition
X		     * the cursor at this point...
X		     */
X		    wrap_needed = 1;
X		}
X		else {
X		    scr_set_cursor(crow, ccol+1);
X		}
X	    }
X	    break;
X    }
X
X}
X
X/************************************************************************
X *	con_ioctl(minor, func, addr)
X *************************************************************************/
XPUBLIC con_ioctl(minor, func, addr)
Xint minor;
Xint func;
Xchar * addr;
X{
X    /* No ioctls supported at the moment... */
X    return;
X}
X
X
X/************************************************************************
X *	con_fc(minor)
X *************************************************************************/
XPUBLIC con_fc(minor)
Xint minor;
X{
X    fc_on = 1;
X}
X
X
X/************************************************************************
X *	con_nofc(minor)
X *************************************************************************/
XPUBLIC con_nofc(minor)
Xint minor;
X{
X    fc_on = 0;
X}
X
X
X/************************************************************************
X *	con_oflush(minor)
X *************************************************************************/
XPUBLIC con_oflush(minor)
Xint minor;
X{
X
X#ifdef CHEAP_CGA
X    scr_flush_output(3);
X#endif CHEAP_CGA
X}
X
X
X/************************************************************************
X *	scr_set_cursor(row, col)
X *************************************************************************/
XPRIVATE scr_set_cursor(row, col)
Xint row;
Xint col;
X{
X
X    row = (row < 0) ? 0 : row;
X    crow = (row > NROWS) ? NROWS : row;
X    col = (col < 0) ? 0 : col;
X    ccol = (col > NCOLS) ? NCOLS : col;
X    cur_cursor = (cur_start + (((crow * NCOLS) + ccol) << 1)); /* & SEG_MASK */
X
X    port_out(ADDR_PORT, CURSOR_LO);
X    port_out(DATA_PORT, (int)(cur_cursor >> 1) & 0xff);
X    port_out(ADDR_PORT, CURSOR_HI);
X    port_out(DATA_PORT, (int)(((cur_cursor >> 1) & 0xff00) >> 8));
X
X    wrap_needed = 0;
X}
X
X
X/************************************************************************
X *	scr_forward_scroll(nlines)
X *************************************************************************/
XPRIVATE scr_forward_scroll(nlines)
Xint nlines;
X{
X    int		i;
X    int		start, end;
X
X#ifdef CHEAP_CGA
X    /* Since this routine will change our origin, we should first
X     * flush all pending output 
X     */
X    scr_flush_output(3);
X#endif CHEAP_CGA
X
X    /* max out nlines at NROWS; scrolling beyond that is meaningless */
X    if(nlines > NROWS) nlines = NROWS;
X
X    /* Zero out that section of video memory that we're going to use. */
X    start = (cur_start + ((NROWS * NCOLS) << 1)) & SEG_MASK;
X    end = (start + ((nlines * NCOLS) << 1)) & SEG_MASK;
X    scr_clear_section(start, end);
X    
X    /* Adjust cur_start, and send it out to the CGA */
X    cur_start = (cur_start + ((nlines * NCOLS) << 1)) & SEG_MASK;
X
X    port_out(ADDR_PORT, START_LO);
X    port_out(DATA_PORT, (int)(cur_start >> 1) & 0xff);
X    port_out(ADDR_PORT, START_HI);
X    port_out(DATA_PORT, (int)(((cur_start >> 1) & 0xff00) >> 8));
X
X
X    /* Adjust cursor row accordingly.  Don't go off the top of the
X     * screen, though...
X     */
X
X    crow -= nlines;
X    if(crow < 0) crow = 0;
X    scr_set_cursor(crow, ccol);
X
X}
X
X#ifdef CHEAP_CGA
X/************************************************************************
X *	scr_flush_output(option)
X *	option - Indicates how hard we are to try to put the next
X *		character out.  If 0, put out one character only 
X *		if we're already in the blanking interval.  If 1,
X *		then if we're already in the blanking interval,
X *		put out as many characters as we can during this
X *		blanking interval.  If 2, wait for the next blanking
X *		interval, then put out as many chars as we can.  If
X *		3, then drain the whole queue, no matter HOW many
X *		blanking intervals it takes.
X *************************************************************************/
XPRIVATE scr_flush_output(option)
Xint option;
X{
X
X    int i;
X    int max_chars;
X    int	status;
X
X    /* Don't bother if we have no work to do */
X    if(oque_size == 0) { return; }
X
X    switch(option) {
X
X	case 0:
X	    /* Output something, but only if blanking is on */
X	    if(vout(VIDEO_SEG, oque[oque_first].address,
X		    (int)oque[oque_first].INTVAL)) {
X
X		oque_first = (oque_first + 1) % NUM_OQUE_ENTRIES;
X		oque_size--;
X	    }
X	    break;
X
X	case 1:
X	    /* Output something while blanking is on */
X	    while(vout(VIDEO_SEG, oque[oque_first].address,
X		    (int)oque[oque_first].INTVAL)) {
X		oque_first = (oque_first + 1) % NUM_OQUE_ENTRIES;
X		oque_size--;
X		if(oque_size == 0) break;
X	    }
X	    break;
X
X	case 2:
X	    /* Wait for blanking interval, then keep outputting
X	     * until blanking interval is over.
X	     */
X
X	    voutsync(VIDEO_SEG, oque[oque_first].address,
X			(int)oque[oque_first].INTVAL);
X
X
X	    oque_first = (oque_first + 1) % NUM_OQUE_ENTRIES;
X	    oque_size--;
X	    if(oque_size == 0) break;
X
X	    while(vout(VIDEO_SEG, oque[oque_first].address,
X		    (int)oque[oque_first].INTVAL)) {
X
X		oque_first = (oque_first + 1) % NUM_OQUE_ENTRIES;
X		oque_size--;
X		if(oque_size == 0) break;
X
X	    }
X	    break;
X
X	case 3:
X	    /* Just drain the queue! */
X	    while(oque_size) {
X	        voutsync(VIDEO_SEG, oque[oque_first].address,
X			 (int)oque[oque_first].INTVAL);
X
X		oque_first = (oque_first + 1) % NUM_OQUE_ENTRIES;
X		oque_size--;
X	    }
X	    break;
X    }
X}
X#endif CHEAP_CGA
X
X/************************************************************************
X *	scr_shortescape(ch)
X *
X *	At present, the following short escape sequences are supported:
X *
X *	<none>
X *************************************************************************/
XPRIVATE scr_shortescape(ch)
Xchar ch;
X{
X    return;
X}
X
X/************************************************************************
X *	scr_longescape(ch)
X *
X *	At present, the following long escape sequences are supported:
X *
X *	ESC [ Pn A	Cursor Up
X *	ESC [ Pn B	Cursor Down
X *	ESC [ Pn C	Cursor Right
X *	ESC [ Pn D	Cursor Left
X *	ESC [ Pl;Pc H	Set cursor (Pl = line, Pc = column)
X *	ESC [ Pl;Pc f	Same as ESC [ Pl;Pc H
X *	ESC [ Pn K	Line erase (0 = cursor to end of line,
X *			1 = Beginning of line to cursor, 2 = whole line)
X *	ESC [ Pn J	Screen erase (0 = Cursor to end of screen, 1 =
X *			beginning of screen to cursor, 2 = whole screen)
X *	ESC Pn;Pn... m	Set character attributes.
X *************************************************************************/
XPRIVATE scr_longescape(ch)
Xchar ch;
X{
X    int		i;
X    int		first_addr;
X    int		last_addr;
X
X    switch (ch) {
X	case 'A':
X	    if(esc_params[0] == 0) esc_params[0] = 1;
X	    scr_set_cursor(crow - esc_params[0], ccol);
X	    break;
X
X	case 'B':
X	    if(esc_params[0] == 0) esc_params[0] = 1;
X	    scr_set_cursor(crow + esc_params[0], ccol);
X	    break;
X
X	case 'C':
X	    if(esc_params[0] == 0) esc_params[0] = 1;
X	    scr_set_cursor(crow, ccol + esc_params[0]);
X	    break;
X
X	case 'D':
X	    if(esc_params[0] == 0) esc_params[0] = 1;
X	    scr_set_cursor(crow, ccol - esc_params[0]);
X	    break;
X
X	case 'H':
X	case 'f':
X	    /* Do 1-origin, but treat 0 as 1 */
X	    if(esc_params[0]) esc_params[0]--;
X	    if(esc_params[1]) esc_params[1]--;
X	    scr_set_cursor(esc_params[0], esc_params[1]);
X	    break;
X
X	case 'K':
X	    switch(esc_params[0]) {
X		case 0:
X		    /* Cursor to end of line */
X		    first_addr=(cur_start + (2 * crow * NCOLS) + (2 * ccol))
X					& SEG_MASK;
X		    last_addr = (cur_start + (2 * (crow + 1) * NCOLS) - 1)
X					& SEG_MASK;
X		    break;
X		case 1:
X		    /* Beginning of line to cursor */
X		    first_addr = (cur_start + (2 * crow * NCOLS)) & SEG_MASK;
X		    last_addr = (cur_start + (2 * crow * NCOLS) + (2 * ccol))
X					& SEG_MASK;
X		    break;
X
X		case 2:
X		    /* Whole line */
X		    first_addr = (cur_start + (2 * crow * NCOLS)) & SEG_MASK;
X		    last_addr = (cur_start + (2 * (crow + 1) * NCOLS) - 1)
X					& SEG_MASK;
X		    break;
X	    }
X	    scr_clear_section(first_addr, last_addr);
X	    break;
X
X	case 'J':
X	    switch(esc_params[0]) {
X		case 0:
X		    /* Cursor to end of screen */
X		    first_addr=(cur_start + (2 * crow * NCOLS) + (2 * ccol))
X					& SEG_MASK;
X		    last_addr = (cur_start + (2 * NROWS * NCOLS) - 1)
X					& SEG_MASK;
X
X		    break;
X
X		case 1:
X		    /* Beginning of screen to cursor */
X		    first_addr = cur_start;
X		    last_addr=(cur_start + (2 * crow * NCOLS) + (2 * ccol))
X					& SEG_MASK;
X		    break;
X
X		case 2:
X		    /* Whole screen */
X		    first_addr = cur_start;
X		    last_addr = (cur_start + (2 * NROWS * NCOLS) - 1)
X					& SEG_MASK;
X		    break;
X	    }
X	    scr_clear_section(first_addr, last_addr);
X	    break;
X
X	case 'm':
X	    for(i = 0; i <= num_esc_params; i++) {
X		switch(esc_params[i]) {
X		    char	_temp;
X
X		    case 0:
X			/* Normal attributes */
X			attrib = A_CHWHITE;
X			break;
X		    case 1:
X			/* Bold */
X			attrib |= A_INTENSE;
X			break;
X		    case 4:
X			/* Underscore.  Since the 6845 does not actually
X			 * support underscoring (near as I can tell, 
X			 * anyway) then on the color monitor we'll
X			 * implement it by using red characters.  On
X			 * a mono monitor, we'll use intense characters.
X			 */
X			if(color) {
X			    if(attrib & A_CHWHITE) {
X			        attrib &= ~(A_CHBLUE | A_CHGREEN);
X			    }
X			    else {
X			        attrib &= ~(A_BGBLUE | A_BGGREEN);
X			    }
X			}
X			else {
X			    attrib |= A_INTENSE;
X			}
X			break;
X
X		    case 5:
X			/* Blink */
X			attrib |= A_BLINK;
X			break;
X
X		    case 7:
X			/* Reverse video */
X			_temp = attrib & A_CHWHITE;
X			attrib &= ~(A_CHWHITE);
X			attrib |= (_temp << 4);
X			break;
X		}
X	    }
X	    break;
X    }
X			
X}
X
X
X/************************************************************************
X *	scr_clear_section(start, end)
X *************************************************************************/
XPRIVATE scr_clear_section(start, end)
Xint	start;
Xint	end;
X{
X
X    if(end < start) {
X#ifdef CHEAP_CGA
X	vidsfill(VIDEO_SEG, start, (MAX_START - start) >> 1, 0x0700);
X	vidsfill(VIDEO_SEG, 0, end >> 1, 0x0700);
X#else
X	vidfill(VIDEO_SEG, start, (MAX_START - start) >> 1, 0x0700);
X	vidfill(VIDEO_SEG, 0, end >> 1, 0x0700);
X#endif
X    }
X    else {
X#ifdef CHEAP_CGA
X	vidsfill(VIDEO_SEG, start, (end - start) >> 1, 0x0700);
X#else
X	vidfill(VIDEO_SEG, start, (end - start) >> 1, 0x0700);
X#endif CHEAP_CGA
X    }
X}
X
X
X/************************************************************************
X * Function:
X *	beep()
X *
X * Description:
X *	Ring the bell.
X * 
X *************************************************************************/
Xbeep(f)
Xint f;
X{
X/* Sound a beep.  The beep is kept short, because interrupts must be
X * disabled during beeping, and it is undesirable to keep them off
X * too long.  This routine works by turning on the bits in port B of the
X * 8255 chip that drive the speaker.
X */
X
X	int	k, x;
X
X	lock();
X	port_out(TIMER3, 0xB6);
X	port_out(TIMER2, f & BYTE);
X	port_out(TIMER2, (f>>8) & BYTE);
X	port_in(PORT_B, &x);
X	port_out(PORT_B, x|3);
X	for(k = 0; k < B_TIME; k++) ;
X	port_out(PORT_B, x);
X	unlock();
X}
X
X
X/************************************************************************
X * Function:
X *	keyboard()
X *
X * Description:
X *	Keyboard interrupt service routine.
X * 
X *************************************************************************/
XPUBLIC keyboard()
X{
X/* A keyboard interrupt has occurred.  Process it. */
X
X  int val, code, k;
X  char ch;
X  char parse_code();
X
X  /* Fetch the character from the keyboard hardware and acknowledge it. */
X  port_in(KEYBD, &code);	/* get the scan code for the key struck */
X  port_in(PORT_B, &val);	/* strobe the keyboard to ack the char */
X  port_out(PORT_B, val | KBIT);	/* strobe the bit high */
X  port_out(PORT_B, val);	/* now strobe it low */
X
X
X  /* The IBM keyboard interrupts twice per key, once when depressed, once when
X   * released.  Filter out the latter, ignoring all but the shift-type keys.
X   * The shift-type keys, 29, 42, 54, 56, 58, and 69 must be processed normally.
X   */
X  k = code - 0200;		/* codes > 0200 mean key release */
X  if (k > 0) {
X	/* A key has been released. */
X	if (k != 29 && k != 42 && k != 54 && k != 56 && k != 58 && k != 69) {
X		port_out(INT_CTL, ENABLE);	/* re-enable interrupts */
X	 	return;		/* don't call tty_task() */
X	}
X  }
X
X  /* Check for CTRL-ALT-DEL, and if found, reboot the computer. */
X  if (control && alt && code == DEL_CODE) reboot();	/* CTRL-ALT-DEL */
X
X  /* Certain function key combinations are used for debugging.  Since
X   * we may want to use the function keys for applications, we'll
X   * require that one hit CTRL-ALT-Fx to do a debug function...
X   */
X
X  if(control && alt && IS_FKEY(code)) {
X	func_key(SCAN_CODE_TO_FKEY(code));
X	port_out(INT_CTL, ENABLE);
X	return;
X  }
X
X  /* If flow control is not on, then send this character up to the
X   * driver 
X   */
X  if (!fc_on) {
X	int	send_charint = 0;
X
X	/* Special function keys get handled specially here. */
X
X	if(IS_FKEY(code)) {
X	    int		fkey_number;
X
X	    fkey_number = SCAN_CODE_TO_FKEY(code) - 1;
X	    send_charint = 1;
X	    if(control) {
X		/* CTRL-Fkey sends up ESC Q <P+fkey_number> */
X		kbd_insert_char(CHAR_ESC);
X		kbd_insert_char('Q');
X		kbd_insert_char((char)('P' + fkey_number));
X	    }
X	    else if (alt) {
X		/* ALT-Fkey sends the programmed string for that
X		 * fkey.
X		 */
X		if(fkey_defs[fkey_number].fk_nchars > 0) {
X		    for(k = 0; k < fkey_defs[fkey_number].fk_nchars; k++) {
X			kbd_insert_char(fkey_defs[fkey_number].fk_text[k]);
X		    }
X		}
X		else {
X		    send_charint = 0;
X		}
X	    }
X	    else {
X		/* Plain Fkey sends up ESC O <P+fkey_number> */
X		kbd_insert_char(CHAR_ESC);
X		kbd_insert_char('O');
X		kbd_insert_char((char)('P' + fkey_number));
X	    }
X	}
X	else {
X	    ch = parse_code(code);
X
X	    if(ch) {
X
X	        if(ch == AT_SIGN) { ch = 0; }
X	        kbd_inser_char(ch);
X		send_charint = 1;
X	    }
X	}
X
X	/* Build and send the interrupt message. */
X	if(send_charint && !cint_sent) {
X	    cint_sent = 1;
X	    keybd_mess.m_type = TTY_CHAR_INT;
X	    keybd_mess.TTY_LINE = 0;
X	    interrupt(TTY, &keybd_mess); /* send a message to tty task */
X	}
X	port_out(INT_CTL, ENABLE);
X  } else {
X	/* Flow control is on.  Discard whatever was typed. */
X	port_out(INT_CTL, ENABLE);	/* re-enable 8259A controller */
X  }
X}
X
Xkbd_insert_char(ch)
Xchar ch;
X{
X    /* Insert the specified char in the keyboard holding
X     * buffer, but only if there's enough room for it.
X     */
X    if(con_size < CIBUFSZ) {
X	con_ibuf[con_last] = ch;
X	con_last = (con_last + 1) & CIBUFSZMASK;
X	con_size++;
X    }
X}
X
X
X
X/************************************************************************
X * Function:
X *	parse_code(ch)
X *
X * Description:
X *	Translate the scan code for the key just struck or released.
X * 
X * Arguments:
X *	ch	Scan code to translate.
X *
X * Return Value:
X *	The character's ascii equivalent if possible.  If no translation
X *	is to be done, returns 0.  Returns special code AT_SIGN if we are
X *	returning an ASCII 0... (isn't return-value overloading wonderful?)
X *
X *************************************************************************/
XPRIVATE char parse_code(ch)
Xchar ch;			/* scan code of key just struck or released */
X{
X/* This routine can handle keyboards that interrupt only on key depression,
X * as well as keyboards that interrupt on key depression and key release.
X * For efficiency, the interrupt routine filters out most key releases.
X */
X
X  int c, make, code;
X
X
X  c = ch & 0177;		/* high-order bit set on key release */
X  make = (ch & 0200 ? 0 : 1);	/* 1 when key depressed, 0 when key released */
X
X  /* Translate scan code to character code */
X  code = (shift1 || shift2 ? sh[c] : unsh[c]);
X  if (control && c < TOP_ROW) code = sh[c];	/* CTRL-(top row) */
X  if (c > 70 && numlock) 		/* numlock depressed */
X	code = (shift1 || shift2 ? unsh[c] : sh[c]);
X
X  code &= BYTE;
X  if (code < 0200 || code >= 0206) {
X	/* Ordinary key, i.e. not shift, control, alt, etc. */
X	if (capslock)
X		if (code >= 'A' && code <= 'Z')
X			code += 'a' - 'A';
X		else if (code >= 'a' && code <= 'z')
X			code -= 'a' - 'A';
X	if (alt) code |= 0200;	/* alt key ORs 0200 into code */
X	if (control) code &= 037;
X	if (code == 0) code = AT_SIGN;	/* @ is 0100, so CTRL-@ = 0 */
X	if (make == 0) code = 0;	/* key release */
X	return(code);
X  }
X
X  /* Table entries 0200 - 0206 denote special actions. */
X  switch(code - 0200) {
X    case 0:	shift1 = make;		break;	/* shift key on left */
X    case 1:	shift2 = make;		break;	/* shift key on right */
X    case 2:	control = make;		break;	/* control */
X    case 3:	alt = make;		break;	/* alt key */
X    case 4:	if (make && caps_off) capslock = 1 - capslock; 
X		caps_off = 1 - make;
X		break;	/* caps lock */
X    case 5:	if (make && num_off) numlock  = 1 - numlock;  
X		num_off = 1 - make;
X		break;	/* num lock */
X  }
X  return(0);
X}
X
X
X/************************************************************************
X * Function:
X *	func_key(k)
X *
X * Description:
X *	Processes the given F-key.  At present, F1 causes a process
X *	dump, and F2 causes a memory dump.
X * 
X * Arguments:
X *	k	Function key number
X *
X *************************************************************************/
Xfunc_key(ch)
Xchar ch;
X{
X    if(ch == 1) p_dmp();
X    if(ch == 2) map_dmp();
X}
X
X/************************************************************************
X * Function:
X *	con_bufin(minor, bufptr, maxchars)
X *
X * Description:
X *	Gets buffered input from the console layer.
X * 
X * Arguments:
X *	minor	Minor device.  Must be zero.  Ignored.
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 con_bufin(minor, bufptr, maxchars)
Xint minor;
Xchar * bufptr;
Xint maxchars;
X{
X    int		nchars;
X    int		i;
X
X
X    /* nchars = min(maxchars, con_size) */
X    nchars = (maxchars < con_size) ? maxchars : con_size;
X
X    for(i = 0; i < nchars; i++) {
X	bufptr[i] = con_ibuf[con_first];
X	con_first = (con_first + 1) & CIBUFSZMASK;
X	con_size--;
X    }
X    cint_sent = 0;
X    return(nchars);
X}
X
X
Xconwrap() { wrap_enabled = 1; }
Xconnowrap() { wrap_enabled = 0; }
X
/
echo x - klib88.newstf
gres '^X' '' > klib88.newstf << '/'
X.globl _vidfill, _vidsfill, _vout, _voutsync
X
X|-----------------------------------------------------------------------
X| Function:
X|	vidfill(seg, start, length, wordval)
X|
X| Description:
X| 	Fills the specified portion of the video buffer with the
X|	specified word value.
X|
X| Arguments:
X|	seg	Video segment number
X|	start	Starting offset within video segment
X|	length	Number of positions to fill
X|	wordval	Word value <char, attrib> to stuff into buffer.
X|
X| Return Value:
X|	None.
X|
X|-----------------------------------------------------------------------
X
X_vidfill:
X	push	bp
X	mov	bp,sp		| BP is frame pointer
X	push	es
X	push	bx		| Save es & bx
X	push	cx
X	mov	ax,4(bp)	| Move desired seg into ES
X	mov	es,ax
X	mov	ax,6(bp)	| Get initial offset
X	mov	bx,ax		| into bx
X	mov	ax,8(bp)	| Get count
X	cmp	ax,#0		| If zero, nothing to do
X	jz	v120
X	mov	cx,ax		| cx <- count
X	mov	ax,10(bp)	| Get value
Xv100:
X	seg	es		| Use ES
X	mov	(bx),ax		| stuff byte
X	inc	bx		| Go to next word
X	inc	bx
X	loop	v100		| Keep going...
Xv120:
X	pop	cx
X	pop	bx		| restore regs
X	pop	es
X	pop	bp
X	ret
X
X|-----------------------------------------------------------------------
X| Function:
X|	vidsfill(seg, start, length, wordval)
X|
X| Description:
X| 	Fills the specified portion of the video buffer with the
X|	specified word value.  Same as vidfill() except it only
X|	touches buffer RAM during the blanking interval.
X|
X| Arguments:
X|	seg	Video segment number
X|	start	Starting offset within video segment
X|	length	Number of positions to fill
X|	wordval	Word value <char, attrib> to stuff into buffer.
X|
X| Return Value:
X|	None.
X|
X|-----------------------------------------------------------------------
X
X_vidsfill:
X	push	bp
X	mov	bp,sp		| BP is frame pointer
X	push	es
X	push	bx		| Save regs
X	push	cx
X	push	dx
X	push	si
X	mov	ax,4(bp)	| Move desired seg into ES
X	mov	es,ax
X	mov	ax,6(bp)	| Get initial offset
X	mov	si,ax		| into si
X	mov	ax,8(bp)	| Get count
X	cmp	ax,#0		| If zero, no work to do
X	jz	v159
X	mov	cx,ax		| cx <- count
X	mov	ax,10(bp)	| Get value
X	mov	bx,ax		| into bx
X	mov	dx,#986		| Wait for blanking interval
Xv150:
X	in
X	testb	al,#1
X	jz	v160
X
X	seg	es		| Use ES
X	mov	#0(si),bx	| stuff byte
X	inc	si		| Go to next word
X	inc	si
X	loop	v150		| Keep going...
Xv159:
X	pop	si
X	pop	dx
X	pop	cx
X	pop	bx		| restore regs
X	pop	es
X	pop	bp
X	ret
X
Xv160:	push	cx		| Delay loop, so we don't flicker display
X	mov	cx,#32		| by sampling too often.
Xv161:
X	loop	v161
X	pop	cx
X	jmp	v150
X
X|-----------------------------------------------------------------------
X| Function:
X|	vout(seg, addr, wordval)
X|
X| Description:
X|	Writes the specified word into the specified location in the
X|	video RAM if it is currently in the blanking interval.  If
X|	not, then don't.  Return a value indicating whether we could
X|	or not.
X| 
X| Arguments:
X|	seg	Video segment
X|	addr	offset within the video segment
X|	wordval	Value to write into the video RAM
X|
X| Return Value:
X|	1 if write was successful.
X|	0 if unsuccessful.
X|-----------------------------------------------------------------------
X
X_vout:
X	push	bp
X	mov	bp,sp		| bp is stack pointer
X	push	es		| Save regs: es, bx, cx, and dx
X	push	bx
X	push	cx
X	push	dx
X
X	mov	ax,4(bp)	| Get segment into es
X	mov	es,ax
X	mov	ax,6(bp)	| Get address into bx
X	mov	bx,ax
X	mov	ax,8(bp)	| Get wordval into cx
X	mov	cx,ax
X	mov	dx,#986		| dx = CGA status port number (0x3da)
X	in			| Read port.
X	testb	al,#1		| Test blanking bit.
X	jz	v200		| Do nothing if zero
X	seg	es
X	mov	(bx),cx		| In we go!
X	mov	ax,#1		| Success!
X	jmp	v210
Xv200:
X	mov	ax,#0		| Failure...
Xv210:
X	pop	dx		| Pop regs
X	pop	cx
X	pop	bx
X	pop	es
X	pop	bp
X	ret
X	
X
X|-----------------------------------------------------------------------
X| Function:
X|	voutsync(seg, addr, wordval)
X|
X| Description:
X|	Wait for a blanking interval if necessary and write the
X|	specified word into the video RAM.
X| 
X| Arguments:
X|	seg	Video segment
X|	addr	offset within the video segment
X|	wordval	Value to write into the video RAM
X|
X| Return Value:
X|	None.
X|-----------------------------------------------------------------------
X
X_voutsync:
X	push	bp
X	mov	bp,sp		| bp is stack pointer
X	push	es		| Save regs: es, bx, cx, and dx
X	push	bx
X	push	cx
X	push	dx
X
X	mov	ax,4(bp)	| Get segment into es
X	mov	es,ax
X	mov	ax,6(bp)	| Get address into bx
X	mov	bx,ax
X	mov	ax,8(bp)	| Get wordval into cx
X	mov	cx,ax
X	mov	dx,#986		| dx = CGA status port number (0x3da)
Xv300:
X	in			| Read port.
X	testb	al,#1		| Test blanking bit.
X	jz	v350		| If zero, keep trying!
X	seg	es
X	mov	(bx),cx		| In we go!
X	pop	dx		| Pop regs
X	pop	cx
X	pop	bx
X	pop	es
X	pop	bp
X	ret
X	
Xv350:	push	cx		| Delay loop
X	mov	cx,#32
Xv351:
X	loop	v351
X	pop	cx
X	jmp	v300
X
/
echo x - mansi.tc
gres '^X' '' > mansi.tc << '/'
X# Termcap entry for MINIX ANSI tty driver (including function keys)
X
XMA|mansi|My own MINIX ansi driver:\
X	:li#25:co#80:pc=\000:cd=\E[J:cm=\E[%i%d;%dH:bc=\010:\
X	:k1=\EOP:k2=\EOQ:k3=\EOR:k4=\EOS:k5=\EOT:k6=\EOU:\
X	:k7=\EOV:k8=\EOW:k9=\EOX:bs:cl=\E[H\E[J:ho=\E[H:\
X	:so=\E[7m:se=\E[0m:
/