460 Notes on Printer#2
1. Review of Printer Interface:
---------------------------------------------------------------------------
Initialization: the printer must be initialized once by :
(1) INIT : write 0x08 to COMMAND reg first. Then
(2). select : write 0x0C to COMMAND reg.
(3). Install printer interrupt handler (IRQ 7, vector 15) by
_pinth: INTH phandler
in ts.s file.
--------------------------------------------------------------------------
To print a char:
Write a char to DATA register;
Strobe once by
writing a 1 followed by a 0 to bit0 of DATA.register.
With interrupts:
write 0x1D = 0001 1101 followed by
0x1C = 0001 1100
strobe width > 0.5 usec
---------------------------------------------------------------------------
Interrupt Handler:
Can print the next char (if it knows where the chars are)
---------------------------------------------------------------------------
2. 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 action:
1. printer_init(){ initialize the printer for action }
=======================================================
Upper HALF: called by process
2. printLine(char *line)
{
Copy line into global pbuf[], set plen, set index=0;
Print fisrt char from pbuf[0];
Then the process may WAIT (or return to do other things);
}
MIDDLE: SHARED DATABASE betwen UPPER and LOWER halves
========================================================
* char pbuf[]; *
* int pLen; // # of chars in pbuf[] *
* int index; // point to char in pbuf[]; *
========================================================
LOWER HALF: Interrupt Handler:
3. Printer_interrupt_handler()
{
if (statau OK){
if (++index>=pLen){
WAKEUP process; return;
}
printChar(pbuf[index]);
}
======================================================
The above (2-halves) framework is a GENERAL model of ANY device driver.
3.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 Index. 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 Index;
mask_in_interrupts();
Question #4
Before a line in pbuf[] is completely printed out, a process should NOT change
pbuf[] and Index. 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:
Processes print one line at a time, emsured by a mutex=1 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
}