/* * lpdev.c: installs the "/dev/lp" device. It is a * buffered Centronics device. This program is free software; see the * file "COPYING" for details. * * This file must be compiled with 16-bit integers. * * Author: Thierry Bousch (bousch@suntopo.matups.fr) * Version: 0.6 (july 93) * * Revision history: * 0.1: First attempt, using SLEEP instead of NAP: it didn't work. * 0.2: Added version number, napping in lp_write(), and the TIOCFLUSH * ioctl function. Cleaned up things a bit. * 0.3: Introduced spl7() and spl() to fix competition problems. Added * a few tests before installation, to check that MiNT is running * and the device is not already installed. * 0.4: Added file locking. This is completely untested, so be * careful. More cleanup and sanity checks during installation. * Modified the sleep conditions in lp_write and lp_select. * 0.5: Deleted the now unnecessary stuff about low and high water marks. * More comments added. * 0.6: Added support for the O_NDELAY and O_LOCK flags, inlined spl7/spl. * Moved the definitions to lpdev.h. */ #include "lpdev.h" #define LP_VERSION "0.6" /* * Global variables */ char *buffer_start, *buffer_end, *buffer_tail; volatile char *buffer_head; volatile long buffer_contents; long selector = 0L; struct kerinfo *kernel; struct flock our_lock = { F_WRLCK, 0, 0L, 0L, -1 }; /* * Forward declarations of the device driver functions */ long lp_open (FILEPTR *f); long lp_write (FILEPTR *f, char *buf, long bytes); long lp_read (FILEPTR *f, char *buf, long bytes); long lp_lseek (FILEPTR *f, long where, int whence); long lp_ioctl (FILEPTR *f, int mode, void *buf); long lp_datime (FILEPTR *f, int *timeptr, int rwflag); long lp_close (FILEPTR *f, int pid); long lp_select (FILEPTR *f, long proc, int mode); void lp_unselect (FILEPTR *f, long proc, int mode); DEVDRV lp_device = { lp_open, lp_write, lp_read, lp_lseek, lp_ioctl, lp_datime, lp_close, lp_select, lp_unselect }; struct dev_descr devinfo = { &lp_device }; /* Initializes the circular buffer */ void reset_buffer (void) { int sr = spl7(); buffer_head = buffer_tail = buffer_start; buffer_end = buffer_start + BUFSIZE; buffer_contents = 0L; spl(sr); } /* Copyright information */ void Version (void) { Cconws("Spooled Centronics device driver, by T.Bousch (version " LP_VERSION ").\r\n" "This program is FREE SOFTWARE, and comes with NO WARRANTY.\r\n" "See the file \"COPYING\" for more information.\r\n"); } /* * Installs everything, returns 0 on success. Must be executed in * supervisor mode. */ long install_things (void) { if (Syield() == EINVFN) { Cconws("lpdev: MiNT is not running\r\n"); return EACCDN; } if (Fsfirst(DEVNAME, 0) == 0) { Cconws("lpdev: device \"" DEVNAME "\" already installed\r\n"); return EACCDN; } if (*(char*)0xFFFFFA09 & /* IERB */ *(char*)0xFFFFFA15 & 1) { /* IMRB */ Cconws("lpdev: Centronics interrupt already in use\r\n"); return EACCDN; } buffer_start = (char *)Malloc(BUFSIZE); if (!buffer_start) { Cconws("lpdev: not enough memory\r\n"); return ENSMEM; } reset_buffer(); kernel = (struct kerinfo *)Dcntl(DEV_INSTALL, DEVNAME, &devinfo); if ((long)kernel <= 0L) { Cconws("lpdev: unable to install device\r\n"); return EACCDN; } /* Finally! */ Mfpint(0, new_centr_vector); Jenabint(0); return 0; } /* * The main routine is very simple now, it just calls install_things * and remains resident if everything went well */ int main() { long ret; ret = Supexec(install_things); if (ret < 0) return ret; /* Installation is complete */ Version(); Ptermres(256L + _base->p_tlen + _base->p_dlen + _base->p_blen, 0); return -999; /* never reached, just to make Gcc happy */ } /* * Will wake any process select'ing the printer; * this routine is called by the interrupt handler, but also when the * buffer is flushed. */ void wake_up (void) { if (selector) WAKESELECT(selector); /* wake selector */ } /* * Sends as many bytes as possible (usually one) to the printer until * he gets busy. This routine is called by lp_write and by the * interrupt handler, so it _must_ be multi-thread. It will not work if * you remove the spl7()/spl() pair. * * On a more general note, it is safest to disable all interrupts before * modifying the volatile variables (buffer_contents and buffer_head). */ #define PRINTER_BUSY (*(char*)0xFFFFFA01 & 1) void print_head (void) { int sr = spl7(); while (!PRINTER_BUSY && buffer_contents) { print_byte( *buffer_head ); --buffer_contents; if (++buffer_head >= buffer_end) buffer_head -= BUFSIZE; } spl(sr); } /* * Copies a linear buffer into the circular one. We assume that's there * enough room for this operation, ie * nbytes + buffer_contents <= BUFSIZE * * Note: the while() loop will be executed at most twice. * Note2: the instruction "buffer_contents += N" looks atomic, but it * isn't (the Gcc outputs several assembly instructions). Therefore it * must be wrapped in spl7()/spl(). */ void print_tail (char *buf, long nbytes) { long N; int sr; while (nbytes) { N = buffer_end - buffer_tail; if (N > nbytes) N = nbytes; bcopy (buf, buffer_tail, N); buf += N; nbytes -= N; sr = spl7(); buffer_contents += N; spl(sr); buffer_tail += N; if (buffer_tail >= buffer_end) buffer_tail -= BUFSIZE; } print_head(); /* To initiate printing */ } /* * Here are the actual device driver functions */ #define LP_LOCKED (our_lock.l_pid >= 0) long lp_open (FILEPTR *f) { TRACE(("lp: open device")); return 0; } long lp_close (FILEPTR *f, int pid) { TRACE(("lp: close device")); if ((f->flags & O_LOCK) && our_lock.l_pid == pid) { TRACE(("lp: releasing lock on close")); f->flags &= ~O_LOCK; our_lock.l_pid = -1; WAKE(IO_Q, (long)&our_lock); } return 0; } long lp_read (FILEPTR *f, char *buf, long bytes) { TRACE(("lp: foolish attempt to read")); return 0; } long lp_datime (FILEPTR *f, int *timeptr, int rwflag) { if (rwflag) { DEBUG(("lp: can't modify date/time")); return EACCDN; } TRACE(("lp: read time and date")); *timeptr++ = TGETTIME(); *timeptr = TGETDATE(); return 0; } long lp_lseek (FILEPTR *f, long where, int whence) { TRACE(("lp: foolish attempt to seek")); if (whence < 0 || whence > 2) return EINVFN; return where ? ERANGE : 0L; } long lp_ioctl (FILEPTR *f, int mode, void *buf) { struct flock *g; if (mode == FIONREAD) { TRACE(("lp: ioctl(FIONREAD)")); *(long *)buf = 0L; } else if (mode == FIONWRITE) { TRACE(("lp: ioctl(FIONWRITE)")); *(long *)buf = BUFSIZE - buffer_contents; } else if (mode == TIOCFLUSH) { TRACE(("lp: clear buffer")); reset_buffer(); wake_up(); /* Wake up any select'ing process */ } else if (mode == F_GETLK) { g = (struct flock *) buf; if (LP_LOCKED) { TRACE(("lp: get_lock succeeded")); *g = our_lock; } else { TRACE(("lp: get_lock failed")); g->l_type = F_UNLCK; } } else if (mode == F_SETLK || mode == F_SETLKW) { g = (struct flock *) buf; switch (g->l_type) { case F_UNLCK: if (!(f->flags & O_LOCK) || g->l_pid != our_lock.l_pid) { DEBUG(("lp: no such lock")); return ENSLOCK; } else { TRACE(("lp: remove lock")); f->flags &= ~O_LOCK; our_lock.l_pid = -1; WAKE(IO_Q, (long)&our_lock); } return 0; case F_RDLCK: TRACE(("lp: read locks are ignored")); return 0; case F_WRLCK: while (LP_LOCKED) { DEBUG(("lp: conflicting locks")); if (mode == F_SETLK) { *g = our_lock; return ELOCKED; } SLEEP(IO_Q, (long)&our_lock); } TRACE(("lp: set lock")); f->flags |= O_LOCK; our_lock.l_pid = g->l_pid; return 0; default: DEBUG(("lp: invalid lock type")); return EINVFN; } } else { DEBUG(("lp: invalid ioctl mode")); return EINVFN; } return 0; } long lp_write (FILEPTR *f, char *buf, long bytes) { long _bytes = bytes; long N; int ndel = (f->flags & O_NDELAY); /* don't wait */ while (bytes) { N = BUFSIZE - buffer_contents; /* * If the data won't fit into the buffer, * and if the buffer itself is almost full, we won't * be able to copy much. So we better sleep a bit (unless * the O_NDELAY flag is set). */ if (N < bytes && N < BUFSIZE/4 && !ndel) { TRACE(("lp: napping in lp_write")); NAP(200); /* let's wait 200 milliseconds */ continue; /* and try again */ } if (bytes < N) N = bytes; /* Now N contains the number of bytes we want to copy * into the circular buffer */ print_tail(buf, N); buf += N; bytes -= N; /* * If the O_NDELAY flag is set, we don't make a second * attempt to write the remaining "bytes" bytes. */ if (ndel) break; } TRACE(("lp: wrote %ld bytes, skipped %ld", _bytes-bytes, bytes)); return _bytes - bytes; } /* Bug: only one process can select the printer */ long lp_select (FILEPTR *f, long proc, int mode) { if (buffer_contents == BUFSIZE && !selector) { TRACE(("lp: select returned 0")); selector = proc; return 0; } TRACE(("lp: select returned 1")); return 1; } void lp_unselect (FILEPTR *f, long proc, int mode) { TRACE(("lp: unselect")); selector = 0L; }