packages feed

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

IMPLEMENTATION MODULE KBdriver;

	(****************************************************************)
	(*								*)
	(*		Device driver for the keyboard.			*)
	(*								*)
	(*	Author:		P. Moylan				*)
	(*	Last edited:	17 March 1995				*)
	(*	Status:		OK					*)
	(*								*)
	(*	The "Beep" call has been removed because of a problem:	*)
	(*	semaphore wait inside an interrupt task.  For the	*)
	(*	moment I have a temporary substitute solution, but it's	*)
	(*	not really satisfactory; the issue of lost codes needs	*)
	(*	re-thinking.						*)
	(*								*)
	(****************************************************************)

(************************************************************************)
(*									*)
(* Scan codes produced by the keyboard are filtered and modified by the	*)
(* keyboard controller; the result appears at port 60H and cause a type	*)
(* 9 interrupt.  The keyboard controller is a semi-intelligent device,	*)
(* and the processor can send commands to it by writing to port 60H or	*)
(* port 64H.  (The distinction is that port 64H is for commands to the	*)
(* keyboard controller, and port 60H is for commands to the keyboard	*)
(* itself.)  After a lot of trial and (mostly) error, I have reached	*)
(* the conclusion that this "intelligence" is more of a hindrance than	*)
(* a help, so the only command sent by this module is the ED (hex)	*)
(* command which turns the LED indicators on and off.  The second byte	*)
(* of this command specifies which LEDs to light:			*)
(*		bit 0		scroll lock				*)
(*		bit 1		num lock				*)
(*		bit 2		caps lock				*)
(*		bits 3-7	must be 0				*)
(* On input from port 64H, bit 1 is 0 when the controller is ready to	*)
(* accept a new command byte.  Bit 0 goes to 1 each time a new data	*)
(* byte is available, but we don't need to check that bit explicitly	*)
(* because it causes an interrupt.  The other bits of that port are	*)
(* status flags which I do not bother to use.				*)
(*									*)
(************************************************************************)

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

FROM LowLevel IMPORT
    (* proc *)	IANDB, IXORB, INOTB, InByte, OutByte;

FROM TaskControl IMPORT
    (* type *)	Lock,
    (* proc *)	WaitForInterrupt, CreateTask, CreateInterruptTask,
		KillInterruptTask,
		NotUsingFloatingPoint, CreateLock, Obtain, Release;

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

FROM TerminationControl IMPORT
    (* proc *)	SetTerminationProcedure;

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

CONST
    InterruptNumber = 9;
    KeyboardDataPort = 60H;
    KeyboardOutputPort = 60H;
    StatusRegister = 64H;
    CommandPort = 64H;
    MaxBufferSubscript = 15;

TYPE
    BufferSubscript = [0..MaxBufferSubscript];

VAR

    (* CodeBuffer is a circular buffer holding scan codes.  The		*)
    (* DataCounter semaphore is a counting semaphore which keeps track	*)
    (* of how many scan codes are at present in the buffer.  GetPlace	*)
    (* points to the next scan code which will be retrieved by the	*)
    (* consumer.  PutPlace is the place where the next keyboard input	*)
    (* will be put.  The condition GetPlace=PutPlace could mean either	*)
    (* that the buffer is completely empty or that it is completely	*)
    (* full.  The flag BufferFull is used to distinguish between these	*)
    (* two cases.							*)

    CodeBuffer:	RECORD
		    BufferFull, OverFlow: BOOLEAN;
		    DataCounter: Semaphore;
		    GetPlace, PutPlace: BufferSubscript;
		    data: ARRAY BufferSubscript OF BYTE
		END;

    (* There are three lights on the keyboard which show the current	*)
    (* state of Caps Lock, Num Lock, and Shift Lock.  The variable	*)
    (* CurrentLEDstatus keeps track of which of these is currently lit.	*)
    (* The encoding is defined by the constants declared in the		*)
    (* the definition module for this module.				*)

    CurrentLEDstatus: BYTE;

    (* We perform a Signal(LEDstatusUpdate) whenever the value of	*)
    (* CurrentLEDstatus changes.					*)

    LEDstatusUpdate: Semaphore;

    (* A Lock to make the sending of a multibyte command to the		*)
    (* keyboard an indivisible operation.				*)

    CommandLock: Lock;

(************************************************************************)
(*			COMMANDS TO THE KEYBOARD			*)
(************************************************************************)

PROCEDURE WaitForKeyboardReady;

    (* Pauses, using a busy wait, until the keyboard status byte says	*)
    (* that the keyboard is ready to receive a command or until we lose	*)
    (* our patience.  This use of a busy wait is admittedly bad		*)
    (* software design, but I couldn't decipher the keyboard controller	*)
    (* documentation well enough to see where there is a way to do this	*)
    (* job using interrupts.  In theory, the keyboard always responds	*)
    (* to a command within 20 milliseconds.  The response to a command	*)
    (* should be an ACK code (0FAH), but it hardly seems worth checking	*)
    (* for that since it comes interleaved with any character data	*)
    (* which might be arriving.  It's simpler just to discard any ACK	*)
    (* codes which arrive with the data.  Of course this means that I	*)
    (* lose the opportunity to check for some transmission errors, but	*)
    (* the keyboard interface is so hard to work with that some		*)
    (* shortcuts seem to be justified.					*)

    CONST patience = 32767;

    VAR count: CARDINAL;

    BEGIN
	count := patience;
	REPEAT
	    DEC (count);
	UNTIL (count = 0) OR (ORD(IANDB(InByte (StatusRegister), 2)) = 0);
    END WaitForKeyboardReady;

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

PROCEDURE LEDdriver;

    (* Runs as a separate task, which updates the keyboard lock		*)
    (* indicator lights every time it gets a Signal(LEDstatusUpdate).	*)
    (* Using a separate task for this job may seem a little silly, but	*)
    (* I couldn't think of a better way to deal with the fact that the	*)
    (* keyboard doesn't always correctly receive commands sent to it	*)
    (* and is inclined to ask for retransmissions.  Anyway, this	*)
    (* module seems to control the LEDs far more reliably than the BIOS	*)
    (* does, so it can't be all that bad a method.			*)

    BEGIN
	NotUsingFloatingPoint;
	LOOP (*forever*)
	    Wait (LEDstatusUpdate);
	    Obtain (CommandLock);
	    WaitForKeyboardReady;
	    OutByte (KeyboardOutputPort, 0EDH);
	    WaitForKeyboardReady;
	    OutByte (KeyboardOutputPort, CurrentLEDstatus);
	    Release (CommandLock);
	END (*LOOP*);
    END LEDdriver;

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

PROCEDURE PutLEDs (LEDcode: BYTE);

    (* Sets the keyboard lock indicator lights, as specified by		*)
    (* LEDcode.  Unlike the following two procedures, which can affect	*)
    (* one LED without disturbing the others, this procedure alters all	*)
    (* three LEDs as a group.						*)

    BEGIN
	CurrentLEDstatus := LEDcode;
	Signal (LEDstatusUpdate);
    END PutLEDs;

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

PROCEDURE ClearLED (LEDcode: BYTE);

    (* Clears one or more of the keyboard lock indicator lights.	*)

    BEGIN
	CurrentLEDstatus := IANDB (CurrentLEDstatus, INOTB(LEDcode));
	Signal (LEDstatusUpdate);
    END ClearLED;

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

PROCEDURE ToggleLED (LEDcode: BYTE);

    (* Toggles one or more of the keyboard lock indicator lights.	*)

    BEGIN
	CurrentLEDstatus := IXORB (CurrentLEDstatus, LEDcode);
	Signal (LEDstatusUpdate);
    END ToggleLED;

(************************************************************************)
(*			  THE INTERRUPT TASK				*)
(************************************************************************)

PROCEDURE KeyboardInterruptTask;

    (* This is the interrupt task which responds to interrupts from the	*)
    (* keyboard.  Each time an interrupt occurs, we deposit the		*)
    (* keyboard scan code in the circular buffer CodeBuffer, and	*)
    (* perform a semaphore Signal to indicate that a new scan code is	*)
    (* available.  Special case: if there is no room in the buffer, we	*)
    (* insert a special "LostCode" byte at the next available		*)
    (* opportunity.  The LostCode code represents one or more lost	*)
    (* scan codes.  Most device drivers should not be designed to lose	*)
    (* data like this, but the keyboard is a special case: the error	*)
    (* indication to the caller seems to be more useful than simply	*)
    (* locking the keyboard.						*)

    CONST RetransmitCode = BYTE(0FEH);  ACKcode = BYTE(0FAH);

    VAR inputdatum: BYTE;  overflow: BOOLEAN;

    BEGIN
	overflow := FALSE;
	LOOP (*FOREVER*)
	    WaitForInterrupt;
	    inputdatum := InByte (KeyboardDataPort);

	    IF inputdatum = ACKcode THEN
		(* do nothing - ignore ACK codes *)

	    (* Check for retransmission request. *)

	    ELSIF inputdatum = RetransmitCode THEN
		Signal (LEDstatusUpdate);

	    ELSE
		WITH CodeBuffer DO
		    IF BufferFull THEN OverFlow := TRUE;
		    ELSE
			data[PutPlace] := inputdatum;
			IF PutPlace = MaxBufferSubscript THEN PutPlace := 0
			ELSE INC (PutPlace);
			END (*IF*);
			BufferFull := PutPlace = GetPlace;
			Signal (DataCounter);
		    END (*IF*);
		END (*WITH*);
	    END (*IF*);

	END (*LOOP*);

    END KeyboardInterruptTask;

(************************************************************************)
(*			ENTRY POINTS FOR THE USER			*)
(************************************************************************)

PROCEDURE GetScanCode () : BYTE;

    (* Gets one scan code from the keyboard input buffer.	*)

    VAR result: BYTE;  savedPSW: CARDINAL;

    BEGIN
	WITH CodeBuffer DO
	    Wait (DataCounter);
	    IF OverFlow THEN
		result := 0FFH;  OverFlow := FALSE;
		Signal (DataCounter);
	    ELSE
		result := data[GetPlace];
		savedPSW := EnterCriticalSection();
		IF GetPlace = MaxBufferSubscript THEN GetPlace := 0
		ELSE INC (GetPlace);
		END (*IF*);
		BufferFull := FALSE;
		LeaveCriticalSection (savedPSW);
	    END (*IF*);
	END (*WITH*);
	RETURN result;
    END GetScanCode;

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

PROCEDURE CheckScanCode () : BYTE;

    (* Like GetScanCode, but returns 0 immediately if no scan code is	*)
    (* available - i.e. does not wait for a keyboard press.		*)

    VAR empty: BOOLEAN;  savedPSW: CARDINAL;

    BEGIN
	savedPSW := EnterCriticalSection();
	WITH CodeBuffer DO
	    empty := (GetPlace = PutPlace) AND NOT BufferFull;
	END (*WITH*);
	LeaveCriticalSection (savedPSW);
	IF empty THEN RETURN 0
	ELSE RETURN GetScanCode();
	END (*IF*);
    END CheckScanCode;

(************************************************************************)
(*			   SHUTDOWN PROCESSING				*)
(************************************************************************)

PROCEDURE ShutDownPhase2;

    (* Removes the interrupt handler. *)

    BEGIN
	KillInterruptTask (InterruptNumber);
    END ShutDownPhase2;

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

PROCEDURE ShutDown;

    (* The main function of this procedure is to ensure that we don't	*)
    (* close down while in the middle of sending a command to the	*)
    (* keyboard.							*)

    BEGIN
	Obtain (CommandLock);
	(* SetTerminationProcedure (ShutDownPhase2); *)
    END ShutDown;

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

BEGIN

    (* Initialize the scan code buffer.	*)

    WITH CodeBuffer DO
	BufferFull := FALSE;  OverFlow := FALSE;
	CreateSemaphore (DataCounter, 0);
	GetPlace := 0;  PutPlace := 0;
    END (*WITH*);

    (* Install the keyboard interrupt routine.	*)

    CreateInterruptTask (InterruptNumber, KeyboardInterruptTask, "Kbd interrupt");

    (* Install the LED driver. *)

    SetTerminationProcedure (ShutDown);
    CreateLock (CommandLock);
    CreateSemaphore (LEDstatusUpdate, 0);
    CreateTask (LEDdriver, 8, "Kbd LED driver");
    CurrentLEDstatus := 0;  Signal (LEDstatusUpdate);

END KBdriver.