Subject: Re: Cabletron "etherd" program for NetBSD
To: Ian Dall <Ian.Dall@dsto.defence.gov.au>
From: Jason Thorpe <thorpej@nas.nasa.gov>
List: port-pc532
Date: 08/25/1996 22:59:03
On Mon, 26 Aug 1996 13:17:09 +0930 
 Ian Dall <Ian.Dall@dsto.defence.gov.au> wrote:

 > The same mechanism might be possible. Either have another "in kernel"
 > process or just use process 0 as the context somehow.

Err, see my previous ... this shouldn't be necessary at all.  Network 
interface drivers don't need a process context to operate in, even if 
they use the SCSI subsystem to access the device registers (really, 
that's all you're doing ... it really wouldn't be any different than a 
regular network interface driver ...)

 > Certainly. Any ethernet driver must have a way of allocating space for
 > packets (presumably the mbuf mechanism) and it makes sense to do the same
 > thing.

Usually, the network interface driver copies the packet from the card's 
RAM into mbuf chains ... or, the card DMA's the packet into a linear buffer 
which might be then copied into an mbuf, or it might DMA directly into a 
pre-allocated mbuf ... lots of options.  Elimination copies is typically 
good, but you don't want it to be to evil, either :-)

 >   > You just have to
 >   > make sure that when they get wrapped in a block I/O "struct buf" to
 >   > hand to the SCSI I/O system that the buffers will never get freed (by
 >   > normal block I/O routines).
 > 
 > Perhaps a custom scsi_done()?

If you call scsi_scsi_command() in the kernel directly, you don't have to 
deal with a struct buf at all... Let's assume that we pre-allocate a 
single linear transmit buffer early (I'd assume you can only transmit one 
thing at a time :-)  I'd guess you could implement transmission like this:

/*
 * Start transmission on the inerface.
 * Always called at splnet().
 */
void
ctscstart(ifp)
	struct ifnet *ifp;
{
	struct ctsc_softc *sc = ifp->if_softc;
	struct scsi_command_thing_wants cmd;
	struct mbuf *m, *m0;
	int len, error;
	u_char *cp;

	/* Don't transmit if interface is busy or not running */
	if ((ifp->if_flags & (IFF_RUNNING|IFF_OACTIVE)) != IFF_RUNNING)
		return;

	IF_DEQUEUE(&ifp->if_snd, m0);
	if (m0 == 0)
		return;

	/* We need to use m->m_pkthdr.len, so require the header */
	if ((m0->m_flags & M_PKTHDR) == 0)
		panic("ctscstart: no header mbuf");
	len = m0->m_pkthdr.len;

	/* Mark the interface busy. */
	ifp->if_flags |= IFF_OACTIVE;

	/* Fill out SCSI command. */
	[ . . . ]

	/* Can we optimize out the copy? */
	if (m0->m_next == NULL) {
		/* Fits in one mbuf, we can cheat. */
		sc->sc_mbuf = m0;	/* We free it later. */
		
		/* Send command to device. */
		error = scsi_scsi_cmd(sc->sc_link,
		    (struct scsi_generic *)&cmd, sizeof(cmd),
		    mtod(m0, u_char *), len, CTSCRETRIES,
		    10000, NULL, SCSI_NOSLEEP|SCSI_DATA_OUT);
		if (error) {
			printf("%s: not queued, error %d\n",
			    sc->sc_dev.dv_xname, error);
			sc->sc_mbuf = NULL;
			m_freem(m0);
			ifp->if_oerrors++;
			ifp->if_flags &= ~IFF_OACTIVE;
		} else
			ifp->if_opackets++;
		return;
	}

	/* Chain; copy into linear buffer we allocated at attach time. */
	sc->sc_mbuf = NULL;	/* sanity */
	cp = sc->sc_opacketbuf;
	for (m = m0; m != NULL; ) {
		bcopy(mtod(m, u_char *), cp, m->m_len);
		cp += m->m_len;
		MFREE(m, m0);
		m = m0;
	}

	/* Send command to device. */
	error = scsi_scsi_cmd(sc->sc_link,
	    (struct scsi_generic *)&cmd, sizeof(cmd),
	    sc->sc_opacketbuf, len, CTSCRETRIES,
	    10000, NULL, SCSI_NOSLEEP|SCSI_DATA_OUT);
	if (error) {
		printf("%s: not queued, error %d\n",
		    sc->sc_dev.dv_xname, error);
		ifp->if_oerrors++;
		ifp->if_flags &= ~IFF_OACTIVE;
	} else
		ifp->if_opackets++;
}

/*
 * Called from the scsibus layer via our scsi device switch.
 */
int
ctsc_scsidone(xs, complete)
	struct scsi_xfer *xs;
	int complete;
{
	struct ctsc_softc *sc = xs->sc_link->device_softc;

	/*
	 * "complete" indicates we're freeing resources used
	 * by the transfer.  We only have something to do on
	 * a "transmit complete".
	 */
	if (complete && (xs->flags & SCSI_DATA_OUT)) {
		/*
		 * If we cheated, free the mbuf.
		 */
		if (sc->sc_mbuf)
			m_freem(sc->sc_mbuf);

		sc->sc_arpcom.ac_if.if_flags &= ~IFF_OACTIVE;
	}

	return (0);
}

...or something like that.  Anyhow, it demonstrates my point, I think. :-)

NOTE!  There's an interesting (and non-obvious) nasty-little side effect
hidden here... Note that packet transmission is done at splnet(), and 
reception should also be protected by splnet() ... you need to make sure 
that splnet() blocks bio interrupts in this case, and that splbio() 
doesn't unblock net interrupts in this case (this is the same icky situation 
that you have in the presence of ppp or slip over serial lines).

 -- save the ancient forests - http://www.bayarea.net/~thorpej/forest/ -- 
Jason R. Thorpe                                       thorpej@nas.nasa.gov
NASA Ames Research Center                               Home: 408.866.1912
NAS: M/S 258-6                                          Work: 415.604.0935
Moffett Field, CA 94035                                Pager: 415.428.6939