/* if_ssc.c ver 0.05 2000/04/22 */ /* * Driver for Sonic Systems SSC-501/502 SCSI-Ethernet * * Written by Hiroshi Noguchi * * This driver is written based on "if_se.c". */ /* * Copyright (c) 1997 Ian W. Dall * 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 Feb 3, 1997 * * Acknowledgement: Thanks are due to Philip L. Budne * 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 "opt_inet.h" #include "opt_atalk.h" #include "opt_ccitt.h" #include "opt_llc.h" #include "opt_ns.h" #include "bpfilter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INET #include #include #endif #ifdef NS #include #include #endif #ifdef NETATALK #include #endif #if defined(CCITT) && defined(LLC) #include #include #include #include #include #endif #if NBPFILTER > 0 #include #include #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_link* sc_link; /* 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((void *)); 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_set_mode(struct ssc_softc *, int, int); 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_link *sc_link, 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", "" } }; struct cfattach ssc_ca = { sizeof(struct ssc_softc), sscmatch, sscattach }; struct scsipi_device 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 */ }; cdev_decl(ssc); extern struct cfdriver ssc_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; struct scsipi_link* sc_link; int priority; sa = aux; sc_link = sa->sa_sc_link; priority = 0; /* * As this adaptor seems to response all LUN(0-7) when "Test Unit Ready", * accept LUN = 0 and reject the others. */ if ( sc_link->scsipi_scsi.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_link* sc_link; struct ifnet* ifp; u_int8_t myaddr[ETHER_ADDR_LEN]; sc = (void *)self; sa = aux; sc_link = sa->sa_sc_link; ifp = &sc->sc_ethercom.ec_if; printf("\n"); SC_DEBUG(sc_link, 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_link = sc_link; sc_link->device = &ssc_switch; sc_link->device_softc = sc; if (sc_link->openings > SSC_OUTSTANDING) sc_link->openings = SSC_OUTSTANDING; 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); #if NBPFILTER > 0 bpfattach(&ifp->if_bpf, ifp, DLT_EN10MB, sizeof(struct ether_header)); #endif } /* * submit SCSI command */ static __inline int ssc_scsipi_cmd(sc_link, scsipi_cmd, cmdlen, data_addr, datalen, retries, timeout, bp, flags) struct scsipi_link *sc_link; 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(sc_link, 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(v) void *v; { int s; struct ssc_softc* sc; struct ifnet* ifp; sc = (struct ssc_softc *) v; 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_link, (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 = xs->sc_link->device_softc; 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_link, (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_link, (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_link, (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_set_mode(sc, len, mode) struct ssc_softc *sc; int len; int mode; { return( 0 ); } 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 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_link, (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_link *sc_link; unit = SSCUNIT(dev); if (unit >= ssc_cd.cd_ndevs) return (ENXIO); sc = ssc_cd.cd_devs[unit]; if (sc == NULL) return (ENXIO); sc_link = sc->sc_link; if ((error = scsipi_adapter_addref(sc_link)) != 0) return (error); SC_DEBUG(sc_link, SDEV_DB1, ("sscopen: dev=0x%x (unit %d (of %d))\n", dev, unit,ssc_cd.cd_ndevs)); sc_link->flags |= SDEV_OPEN; SC_DEBUG(sc_link, SDEV_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; sc = ssc_cd.cd_devs[SSCUNIT(dev)]; SC_DEBUG(sc->sc_link, SDEV_DB1, ("closing\n")); scsipi_wait_drain(sc->sc_link); scsipi_adapter_delref(sc->sc_link); sc->sc_link->flags &= ~SDEV_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 = ssc_cd.cd_devs[SSCUNIT(dev)]; return (scsipi_do_ioctl(sc->sc_link, dev, cmd, addr, flag, p)); } /* * end of file */