packages feed

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

IMPLEMENTATION MODULE Printer;

	(********************************************************)
	(*							*)
	(*	    Device driver for the printer.		*)
	(*							*)
	(*	Author:		P. Moylan			*)
	(*	Last edited:	6 March 1995			*)
	(*	Status:		Partly working			*)
	(*							*)
	(*	More precisely: it was working with my earlier	*)
	(*	Epson printer, but is leaving the DeskJet in	*)
	(*	a "busy" state at the completion of printing.	*)
	(*	I haven't yet tracked down the reason.		*)
	(*							*)
	(********************************************************)

(************************************************************************)
(*									*)
(*  The printer interface is addressed through three consecutive I/O	*)
(*  ports: the data register, the status register, and the control	*)
(*  register, in that order.  An unusual feature of the interface is	*)
(*  that putting a character into the data register does not in itself	*)
(*  cause the character to be printed - it is also necessary to		*)
(*  manipulate a "strobe" bit in the control register.			*)
(*									*)
(*  The status register is almost unused by this module, but for	*)
(*  interest's sake here are the bit assignments:			*)
(*	   bit	7	1=not busy, i.e. ready				*)
(*		6	1=acknowledge (not sure what is acknowledged)	*)
(*		5	1=out of paper					*)
(*		4	1=selected					*)
(*		3	1=I/O error					*)
(*		2-0	unused						*)
(*									*)
(*  The bit assignments for the control register, deduced from tests	*)
(*  and from some very sketchy documentation, appear to	be:		*)
(*	   bit	7-5	unknown (unused?)				*)
(*		4	1 = interrupt enable				*)
(*		3	should be 1, not sure why			*)
(*		2	1=normal operation, 0=init			*)
(*		1	1=automatic line feed after carriage return	*)
(*		0	1=output strobe					*)
(*									*)
(*  For normal operation, we use a value of 1CH, which specifies	*)
(*  interrupt enable, no initialisation, no auto line feed.  When we	*)
(*  actually send a character, we also have to set the strobe bit.	*)
(*									*)
(*  Bit 3 is still a mystery to me.  The tests I have done so far	*)
(*  suggest that this bit has no effect.				*)
(*									*)
(************************************************************************)

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

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

FROM TerminationControl IMPORT
    (* proc *)	SetTerminationProcedure;

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

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

CONST
    IntMaskRegister = 21H;

    PrinterDataPort = 278H;
    PrinterInterruptNumber = 15;
    InterruptMask = 80H;

    (********************************************************************)
    (*									*)
    (*  Note: the "official" values of these constants are:		*)
    (*					port #1		port #2		*)
    (*		PrinterDataPort          378H		 278H		*)
    (*		PrinterInterruptNumber	  15		  13		*)
    (*		InterruptMask		  80H             20H		*)
    (*									*)
    (*  For some reason, my printer seems to be using the interrupt	*)
    (*  belonging to printer port #1, but the port number of printer	*)
    (*  port #2.							*)
    (*									*)
    (********************************************************************)

    PrinterStatusPort = PrinterDataPort + 1;
    PrinterControlPort = PrinterDataPort + 2;
    ControlByte = 1CH;
    MaxBufferSubscript = 7;

TYPE
    BufferSubscript = [0..MaxBufferSubscript];

VAR

    (* OutputBuffer is a circular buffer holding characters to be sent	*)
    (* to the printer.  The SpaceAvailable semaphore is a counting	*)
    (* semaphore which keeps track of how many unused character slots	*)
    (* remain in the buffer.  OutPlace points to the next character	*)
    (* which will be sent to the printer.  PutPlace is the place where	*)
    (* the next user output character will be put.  The condition	*)
    (* OutPlace=PutPlace could mean either that the buffer is		*)
    (* completely empty or that it is completely full.  We can tell the	*)
    (* difference by knowing whether we have just put something in the	*)
    (* buffer or removed something from it.				*)

    OutputBuffer:   RECORD
			SpaceAvailable: Semaphore;
			OutPlace, PutPlace: BufferSubscript;
			data: ARRAY BufferSubscript OF CHAR
		    END;

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

PROCEDURE EnablePrinterInterrupts;

    (* Allows printer interrupts to reach the processor, by clearing	*)
    (* the appropriate bit in the interrupt mask register.  This works	*)
    (* provided that there are no other devices sharing the same	*)
    (* interrupt request.  If interrupt lines were shared, then		*)
    (* interrupts would have to be turned on and off at the printer	*)
    (* interface itself.						*)

    BEGIN
	OutByte (IntMaskRegister,
		IANDB (InByte(IntMaskRegister), 0FFH - InterruptMask));
    END EnablePrinterInterrupts;

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

PROCEDURE DisablePrinterInterrupts;

    BEGIN
	OutByte (IntMaskRegister,
			IORB (InByte(IntMaskRegister), InterruptMask));
    END DisablePrinterInterrupts;

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

PROCEDURE PrinterInterruptTask;

    (* This task spends most of its time idle in the WaitForInterrupt	*)
    (* call, and is resumed by an interrupt from the printer.  It then	*)
    (* takes the first available character from the circular buffer	*)
    (* OutputBuffer, and performs a semaphore Signal to indicate that	*)
    (* there is extra free space in the buffer.  If the buffer is empty	*)
    (* after this operation, further printer interrupts are disabled.	*)
    (* Procedure PrintChar (see below) re-enables printer interrupts	*)
    (* when it puts a new character in the buffer.			*)

    CONST strobe = 1;

    BEGIN
	LOOP (*FOREVER*)
	    WaitForInterrupt;
	    WITH OutputBuffer DO

		OutByte (PrinterDataPort, data[OutPlace]);

		(* With this particular printer interface, writing to	*)
		(* the printer data port does not strobe the data out	*)
		(* to the printer.  (Why not?  No obvious reason, but	*)
		(* perhaps the hardware designer just had had a bad	*)
		(* day).  Instead, we must explicitly create a strobe	*)
		(* pulse by setting the low order bit of the control	*)
		(* port, and then clearing it.				*)

		OutByte (PrinterControlPort, ControlByte + strobe);
		OutByte (PrinterControlPort, ControlByte);

		IF OutPlace = MaxBufferSubscript THEN OutPlace := 0
		ELSE INC (OutPlace);
		END (*IF*);

		(* If now OutPlace=PutPlace, the buffer is empty, so	*)
		(* the printer must be disabled until the buffer again	*)
		(* contains something to print.				*)

		IF OutPlace = PutPlace THEN
		    DisablePrinterInterrupts;
		END (*IF*);

		Signal (SpaceAvailable);

	    END (*WITH*);
	END (*LOOP*);
    END PrinterInterruptTask;

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

PROCEDURE PrintChar (ch: CHAR);

    (* Puts one character into the output buffer.  This procedure	*)
    (* contains a somewhat subtle critical section, which can be	*)
    (* explained as follows.  When the buffer is empty, printer		*)
    (* interrupts need to be disabled, since otherwise the interrupt	*)
    (* routine will print rubbish.  Thus, we should re-enable the	*)
    (* printer after putting a new character in the buffer.  However,	*)
    (* there is a possible sequence of events in which we put a new	*)
    (* character in the buffer, and then the interrupt routine prints	*)
    (* that character before we have updated the buffer pointer		*)
    (* PutPlace.  In that case the interrupt routine could make a wrong	*)
    (* decision about whether the buffer is empty.  The detailed	*)
    (* consequences depend on whether we enable interrupts before or	*)
    (* after updating PutPlace, but in either case we could end up with	*)
    (* printer interrupts disabled when they should be enabled, or vice	*)
    (* versa.  The solution is to make the combination of updating	*)
    (* PutPlace and enabling printer interrupts indivisible.  We do	*)
    (* this by disabling printer interrupts (whether or not they were	*)
    (* already disabled) before modifying PutPlace.			*)

    BEGIN
	WITH OutputBuffer DO
	    Wait (SpaceAvailable);
	    data[PutPlace] := ch;

	    (* Since it is possible that the buffer was empty before	*)
	    (* adding this new character, printer interrupts might be	*)
	    (* currently disabled, and we should enable them.  The	*)
	    (* combined operations of updating PutPlace and enabling	*)
	    (* the printer creates a critical section, which we protect	*)
	    (* by disabling the printer in case it was already enabled.	*)

	    DisablePrinterInterrupts;
	    IF PutPlace = MaxBufferSubscript THEN PutPlace := 0
	    ELSE INC (PutPlace);
	    END (*IF*);
	    EnablePrinterInterrupts;

	END (*WITH*);
    END PrintChar;

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

PROCEDURE WaitForPrinterIdle;

    (* Does not return until the printer buffer is empty.  Of course,	*)
    (* the caller should also ensure that there are no future calls to	*)
    (* PrintChar; otherwise there is little point in calling this	*)
    (* procedure.							*)

    BEGIN
	WITH OutputBuffer DO
	    REPEAT
		Wait (SpaceAvailable);  Signal (SpaceAvailable);
	    UNTIL OutPlace = PutPlace;
	END (*WITH*);
    END WaitForPrinterIdle;

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

PROCEDURE InitialisePrinter;

    (* Performs the appropriate hardware initialisation to install the	*)
    (* printer interrupt task and make the printer ready for use.	*)

    BEGIN

	CreateInterruptTask (PrinterInterruptNumber, PrinterInterruptTask,
						"Printer int task");

	(* Initialise printer by driving bit 2 low while keeping bit 3	*)
	(* high.							*)

	OutByte (PrinterControlPort, 8);

	(* Disable interrupts on IRQ 7 of 8259.  The time taken by this	*)
	(* operation seems to be about right for the time we need to	*)
	(* kill while keeping the initialisation bit low.		*)

	DisablePrinterInterrupts;

	(* Now send bit 2 high to finish initialisation operation, and	*)
	(* set bit 4 to enable interrupts.  Meanwhile bit 3 remains	*)
	(* high, I don't know why but that's how I got it to work.	*)
	(* From the documentation, I know I am setting non auto LF, not	*)
	(* sure what else.						*)

	OutByte (PrinterControlPort, ControlByte);

	(* Wait for the printer "ready" bit to be set, to ensure that	*)
	(* hardware initialisation is complete.				*)
(*
	REPEAT
	    (* do nothing *)
	UNTIL ORD(IANDB (InByte (PrinterStatusPort), 80H)) <> 0;
*)
	(* At this stage the printer interrupt task exists, the printer	*)
	(* interface has been initialised and its "interrupt enable"	*)
	(* bit has been set, but the interrupt mask register of the	*)
	(* 8259A interrupt controller has been set up so that it will	*)
	(* not yet pass on printer interrupts to the processor.  Thus,	*)
	(* the first interrupt will not occur until there is something	*)
	(* waiting to be printed.					*)

    END InitialisePrinter;

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

BEGIN

    (* Initialize the character buffer.	*)

    WITH OutputBuffer DO
	CreateSemaphore (SpaceAvailable, MaxBufferSubscript+1);
	OutPlace := 0;  PutPlace := 0;
    END (*WITH*);

    (* Install the printer interrupt routine.	*)

    InitialisePrinter;

    (* Provide for an orderly shutdown.	*)

    SetTerminationProcedure (WaitForPrinterIdle);

END Printer.