packages feed

language-Modula2-0.1: examples/Modula-2_Libraries/PMOS/sources/general/serialio.mod

IMPLEMENTATION MODULE SerialIO;

	(********************************************************)
	(*							*)
	(*	     Serial I/O through the COM ports		*)
	(*							*)
	(*	This module supports the NS16450 or INS8250	*)
	(*	serial interfaces on COM1 and COM2.  Support	*)
	(*	for COM3 and COM4 has not yet been implemented.	*)
	(*							*)
	(*  Programmer:		P. Moylan			*)
	(*  Last edited:	16 March 1995			*)
	(*  Status:		Working, needs more testing.	*)
	(*	Faults:						*)
	(*	 1. Can't get loopback mode to work, although	*)
	(*	    it did work in the non-interrupt version.	*)
	(*	 2. High-speed input seems to crash system.	*)
	(*	 3. Spurious input on channel 1 - this goes	*)
	(*	    away when I unplug my modem, so might not	*)
	(*	    be a problem with this module.		*)
	(*	Other shortcomings:				*)
	(*	 1. Receive overrun simply ignored.		*)
	(*	 2. No ^S/^Q controls.				*)
	(*	 3. No support for advanced features.		*)
	(*							*)
	(********************************************************)

FROM LowLevel IMPORT
    (* proc *)	InByte, OutByte, LowByte, HighByte, IANDB, IORB, Div;

FROM MiscPMOS IMPORT
    (* proc *)	EnterCriticalSection, LeaveCriticalSection;

FROM TerminationControl IMPORT
    (* proc *)	SetTerminationProcedure;

FROM TaskControl IMPORT
    (* proc *)	CreateInterruptTask, WaitForInterrupt, KillInterruptTask;

FROM Semaphores IMPORT
    (* type *)	Semaphore,
    (* proc *)	CreateSemaphore, Wait, Signal;

FROM GlassTTY IMPORT	(* for testing *)
    (* proc *)	WriteChar, WriteHexByte;

(************************************************************************)

TYPE PortNumbers = ARRAY SerialChannelNumber OF CARDINAL;
     PortMasks = ARRAY SerialChannelNumber OF BYTE;
     ParityCode = ARRAY Parity OF SHORTCARD;

CONST
    (* Starting port numbers for the four UARTs.	*)
    (* Still have to look up the details for COM3,COM4.	*)

    BasePort = PortNumbers (3F8H, 2F8H, 0, 0);

    (* Interrupt numbers for the four UARTs.		*)
    (* Still have to look up the details for COM3,COM4.	*)

    IntNum = PortNumbers (12, 11, 0, 0);
    IntNumMask = PortMasks (0EFH, 0F7H, 0FFH, 0FFH);

    (* Port numbers within a UART, expressed as offsets from BasePort.	*)

    TXD = 0;		(* transmitter data register	*)
    RXD = 0;		(* receiver data register	*)
    IER = 1;		(* interrupt enable register	*)
    IIR = 2;		(* interrupt identification reg	*)
    LCR = 3;		(* line control register	*)
    MCR = 4;		(* modem control register	*)
    LSR = 5;		(* line status register		*)
    MSR = 6;		(* modem status register	*)

    (* Mapping of parity codes into LCR bit patterns.	*)

    ParitySpec = ParityCode (00H, 08H, 18H, 28H, 38H);

(************************************************************************)

CONST MaxBufferSubscript = 31;

TYPE
    BufferSubscript = [0..MaxBufferSubscript];

    (* A CircBuffer is a circular buffer.  The DataCounter semaphore is	*)
    (* a counting semaphore which keeps track of how many bytes are at	*)
    (* present in the buffer (for an input buffer) or how many spare	*)
    (* slots are still available (for an output buffer).  GetPlace	*)
    (* points to the next byte which will be retrieved by the consumer.	*)
    (* PutPlace is the place where the next datum will be added.	*)
    (* The condition GetPlace=PutPlace could mean either that the	*)
    (* buffer is completely empty or that it is completely full.  The	*)
    (* flag BufferFlag is used to distinguish between these two cases.	*)
    (* The condition BufferFlag=TRUE means buffer full for an input	*)
    (* buffer, or buffer empty for an output buffer.			*)

    CircBuffer =    RECORD
			BufferFlag: BOOLEAN;
			DataCounter: Semaphore;
			GetPlace, PutPlace: BufferSubscript;
			data: ARRAY BufferSubscript OF BYTE
		    END;

VAR
    (* Receiver and transmitter circular buffers. *)

    RXBuffer, TXBuffer: ARRAY SerialChannelNumber OF CircBuffer;

    (* Saved state of the UARTS at startup, so we can restore them later. *)

    SavedRegisters: ARRAY SerialChannelNumber OF ARRAY[1..8] OF BYTE;

(************************************************************************)
(*	    THE INTERRUPT TASKS AND THEIR SUBSIDIARY PROCEDURES		*)
(************************************************************************)

PROCEDURE PutRXBuffer (chan: SerialChannelNumber;  datum: BYTE);

    (* Puts the new datum into the circular buffer used for input from	*)
    (* the given channel.						*)

    BEGIN
	WITH RXBuffer[chan] DO
	    IF BufferFlag THEN
		(*Beep*);		(* no room in buffer *)
	    ELSE
		data[PutPlace] := datum;
		IF PutPlace = MaxBufferSubscript THEN PutPlace := 0
		ELSE INC (PutPlace);
		END (*IF*);
		BufferFlag := PutPlace = GetPlace;
		Signal (DataCounter);
	    END (*IF*);
	END (*WITH*);
    END PutRXBuffer;

(************************************************************************)

PROCEDURE SendFromTXBuffer (chan: SerialChannelNumber);

    (* Sends a new value from the transmitter circular buffer to the	*)
    (* outside world.  Disables "transmitter ready" interrupts if this	*)
    (* empties the circular buffer.					*)

    VAR UART: CARDINAL;

    BEGIN
	UART := BasePort[chan];
	WITH TXBuffer[chan] DO
	    OutByte (UART+TXD, data[GetPlace]);
	    IF GetPlace = MaxBufferSubscript THEN GetPlace := 0
	    ELSE INC (GetPlace);
	    END (*IF*);
	    BufferFlag := PutPlace = GetPlace;
	    IF BufferFlag THEN
		(* Buffer is empty - disable further output. *)
		OutByte (UART+IER, IANDB (InByte(UART+IER), 0DH));
	    END (*IF*);
	    Signal (DataCounter);
	END (*WITH*);
    END SendFromTXBuffer;

(************************************************************************)

PROCEDURE HandleInterrupt (chan: SerialChannelNumber);

    (* This procedure deals with an interrupt from any serial port.	*)
    (* The input parameter identifies the starting port number of the	*)
    (* hardware interface.						*)
    (* Note: because there are multiple sources of interrupts attached	*)
    (* to the same interrupt request, this procedure keeps looping	*)
    (* until all causes of the interrupt have been dealt with.		*)

    VAR condition, dummy: SHORTCARD;
	UART: CARDINAL;

    BEGIN
	UART := BasePort[chan];
	LOOP
	    (* Read the interrupt identification register.	*)

	    condition := InByte (UART+IIR);

	    (* Bit 0 set means no conditions are pending.	*)

	    IF ODD(condition) THEN
		EXIT(*LOOP*)
	    END(*IF*);

	    IF condition = 6 THEN

		(* Code 6 is receiver line status error.  In this	*)
		(* version we do nothing except clear the condition by	*)
		(* reading the Line Status Register.			*)

		dummy := InByte (UART+LSR);

	    ELSIF condition = 4 THEN

		(* Code 4 means received data available.	*)

		PutRXBuffer (chan, InByte (UART+RXD));

	    ELSIF condition = 2 THEN

		(* Code 2 means transmitter ready.	*)

		SendFromTXBuffer (chan);

	    ELSIF condition = 0 THEN

		(* Code 0 is for modem status interrupt.  In this	*)
		(* version we do nothing except clear the condition	*)
		(* by reading the Modem Status Register.		*)

		dummy := InByte (UART+MSR);

	    END (*IF*);

	END (*LOOP*);

    END HandleInterrupt;

(************************************************************************)

PROCEDURE COM1InterruptTask;

    BEGIN
	LOOP
	    WaitForInterrupt;
	    HandleInterrupt (1);
	END (*LOOP*);
    END COM1InterruptTask;

(************************************************************************)

PROCEDURE COM2InterruptTask;

    BEGIN
	LOOP
	    WaitForInterrupt;
	    HandleInterrupt (2);
	END (*LOOP*);
    END COM2InterruptTask;

(************************************************************************)
(*			CHANNEL INITIALISATION				*)
(************************************************************************)

PROCEDURE ModemControl (chan: SerialChannelNumber;  code: BYTE);

    (* Sends the specified code to the Modem Control Register.  The	*)
    (* code bits are: bit0=DTR, bit1=RTS, bit2=Out1, bit3=Out2,		*)
    (* bit4=Loopback.  (The other 3 bits should be zero.)		*)
    (* You don't normally need to call this procedure.			*)

    BEGIN
	OutByte (BasePort[chan]+MCR, code);
    END ModemControl;

(************************************************************************)

PROCEDURE MouseReset (chan: SerialChannelNumber);

    (* Special break sequence needed for resetting a serial mouse.	*)
    (* (But it doesn't seem to do what I want anyway.  Logitech mouse	*)
    (* still fails to act like a Microsoft mouse.)			*)

    VAR LCRcode: BYTE;  i, j: CARDINAL;

    BEGIN
	LCRcode := InByte(BasePort[chan]+LCR);
	OutByte (BasePort[chan]+LCR, IORB(LCRcode, 40H));
	OutByte (BasePort[chan]+MCR, 8);
	FOR i := 0 TO 1000 DO
	    FOR j := 0 TO 1000 DO
	    END (*FOR*);
	END (*FOR*);
	OutByte (BasePort[chan]+LCR, LCRcode);
	OutByte (BasePort[chan]+MCR, 0BH);
    END MouseReset;

(************************************************************************)

PROCEDURE InitSerialChannel (chan: SerialChannelNumber;  baud: CARDINAL;
				wordlength: WordLength;
				parity: Parity; stopbits: StopBits);

    (* Performs initialisation on channel "chan".			*)

    CONST IntMaskRegister = 21H;

    VAR UART: CARDINAL;
	divisor: CARDINAL;
	dummy: CHAR;

    BEGIN
	UART := BasePort[chan];

	(* Clear the line control register.	*)

	OutByte (UART+LCR, 0);

	(* Set the divisor latch access bit.	*)

	OutByte (UART+LCR, 80H);

	(* Set the desired baud rate.  Assuming a 1.8432 MHz crystal,	*)
	(* and an internal division by 16, we need divisor=115200/baud.	*)

	divisor := Div (115200+LONGCARD(baud DIV 2), baud);
	OutByte (UART, LowByte (divisor));
	OutByte (UART+1, HighByte (divisor));

	(* Set desired code for stop bits, data bits, and parity.	*)

	OutByte (UART+LCR, ParitySpec[parity] + 4*(stopbits-1) + wordlength-5);

	(* Set up modem control bits.  In this version we are setting	*)
	(* DTR (bit 0), RTS (bit 1), Out2 (bit 3) and sometimes		*)
	(* loopback (bit 4).  I don't know why I should set Out2, but	*)
	(* I couldn't get the software to work without it.  I haven't	*)
	(* yet managed to get the loopback option to work.		*)

	OutByte (UART+MCR, 0BH);

	(* Clear out spurious characters and error conditions.  We can	*)
	(* clear line and modem status errors by reading the Line	*)
	(* Status Register and the Modem Status Register, respectively.	*)

	dummy := InByte (UART+RXD);
	dummy := InByte (UART+LSR);
	dummy := InByte (UART+MSR);

	(* Enable interrupts for receiver, receiver line status, and	*)
	(* modem status.  We don't yet enable transmitter inputs, since	*)
	(* we have nothing to transmit so far.				*)

	OutByte (UART+IER, 0DH);
	OutByte (IntMaskRegister,
			IANDB (InByte(IntMaskRegister), IntNumMask[chan]));

    END InitSerialChannel;

(************************************************************************)
(*				INPUT					*)
(************************************************************************)

PROCEDURE ReadSerial (chan: SerialChannelNumber;  VAR (*OUT*) value: BYTE);

    (* Reads one byte from a serial channel.	*)

    VAR savedPSW: CARDINAL;

    BEGIN
	WITH RXBuffer[chan] DO
	    Wait (DataCounter);
	    value := data[GetPlace];
	    savedPSW := EnterCriticalSection();	(* while updating BufferFlag *)
	    IF GetPlace = MaxBufferSubscript THEN GetPlace := 0
	    ELSE INC (GetPlace);
	    END (*IF*);
	    BufferFlag := FALSE;
	    LeaveCriticalSection (savedPSW);
	END (*WITH*);
    END ReadSerial;

(************************************************************************)
(*				OUTPUT					*)
(************************************************************************)

PROCEDURE WriteSerial (chan: SerialChannelNumber;  value: BYTE);

    (* Sends one byte to an output channel.	*)

    VAR savedPSW, port: CARDINAL;

    BEGIN
	WITH TXBuffer[chan] DO
	    Wait (DataCounter);
	    data[PutPlace] := value;
	    savedPSW := EnterCriticalSection();	(* while updating BufferFlag *)
	    IF PutPlace = MaxBufferSubscript THEN PutPlace := 0
	    ELSE INC (PutPlace);
	    END (*IF*);
	    BufferFlag := FALSE;

	    (* Enable transmitter interrupts, in case they were disabled. *)

	    port := BasePort[chan] + IER;
	    OutByte (port, IORB(InByte(port), 2));

	    LeaveCriticalSection (savedPSW);
	END (*WITH*);

    END WriteSerial;

(************************************************************************)
(*			   MODULE INITIALISATION			*)
(************************************************************************)

PROCEDURE InitialiseBuffers;

    (* Sets up our global variables, but does not yet set up the	*)
    (* interrupt tasks or enable interrupts.				*)

    VAR chan: SerialChannelNumber;

    BEGIN
	FOR chan := MIN(SerialChannelNumber) TO MAX(SerialChannelNumber) DO
	    WITH RXBuffer[chan] DO
		BufferFlag := FALSE;
		CreateSemaphore (DataCounter, 0);
		GetPlace := MIN(BufferSubscript);
		PutPlace := MIN(BufferSubscript);
	    END (*WITH*);
	    WITH TXBuffer[chan] DO
		BufferFlag := TRUE;
		CreateSemaphore (DataCounter, MaxBufferSubscript+1);
		GetPlace := MIN(BufferSubscript);
		PutPlace := MIN(BufferSubscript);
	    END (*WITH*);
	END (*FOR*);
    END InitialiseBuffers;

(************************************************************************)

PROCEDURE Test;

    (* Not normally used.  It's here for when I need to dump the	*)
    (* UART registers.							*)

    VAR j: [0..7];  save: BYTE;  UART: CARDINAL;

    BEGIN
	UART := BasePort[1];
	save := InByte (UART+LCR);
	WriteHexByte (save);  WriteChar (" ");
	OutByte (UART+LCR, IORB(80H, save));
	WriteHexByte (InByte (UART));  WriteChar (" ");
	WriteHexByte (InByte (UART+1));  WriteChar (" ");
	OutByte (UART+LCR, IANDB(7FH, save));
	FOR j := 1 TO 7 DO
	    WriteHexByte (InByte (UART+j));  WriteChar (" ");
	END (*FOR*);
    END Test;

(************************************************************************)

PROCEDURE SaveUARTSettings;

    (* Saves the current state of the UARTS in the system.  Used only	*)
    (* during module initialisation.					*)

    VAR channel: SerialChannelNumber;
	j: [0..6];  save: BYTE;  UART: CARDINAL;

    BEGIN
	FOR channel := 1 TO 2 DO
	    UART := BasePort[channel];
	    save := InByte (UART+LCR);

	    (* Save the main control registers. *)

	    OutByte (UART+LCR, IANDB(7FH, save));
	    FOR j := 1 TO 6 DO
		SavedRegisters[channel][j] := InByte (UART+j);
	    END (*FOR*);

	    (* Save the baud rate divisor. *)

	    OutByte (UART+LCR, IORB(80H, save));
	    FOR j := 0 TO 1 DO
		SavedRegisters[channel][j+7] := InByte (UART+j);
	    END (*FOR*);

	    (* Put back the original contents of the LCR. *)

	    OutByte (UART+LCR, save);
	    SavedRegisters[channel][LCR] := save;

	END (*FOR*);

    END SaveUARTSettings;

(************************************************************************)

PROCEDURE RestoreUARTSettings;

    (* Removes the interrupt tasks, and loads the UARTS with the values	*)
    (* saved during module initialisation.				*)

    VAR channel: SerialChannelNumber;
	j: [0..6];  save: BYTE;  UART: CARDINAL;

    BEGIN
	FOR channel := 1 TO 2 DO
	    KillInterruptTask (IntNum[channel]);
	    UART := BasePort[channel];
	    save := SavedRegisters[channel][LCR];

	    (* Restore the baud rate divisor. *)

	    OutByte (UART+LCR, IORB(80H, save));
	    FOR j := 0 TO 1 DO
		OutByte (UART+j, SavedRegisters[channel][j+7]);
	    END (*FOR*);

	    (* Restore the main control registers. *)

	    OutByte (UART+LCR, IANDB(7FH, save));
	    FOR j := 1 TO 6 DO
		OutByte (UART+j, SavedRegisters[channel][j]);
	    END (*FOR*);

	END (*FOR*);

    END RestoreUARTSettings;

(************************************************************************)

VAR dummy: BYTE;

BEGIN
    (*Test;*)
    dummy := InByte (21H);
    SaveUARTSettings;  InitialiseBuffers;
    SetTerminationProcedure (RestoreUARTSettings);
    CreateInterruptTask (IntNum[1], COM1InterruptTask, "COM1 interrupt");
    CreateInterruptTask (IntNum[2], COM2InterruptTask, "COM2 interrupt");
END SerialIO.