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(); }