dr11w driver

Chris Torek chris at mimsy.UUCP
Thu Jan 22 18:43:43 AEST 1987


>In article <5127 at mimsy.UUCP> I wrote:
>>Why is it that as soon as it is explained why [asking for a DR11W
>>driver] is an unreasonable or incomplete request, six of them appear?

In article <4187 at utah-cs.UUCP> cetron at utah-cs.UUCP (Edward J Cetron) writes:
>I disagree, a dr11w is going to use the same general interface
>REGARDLESS of what is on the other end. ... Granted that there will
>be major philosophy differences depending on what it will talk to,
>but I'd rather have a working driver for the right board which is
>CLOSE, then start from scratch......

This is a good point.  I also feel I should apologise for calling it
an `unreasonable' request.  Incomplete it is, however.

>I think that the hardest part of writing a driver is the device
>interface and its structures and the kernel interface and its
>structures.... the flow of the code is easy by comparison.....

I do not agree here, but that may come from many hours' experience
rewriting drivers:  I know now the interface levels quite well.
The hard part is figuring out what the device really *does* when
you touch it.

Since I am not in the least sleepy, although I must (alas!) arise
early tomorrow for Usenix, I will now attempt to dispel some of
the mystery involved in 4.3BSD style device drivers, with an example
made of a (broken) DR11B driver.  Perhaps this will provide the
necessary drowsiness :-).  The notes below are just first thoughts,
typed in since I will need them eventually anyway; please excuse
any rambling.  You get no more than what you pay for :-).

------------

The file begins with a conditional compilation directive:

	#include "drb.h"	/* find out whether we have a drb */
	#if NDRB > 0		/* there is at least one drb in the system */

Then there is a series of include files, summarised here:

	../machine/pte.h

	param.h systm.h buf.h dir.h user.h ioctl.h map.h vm.h cmap.h uio.h
				(or ../h/<whatever>)

	../vax/cpu.h

	ubareg.h ubavar.h	(or ../vaxuba/<x>)

	drbreg.h		(DRB registers)

Character special devices do one `raw' transfer (read or write) at
a time, using these:

	/* Raw I/O buffer headers */
	struct buf rdrbbuf[NDRB];

Unibus drivers are very stylised.  There are some number of interrupt
catch routines, each of which takes a device unit number.  These are
listed in the machine configuration file (conf/PICKLE or whatnot),
along with the device's CSR address as, e.g.,

`device drb0 at uba0 csr 0177420 vector drbintr'

This puts the first DRB (drb0) on the first Unibus adapter at csr
777420 (I just made that up) and says that this device uses only
one interrupt vector, and that that should call drbintr with zero
as its single argument.  The csr address is listed without the top
two bits set.  I have never quite figured out why; it makes using
DEC diagnostics annoying, though, as they want these bits.

To return to the driver:  Next we have some more-or-less `boilerplate'
code:

	/* Unibus driver routines */
	int	drbprobe(), drbattach();
	struct	uba_device *drbinfo[NDRB];
	u_short	drbstd[] = { 0 };
	struct	uba_driver drbdriver =
		{ drbprobe, 0, drbattach, 0, drbstd, "drb", drbinfo };

drbprobe and drbattach are declared so that they may be used to
initialise drbdriver.  drbinfo is an array of pointers to struct
uba_device; struct uba_device holds all the information the generic
Unibus code ever needs in order to deal with this drb.  A fair bit
of this information is directly useful (in the driver itself) as
well.  drbstd[] is supposed to be filled in with a list of `standard'
addresses at which one might find a DR11B: that is, the places DEC
would put it as a first, second, third, ... choice in a standard
Vax configuration.  The list must be terminated with a zero.  [This
drbstd array supplies no standard addresses, but DR11Bs are
nonstandard anyway. :-)]

The values in drbdriver are (in order, including the zeroes):
pointers to the `probe', `slave', `attach', and `dgo' routines; a
pointer to a short that is the first of an array of standard
addresses; a pointer to a string that names the device; and a
pointer to a pointer that is the first of an array, whose members
each point to `struct uba_device's.  (Got that?  [No doubt `no'....]
The type of this last field is `struct uba_device **'.)  There are
more fields, but these are used only for mass storage devices (disk
and tape drives and their controllers).

Moving on ...

Next we have some handy macros:

	#define DRUNIT(dev) (minor(dev))
	#define DRADDR(ui)  ((struct drbdevice *)((ui)->ui_addr))

DRUNIT extracts the unit number (drb3 is unit 3, e.g.) from a
`dev_t' parameter.  We will see many of those later.  DRADDR takes
a `struct uba_device *', and finds its Unibus address, and thereby,
the address of its csr and other registers.  DRADDR(ui)->drb_st is
thus the status register.  (I suppose I should give the structure
here.  Push context:

	struct drbdevice {
		short	dr_wc;		/* word count */
		u_short	dr_ba;		/* buffer address (low 16 bits) */
		short	dr_st;		/* status register */
		short	dr_db;		/* data register (not used with DMA) */
	};

The DR11B's address in the configuration file is actually the address
of its word count register, only because this is first.  The `CSR'
in `device ... csr 0177420 ...' is not necessarily the true status
register, if any; it is the address of the *first* register for *any*
sort of control or status.  Pop context.)

If the driver ever needs to put user code to sleep, it must specify
a priority.  DRB_PRI is just a bit too low for signals to abort
such sleeps, but not too low to interfere with important things
like tty I/O: we have to move netnews!

	#define DRB_PRI (PZERO-1)

Here comes the bane of the DR11 series:

	/* would like to have these, but they ain't there! */
	#ifdef notdef
	#define DRREAD (DRB_READ|DRB_GO)
	#define DRWRITE (DRB_WRITE|DRB_GO)
	#endif

There is no DRB_READ: the DR11B cannot be told to do a read or a
write.  Only the device on the far side of the DR can do a read or
a write.  By convention, one or two of the three `function' bits
is used to tell the other device whether the Vax wants to read or
to write.

Ah well.  Onward!  The driver needs to keep track of some more
status per DR11B.  There is no room for this in `struct uba_device',
so we create another structure to hold it.

	/*
	 * Software state per device
	 */
	struct drb_softc {
		short	sc_openf;	/* open flag */
		short	sc_st;		/* stuff for DRST */
		int	sc_ubainfo;	/* uba address */
	} drb_softc[NDRB];

sc_openf is used to prevent more than one user from running the
DR11B at a time.  Just how this is done we shall see later.  sc_st
is used to hold a status register value to take care of a DR11B
peculiarity; this too will be explained later.  sc_ubainfo holds
the Unibus address allocated to map each transfer.  The transfer
mapping can often be done by the generic Unibus code; I am not now
sure why this driver does it itself.  (I may find out as I go on.)

	/*
	 * drbprobe - determine if DRB exists
	 */
	/*ARGSUSED*/
	drbprobe(reg)
		caddr_t reg;
	{
		register int br, cvec;	/* r11, r10 -- ``value-result'' */

	#ifdef lint
		br = 0; cvec = br; br = cvec;
	#endif lint
		/* TOO HARD TO MAKE IT INTERRUPT */
		br = 0x15;
		cvec = 0124;
		return (sizeof (struct drbdevice));
	}

drbprobe() is that routine we listed earlier in drbdriver.  Its
job is to make a DR11B interrupt, if one is there, and to avoid
confusing another device, if some other device is there instead.
This is a difficult task, made much worse by the fact that the
DR11B will never interrupt until it has moved some data.  This
routine just assumes the device is there.  It returns sizeof(struct
drbdevice), which being nonzero, tells the configuration routines
that this device is at the location given by `reg' and that it
interrupts at IPL 15 (br=0x15) through vector 124 (csr=0124).
If either of these is false, the driver fails.  Simple!

This routine is rather peculiar in another way.  Note the comment
on the declarations for `br' and `cvec'.  These two MUST occupy
registers r11 and r10 (respectively) for the autoconfiguration code
to work.  Autoconf modifies the drbprobe() at Unix startup so as
not to save r11 and r10, then uses them as global variables during
Unibus configuration.  For this reason, `reg' must NOT be declared
`register'.  This can be considered `magic'.  If the DR11B had
a maintenance interrupt, and had a bit to note its own presence,
drbprobe() might better be written thus:

	drbprobe(reg)
		caddr_t reg;	/* `Core ADDRess Type', or `char *' on vax */
	{
		register int br, cvec;	/* magic */

		/* see if there is a drb here */
		if ((((struct drbdevice *) reg)->drb_st & DRB_PRESENT) == 0)
			return (0);	/* no drb */
		/* now make it interrupt */
		((struct drbdevice *) reg)->drb_st = DRB_MAINT|DRB_IE;
		/* it will interrupt within 1 millisecond, so wait 1000 ns */
		DELAY(1000);
		/* then clear the interrupt mode */
		((struct drbdevice *) reg)->drb_st = 0;
		/* and say we found one */
		return (sizeof (struct drbdevice));
	}

When (if) the drb interrupts, `br' and `cvec' will be altered to
reflect its actual IPL and vector.  Alas, real DR11Bs are not so
nice.

Next we have the attach routine:

	/*ARGSUSED*/
	drbattach(ui)
		struct uba_device *ui;
	{
	}

The DR11B never detaches itself, and there is no software status
information to adjust, since at boot time all uninitialised global
variables are filled with zero bytes.  drb_softc[] is thus all zero.

Next we have the open routine.  This is called whenever someone
attempts an `open("/dev/drb0", mode)' (or whatever).  The `dev'
argument tells the driver which of several DR11Bs might be being
opened, and the `flag' argument says whether it is for reading,
for writing, or for both: flag & FREAD, or flag & FWRITE, or both.

	/*ARGSUSED*/
	drbopen(dev, flag)
		dev_t dev;
		int flag;
	{
		register int unit = DRUNIT(dev);
		register struct drb_softc *sc;
		register struct uba_device *ui;

(unit is now the unit number: drb3 => 3, drb0 => 0.)

		if (unit >= NDRB || (ui = drbinfo[unit]) == NULL ||
		    !ui->ui_alive)
			return (ENXIO);
(the specified unit must exist, and must have been found at boot time)
		sc = &drb_softc[unit];
		if (sc->sc_openf)
			return (EBUSY);
(if this drb is already in use, say `device busy')
		DRADDR(ui)->dr_st = DRB_IE;
(set the DR11B status to interrupt enable only)
		sc->sc_openf++;
(remember that it is now open)
		sc->sc_st = 0;
(clear the stored state)
		return (0);
(and allow the open to complete)
	}

Next we have a nice simple close routine:

	/* close a DRB */
	drbclose(dev)
		dev_t dev;
	{
		int unit = DRUNIT(dev);
		struct drbdevice *dr = DRADDR(drbinfo[unit]);

		drb_softc[unit].sc_openf = 0;
(mark it no longer open)
		dr->dr_st = 0;
(disallow future interrupts)
		dr->dr_wc = 0;
(and clear the transfer word count)
	}

Deep in the heart of the driver is the most important routine, the
`strategy' for reading or writing.

	/* Read or write the DRB */
	drbstrategy(bp)
		register struct buf *bp;
(bp is a pointer to a buffer that describes the transfer.)
	{
		register int unit = DRUNIT(bp->b_dev);
(get the unit number)
		register struct uba_device *ui = drbinfo[unit];
(and the device information)
		register struct drbdevice *dr = DRADDR(ui);
(find the device registers)
		register struct drb_softc *sc = &drb_softc[unit];
(and the software status)
		int s = spl5();
(and lock out interrupts, so that changes to any variables will be
atomic [yes, the kernel should use semaphores])

		/* Should maybe check for 0 length transfer? */
		sc->sc_ubainfo = ubasetup(ui->ui_ubanum, bp, UBA_NEEDBDP);
(allocate resources for this transfer, requesting a buffered data
path as well; since UBA_CANTWAIT is *not* asserted, wait until
those resources are available if necessary)
		if (sc->sc_ubainfo == 0) {
			splx(s);
			bp->b_flags |= B_ERROR;
			return;
		}
(if no resources are available, something must be wrong; abort)
		dr->dr_wc = -((bp->b_bcount) >> 1);
(set the word count register according to the transfer byte count;
the hardware wants 2's complement as well)
		dr->dr_ba = sc->sc_ubainfo;/* store low 16 bits of address */
(set the low part of the transfer address)
		sc->sc_st &= DRB_FNCT3|DRB_FNCT2|DRB_FNCT1;
		sc->sc_st |= (bp->b_flags & B_READ) ? DRREAD : DRWRITE;
		dr->dr_st = sc->sc_st | ((sc->sc_ubainfo >> 12) & 060);
(clear junk, and set the status bits appropriately to tell the drb
what to do, including the upper two bits of the transfer address)

		/* wait for the transfer to complete */
		while ((dr->dr_st & DRB_READY) == 0) {
			sleep((caddr_t)sc, DRB_PRI);
(the sleep() enables interrupts; wait for the ready bit to come on
after the device interrupts to note transfer complete)
			if (bp->b_flags & B_ERROR)
				break;
(if something went wrong, stop)
		}
(otherwise wait until the transfer is *done* ... see drbintr)
		splx(s);
(reenable interrupts)
		ubarelse(ui->ui_ubanum, &sc->sc_ubainfo);
(give up Unibus resources)
		iodone(bp);
(and mark the transfer complete)
	}

Now that was not too bad, was it?

Next we have the read and write syscalls.  These are nearly clones of
each other, so I will explain only one.

	/* Write system call */
	drbwrite(dev, uio)
		dev_t dev;
		struct uio *uio;
(`uio' describes the transfer; older kernels keep the information tested
below in u.u_resid)
	{

		if (uio->uio_resid & 1)
			return (EINVAL);
(reject odd length transfers, since the hardware cannot do them)
		return (physio(drbstrategy, &rdrbbuf[DRUNIT(dev)], dev, B_WRITE,
			minphys, uio));
(call physio to have it do all the hard work: recording transfer info
in rdrbbuf[unit] and paging in the data and so forth; it will call
drbstrategy when ready)
	}

	/* Read system call */
	drbread(dev, uio)
		dev_t dev;
		struct uio *uio;
	{

		if (uio->uio_resid & 1)
			return (EINVAL);
		return (physio(drbstrategy, &rdrbbuf[DRUNIT(dev)], dev, B_READ,
			minphys, uio));
	}

Next we have the interrupt handler.  This is not as trivial as one
might hope, because of some peculiarities of the DR11B.  In particular,
it has trouble when its bus address register overflows 16 bits.

	/* Interrupt service routine */
	drbintr(unit)
		register int unit;
	{
		register struct drbdevice *dr;
		register struct drb_softc *sc;

		sc = &drb_softc[unit];
		dr = DRADDR(drbinfo[unit]);
		if ((dr->dr_st & DRB_ERROR) == 0)
			goto done;
(if the device interrupted without an error, it must be done; go
clean up)
		if ((dr->dr_st & (DRB_NXM|DRB_ATTN)) == 0) {
(figure out what kind of error: after eliminating NXM and ATTN...)
			/*
			 * Must be address overflow.  Word count must be
			 * decremented because the overflow halted the
			 * transfer AFTER the word count had been
			 * incremented.  (Note that this can only happen
			 * once at most, so we can just use addr+0x10000)
			 */
			dr->dr_wc--;
			dr->dr_ba = 0;/* also clears error */
			dr->dr_st = sc->sc_st |
				(((sc->sc_ubainfo >> 12) + 16) & 060);
			/* that is sc_ubainfo + 0x10000, shifted 12, masked
			   to get bits 4 & 5, for the XBA16/XBA17 bits */
			return;
(handle address overflow by restarting the transfer)
		}
		printf("drb%d: hard error st=%b wc=%d ba=%x\n", unit,
			dr->dr_st, DRB_BITS, -dr->dr_wc, dr->dr_ba);
		dr->dr_st = DR_IE;
		rdrbbuf[unit].b_flags |= B_ERROR;
(handle others by complaining and asserting B_ERROR)
	done:
		wakeup((caddr_t)sc);
(in any case, awaken sleepers)
	}

Next is a routine common to all Unibus drivers, the reset recovery.
This is sometimes tricky.

	/* Reset state on Unibus reset */
	drbreset(uban)
		int uban;
	{
		register int i;
		register struct uba_device *ui;
		register struct drb_softc *sc;

		for (i = 0, sc = &drb_softc[0]; i < NDRB; i++, sc++) {
(for all DRBs)
			if ((ui = DRBinfo[i]) == NULL || ui->ui_alive == 0 ||
			    ui->ui_ubanum != uban || sc->sc_openf == 0)
				continue;
(if this one is around and on this Unibus and in use...)
			printf(" drb%d", i);
(note it)
			DRADDR(ui)->dr_st = sc->sc_st & DRB_IE;
(clear it)
			sc->sc_ubainfo = 0;
(note the fact that resources have been destroyed by the reset)
			rdrbbuf[i].b_flags |= B_ERROR;
			wakeup((caddr_t)sc);
(abort transfer, if any)
		}
	}

Here is where an ioctl() routine would live; then the driver is
done.

	#endif NDRB > 0

Well, (*yawn*) that seems to have done it.  Good night....
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7690)
UUCP:	seismo!mimsy!chris	ARPA/CSNet:	chris at mimsy.umd.edu



More information about the Comp.org.usenix mailing list