[comp.os.minix] New printer.c

75008378%VAX2.NIHED.IE@cunyvm.cuny.edu (09/06/88)

Hi All,

Well here's my *really* kludged printer.c.  Dr. T suggested I just
post it and see what people think.  Of course, just as I was about
to do so, I discovered just one more itsy bitsy teensy weensy bug -
that can cause the driver to hang at fairly arbitrary points in
a document.  So I delayed 'till I had a fix for that too. I *have*
fixed it below, but it hasn't had much testing since (though I have
run two 30 page documents through it successfully), so, who knows,
I may have killed one bug but added two more?

I'm posting the whole thing rather than a diff, 'cause I'm not up to
V1.3x yet, and there are probably lots of people out there in
various stages of undress between 1.2 and 1.3 anyway.  It'll
probably need some (minor?) tweaking to compile under anything
but vanilla 1.2.

It *is* derived from V1.2, but the changes are extensive in places,
although the overall structure is unchanged.  I have looked at the
V1.3 diff, already posted, and I don't think that actually
corrects any of the *real* problems in the driver.

Do what you like with this - throw it away, pass it on, fix it to
work under V1.3 (I think that's a fairly minimal job).  You'll
notice I have a local, site specific, header on it: you can drop
that if you like.  It works "perfectly" on my system - a
"NONAME" (PROMPT> XT) Taiwanese XT clone (10MHz), and a
Star NL-10 printer.

My analysis of the code makes me believe that *virtually* all
timing conflicts have been eliminated now, but more testing would
be in order (*no* prizes for the first one to prove me wrong!).
The only parameter I would bother tuning is
MAX_TRIES - and this should only improve efficiency, not make
the difference between working and not.

The bad news is that, in principle at least, the driver (still)
disables interrupts for *indefinitely* long periods of time
(essentially, there is a "large" upper limit - it depends on how big
a chunk it is given to print - could be up to 64k I guess - and
whether the printer/buffer can gobble it all in "one" go). In
practise it's not quite that bad, but, under V1.3, I would
anticipate problems with trying to print while using serial comms
(so what else is new?).  The real problem here is that because of the
multiple invocation sites for the pr_char routine (direct from the
driver, printer int, and clock task) the only simple way to provide
effective mutual exclusion is to disable interrupts for more or less
the entire execution of pr_char.  I have some ideas for how to redo
the entire thing so as to avoid all this kludginess - but, for
the moment, I have something that works, and I don't have time
to pursue it further.

Barry McMullin, NIHED, Dublin, IRELAND.
EARN/BITNET/EUNET: <MCMULLINB@VAX2.NIHED.IE>
  [This is a synonym address - easier to remember than the funny
   number you see in the "From: " field? ]



--------------------------- CUT HERE -------------------------------
/*

    SYSTEM:     MINIX (MINi unIX - Operating System)
    VERSION:    V1.2.1
    FILE:        kernel/printer.c

    Copyright (C) 1987 by Prentice-Hall, Inc.
    Copyright (C) 1988, Barry McMullin, NIHE Dublin, IRELAND.

   See doc/sysver for detailed information on system version, copyright,
   licensing and usage conditions.

REV#    SCN#    DATE        ENGINEER    CHANGE
----    ----    ----        --------    ------
000    008    28-JUL-88   B.McMullin  Timing bug fix, per article
                    21200@cheviot.newcastle.ac.uk.

001    009    12-AUG-88   B.McMullin  Further tighten up on possible
                    timing problems.  This is related
                    to the scenario described in
                    21200@cheviot.newcastle.ac.uk,
                    but relates to interaction from
                    the clock task invocations of
                    pr_char() also.  NB: such invocations
                    are of dubious merit, but are quite
                    likely to occur with a buffered
                    printer. Also, remove unbalanced
                    call to lock() (no obvious reason
                    for it) in do_write(). Also, tidy
                    printer busy checking and
                    processing, to make operation more
                    efficient.

002    011    05-SEP-88   B.McMullin    OOPS: I made the busy checking a
                    mite too efficient! If a block
                    end coincides with the printer
                    buffer becoming full, there may be
                    a "long" delay in getting the next
                    block started.  As was, the driver
                    was liable to decide the printer
                    was dead. Now it "starts" printing
                    (calls pr_char) regardless of
                    current printer status (pr_char will
                    check anyway) and, for good measure,
                    pr_char doesn't declare a block
                    finished until the printer is ready
                    again.

*/

/* This file contains the printer driver. It is a fairly simple driver,
 * supporting only one printer.  Characters that are written to the driver
 * are written to the printer without any changes at all.
 *
 * The valid messages and their parameters are:
 *
 *   TTY_O_DONE:   output completed
 *   TTY_WRITE:    a process wants to write on a terminal
 *   CANCEL:       terminate a previous incomplete system call immediately
 *
 *    m_type      TTY_LINE   PROC_NR    COUNT    ADDRESS
 * -------------------------------------------------------
 * | TTY_O_DONE  |minor dev|         |         |         |
 * |-------------+---------+---------+---------+---------|
 * | TTY_WRITE   |minor dev| proc nr |  count  | buf ptr |
 * |-------------+---------+---------+---------+---------|
 * | CANCEL      |minor dev| proc nr |         |         |
 * -------------------------------------------------------
 *
 * Note: since only 1 printer is supported, minor dev is not used at present.
 */

#include "../h/const.h"
#include "../h/type.h"
#include "../h/callnr.h"
#include "../h/com.h"
#include "../h/error.h"
#include "const.h"
#include "type.h"
#include "glo.h"
#include "proc.h"

#define NORMAL_STATUS   0x90    /* printer gives this status when idle */
#define BUSY_STATUS     0x10    /* printer gives this status when busy */
#define ASSERT_STROBE   0x1D    /* strobe a character to the interface */
#define NEGATE_STROBE   0x1C    /* enable interrupt on interface */
#define SELECT          0x0C    /* select printer bit */
#define INIT_PRINTER    0x08    /* init printer bits */
#define NO_PAPER        0x20    /* status bit saying that paper is up */
#define OFF_LINE        0x10    /* status bit saying that printer not online*/
#define PR_ERROR        0x08    /* something is wrong with the printer */
#define PR_COLOR_BASE  0x378    /* printer port when color display used */
#define PR_MONO_BASE   0x3BC    /* printer port when mono display used */
#define LOW_FOUR         0xF    /* mask for low-order 4 bits */
#define CANCELED        -999    /* indicates that command has been killed */
#define INIT_DELAY       100    /* regulates delay during initialisation */
#define MAX_START_DLY  10000    /* No longer used? */
#define MAX_TRIES        100    /* Max tries when printer is busy */
#define STATUS_MASK    0xB0    /* mask to filter out status bits */

PRIVATE int port_base;        /* I/O port for printer: 0x 378 or 0x3BC */
PRIVATE int caller;        /* process to tell when printing done (FS) */
PRIVATE int proc_nr;        /* user requesting the printing */
PRIVATE int orig_count;        /* original byte count */
PRIVATE int es;            /* (es, offset) point to next character to */
PRIVATE int offset;        /* print, i.e., in the user's buffer */
PUBLIC int pcount;        /* number of bytes left to print */
PUBLIC int pr_busy;        /* TRUE when printing, else FALSE */
PUBLIC int cum_count;        /* cumulative # characters printed */
PUBLIC int prev_ct;        /* value of cum_count 100 msec ago */

/*===========================================================================*
 *                printer_task                     *
 *===========================================================================*/
PUBLIC printer_task()
{
/* Main routine of the printer task. */

  message print_mess;        /* buffer for all incoming messages */

  print_init();            /* initialize */

  while (TRUE) {
    receive(ANY, &print_mess);
    switch(print_mess.m_type) {
        case TTY_WRITE:    do_write(&print_mess);    break;
        case CANCEL   :    do_cancel(&print_mess);    break;
        case TTY_O_DONE:    do_done(&print_mess);    break;
            default:                    break;
    }
  }
}


/*===========================================================================*
 *                do_write                     *
 *===========================================================================*/
PRIVATE do_write(m_ptr)
message *m_ptr;            /* pointer to the newly arrived message */
{
/* The printer is used by sending TTY_WRITE messages to it. Process one. */

  int i, j, r, value;
  struct proc *rp;
  phys_bytes phys;
  extern phys_bytes umap();

  r = OK;            /* so far, no errors */

  /* Reject command if printer task is busy or count is not positive. */
  if (pr_busy) r = EAGAIN;
  if (m_ptr->COUNT <= 0) r = EINVAL;

  /* Compute the physical address of the data buffer within user space. */
  rp = proc_addr(m_ptr->PROC_NR);
  phys = umap(rp, D, (vir_bytes) m_ptr->ADDRESS, m_ptr->COUNT);
  if (phys == 0) r = E_BAD_ADDR;

  if (r == OK) {
      /* Save information needed later. */

      caller = m_ptr->m_source;
      proc_nr = m_ptr->PROC_NR;
      pcount = m_ptr->COUNT;
      orig_count = m_ptr->COUNT;
      es = (int) (phys >> CLICK_SHIFT);
      offset = (int) (phys & LOW_FOUR);

      /* Start printing. */

     pr_busy = TRUE;
    pr_char();    /* print first character(s) */
    r = SUSPEND;    /* tell FS to suspend user until done */
  }

  /* Reply to FS, no matter what happened. */
  reply(TASK_REPLY, m_ptr->m_source, m_ptr->PROC_NR, r);
}


/*===========================================================================*
 *                do_done                         *
 *===========================================================================*/
PRIVATE do_done(m_ptr)
message *m_ptr;            /* pointer to the newly arrived message */
{
/* Printing is finished.  Reply to caller (FS). */

  int status;

  status = (m_ptr->REP_STATUS == OK ? orig_count : EIO);
  if (proc_nr != CANCELED) {
      reply(REVIVE, caller, proc_nr, status);
      if (status == EIO) pr_error(m_ptr->REP_STATUS);
  }
  pr_busy = FALSE;
}


/*===========================================================================*
 *                do_cancel                     *
 *===========================================================================*/
PRIVATE do_cancel(m_ptr)
message *m_ptr;            /* pointer to the newly arrived message */
{
/* Cancel a print request that has already started.  Usually this means that
 * the process doing the printing has been killed by a signal.
 */

  if (pr_busy == FALSE) return;    /* this statement avoids race conditions */
  pr_busy = FALSE;        /* mark printer as idle */
  pcount = 0;            /* causes printing to stop at next interrupt*/
  proc_nr = CANCELED;        /* marks process as canceled */
  reply(TASK_REPLY, m_ptr->m_source, m_ptr->PROC_NR, EINTR);
}


/*===========================================================================*
 *                reply                         *
 *===========================================================================*/
PRIVATE reply(code, replyee, process, status)
int code;            /* TASK_REPLY or REVIVE */
int replyee;            /* destination for message (normally FS) */
int process;            /* which user requested the printing */
int status;            /* number of  chars printed or error code */
{
/* Send a reply telling FS that printing has started or stopped. */

  message pr_mess;

  pr_mess.m_type = code;    /* TASK_REPLY or REVIVE */
  pr_mess.REP_STATUS = status;    /* count or EIO */
  pr_mess.REP_PROC_NR = process;    /* which user does this pertain to */
  send(replyee, &pr_mess);    /* send the message */
}


/*===========================================================================*
 *                pr_error                     *
 *===========================================================================*/
PRIVATE pr_error(status)
int status;            /* printer status byte */
{
/* The printer is not ready.  Display a message on the console telling why. */

  if (status & NO_PAPER) printf("Printer is out of paper\n");
  if ((status & OFF_LINE) == 0) printf("Printer is not on line\n");
  if ((status & PR_ERROR) == 0) printf("Printer error\n");
}


/*===========================================================================*
 *                print_init                     *
 *===========================================================================*/
PRIVATE print_init()
{
/* Color display uses 0x378 for printer; mono display uses 0x3BC. */

  int i;
  extern int color;

  port_base = (color ? PR_COLOR_BASE : PR_MONO_BASE);
  pr_busy = FALSE;
  port_out(port_base + 2, INIT_PRINTER);
  for (i = 0; i < INIT_DELAY; i++) ;    /* delay loop */
  port_out(port_base + 2, SELECT);
}


/*===========================================================================*
 *                pr_char                     *
 *===========================================================================*/
PUBLIC pr_char()
{
/* This is the interrupt handler.  When a character has been printed, an
 * interrupt occurs, and the assembly code routine trapped to calls pr_char().
 * One annoying problem is that the 8259A controller sometimes generates
 * spurious interrupts to vector 15, which is the printer vector.  Ignore them.
 */

/* Note that this routine is *also* called on a non-interrupt level to
   start printing originally, and possibly to restart it if it gets hung.
   The restart call is issued by the clock task.  The possibility of needing
   a restart arises (I think) if the printing terminates on the original
   call to pr_char(), (hence the done message can't immediately be
   delivered, cause the driver is not yet receiving again) but the
   int_mess then gets clobbered (by clock int?)  before the driver gets a
   chance to deal with it!
*/

  int value, ch, i;
  char c;
  extern char get_byte();

  if (pcount != orig_count) port_out(INT_CTL, ENABLE);
  if (pr_busy == FALSE) return;    /* spurious 8259A interrupt */

/* Make all the critical updating atomic, to guard against timing
   problems with multiple pr_char() invocations - either due to
   printer interrupts or clock interrupts. */

  lock();
  while (pcount >= 0) {

    for (i = 0; i < MAX_TRIES; i++) {
          port_in(port_base + 1, &value);    /* get printer status */
        if ((value&STATUS_MASK) != BUSY_STATUS) break;
    }
            
      if ((value&STATUS_MASK) == NORMAL_STATUS) {
        if (pcount == 0) {
            /* All chars printed, & printer ready again...
               flag that we're finished! */
            pcount--;
        } else {
            c = get_byte(es, offset);
            ch = c & BYTE;
            port_out(port_base, ch);
            offset++;
            pcount--;
            port_out(port_base + 2, ASSERT_STROBE);
            port_out(port_base + 2, NEGATE_STROBE);
            cum_count++;    /* count characters output */
        }
    } else     break; /* printer is busy/broken. */

  }

 if (pcount < 0) {
    int_mess.m_type = TTY_O_DONE;
    int_mess.REP_STATUS = OK;
    interrupt(PRINTER, &int_mess);
  } else if ((value&STATUS_MASK) != BUSY_STATUS) {
    int_mess.m_type = TTY_O_DONE;
    int_mess.REP_STATUS = value;
    interrupt(PRINTER, &int_mess);
  }

  restore();

}