/* $NetBSD$ */

/*  1 Mar 04 EL Only accept LUN=0 in sscmatch()
 *	EL Disable bad SSC_DEBUG in ssc_set_multi()
 */

/* if_ssc.c  ver 0.05   2000/04/22 */

/*
 * Driver for Sonic Systems SSC-501/502  SCSI-Ethernet
 *
 * Written by Hiroshi Noguchi <noguchi@npost1.netspace.or.jp>
 *
 * This driver is written based on "if_se.c".
 */

/*
 * Copyright (c) 1997 Ian W. Dall <ian.dall@dsto.defence.gov.au>
 * 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 product includes software developed by Ian W. Dall.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
 */

/*
 * Driver for Cabletron EA41x scsi ethernet adaptor.
 *
 * Written by Ian Dall <ian.dall@dsto.defence.gov.au> Feb 3, 1997
 *
 * Acknowledgement: Thanks are due to Philip L. Budne <budd@cs.bu.edu>
 * who reverse engineered the the EA41x. In developing this code,
 * Phil's userland daemon "etherd", was refered to extensively in lieu
 * of accurate documentation for the device.
 *
 * This is a weird device! It doesn't conform to the scsi spec in much
 * at all. About the only standard command supported is inquiry. Most
 * commands are 6 bytes long, but the recv data is only 1 byte.  Data
 * must be received by periodically polling the device with the recv
 * command.
 *
 * This driver is also a bit unusual. It must look like a network
 * interface and it must also appear to be a scsi device to the scsi
 * system. Hence there are cases where there are two entry points. eg
 * sestart is to be called from the scsi subsytem and ssc_ifstart from
 * the network interface subsystem.  In addition, to facilitate scsi
 * commands issued by userland programs, there are open, close and
 * ioctl entry points. This allows a user program to, for example,
 * display the ea41x stats and download new code into the adaptor ---
 * functions which can't be performed through the ifconfig interface.
 * Normal operation does not require any special userland program.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD$");

#include "opt_inet.h"
#include "opt_atalk.h"
#include "opt_ccitt.h"
#include "opt_llc.h"
#include "opt_ns.h"
#include "bpfilter.h"

#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/callout.h>
#include <sys/syslog.h>
#include <sys/kernel.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/buf.h>
#include <sys/uio.h>
#include <sys/malloc.h>
#include <sys/errno.h>
#include <sys/device.h>
#include <sys/disklabel.h>
#include <sys/disk.h>
#include <sys/proc.h>
#include <sys/conf.h>

#include <dev/scsipi/scsipi_all.h>
#include <dev/scsipi/scsiconf.h>

#include <sys/mbuf.h>

#include <sys/socket.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_ether.h>
#include <net/if_media.h>

#ifdef INET
#include <netinet/in.h>
#include <netinet/if_inarp.h>
#endif

#ifdef NS
#include <netns/ns.h>
#include <netns/ns_if.h>
#endif

#ifdef NETATALK
#include <netatalk/at.h>
#endif

#if defined(CCITT) && defined(LLC)
#include <sys/socketvar.h>
#include <netccitt/x25.h>
#include <netccitt/pk.h>
#include <netccitt/pk_var.h>
#include <netccitt/pk_extern.h>
#endif

#if NBPFILTER > 0
#include <net/bpf.h>
#include <net/bpfdesc.h>
#endif


/*
 * debug flag
 */
#if 0
#define	SSC_DEBUG
#endif


#define SSC_TIMEOUT		(1000)
#define	SSC_OUTSTANDING	(4)
#define	SSC_RETRIES		(4)
#define SSC_MINSIZE		(60)

#define ETHER_CRC		(4)
#define SSC_EXTRAS_TX	(2 + 2)	/* header 2 bytes + tail 2 bytes */
#define SSC_EXTRAS_RX	(2 + 2)	/* header 2 + 1 bytes + tail 1 bytes */
#define SSC_HEADER_RX	(2 + 1)	/* header 2 + 1 bytes */

#define	MAX_BYTES_RX	(ETHERMTU + sizeof(struct ether_header) + ETHER_CRC)

/* 10 full length packets appears to be the max ever returned. 16k is OK */
#define RBUF_LEN	(16 * 1024)

/*
 * Tuning parameters:
 *   We will attempt to adapt to polling fast enough to get RDATA_GOAL packets
 *   per read
 */
#define RDATA_MAX	(10)	/* maximum of returned packets (guessed) */
#define RDATA_GOAL 	(8)

/*
 * maximum of available multicast address entries (guessed)
 */
#define	SSC_MCAST_MAX	(10)


/* ssc_poll and ssc_poll0 are the normal polling rate and the minimum
 * polling rate respectively. ssc_poll0 should be chosen so that at
 * maximum ethernet speed, we will read nearly RDATA_MAX packets. ssc_poll
 * should be chosen for reasonable maximum latency.
 * In practice, if we are being saturated with min length packets, we
 * can't poll fast enough. Polling with zero delay actually
 * worsens performance. ssc_poll0 is enforced to be always at least 1
 */
#define SSC_POLL		(40)	/* default in milliseconds */
#define SSC_POLL0 		(10)	/* default in milliseconds */
int ssc_poll			= 0;	/* Delay in ticks set at attach time */
int ssc_poll0			= 0;
int ssc_max_received	= 0;	/* Instrumentation */




/*==========================================
  data type defs
==========================================*/
typedef struct {
	/* standard */
	u_int8_t	device;		/* 3 (T_CPU) */
	u_int8_t	dev_qual2;	/* 0 (fixed) */
	u_int8_t	version;	/* 0 */
	u_int8_t	response_format;	/* 0 */
	u_int8_t	additional_len;		/* 31 */
	u_int8_t	unused[2];	/* 0,0 */
	u_int8_t	flags;		/* 0x00 */
	char	vendor[8];		/* ie; "SonicSys" */
	char	product[16];	/* ie; "MicroSCSI" */
	char	revision[4];	/* ie; "2.00" */
	char	extra[8];		/* none */
} scsi_sonic_ether_inquiry_data;


struct ssc_softc {
	struct	device sc_dev;
	struct	ethercom sc_ethercom;	/* Ethernet common part */
	struct scsipi_periph *sc_periph;/* contains our targ, lun, etc. */

	struct callout sc_ifstart_ch;
	struct callout sc_recv_ch;

	char *sc_tbuf;
	char *sc_rbuf;
	int	sc_debug;
	int	sc_flags;
	int	sc_last_timeout;
	int	sc_enabled;
};

/* bit defs of "sc_flags" */
#define SSC_NEED_RECV	(0x1)


static int	sscmatch __P((struct device *, struct cfdata *, void *));
static void	sscattach __P((struct device *, struct device *, void *));

static void	sscstart __P((struct scsipi_periph  *));
static void	ssc_ifstart __P((struct ifnet *));
static void	ssc_delayed_ifstart __P((void *));

static void	sscdone __P((struct scsipi_xfer *));
static int	ssc_ioctl __P((struct ifnet *, u_long, caddr_t));
static void	sscwatchdog __P((struct ifnet *));

static void	ssc_recv __P((void *));
static struct mbuf*	ssc_get __P((struct ssc_softc *, char *, int));
static int	ssc_read __P((struct ssc_softc *, char *, int));

static int	ssc_get_addr __P((struct ssc_softc *, u_int8_t *));
static int	ssc_set_multi __P((struct ssc_softc *));

static int	ssc_reset __P((struct ssc_softc *));

static int	ssc_init __P((struct ssc_softc *));
static void	ssc_stop __P((struct ssc_softc *));

static __inline u_int16_t	ether_cmp __P((void *, void *));
static __inline int	ssc_scsipi_cmd __P((struct scsipi_periph *periph,
					    struct scsipi_generic *scsipi_cmd,
					    int cmdlen, u_char *data_addr, int datalen,
					    int retries, int timeout, struct buf *bp,
					    int flags));

int	ssc_enable __P((struct ssc_softc *));
void	ssc_disable __P((struct ssc_softc *));


typedef struct {
	u_int8_t	opcode[2];
	u_int8_t	byte3;
	u_int8_t	length[2];
	u_int8_t	byte6;
} scsi_sonic_ether_generic;

#define	SONIC_CMD_SEND	(0x0A)	/* same as generic "Write" */
#define	SONIC_CMD_RECV	(0x08)	/* same as generic "Read" */

#define	SONIC_CMD_GET_ADDR	(0x04)	/* ???  (same as generic "Format Unit") */
#define	SONIC_CMD_SET_MULTI	(0x06)	/* set multicast address */

#define	SONIC_CMD_VENDOR1	(0x05)	/* ???  initialize signal ? */

#define IS_SEND(generic) ((generic)->opcode == SONIC_CMD_SEND)
#define IS_RECV(generic) ((generic)->opcode == SONIC_CMD_RECV)

/*
 * command templates
 */
/* unknown commands */
/* Vendor #1 */
static const scsi_sonic_ether_generic	sonic_ether_vendor1 = {
	{ SONIC_CMD_VENDOR1, 0x01 },
	0x00,
	{ 0x00, 0x00 },
	0x00
};

/* other generic commands */
static const scsi_sonic_ether_generic	sonic_ether_template = {
	{ 0x00 }
};


struct scsipi_inquiry_pattern ssc_patterns[] = {
	{	T_PROCESSOR,	T_FIXED,
		"SonicSys",		"MicroSCSI",		"" }
};


CFATTACH_DECL(ssce, sizeof(struct ssc_softc),
    sscmatch, sscattach, NULL, NULL);

const struct scsipi_periphsw ssc_switch = {
	NULL,			/* Use default error handler */
	sscstart,		/* have a queue, served by this */
	NULL,			/* have no async handler */
	sscdone,			/* deal with stats at interrupt time */
};

dev_type_open(sscopen);
dev_type_close(sscclose);
dev_type_ioctl(sscioctl);

const struct cdevsw ssce_cdevsw = {
	sscopen, sscclose, noread, nowrite, sscioctl,
	nostop, notty, nopoll, nommap, nokqfilter,
};

extern struct cfdriver ssce_cd;

/*
 * Compare two Ether/802 addresses for equality, inlined and
 * unrolled for speed.
 * Note: use this like bcmp()
 */
static __inline u_int16_t
ether_cmp(one, two)
	void *one, *two;
{
	register u_int16_t*	a;
	register u_int16_t*	b;
	register u_int16_t diff;

	a = (u_int16_t *) one;
	b = (u_int16_t *) two;

	diff = (a[0] - b[0]) | (a[1] - b[1]) | (a[2] - b[2]);

	return (diff);
}

#define ETHER_CMP	ether_cmp


/*
 * check to match with SCSI inquiry information
 */
static int
sscmatch(parent, match, aux)
	struct device *parent;
	struct cfdata *match;
	void *aux;
{
	struct scsipibus_attach_args* sa;
	int priority;

	sa = aux;
	priority = 0;

/*
 * As this adaptor seems to response all LUN(0-7) when "Test Unit Ready",
 * accept LUN = 0 and reject the others.
 */
	if (sa->sa_periph->periph_lun != 0)
		goto l_end;

	(void)scsipi_inqmatch( &sa->sa_inqbuf,
	    (caddr_t)ssc_patterns, sizeof(ssc_patterns) / sizeof(ssc_patterns[0]),
		sizeof(ssc_patterns[0]), &priority );

l_end:;
	return (priority);
}


/*
 * The routine called by the low level scsi routine when it discovers
 * a device suitable for this driver.
 */
static void
sscattach(parent, self, aux)
	struct device *parent, *self;
	void *aux;
{
	struct ssc_softc* sc;
	struct scsipibus_attach_args* sa;
	struct scsipi_periph *periph = sa->sa_periph;
	struct ifnet* ifp;
	u_int8_t myaddr[ETHER_ADDR_LEN];

	sc = (void *)self;
	sa = aux;
	periph = sa->sa_periph;
	ifp = &sc->sc_ethercom.ec_if;

	printf("\n");
	SC_DEBUG(periph, SDEV_DB2, ("sscattach: "));

	callout_init( &sc->sc_ifstart_ch );
	callout_init( &sc->sc_recv_ch );

	/* Store information needed to contact our base driver */
	sc->sc_periph = periph;
	periph->periph_dev = &sc->sc_dev;
	periph->periph_switch = &ssc_switch;

	ssc_poll = (SSC_POLL * hz) / 1000;
	ssc_poll = ssc_poll? ssc_poll: 1;
	ssc_poll0 = (SSC_POLL0 * hz) / 1000;
	ssc_poll0 = ssc_poll0? ssc_poll0: 1;

	/*
	 * Initialize and attach a buffer
	 */
	sc->sc_tbuf = malloc( ETHERMTU + sizeof(struct ether_header) + SSC_EXTRAS_TX, M_DEVBUF, M_NOWAIT );
	if ( sc->sc_tbuf == NULL )
		panic("sscattach: can't allocate transmit buffer");

	sc->sc_rbuf = malloc( RBUF_LEN, M_DEVBUF, M_NOWAIT );/* A Guess */
	if (sc->sc_rbuf == 0)
		panic("sscattach: can't allocate receive buffer");

	/* initialize adaptor and obtain MAC address */
	ssc_get_addr(sc, myaddr);

	/* Initialize ifnet structure. */
	bcopy(sc->sc_dev.dv_xname, ifp->if_xname, IFNAMSIZ);
	ifp->if_softc = sc;
	ifp->if_start = ssc_ifstart;
	ifp->if_ioctl = ssc_ioctl;
	ifp->if_watchdog = sscwatchdog;
	ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_NOTRAILERS | IFF_MULTICAST;

	/* Attach the interface. */
	if_attach(ifp);
	ether_ifattach(ifp, myaddr);
}


/*
 * submit SCSI command
 */
static __inline int
ssc_scsipi_cmd(periph, scsipi_cmd, cmdlen, data_addr, datalen,
		       retries, timeout, bp, flags)
	struct scsipi_periph *periph;
	struct scsipi_generic *scsipi_cmd;
	int cmdlen;
	u_char *data_addr;
	int datalen;
	int retries;
	int timeout;
	struct buf *bp;
	int flags;
{
	int error;
	int s;

	s = splbio();
	error = scsipi_command(periph, scsipi_cmd, cmdlen, data_addr,
						   datalen, retries, timeout, bp, flags);
	splx(s);

	return (error);
}


/* Start routine for calling from scsi sub system */
static void
sscstart(periph)
	struct scsipi_periph *periph;
{
	int s;
	struct ssc_softc* sc;
	struct ifnet* ifp;

	sc = (struct ssc_softc *)periph->periph_dev;
	ifp = &sc->sc_ethercom.ec_if;

	s = splnet();
	ssc_ifstart(ifp);
	(void) splx(s);
}


static void
ssc_delayed_ifstart(v)
	void *v;
{
	struct ifnet* ifp;
	struct ssc_softc* sc;
	int s;

	ifp	= v;
	sc	= ifp->if_softc;

	s = splnet();
	if (sc->sc_enabled) {
		ifp->if_flags &= ~IFF_OACTIVE;
		ssc_ifstart(ifp);
	}
	splx(s);
}


/*
 * Start transmission on the interface.
 * Always called at splnet().
 */
static void
ssc_ifstart(ifp)
	struct ifnet *ifp;
{
	struct ssc_softc* sc;
	scsi_sonic_ether_generic cmd_send;
	struct mbuf* m;
	struct mbuf* m0;
	int	len, scsilen, error;
	u_char*	cp;

	sc = ifp->if_softc;

	/* 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 == NULL )
		return;

#if NBPFILTER > 0
	/* If BPF is listening on this interface, let it see the
	 * packet before we commit it to the wire.
	 */
	if ( ifp->if_bpf )
		bpf_mtap(ifp->if_bpf, m0);
#endif

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

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

	/* set frame length */
	_lto2b( len, sc->sc_tbuf );

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

	/* set zero into tail extras */
	_lto2b( 0x0000, &(sc->sc_tbuf[2 + len]) );

	/* Fill out SCSI command. */
	scsilen = len + SSC_EXTRAS_TX;
	cmd_send = sonic_ether_template;
	cmd_send.opcode[0] = SONIC_CMD_SEND;
	_lto2b( scsilen, cmd_send.length );

	/* Send command to device. */
	error = ssc_scsipi_cmd(sc->sc_periph,
            (struct scsipi_generic *)&cmd_send, sizeof(cmd_send),
	    sc->sc_tbuf, scsilen, SSC_RETRIES,
	    SSC_TIMEOUT, NULL, XS_CTL_NOSLEEP | XS_CTL_ASYNC | XS_CTL_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++;

#ifdef SSC_DEBUG
	printf("ssc_ifstart: scsilen = %d, packetlen = %d, proto = 0x%04x\n", scsilen, len,
	    ntohs(((struct ether_header *)&(sc->sc_tbuf[2]))->ether_type));
#endif

	if ( sc->sc_flags & SSC_NEED_RECV ) {
		sc->sc_flags &= ~SSC_NEED_RECV;
		ssc_recv((void *) sc );
	}
}


/*
 * Called from the scsibus layer via our scsi device switch.
 */
static void
sscdone(xs)
	struct scsipi_xfer *xs;
{
	struct ssc_softc* sc;
	struct scsipi_generic* cmd;
	struct ifnet* ifp;
	int error;
	int s;

	sc	= (void *)xs->xs_periph->periph_dev;
	cmd = xs->cmd;
	ifp = &sc->sc_ethercom.ec_if;

	error = !(xs->error == XS_NOERROR);

	s = splnet();

	if ( IS_SEND(cmd) ){
		if (xs->error == XS_BUSY) {
			printf("ssc: busy, retry txmit\n");
			callout_reset( &sc->sc_ifstart_ch, hz,
				ssc_delayed_ifstart, ifp );
		} else {
			ifp->if_flags &= ~IFF_OACTIVE;
			/* the generic scsipi_done will call
			 * sscstart (through scsipi_free_xs).
			 */
		}
	} else if ( IS_RECV(cmd) ) {
		/* RECV complete */
		/* pass data up. reschedule a recv */
		/* scsipi_free_xs will call start. Harmless. */
		if ( error ) {
			/* Reschedule after a delay */
			callout_reset( &sc->sc_recv_ch, ssc_poll,
				ssc_recv, (void *)sc );
		} else {
			int		n, ntimeo;

			n = ssc_read( sc, xs->data, xs->datalen - xs->resid );
			if ( n > ssc_max_received )
				ssc_max_received = n;
			if ( n == 0 )
				ntimeo = ssc_poll;
			else if ( n >= RDATA_MAX )
				ntimeo = ssc_poll0;
			else {
				ntimeo = sc->sc_last_timeout;
				ntimeo = (ntimeo * RDATA_GOAL)/ n;
				ntimeo = (ntimeo < ssc_poll0 ? ssc_poll0: ntimeo);
				ntimeo = (ntimeo > ssc_poll ? ssc_poll: ntimeo);
			}
			sc->sc_last_timeout = ntimeo;

			if ( (ntimeo == ssc_poll0) && ifp->if_snd.ifq_head ){
				/* Output is pending. Do next recv
				 * after the next send.  */
				sc->sc_flags |= SSC_NEED_RECV;
			} else
				callout_reset( &sc->sc_recv_ch, ntimeo,
					ssc_recv, (void *)sc );
		}
	}

	splx(s);
}


/*
 * do a recv command
 */
static void
ssc_recv(v)
	void *v;
{
	struct ssc_softc* sc;
	scsi_sonic_ether_generic cmd_recv;
	int error;
	int	len;

	sc = (struct ssc_softc *) v;

	if ( sc->sc_enabled == 0 )
		return;

	/* fill out command buffer */
	cmd_recv = sonic_ether_template;
	cmd_recv.opcode[0] = SONIC_CMD_RECV;
	len = ETHERMTU + sizeof(struct ether_header) + ETHER_CRC + SSC_EXTRAS_RX;
	_lto2b( len, cmd_recv.length );

	error = ssc_scsipi_cmd(sc->sc_periph,
	    (struct scsipi_generic *)&cmd_recv, sizeof(cmd_recv),
		sc->sc_rbuf, RBUF_LEN,
		SSC_RETRIES, SSC_TIMEOUT, NULL, XS_CTL_NOSLEEP | XS_CTL_ASYNC | XS_CTL_DATA_IN );
	if (error)
		callout_reset( &sc->sc_recv_ch, ssc_poll, ssc_recv, (void *)sc );
}


/*
 * We copy the data into mbufs.  When full cluster sized units are present
 * we copy into clusters.
 */
static struct mbuf *
ssc_get(sc, data, totlen)
	struct ssc_softc *sc;
	char *data;
	int totlen;
{
	struct ifnet* ifp;
	struct mbuf	*m, *m0, *newm;
	int len;

	ifp = &sc->sc_ethercom.ec_if;

	MGETHDR(m0, M_DONTWAIT, MT_DATA);
	if ( m0 == NULL )
		return ( NULL );

	m0->m_pkthdr.rcvif = ifp;
	m0->m_pkthdr.len = totlen;
	len	= MHLEN;
	m = m0;

	while ( totlen > 0 ){
		if ( totlen >= MINCLSIZE ){
			MCLGET(m, M_DONTWAIT );
			if( (m->m_flags & M_EXT) == 0 )
				goto bad;
			len = MCLBYTES;
		}

		if ( m == m0 ){
			caddr_t newdata = (caddr_t)
			    ALIGN(m->m_data + sizeof(struct ether_header)) -
			    sizeof(struct ether_header);
			len -= newdata - m->m_data;
			m->m_data = newdata;
		}

		m->m_len = len = min(totlen, len);
		bcopy(data, mtod(m, caddr_t), len);
		data += len;

		totlen -= len;
		if ( totlen > 0 ){
			MGET(newm, M_DONTWAIT, MT_DATA);
			if ( newm == NULL )
				goto bad;

			len = MLEN;
			m = m->m_next = newm;
		}
	}

	return (m0);

bad:
	m_freem(m0);
	return ( NULL );
}


/*
 * Pass packets to higher levels.
 */
static int
ssc_read(sc, data, datalen)
	register struct ssc_softc *sc;
	char *data;
	int datalen;
{
	struct mbuf* m;
	struct ether_header* eh;
	struct ifnet* ifp;
	int n;
	int	len;

	ifp = &sc->sc_ethercom.ec_if;

	n = 0;
	while ( datalen > 3 ){
		/* fetch frame length */
		len = _2btol( data );
		data	+= 2;
		datalen -= 2;

		/* skip more extras (status byte ?) */
		data	+= 1;
		datalen -= 1;

		if ( len == 0 )
			break;

#ifdef SSC_DEBUG
		printf("ssc_read: datalen = %d, packetlen = %d, proto = 0x%04x\n", datalen, len,
		    ntohs(((struct ether_header *)data)->ether_type));
#endif
		if ( (len <= sizeof(struct ether_header)) || (len > MAX_BYTES_RX) ){
#ifdef SSC_DEBUG
			printf("%s: invalid packet size %d; dropping\n",
			       sc->sc_dev.dv_xname, len);
#endif
			ifp->if_ierrors++;
			goto next_packet;
		}

		/* Don't need crc. Must keep ether header for BPF */
		m = ssc_get( sc, data, len - ETHER_CRC );
		if ( m == NULL ){
#ifdef SSC_DEBUG
			if (sc->sc_debug)
				printf("ssc_read: ssc_get returned null\n");
#endif
			ifp->if_ierrors++;
			goto next_packet;
		}
		ifp->if_ipackets++;

		/* We assume that the header fit entirely in one mbuf. */
		eh = mtod(m, struct ether_header *);

#if NBPFILTER > 0
		/*
		 * Check if there's a BPF listener on this interface.
		 * If so, hand off the raw packet to BPF.
		 */
		if ( ifp->if_bpf ){
			bpf_mtap(ifp->if_bpf, m);

			/* Note that the interface cannot be in
			 * promiscuous mode if there are no BPF
			 * listeners.  And if we are in promiscuous
			 * mode, we have to check if this packet is
			 * really ours.
			 */
			if ( ((ifp->if_flags & IFF_PROMISC) != 0) &&
			    	((eh->ether_dhost[0] & 1) == 0) && /* !mcast and !bcast */
			   		ETHER_CMP( eh->ether_dhost, LLADDR(ifp->if_sadl)) ){
				m_freem(m);
				goto next_packet;
			}
		}
#endif

 		/* Pass the packet up. */
 		(*ifp->if_input)(ifp, m);

  next_packet:
		data	+= len;
		datalen	-= len;
		n++;
	}

	return (n);
}


static void
sscwatchdog(ifp)
	struct ifnet *ifp;
{
	struct ssc_softc* sc;

	sc = ifp->if_softc;

	log(LOG_ERR, "%s: device timeout\n", sc->sc_dev.dv_xname);
	++ifp->if_oerrors;

	ssc_reset( sc );
}


static int
ssc_reset(sc)
	struct ssc_softc *sc;
{
	int error;
	int s;

	s = splnet();

#if 0
	/* Maybe we don't *really* want to reset the entire bus
	 * because the ctron isn't working. We would like to send a
	 * "BUS DEVICE RESET" message, but don't think the ctron
	 * understands it.
	 */
	error = ssc_scsipi_cmd(sc->sc_link, 0, 0, 0, 0, SSC_RETRIES, 2000, NULL,
	    XS_CTL_RESET);
#endif
	error = ssc_init(sc);

	splx(s);

	return (error);
}


static int
ssc_get_addr(sc, myaddr)
	struct ssc_softc *sc;
	u_int8_t *myaddr;
{
	int error;
	scsi_sonic_ether_generic cmd_get_addr;
	scsi_sonic_ether_generic cmd_vend1;

	/* magic: this command is neccesary (initialize signal ?) */
	cmd_vend1 = sonic_ether_vendor1;
	error = ssc_scsipi_cmd(sc->sc_periph,
	    (struct scsipi_generic *)&cmd_vend1, sizeof(cmd_vend1),
	    NULL, 0,
	    SSC_RETRIES, SSC_TIMEOUT, NULL, XS_CTL_DATA_IN );
	if ( error )
		goto l_end;

	cmd_get_addr = sonic_ether_template;
	cmd_get_addr.opcode[0] = SONIC_CMD_GET_ADDR;
	_lto2b( ETHER_ADDR_LEN, cmd_get_addr.length );

	error = ssc_scsipi_cmd(sc->sc_periph,
	    (struct scsipi_generic *)&cmd_get_addr, sizeof(cmd_get_addr),
	    myaddr, ETHER_ADDR_LEN,
	    SSC_RETRIES, SSC_TIMEOUT, NULL, XS_CTL_DATA_IN );
	printf("%s: ethernet address %s\n", sc->sc_dev.dv_xname,
		   ether_sprintf(myaddr));

  l_end:;
	return (error);
}


static int
ssc_init(sc)
	struct ssc_softc *sc;
{
	struct ifnet* ifp;
	int error;

	ifp = &sc->sc_ethercom.ec_if;
	error = 0;

	if ((ifp->if_flags & (IFF_RUNNING|IFF_UP)) == IFF_UP) {
		ifp->if_flags |= IFF_RUNNING;
		ssc_recv(sc);
		ifp->if_flags &= ~IFF_OACTIVE;
		ssc_ifstart(ifp);
	}

	return (error);
}


/*---------------------------
  ssc_set_multi:
---------------------------*/
static int
ssc_set_multi(sc)
	 struct ssc_softc*  sc;
{
	scsi_sonic_ether_generic cmd_set_multi;
	struct ether_multistep step;
	struct ether_multi* enm;
	u_char* cp;
	u_char* mybuf;
	int error;
	int len;

	error = 0;

#ifdef SSC_DEBUG____0
	printf("%s: ssc_set_multi: %s\n", sc->sc_dev.dv_xname, ether_sprintf(addr));
#endif

	mybuf = malloc( ETHER_ADDR_LEN * SSC_MCAST_MAX, M_DEVBUF, M_NOWAIT );
	if( mybuf == NULL ){
		error = EIO;
		goto l_end;
	}

	/*
	 * copy all entries to transfer buffer
	 */
	cp = mybuf;
	len = 0;
	ETHER_FIRST_MULTI( step, &(sc->sc_ethercom), enm );
	while ( (len < SSC_MCAST_MAX) && (enm != NULL) ){
		bcopy( enm->enm_addrlo, cp, ETHER_ADDR_LEN );	/* ### refer low side entry */

		cp += ETHER_ADDR_LEN;
		len++;
		ETHER_NEXT_MULTI( step, enm );
	}
	len *= ETHER_ADDR_LEN;

	cmd_set_multi = sonic_ether_template;
	cmd_set_multi.opcode[0] = SONIC_CMD_SET_MULTI;
	_lto2b( len, cmd_set_multi.length );

	error = ssc_scsipi_cmd(sc->sc_periph,
	    (struct scsipi_generic*)&cmd_set_multi, sizeof(cmd_set_multi),
	    mybuf, len, SSC_RETRIES, SSC_TIMEOUT, NULL, XS_CTL_DATA_OUT );

	free( mybuf, M_DEVBUF );

  l_end:;
	return ( error );
}


static void
ssc_stop(sc)
	struct ssc_softc *sc;
{
	/* Don't schedule any reads */
	callout_stop( &sc->sc_recv_ch );

	/* How can we abort any scsi cmds in progress? */
}


/*
 * Process an ioctl request.
 */
static int
ssc_ioctl(ifp, cmd, data)
	register struct ifnet *ifp;
	u_long cmd;
	caddr_t data;
{
	register struct ssc_softc* sc;
	struct ifaddr* ifa;
	struct ifreq* ifr;
	int s, error;

	error = 0;
	sc = ifp->if_softc;
	ifa = (struct ifaddr *)data;
	ifr = (struct ifreq *)data;

	s = splnet();

	switch ( cmd ){
	case SIOCSIFADDR:
		if ( (error = ssc_enable(sc)) != 0 )
			break;
		ifp->if_flags |= IFF_UP;

		switch ( ifa->ifa_addr->sa_family ){
#ifdef INET
		case AF_INET:
			if ( (error = ssc_init(sc)) != 0 )
				break;
			arp_ifinit(ifp, ifa);
			break;
#endif
#ifdef NS
		case AF_NS:
		    {
				register struct ns_addr *ina = &IA_SNS(ifa)->sns_addr;

				if ( ns_nullhost(*ina) )
					ina->x_host = *(union ns_host *)LLADDR(ifp->if_sadl);
				else
					bcopy(ina->x_host.c_host, LLADDR(ifp->if_sadl), ETHER_ADDR_LEN);

				/* Set new address. */
				error = ssc_init(sc);
		    }
			break;
#endif
#ifdef NETATALK
		case AF_APPLETALK:
			if ( (error = ssc_init(sc)) != 0 )
				break;
			break;
#endif
		default:
			error = ssc_init(sc);
			break;
		}
		break;

#if defined(CCITT) && defined(LLC)
	case SIOCSIFCONF_X25:
		if ( (error = ssc_enable(sc)) != 0 )
			break;
		ifp->if_flags |= IFF_UP;
		ifa->ifa_rtrequest = cons_rtrequest; /* XXX */
		error = x25_llcglue(PRC_IFUP, ifa->ifa_addr);
		if (error == 0)
			error = ssc_init(sc);
		break;
#endif /* CCITT && LLC */

	case SIOCSIFFLAGS:
		if ( ((ifp->if_flags & IFF_UP) == 0) &&
		   		((ifp->if_flags & IFF_RUNNING) != 0) ){
			/*
			 * If interface is marked down and it is running, then
			 * stop it.
			 */
			ssc_stop(sc);
			ifp->if_flags &= ~IFF_RUNNING;
			ssc_disable(sc);
		} else{
			if ( ((ifp->if_flags & IFF_UP) != 0) &&
					((ifp->if_flags & IFF_RUNNING) == 0) ){
				/*
				 * If interface is marked up and it is stopped, then
				 * start it.
				 */
				if ( (error = ssc_enable(sc)) != 0 )
					break;
				error = ssc_init(sc);
			} else if ( sc->sc_enabled ){
				/*
				 * Reset the interface to pick up changes in any other
				 * flags that affect hardware registers.
				 */
				error = ssc_init(sc);
			}
		}
#ifdef SSC_DEBUG
		if ( ifp->if_flags & IFF_DEBUG )
			sc->sc_debug = 1;
		else
			sc->sc_debug = 0;
#endif
		break;

	case SIOCADDMULTI:
		if ( sc->sc_enabled == 0 ){
			error = EIO;
			break;
		}
		if ( ether_addmulti(ifr, &sc->sc_ethercom) == ENETRESET ){
			error = ssc_set_multi(sc);
#ifdef SSC_DEBUG
			printf("%s: add multi: %s\n", sc->sc_dev.dv_xname,
				   ether_sprintf(ifr->ifr_addr.sa_data));
#endif
		} else
			error = 0;
		break;

	case SIOCDELMULTI:
		if ( sc->sc_enabled == 0 ){
			error = EIO;
			break;
		}
		if ( ether_delmulti(ifr, &sc->sc_ethercom) == ENETRESET ){
			error = ssc_set_multi(sc);
#ifdef SSC_DEBUG
			printf("%s: delete multi: %s\n", sc->sc_dev.dv_xname,
			    ether_sprintf(ifr->ifr_addr.sa_data));
#endif
		} else
			error = 0;
		break;

	default:
		error = EINVAL;
		break;
	}

	splx(s);

	return (error);
}


/*
 * Enable the network interface.
 */
int
ssc_enable(sc)
	struct ssc_softc *sc;
{
#if 1
	sc->sc_enabled = 1;
	return ( 0 );
#else
	int error = 0;

	if ( (sc->sc_enabled == 0) &&
		    ((error = scsipi_adapter_addref(sc->sc_link)) == 0) )
		sc->sc_enabled = 1;
	} else
		printf("%s: device enable failed\n", sc->sc_dev.dv_xname);

	return (error);
#endif
}


/*
 * Disable the network interface.
 */
void
ssc_disable(sc)
	struct ssc_softc *sc;
{
#if 1
	sc->sc_enabled = 0;
#else
	if ( sc->sc_enabled != 0 ){
		scsipi_adapter_delref(sc->sc_link);
		sc->sc_enabled = 0;
	}
#endif
}


#define	SSCUNIT(z)	(minor(z))

/*
 * open the device.
 */
int
sscopen(dev, flag, fmt, p)
	dev_t dev;
	int flag, fmt;
	struct proc *p;
{
	int unit, error;
	struct ssc_softc *sc;
	struct scsipi_periph *periph;
	struct scsipi_adapter *adapt;

	unit = SSCUNIT(dev);
	if (unit >= ssce_cd.cd_ndevs)
		return (ENXIO);

	sc = ssce_cd.cd_devs[unit];
	if (sc == NULL)
		return (ENXIO);

	periph = sc->sc_periph;
	adapt = periph->periph_channel->chan_adapter;

	if ((error = scsipi_adapter_addref(adapt)) != 0)
		return (error);

	SC_DEBUG(periph, SCSIPI_DB1,
	    ("sscopen: dev=0x%x (unit %d (of %d))\n",
	    dev, unit,ssce_cd.cd_ndevs));

	periph->periph_flags |= PERIPH_OPEN;

	SC_DEBUG(periph, SCSIPI_DB3, ("open complete\n"));

	return (0);
}


/*
 * close the device.. only called if we are the LAST
 * occurence of an open device
 */
int
sscclose(dev, flag, fmt, p)
	dev_t dev;
	int flag, fmt;
	struct proc *p;
{
	struct ssc_softc* sc;
	struct scsipi_periph *periph;
	struct scsipi_adapter *adapt;

	sc = ssce_cd.cd_devs[SSCUNIT(dev)];
	periph = sc->sc_periph;
	adapt = periph->periph_channel->chan_adapter;

	SC_DEBUG(sc->sc_periph, SCSIPI_DB1, ("closing\n"));

	scsipi_wait_drain(periph);

	scsipi_adapter_delref(adapt);
	periph->periph_flags &= ~PERIPH_OPEN;

	return (0);
}


/*
 * Perform special action on behalf of the user
 * Only does generic scsi ioctls.
 */
int
sscioctl(dev, cmd, addr, flag, p)
	dev_t dev;
	u_long cmd;
	caddr_t addr;
	int flag;
	struct proc *p;
{
	register struct ssc_softc* sc;

	sc = ssce_cd.cd_devs[SSCUNIT(dev)];

	return (scsipi_do_ioctl(sc->sc_periph, dev, cmd, addr, flag, p));
}


/*
 * end of file
 */
