From: ajv@xhost92.csd.mot.com
Newsgroups: comp.unix.bsd
Subject: Re: replacement for lpt.c without interrupts
Date: 9 Sep 92 06:00:18 GMT

wolf@dentaro.GUN.de (Wolfgang Stanglmeier) writes:

>I hacked the 386bsd LPT-driver to work without interrupts.
>The busy loop timeout value adapts automatically to
>the printer and system speed.

<excellent driver follows...>

I was working on something very much along the same lines.  However,
Mr. Stanglmeier hit the nail right on the head first.  I have rolled
in some of my other work on handling syscall restarts, and in the
process applied my BSD-kernel style of indentation.  What follows is
substantially the same driver, with my own minor improvements.

Kudos, Mr. Stanglmeier!  My printer thanks you. :-)

					Andy Valencia
					ajv@csd.mot.com (for now)
					jtk@netcom.com (if it bounces)

/*
 * Copyright (c) 1990 William F. Jolitz, TeleMuse
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *   This software is a component of "386BSD" developed by 
 *   William F. Jolitz, TeleMuse.
 * 4. Neither the name of the developer nor the name "386BSD"
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS A COMPONENT OF 386BSD DEVELOPED BY WILLIAM F. JOLITZ 
 * AND IS INTENDED FOR RESEARCH AND EDUCATIONAL PURPOSES ONLY. THIS 
 * SOFTWARE SHOULD NOT BE CONSIDERED TO BE A COMMERCIAL PRODUCT. 
 * THE DEVELOPER URGES THAT USERS WHO REQUIRE A COMMERCIAL PRODUCT 
 * NOT MAKE USE OF THIS WORK.
 *
 * FOR USERS WHO WISH TO UNDERSTAND THE 386BSD SYSTEM DEVELOPED
 * BY WILLIAM F. JOLITZ, WE RECOMMEND THE USER STUDY WRITTEN 
 * REFERENCES SUCH AS THE  "PORTING UNIX TO THE 386" SERIES 
 * (BEGINNING JANUARY 1991 "DR. DOBBS JOURNAL", USA AND BEGINNING 
 * JUNE 1991 "UNIX MAGAZIN", GERMANY) BY WILLIAM F. JOLITZ AND 
 * LYNNE GREER JOLITZ, AS WELL AS OTHER BOOKS ON UNIX AND THE 
 * ON-LINE 386BSD USER MANUAL BEFORE USE. A BOOK DISCUSSING THE INTERNALS 
 * OF 386BSD ENTITLED "386BSD FROM THE INSIDE OUT" WILL BE AVAILABLE LATE 1992.
 *
 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPER ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE DEVELOPER BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

/*
 * Device Driver for AT parallel printer port
 * Written by William Jolitz 12/18/90
 * Modified to run without interrupts
 * 92-08-19  Wolfgang Stanglmeier  <wolf@dentaro.GUN.de>
 * Slight cleanup and reorganization, try to handle restarted syscalls
 * 92-09-08  Andy Valencia <jtk@netcom.com>
 */

#include "lpt.h"
#if NLPT > 0

#include "param.h"
#include "buf.h"
#include "systm.h"
#include "ioctl.h"
#include "tty.h"
#include "proc.h"
#include "user.h"
#include "uio.h"
#include "kernel.h"
#include "malloc.h"

#include "i386/isa/isa_device.h"
#include "i386/isa/lptreg.h"

/* internal used flags */
#define   OPEN        (0x01)   /* device is open */
#define   INIT        (0x02)   /* device in open procedure */

/* flags from minor device */
#define   LPT_PRIME   (0x20)   /* prime printer on open   */
#define   LPT_ERROR   (0x10)   /* log error conditions    */

#define   LPT_FLAG(x) ((x) & 0xfc)
#define   LPT_UNIT(x) ((x) & 0x03)

/* Printer Ready condition */
#define   LPS_INVERT  (LPS_NBSY | LPS_NACK |           LPS_SEL | LPS_NERR)
#define   LPS_MASK    (LPS_NBSY | LPS_NACK | LPS_OUT | LPS_SEL | LPS_NERR)
#define   NOT_READY()   ((inb(sc->sc_stat)^LPS_INVERT)&LPS_MASK)

/* tsleep priority */
#define   LPPRI       ((PZERO+8) | PCATCH)

int lptprobe(), lptattach();
struct   isa_driver lptdriver = {lptprobe, lptattach, "lpt"};

/*
 *   copy usermode data into sysmode buffer
 */
#define   BUFSIZE      1024

/*
**   Waittimes
*/
#define   TIMEOUT   (hz*16)   /* Timeout while open device */
#define   LONG      (hz* 1)   /* Timesteps while open      */

#define   MAX_SPIN  255       /* max loop counter for busy wait */

/*   Valid Controlbits for probe ...
**
**   The lower 5 bits of controlport should be
**   readable and writable,
** 
**   .... but if my deskjet is power down, it clobbers
**   some lines, and the port will not be configured.
**   So I mask them out
*/
#define    LPC_MASK    (0xfa)

struct lpt_softc {
	char	*sc_cp;		/* current data to print	*/
	int	sc_count;	/* bytes queued in sc_inbuf	*/
	short	sc_data;	/* printer data port		*/
	short	sc_stat;	/* printer control port		*/
	short	sc_ctrl;	/* printer status port		*/
	u_char	sc_flags;	/* flags (open and internal)	*/
	u_char	sc_unit;	/* unit-number			*/
	u_char	sc_smax;	/* current max busy loop cnt	*/
	char			/* buffer for data		*/
	 *sc_inbuf;
} lpt_sc[NLPT];

/* In fact, I need no interrupt, but how can I explain it to config ??? */
lptintr(unit)
	int unit;
{
	/* dummy */ ;
}

/*
 * lptprobe()
 *	Probe for hardware
 */
lptprobe(idp)
	struct isa_device *idp;
{   
	unsigned v, w, n = 0;

	/* status */
	do {
		if (++n >= 4)
			return (0);

		/*
		 * Status port should be read only,
		 * so readback value may not change
		 */
		outb(idp->id_iobase+lpt_status,0xf0);
		v = inb(idp->id_iobase+lpt_status);
		outb(idp->id_iobase+lpt_status,0);
		w = inb(idp->id_iobase+lpt_status);
	} while (v != w);

	/* control: the lower 5 bits of controlport should read back */
	outb(idp->id_iobase+lpt_control,0xff);
	DELAY(100);

	w = inb(idp->id_iobase+lpt_control);
	if ((w ^ 0xff) & LPC_MASK) return(0);

	outb(idp->id_iobase+lpt_control,0);
	DELAY(100);
	w = inb(idp->id_iobase+lpt_control);
	if ((w ^ 0xe0) & LPC_MASK)
		return(0);
	return(1);
}

/*
 * lptattach()
 *	Install device
 */
lptattach(isdp)
	struct isa_device *isdp;
{
	struct   lpt_softc   *sc;

	sc = lpt_sc + isdp->id_unit;
	sc->sc_unit = isdp->id_unit;
	sc->sc_data = isdp->id_iobase + lpt_data;
	sc->sc_stat = isdp->id_iobase + lpt_status;
	sc->sc_ctrl = isdp->id_iobase + lpt_control;
	outb(sc->sc_ctrl, LPC_NINIT);
	return (1);
}

/*
 * lptopen()
 *	New open on device.
 *
 * We forbid all but first open
 */
lptopen(dev, flag)
	dev_t dev;
	int flag;
{
	struct lpt_softc *sc;
	int delay;	/* slept time in 1/hz seconds of tsleep */
	int err;
	u_char sta, unit;

	unit= LPT_UNIT(minor(dev));
	sta = LPT_FLAG(minor(dev));

	/* minor number out of limits ? */
	if (unit >= NLPT)
		return (ENXIO);
	sc = lpt_sc + unit;

	/* Attached ? */
	if (!sc->sc_ctrl) { /* not attached */
		return(ENXIO);
	}

	/* Printer busy ? */
	if (sc->sc_flags) { /* too late .. */
		return(EBUSY);
	}

	/* Have memory for buffer? */
	sc->sc_inbuf = malloc(BUFSIZE, M_DEVBUF, M_WAITOK);
	if (sc->sc_inbuf == 0)
		return(ENOMEM);

	/* Init printer */
	sc->sc_flags = sta | INIT;
	if (sc->sc_flags & LPT_PRIME) {
		outb(sc->sc_ctrl, 0);
	}

	/* Select printer */
	outb(sc->sc_ctrl, LPC_SEL|LPC_NINIT);

	/* and wait for ready .. */
	for (delay=0; NOT_READY(); delay+= LONG) {
		if (delay >= TIMEOUT) { /* too long waited .. */
			sc->sc_flags = 0;
			return (EBUSY);
		}

		/* sleep a moment */
		if ((err = tsleep (sc, LPPRI, "lpt: open", LONG)) !=
				EWOULDBLOCK) {
			sc->sc_flags = 0;
			return (EBUSY);
		}
	}

	/* Printer ready .. set variables */
	sc->sc_flags |= OPEN;
	sc->sc_count = 0;

	return(0);
}

/*
 * pushbytes()
 *	Workhorse for actually spinning and writing bytes to printer
 */
static
pushbytes(sc)
	struct lpt_softc *sc;
{
	int spin, err, tic;
	char ch;

	/* loop for every character .. */
	while (sc->sc_count > 0) {
		/* printer data */
		ch = *(sc->sc_cp);
		sc->sc_cp += 1;
		sc->sc_count -= 1;
		outb(sc->sc_data, ch);

		/* Busy wait for printer ready .. */
		spin = tic = 0;
		while (NOT_READY()) {
			if (++spin >= sc->sc_smax) {
				/*
				 * Now sleep, every cycle a
				 * little longer ..
				 */
				tic = tic + tic + 1;
				err = tsleep(sc, LPPRI, "lpt: write", tic);
				if (err != EWOULDBLOCK) {
					return (err);
				}
			}
		}

		/* strobe */
		outb(sc->sc_ctrl, LPC_NINIT|LPC_SEL|LPC_STB);
		outb(sc->sc_ctrl, LPC_NINIT|LPC_SEL);

		/* Adapt busy-wait length... */
		if (spin >= sc->sc_smax) { /* was sleep wait */
			if (sc->sc_smax<MAX_SPIN)
				sc->sc_smax++;
		}
		if (spin*2 < sc->sc_smax) {
			sc->sc_smax--;
		}
	}
	return(0);
}

/*
 * lptclose()
 *	Close on lp.  Try to flush data in buffer out.
 */
lptclose(dev, flag)
	dev_t dev;
	int flag;
{
	struct lpt_softc *sc = lpt_sc + LPT_UNIT(minor(dev));

	/* If there's queued data, try to flush it */
	(void)pushbytes(sc);

	/* really close .. quite simple :-)  */
	outb(sc->sc_ctrl, LPC_NINIT);
	sc->sc_flags = 0;
	free(sc->sc_inbuf, M_DEVBUF);
	sc->sc_inbuf = 0;	/* Sanity */
	return(0);
}

/*
 * lptwrite()
 *	Copy from user's buffer, then print
 */
lptwrite(dev, uio)
	dev_t dev;
	struct uio *uio;
{
	struct lpt_softc *sc = lpt_sc + LPT_UNIT(minor(dev));
	int err;

	/* Write out old bytes from interrupted syscall */
	if (sc->sc_count > 0) {
		err = pushbytes(sc);
		if (err)
			return(err);
	}

	/* main loop */
	while ((sc->sc_count = MIN(BUFSIZE, uio->uio_resid)) > 0) {
		/*  get from user-space  */
		sc->sc_cp = sc->sc_inbuf;
		uiomove(sc->sc_inbuf, sc->sc_count, uio);
		err = pushbytes(sc);
		if (err)
			return(err);
	}
	return(0);
}
#endif /* NLP > 0 */