[comp.unix.i386] A parallel port driver for SysV/386

pcg@cs.aber.ac.uk (Piercarlo Grandi) (07/23/90)

I am posting here the full source of a parallel port driver, inspired by
one posted on these screen my Mike Grenier, that has some qualities that
may make it preferable over the one shipped standard with the various
versions of System V/386.

They are mainly that it is small, does not use termio, does not use any
interrupt lines, thus using polling, and this polling mode probably
consumes less CPU than interrupt overheads.

The driver has been developed on ESIX, but should work on nearly every
other System V/386. The only thing I am not sure exists in other
versions is a referenc eto 'tenmicrosec', a procedure that just busy
waits for 10 microseconds; if you don;t have it, it's fairly easy to
roll your own (no, it's not important that it last exactly ten
microseconds). Also, the conditions for the two polling loops have been
designed around my printer, an Epson SQ850 inkjet; I suspect that its
parallel interface is a bit odd. If you have problems with excessive
busy waits (espcially for offline or powered off printers), slow
printing, etc..., revise the polling conditions (but it should not be
really necessary).

As you will notice I have used a reference to the GPL, with the
concession that linking it with a kernel will not require distributing
the rest of the kernel be distributed uder the GPL as well.

----------------------------cut here-------------------------
#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 1 (of 1)."
# Contents:  Makefile Master Name Node README pp.c pp.h space.c
# Wrapped by sw@aware on Mon Jul 23 11:26:19 1990
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
echo '
    Copyright 1990 Piercarlo Grandi. All rights reserved.

    This shar archive contains free software; you can redistribute it
    and/or modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation; either version 1, or
    (at your option) any later version.

    As a special case, the driver contained herein may be incorporated
    in any OS kernel, whether the GNU General Public License applies to
    it or not.

    This driver is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You may have received a copy of the GNU General Public License
    along with this driver; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
'
echo 4
if test -f 'README' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'README'\"
else
echo shar: Extracting \"'README'\" \(1928 characters\)
sed "s/^X//" >'README' <<'END_OF_FILE'
XThis is the source of a parallel port driver for System V/386, rel. 3.2.
X
XIt is different from the standard lp(7) driver because it does only raw
Xoutput (while lp(7) uses the termio(4) line discipline), and it does not use
Xinterrupts. It assumes that the printer has an internal buffer, so that the
Xmost efficient style of communication is to pump characters as fast as
Xpossible until the buffer fills, then sleep for a relatively long period
Xwhen it signals not ready, and try again thereafter.
X
XThis implies that no interrupt processing is done, which has the advantages
Xof simplicity and low overhead, and, perhaps most importantly, of not using
Xone of the scarce interurpt lines of the ISA bus.
X
XOutput is done entirely in the top half og the driver, and therefore not
Xinside a critical region. This could mean that on a very busy system the
Xpumping could be interrupted by task switching. This, if important, can be
Xeasily obviated, by turning the pumping loop into a critical region; if this
Xis done it becomes important to limit the number of characters pumped in one
Xgo, by having two loops, the outer one that runs until busy is signaled, the
Xinner one for a fixed number of characters, and put only the inner one in
Xthe critical region.
X
XThis driver was originally written by Mike Grenier in 1989; Piercarlo Grandi
Xin 1990 substantially recoded  and repackaged it.
X
XAs it stands it is especially designed for a stock System V/386 rel. 3.2
Xsystem, e.g. ESIX, especially inasmuch installation is concerned. It should
Xbe sufficient to say 'make install' to install it, and then you should say
X'idconfig' and 'idmkunix' to rebuild the kernel.
X
XIt is so simple that it should port very quickly to other flavours of Unix;
Xthe only thing that may not be portable is the reference to a kernel
Xprocedure called 'tenmicrosec()' in pp.c, which does a busy loop of 10
Xmicroseconds.  It is fairly easy to roll your own, of course.
END_OF_FILE
if test 1928 -ne `wc -c <'README'`; then
    echo shar: \"'README'\" unpacked with wrong size!
fi
# end of 'README'
fi
if test -f 'Makefile' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Makefile'\"
else
echo shar: Extracting \"'Makefile'\" \(201 characters\)
sed "s/^X//" >'Makefile' <<'END_OF_FILE'
XCONF		=/etc/conf/bin/
XCC		=cc
XCFLAGS		=-O -DINKERNEL
X
XDriver.o:	pp.o;			ln $? $@
XSpace.c:	space.c;		ln $? $@
X
Xpp.o:		pp.h
Xspace.o:	pp.h
X
Xinstall:	Driver.o Space.c Master System Node Name
X	$(CONF)idinstall -keu pp
X	$(CONF)idmknod
END_OF_FILE
if test 201 -ne `wc -c <'Makefile'`; then
    # Ahem. I have just edited the shar file...
fi
# end of 'Makefile'
fi
if test -f 'Master' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Master'\"
else
echo shar: Extracting \"'Master'\" \(29 characters\)
sed "s/^X//" >'Master' <<'END_OF_FILE'
Xpp	Iocw	icoHGO	pp	0	0	1	3	-1
END_OF_FILE
if test 29 -ne `wc -c <'Master'`; then
    echo shar: \"'Master'\" unpacked with wrong size!
fi
# end of 'Master'
fi
if test -f 'Name' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Name'\"
else
echo shar: Extracting \"'Name'\" \(43 characters\)
sed "s/^X//" >'Name' <<'END_OF_FILE'
Xpp Parallel port driver, raw polled output
END_OF_FILE
if test 43 -ne `wc -c <'Name'`; then
    echo shar: \"'Name'\" unpacked with wrong size!
fi
# end of 'Name'
fi
if test -f 'Node' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Node'\"
else
echo shar: Extracting \"'Node'\" \(33 characters\)
sed "s/^X//" >'Node' <<'END_OF_FILE'
Xpp	pp0	c	0
Xpp	pp1	c	1
Xpp	pp2	c	2
END_OF_FILE
if test 33 -ne `wc -c <'Node'`; then
    echo shar: \"'Node'\" unpacked with wrong size!
fi
# end of 'Node'
fi
if test -f 'pp.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'pp.c'\"
else
echo shar: Extracting \"'pp.c'\" \(6271 characters\)
sed "s/^X//" >'pp.c' <<'END_OF_FILE'
X/*
X    $Header$
X*/
X
Xstatic char Notice[] =
X    "Copyright 1990 Piercarlo Grandi. All rights reserved.";
X
X/*
X    This driver is free software; you can redistribute it and/or
X    modify it under the terms of the GNU General Public License as
X    published by the Free Software Foundation; either version 1, or
X    (at your option) any later version.
X
X    As a special case, this driver may be incorporated in any OS kernel,
X    whether the GNU General Public License applies to it or not.
X
X    This driver is distributed in the hope that it will be useful,
X    but WITHOUT ANY WARRANTY; without even the implied warranty of
X    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
X    GNU General Public License for more details.
X
X    You may have received a copy of the GNU General Public License
X    along with this driver; if not, write to the Free Software
X    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
X*/
X
X/*
X    Fast parallel port driver, polling, no interrupts (somewhat inspired by a
X    driver from Michael Grenier <mike@cimcor.mn.org>)
X*/
X
X#include "sys/param.h"
X#include "sys/types.h"
X#include "sys/dir.h"
X#include "sys/signal.h"
X#include "sys/user.h"
X#include "sys/errno.h"
X#include "sys/sysmacros.h"
X
X#ifdef ppDEBUG
X    int			    pp_debug = 1;
X#   define DEBUG(list)	    do if (pp_debug) (list); while (0);
X    int			    pp_pause = 0;
X    int			    pp_spin = 0;
X#else
X#   define DEBUG(list)	    /* skip */
X#endif
X
X#include "sys/pp.h"
X
X/*
X    A long pause for polling the printer until it is online again
X    the pause while the strobe is high, and a short pause to wait
X    for the interface to become ready for the next character.
X    The longer pause is done sleeping, the other two are done
X    spinning, so they had better be few.
X*/
X
Xextern int		tenmicrosec();
X
X#define ppSPIN()	(void) (tenmicrosec())
X#define ppSTROBESPIN()	{ int n = pp_strobe; do (void) tenmicrosec(); while (--n); }
X
Xextern int		Hz;
Xextern int		wakeup();
Xextern int		timeout();
Xextern int		sleep();
X
X#define ppPAUSE(u)	(void) (timeout(wakeup,(u),Hz/3),sleep((u),PSLEP))
X
Xextern int		inb();
Xextern			outb();
X
Xextern int		ppinit()
X{
X    int			    unit;
X
X    for (unit = 0; unit < pp_max; unit++)
X    {
X	outb(pp_config[unit].control,(ppSELECT|ppNOTRESET));
X	DEBUG(printf("Initial status of /dev/pp%d is %x\n",
X		unit,inb(pp_config[unit].status)));
X    }
X}
X
Xextern int              ppopen(dev,mode)
X    int			    dev,mode;
X{
X    register int	    unit = minor(dev);
X    register int	    status;
X
X    if (unit >= pp_max)
X    {
X	u.u_error = EIO;
X	return;
X    }
X
X    status = inb(pp_config[unit].status);
X
X    if ((status&0xff) == 0xff)
X    {
X	u.u_error = ENXIO;
X	DEBUG(printf("Printer /dev/pp%d does not exist\n",unit));
X	return;
X    }
X
X    /*
X	We ignore error condition except when printer is online ready.
X    */
X
X    if ((status&(ppONLINE|ppNOTBUSY|ppNOTERROR)) == (ppONLINE|ppNOTBUSY))
X    {
X	u.u_error = EIO;
X	DEBUG(printf("Error status %x detected on /dev/pp%d\n",status,unit));
X	return;
X    }
X
X    DEBUG((pp_pause = 0, pp_spin = 0));
X}
X
Xextern int              ppclose(dev)
X    int			    dev;
X{
X    DEBUG(printf("Printer paused %d and spun %d times\n",pp_pause,pp_spin));
X}
X
X/*
X    The exit conditions from the two polling loops here are a bit funny,
X    my printer (EPSON SQ850) and paralle port give the following statuses:
X
X				NOTBUSY NOTACK  NOPAPER ONLINE  NOTERROR
X
X	port missing		1	1	1	1	1
X	printer power off	1	0	0	0	0
X	printer not there	1	1	0	1	0
X	offline			0	1	0	1	0
X	online			1	1	0	1	1
X	interface busy		0	1	0	1	1
X
X    These are somewhat surprising; in particular the role of NOTACK and
X    NOTBUSY seem a bit confused, and NOTERROR is low when the printer
X    if offline, which is very strange. The conditions in the two polling
X    loops have been designed with these states in mind.
X*/
X
Xextern int              ppwrite(dev)
X    int			    dev;
X{
X    register struct pp_config *pp;
X    register int	    status;
X    register int	    statusp;
X    register int	    ch;
X
X    pp	    = &pp_config[minor(dev)];
X    statusp = pp->status;
X
X    while ((ch = cpass()) >= 0)
X    {
X	status = inb(statusp);
X
X	/*
X	    This do ... while (...); may be rewritten as a for (...),
X	    but this form is probably more "efficient", because we expect
X	    that on average we will have to execute the body.
X	*/
X
X	do
X	{
X
X	wait_printer_online:
X
X	    /*
X		Here we wait for the printer to be ready, and this may
X		be a longish wait, because of paper end, offline, power off.
X		We sleep, polling a few times per second, as this is simpler
X		than taking an interrupt and the cost is very low.
X	    */
X
X	    for
X	    (
X		status;
X		(status & (ppONLINE|ppNOTERROR|ppNOPAPER))
X		    != (ppONLINE|ppNOTERROR);
X		status = inb(statusp)
X	    )
X	    {
X		ppPAUSE((int) &pp->status);
X		DEBUG(pp_pause++);
X	    }
X
X	wait_interface_ready:
X
X	    /*
X		Here we wait for the interface to be ready to accept another
X		character, and thus we spin, because we expect the wait to be
X		short (e.g. 2-4 turns). This is cheaper than the overheads
X		involved in sleeping or even in taking an interrupt.
X
X		Notice the form of the exit condition; we wait until
X		the interface is ready, unless error or paper end are
X		signaled, because those are long term conditions on
X		which we must not spin wait.
X	    */
X
X	    for
X	    (
X		status;
X		(status & (ppNOTACK|ppNOTBUSY)) != (ppNOTACK|ppNOTBUSY)
X		&& (status & (ppONLINE|ppNOPAPER|ppNOTERROR))
X		    == (ppONLINE|ppNOTERROR);
X		status = inb(statusp)
X	    )
X	    {
X		ppSPIN();
X		DEBUG(pp_spin++);
X	    }
X
X	    /*
X		We exit from the loop only if everything is clear; we are
X		paranoid, and check for all conditions. After all the
X		printer may have become not ready while in the interface
X		readyness testing loop.
X	    */
X	}
X	while ((status & (ppONLINE|ppNOTERROR|ppNOPAPER|ppNOTBUSY|ppNOTACK))
X	    != (ppONLINE|ppNOTERROR|ppNOTBUSY|ppNOTACK));
X
X    latch_character:
X
X	/*
X	    We do not wait for any settling time on this
X	*/
X
X	outb(pp->data,ch);
X
X    strobe_awhile:
X
X	/*
X	    We wait for the printer to have a good chance to
X	    see the strobe high, and then we wait for it to latch
X	    in the character when we drive it back down.
X	*/
X
X	outb(pp->control,(ppSELECT|ppNOTRESET|ppSTROBE));
X	ppSTROBESPIN();
X	outb(pp->control,(ppSELECT|ppNOTRESET));
X    }
X}
END_OF_FILE
if test 6271 -ne `wc -c <'pp.c'`; then
    echo shar: \"'pp.c'\" unpacked with wrong size!
fi
# end of 'pp.c'
fi
if test -f 'pp.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'pp.h'\"
else
echo shar: Extracting \"'pp.h'\" \(805 characters\)
sed "s/^X//" >'pp.h' <<'END_OF_FILE'
X/*
X    Status port defines
X*/
X
X#define ppNOTERROR	0x08	/* All is OK				*/
X#define ppONLINE	0x10	/* Printer is online			*/
X#define ppNOPAPER	0x20	/* Paper is present			*/
X#define ppNOTACK	0x40	/* Interface ready to be strobed	*/
X#define ppNOTBUSY	0x80	/* Printer ready to print		*/
X
X/*
X    Control port defines
X*/
X
X#define ppSTROBE	0x01	/* Strobe a character in		*/
X#define ppAUTOLF	0x02	/* Enable auto line feed		*/
X#define ppNOTRESET	0x04	/* Normal operation			*/
X#define ppSELECT	0x08	/* Put the printer online		*/
X#define ppINTERRUPT	0x10	/* Send interrupts			*/
X
X#ifdef INKERNEL
Xextern short		pp_strobe;
X
Xstruct pp_config
X{
X    short                   data;
X    short                   status;
X    short                   control;
X};
X
Xextern struct pp_config	pp_config[];
Xextern short		pp_max;
X#endif
END_OF_FILE
if test 805 -ne `wc -c <'pp.h'`; then
    echo shar: \"'pp.h'\" unpacked with wrong size!
fi
# end of 'pp.h'
fi
if test -f 'space.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'space.c'\"
else
echo shar: Extracting \"'space.c'\" \(435 characters\)
sed "s/^X//" >'space.c' <<'END_OF_FILE'
X/*
X * pp/space.c, created 8/27/89, cimcor!mike
X */
X
X#include "sys/pp.h"
X
X/*
X    How many tens of microseconds the strobe line
X    shall stay high for the interface to recognize it.
X*/
Xshort			pp_strobe = 1;
X
Xstruct pp_config	pp_config[] =
X{
X    /*	data,	status,	control */
X    {	0x3bc,	0x3bd,	0x3be    },
X    {	0x378,	0x379,	0x37a    },
X    {	0x278,	0x279,	0x27a    }
X};
X
Xshort			pp_max = sizeof pp_config / sizeof (struct pp_config);
END_OF_FILE
if test 435 -ne `wc -c <'space.c'`; then
    echo shar: \"'space.c'\" unpacked with wrong size!
fi
# end of 'space.c'
fi
echo shar: End of archive 1 \(of 1\).
cp /dev/null ark1isdone
MISSING=""
for I in 1 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have the archive.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0

--
Piercarlo "Peter" Grandi           | ARPA: pcg%cs.aber.ac.uk@nsfnet-relay.ac.uk
Dept of CS, UCW Aberystwyth        | UUCP: ...!mcsun!ukc!aber-cs!pcg
Penglais, Aberystwyth SY23 3BZ, UK | INET: pcg@cs.aber.ac.uk