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 
}