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 
}