460 Notes on Printer Driver
Parallel Printer Info:
IRQ7 ==> Vector 15
LPT1: LPT2:
----- -----
1. DATA register = 0x378 0x3BC
2. STATUS reg: 0x379 0x3BD
3. COMMAND reg: 0x37A 0x3BE
=====================================================================
4. Status Register Contents:
7 6 5 4 3 2 1 0
NOTBUSY - NOPAPER SELECT NOERROR - - -
------- --------- -------- ----------
1=READY 1=NOPAPER 1=OnLine 1=NOERROR
0=BUSY 0=Paper 0=not 0=ERROR
-----------------------------------------------------------------
5. Command Register Usage:
7 6 5 4 3 2 1 0
- - - EnableIRQ INIT SELECT STROBE
-----------------------------------------------------------------------
Before printing, the printer must be initialized once by :
(1) To INIT : write 0x08 to COMMAND reg first. Then
(2). To select : write 0x0C to COMMAND reg.
--------------------------------------------------------------------------
After writing a char to DATA register, must strobe once by
writing to bit0 a 1 followed by a 0.
NO interrupts:
write 0x0D = 0000 1101 followed by
0x0C = 0000 1100.
With interrupts:
write 0x1D = 0001 1101 followed by
0x1C = 0001 1100
strobe width > 0.5 usec
---------------------------------------------------------------------------
1. REVIEW of the simple printer driver
First, the printer driver consists of TWO parts: upper half and lower half:
Process calls upper-half routines to START the actions:
1. printer_init(){ initialize the printer for action }
=======================================================
2. printLine(char *line)
{
Caller copies line into global pbuf[];
UPPER HALF: Prints the fisrt char from pbuf[]
Then the process may return to do OTHER THINGS
}
3. printChar(char c)
{
sends a char to printer, strobe with interrupts ON
}
======================================================
SHARED DATABASE betwen UPPER and LOWER halves
char pbuf[];
int pIndex; /* index of next char to print */
======================================================
Printer_interrupt ==> CURRENT PROCESS executes handler:
4. Printer_interrupt_handler()
{
LOWER HALF: if (pbuf[Index]==0){
turn off printer interrupt; return
}
printChar(pbuf[pIndex++];
}
======================================================
The above design is GENERAL. It applies equally well to "serial port outputs
desgin" if you replace the term "printer" with "serial_port"
2. Refinements of the driver design:
In the above driver design, several questions can be raised:
Question #1:
A process should NOT put another line into pbuf[] until the current line has
been printed out. How can a process know this WITHOUT polling?
Method 1: int io_done;
Process: sleep(&io_done); Interrupt_handler : wakeup(&io_done)
Method 2: struct semaphore io_done = 0;
Process: P(&io_done); Interrupt_handler : V(&io_done);
Question #2:
What if several processes try to print lines to the same printer?
If not controlled properly, they may write different lines to the SAME pbuf[],
causing total caos. So, how to ensure "Only one process print at a time"?
Question #3:
While a process is manipulating pbuf[] and Index in printLine(), printer
interrupt may occur, which also modifies pbuf[] and pIndex. How to ensure they
do not interfere with each other?
This should work for process in any driver function:
mask_out_interrupts();
manipulate SHARED variables, e.g.pbuf[] and pIndex;
mask_in_interrupts();
Question #4
Before a line in pbuf[] is completely printed out, a process should NOT change
pbuf[] and pIndex. This represents an inefficient use of the pbuf[] space.
Can we do better? Consider this:
char pbuf[N]; int head=tail=0;
struct semphore hasRoom = N;
Process: InterruptHandler:
{ P(&hasRoom); { if (hasRoom.value == N){
lock(); turn_off interrupts; return;
pbuf[head++] = c; }
head %= N; // hasRoom < N
unlock(); print pbuf[tail++];
} tail %= N;
V(&hasRoom);
}
=============================================================================
/******************************************************************
Refined printer driver:
Each process prints one line at a time, emsured by a mutex semaphore.
Process and interrupt handler share a CIRCULAR buffer, with head, tail
pointers. To print a line, process calls prline(), which calls prchar()
to deposit chars into pbuf[], waiting if no room, and prints the first
char if the printer is not printing, Interrupt handler will print the
remaining chars in pbuf[], waking up the blocked process. When a line
is printed, it V(done) to wakeup the waiting process, which V(mutex)
to allow another process to print.
*******************************************************************/
#define NPR 1
#define PORT 0x378 // #define PORT 0x3BC for LPT2
#define STATUS PORT+1
#define COMD PORT+2
#define PLEN 128
#include "pv.c"
struct para{
int port;
int printing; // 1 if printer is printing
char pbuf[PLEN];
int head, tail;
struct semaphore mutex;
struct semaphore room;
struct semaphore done;
} printer; /* printer[NPR] if many printers */
int delay()
{
int i;
for (i=0; i<32000; i++);
}
pr_init()
{
struct para *p;
p = &printer;
printf("pr_init %x\n", PORT);
p->port = PORT;
p->head = p->tail = 0;
p->mutex.value = 1; p->mutex.queue = 0;
p->room.value = PLEN; p->room.queue = 0;
p->done.value = 0; p->done.queue = 0;
/* initialize printer at PORT */
out_byte(p->port+2, 0x08); /* init */
out_byte(p->port+2, 0x0C); /* int, init, select on */
enable_irq(7);
p->printing = 0;
}
int phandler()
{
int status; int c;
struct para *p = &printer;
printf("printer interrupt!\n");
status = in_byte(p->port+1);
if ((status & 0xB0) != 0x90){ /* RARE : interrupted but not ready */
printf("printer not ready\n");
goto out;
}
if ((status & 0xB0) == 0x90){ /* normal status */
if (p->room.value == PLEN){ /* pbuf[] empty, nothing to print */
out_byte(p->port+2, 0x0C); /* turn off interrupts */
V(&p->done); /* tell task print is DONE */
p->printing = 0; /* is no longer printing */
goto out;
}
/* p->pbuf[] not empty ==> print next char */
c = p->pbuf[p->tail++] & 0x7F;
p->tail %= PLEN;
out_byte(p->port, c);
out_byte(p->port+2, 0x1D);
delay();
out_byte(p->port+2, 0x1C);
V(&p->room);
goto out;
}
/* abnormal status: should handle it but ignored here */
out:
out_byte(0x20, 0x20); /* re-enable the 8259 */
}
/************** Upper half driver ************************/
int prchar(c) int c;
{
struct para *p = &printer;
P(&p->room); // wait for room in pbuf[]
lock();
p->pbuf[p->head++] = c;
p->head %= PLEN;
if (!p->printing){ // if NOT printing, print the first char
out_byte(p->port, p->pbuf[p->tail++]);
p->tail %= PLEN;
out_byte(p->port+2, 0x1D);
delay();
out_byte(p->port+2, 0x1C);
V(&p->room);
p->printing = 1; // printer is now printing
}
unlock();
}
int prline(line) char *line;
{
struct para *p = &printer;
P(&p->mutex); /* one process prints LINE at a time */
while (*line)
prchar(*line++);
P(&p->done); /* wait until pbuf[ ] is DONE */
V(&p->mutex); /* allow another process to print */
}
int upline(uline) char *uline;
{
// for syscall to print line from Umode
}