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.