bring up to revision 7 for tahoe release
[unix-history] / usr / src / sys / tahoe / if / if_enp.c
/*
* Copyright (c) 1988 Regents of the University of California.
* All rights reserved.
*
* Redistribution and use in source and binary forms are permitted
* provided that this notice is preserved and that due credit is given
* to the University of California at Berkeley. The name of the University
* may not be used to endorse or promote products derived from this
* software without specific prior written permission. This software
* is provided ``as is'' without express or implied warranty.
*
* @(#)if_enp.c 7.1 (Berkeley) %G%
*/
#include "enp.h"
#if NENP > 0
/*
* CMC ENP-20 Ethernet Controller.
*/
#include "param.h"
#include "systm.h"
#include "mbuf.h"
#include "buf.h"
#include "protosw.h"
#include "socket.h"
#include "vmmac.h"
#include "ioctl.h"
#include "errno.h"
#include "vmparam.h"
#include "syslog.h"
#include "uio.h"
#include "../net/if.h"
#include "../net/netisr.h"
#include "../net/route.h"
#ifdef INET
#include "../netinet/in.h"
#include "../netinet/in_systm.h"
#include "../netinet/in_var.h"
#include "../netinet/ip.h"
#include "../netinet/ip_var.h"
#include "../netinet/if_ether.h"
#endif
#ifdef NS
#include "../netns/ns.h"
#include "../netns/ns_if.h"
#endif
#include "../tahoe/cpu.h"
#include "../tahoe/pte.h"
#include "../tahoe/mtpr.h"
#include "../tahoevba/vbavar.h"
#include "../tahoeif/if_enpreg.h"
#define ENPSTART 0xf02000 /* standard enp start addr */
#define ENPUNIT(dev) (minor(dev)) /* for enp ram devices */
/* macros for dealing with longs in i/o space */
#define ENPGETLONG(a) ((((u_short *)(a))[0] << 16)|(((u_short *)(a))[1]))
#define ENPSETLONG(a,v) \
{ register u_short *wp = (u_short *)(a); \
wp[0] = ((u_short *)&(v))[0]; wp[1] = ((u_short *)&(v))[1];}
int enpprobe(), enpattach(), enpintr();
long enpstd[] = { 0xfff41000, 0xfff61000, 0 };
struct vba_device *enpinfo[NENP];
struct vba_driver enpdriver =
{ enpprobe, 0, enpattach, 0, enpstd, "enp", enpinfo, "enp-20", 0 };
int enpinit(), enpioctl(), enpreset(), enpoutput();
struct mbuf *enpget();
/*
* Ethernet software status per interface.
*
* Each interface is referenced by a network interface structure,
* es_if, which the routing code uses to locate the interface.
* This structure contains the output queue for the interface, its address, ...
*/
struct enp_softc {
struct arpcom es_ac; /* common ethernet structures */
#define es_if es_ac.ac_if
#define es_addr es_ac.ac_enaddr
short es_ivec; /* interrupt vector */
} enp_softc[NENP];
extern struct ifnet loif;
enpprobe(reg, vi)
caddr_t reg;
struct vba_device *vi;
{
register br, cvec; /* must be r12, r11 */
register struct enpdevice *addr = (struct enpdevice *)reg;
struct enp_softc *es = &enp_softc[vi->ui_unit];
#ifdef lint
br = 0; cvec = br; br = cvec;
enpintr(0);
#endif
if (badaddr((caddr_t)addr, 2) || badaddr((caddr_t)&addr->enp_ram[0], 2))
return (0);
es->es_ivec = --vi->ui_hd->vh_lastiv;
addr->enp_state = S_ENPRESET; /* reset by VERSAbus reset */
br = 0x14, cvec = es->es_ivec; /* XXX */
return (sizeof (struct enpdevice));
}
/*
* Interface exists: make available by filling in network interface
* record. System will initialize the interface when it is ready
* to accept packets.
*/
enpattach(ui)
register struct vba_device *ui;
{
struct enp_softc *es = &enp_softc[ui->ui_unit];
register struct ifnet *ifp = &es->es_if;
ifp->if_unit = ui->ui_unit;
ifp->if_name = "enp";
ifp->if_mtu = ETHERMTU;
ifp->if_init = enpinit;
ifp->if_ioctl = enpioctl;
ifp->if_output = enpoutput;
ifp->if_reset = enpreset;
ifp->if_flags = IFF_BROADCAST;
if_attach(ifp);
}
/*
* Reset of interface after "system" reset.
*/
enpreset(unit, vban)
int unit, vban;
{
register struct vba_device *ui;
if (unit >= NENP || (ui = enpinfo[unit]) == 0 || ui->ui_alive == 0 ||
ui->ui_vbanum != vban)
return;
printf(" enp%d", unit);
enpinit(unit);
}
/*
* Initialization of interface; clear recorded pending operations.
*/
enpinit(unit)
int unit;
{
struct enp_softc *es = &enp_softc[unit];
register struct vba_device *ui = enpinfo[unit];
struct enpdevice *addr;
register struct ifnet *ifp = &es->es_if;
int s;
if (ifp->if_addrlist == (struct ifaddr *)0)
return;
if ((ifp->if_flags & IFF_RUNNING) == 0) {
addr = (struct enpdevice *)ui->ui_addr;
s = splimp();
RESET_ENP(addr);
DELAY(200000);
es->es_if.if_flags |= IFF_RUNNING;
splx(s);
}
}
/*
* Ethernet interface interrupt.
*/
enpintr(unit)
int unit;
{
register struct enpdevice *addr;
register BCB *bcbp;
addr = (struct enpdevice *)enpinfo[unit]->ui_addr;
#if ENP == 30
if (!IS_ENP_INTR(addr))
return;
ACK_ENP_INTR(addr);
#endif
while ((bcbp = (BCB *)ringget((RING *)&addr->enp_tohost )) != 0) {
(void) enpread(&enp_softc[unit], bcbp);
(void) ringput((RING *)&addr->enp_enpfree, bcbp);
}
}
/*
* Read input packet, examine its packet type, and enqueue it.
*/
enpread(es, bcbp)
struct enp_softc *es;
register BCB *bcbp;
{
register struct ether_header *enp;
struct mbuf *m;
int s, len, off, resid;
register struct ifqueue *inq;
es->es_if.if_ipackets++;
/*
* Get input data length.
* Get pointer to ethernet header (in input buffer).
* Deal with trailer protocol: if type is PUP trailer
* get true type from first 16-bit word past data.
* Remember that type was trailer by setting off.
*/
len = bcbp->b_msglen - sizeof (struct ether_header);
enp = (struct ether_header *)ENPGETLONG(&bcbp->b_addr);
#define enpdataaddr(enp, off, type) \
((type)(((caddr_t)(((char *)enp)+sizeof (struct ether_header))+(off))))
enp->ether_type = ntohs((u_short)enp->ether_type);
if (enp->ether_type >= ETHERTYPE_TRAIL &&
enp->ether_type < ETHERTYPE_TRAIL+ETHERTYPE_NTRAILER) {
off = (enp->ether_type - ETHERTYPE_TRAIL) * 512;
if (off >= ETHERMTU)
goto setup;
enp->ether_type = ntohs(*enpdataaddr(enp, off, u_short *));
resid = ntohs(*(enpdataaddr(enp, off+2, u_short *)));
if (off + resid > len)
goto setup;
len = off + resid;
} else
off = 0;
if (len == 0)
goto setup;
/*
* Pull packet off interface. Off is nonzero if packet
* has trailing header; enpget will then force this header
* information to be at the front, but we still have to drop
* the type and length which are at the front of any trailer data.
*/
m = enpget((u_char *)enp, len, off, &es->es_if);
if (m == 0)
goto setup;
if (off) {
struct ifnet *ifp;
ifp = *(mtod(m, struct ifnet **));
m->m_off += 2 * sizeof (u_short);
m->m_len -= 2 * sizeof (u_short);
*(mtod(m, struct ifnet **)) = ifp;
}
switch (enp->ether_type) {
#ifdef INET
case ETHERTYPE_IP:
schednetisr(NETISR_IP);
inq = &ipintrq;
break;
#endif
case ETHERTYPE_ARP:
arpinput(&es->es_ac, m);
goto setup;
#ifdef NS
case ETHERTYPE_NS:
schednetisr(NETISR_NS);
inq = &nsintrq;
break;
#endif
default:
m_freem(m);
goto setup;
}
if (IF_QFULL(inq)) {
IF_DROP(inq);
m_freem(m);
goto setup;
}
s = splimp();
IF_ENQUEUE(inq, m);
splx(s);
setup:
return (0);
}
/*
* Ethernet output routine. (called by user)
* Encapsulate a packet of type family for the local net.
* Use trailer local net encapsulation if enough data in first
* packet leaves a multiple of 512 bytes of data in remainder.
* If destination is this address or broadcast, send packet to
* loop device to kludge around the fact that 3com interfaces can't
* talk to themselves.
*/
enpoutput(ifp, m0, dst)
struct ifnet *ifp;
struct mbuf *m0;
struct sockaddr *dst;
{
register struct enp_softc *es = &enp_softc[ifp->if_unit];
register struct mbuf *m = m0;
register struct ether_header *enp;
register int off;
struct mbuf *mcopy = (struct mbuf *)0;
int type, s, error, usetrailers;
u_char edst[6];
struct in_addr idst;
if ((ifp->if_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING)) {
error = ENETDOWN;
goto bad;
}
switch (dst->sa_family) {
#ifdef INET
case AF_INET:
idst = ((struct sockaddr_in *)dst)->sin_addr;
if (!arpresolve(&es->es_ac, m, &idst, edst, &usetrailers))
return (0); /* if not yet resolved */
if (!bcmp((caddr_t)edst, (caddr_t)etherbroadcastaddr,
sizeof (edst)))
mcopy = m_copy(m, 0, (int)M_COPYALL);
off = ntohs((u_short)mtod(m, struct ip *)->ip_len) - m->m_len;
if (usetrailers && off > 0 && (off & 0x1ff) == 0 &&
m->m_off >= MMINOFF + 2 * sizeof (u_short)) {
type = ETHERTYPE_TRAIL + (off>>9);
m->m_off -= 2 * sizeof (u_short);
m->m_len += 2 * sizeof (u_short);
*mtod(m, u_short *) = htons((u_short)ETHERTYPE_IP);
*(mtod(m, u_short *) + 1) = htons((u_short)m->m_len);
goto gottrailertype;
}
type = ETHERTYPE_IP;
off = 0;
goto gottype;
#endif
#ifdef NS
case AF_NS:
bcopy((caddr_t)&(((struct sockaddr_ns *)dst)->sns_addr.x_host),
(caddr_t)edst, sizeof (edst));
if (!bcmp((caddr_t)edst, (caddr_t)&ns_broadhost, sizeof (edst)))
mcopy = m_copy(m, 0, (int)M_COPYALL);
else if (!bcmp((caddr_t)edst, (caddr_t)&ns_thishost,
sizeof (edst)))
return (looutput(&loif, m, dst));
type = ETHERTYPE_NS;
off = 0;
goto gottype;
#endif
case AF_UNSPEC:
enp = (struct ether_header *)dst->sa_data;
bcopy((caddr_t)enp->ether_dhost, (caddr_t)edst, sizeof (edst));
type = enp->ether_type;
goto gottype;
default:
log(LOG_ERR, "enp%d: can't handle af%d\n",
ifp->if_unit, dst->sa_family);
error = EAFNOSUPPORT;
goto bad;
}
gottrailertype:
/*
* Packet to be sent as trailer: move first packet
* (control information) to end of chain.
*/
while (m->m_next)
m = m->m_next;
m->m_next = m0;
m = m0->m_next;
m0->m_next = 0;
m0 = m;
gottype:
/*
* Add local net header. If no space in first mbuf,
* allocate another.
*/
if (m->m_off > MMAXOFF ||
MMINOFF + sizeof (struct ether_header) > m->m_off) {
m = m_get(M_DONTWAIT, MT_HEADER);
if (m == 0) {
error = ENOBUFS;
goto bad;
}
m->m_next = m0;
m->m_off = MMINOFF;
m->m_len = sizeof (struct ether_header);
} else {
m->m_off -= sizeof (struct ether_header);
m->m_len += sizeof (struct ether_header);
}
enp = mtod(m, struct ether_header *);
bcopy((caddr_t)edst, (caddr_t)enp->ether_dhost, sizeof (edst));
bcopy((caddr_t)es->es_addr, (caddr_t)enp->ether_shost,
sizeof (es->es_addr));
enp->ether_type = htons((u_short)type);
/*
* Queue message on interface if possible
*/
s = splimp();
if (enpput(ifp->if_unit, m)) {
error = ENOBUFS;
goto qfull;
}
splx(s);
es->es_if.if_opackets++;
return (mcopy ? looutput(&loif, mcopy, dst) : 0);
qfull:
splx(s);
m0 = m;
bad:
m_freem(m0);
if (mcopy)
m_freem(mcopy);
return (error);
}
/*
* Routine to copy from mbuf chain to transmitter buffer on the VERSAbus.
*/
enpput(unit, m)
int unit;
struct mbuf *m;
{
register BCB *bcbp;
register struct enpdevice *addr;
register struct mbuf *mp;
register u_char *bp;
register u_int len;
u_char *mcp;
addr = (struct enpdevice *)enpinfo[unit]->ui_addr;
if (ringempty((RING *)&addr->enp_hostfree))
return (1);
bcbp = (BCB *)ringget((RING *)&addr->enp_hostfree);
bcbp->b_len = 0;
bp = (u_char *)ENPGETLONG(&bcbp->b_addr);
for (mp = m; mp; mp = mp->m_next) {
len = mp->m_len;
if (len == 0)
continue;
mcp = mtod(mp, u_char *);
enpcopy(mcp, bp, len);
bp += len;
bcbp->b_len += len;
}
bcbp->b_len = MAX(ETHERMIN+sizeof (struct ether_header), bcbp->b_len);
bcbp->b_reserved = 0;
if (ringput((RING *)&addr->enp_toenp, bcbp) == 1)
INTR_ENP(addr);
m_freem(m);
return (0);
}
/*
* Routine to copy from VERSAbus memory into mbufs.
*
* Warning: This makes the fairly safe assumption that
* mbufs have even lengths.
*/
struct mbuf *
enpget(rxbuf, totlen, off0, ifp)
u_char *rxbuf;
int totlen, off0;
struct ifnet *ifp;
{
register u_char *cp, *mcp;
register struct mbuf *m;
struct mbuf *top = 0, **mp = &top;
int len, off = off0;
cp = rxbuf + sizeof (struct ether_header);
while (totlen > 0) {
MGET(m, M_DONTWAIT, MT_DATA);
if (m == 0)
goto bad;
if (off) {
len = totlen - off;
cp = rxbuf + sizeof (struct ether_header) + off;
} else
len = totlen;
if (len >= NBPG) {
MCLGET(m);
if (m->m_len == CLBYTES)
m->m_len = len = MIN(len, CLBYTES);
else
m->m_len = len = MIN(MLEN, len);
} else {
m->m_len = len = MIN(MLEN, len);
m->m_off = MMINOFF;
}
mcp = mtod(m, u_char *);
if (ifp) {
/*
* Prepend interface pointer to first mbuf.
*/
*(mtod(m, struct ifnet **)) = ifp;
mcp += sizeof (ifp);
len -= sizeof (ifp);
ifp = (struct ifnet *)0;
}
enpcopy(cp, mcp, (u_int)len);
cp += len;
*mp = m;
mp = &m->m_next;
if (off == 0) {
totlen -= len;
continue;
}
off += len;
if (off == totlen) {
cp = rxbuf + sizeof (struct ether_header);
off = 0;
totlen = off0;
}
}
return (top);
bad:
m_freem(top);
return (0);
}
enpcopy(from, to, cnt)
register u_char *from, *to;
register u_int cnt;
{
register c;
register short *f, *t;
if (((int)from&01) && ((int)to&01)) {
/* source & dest at odd addresses */
*to++ = *from++;
--cnt;
}
if (cnt > 1 && (((int)to&01) == 0) && (((int)from&01) == 0)) {
t = (short *)to;
f = (short *)from;
for (c = cnt>>1; c; --c) /* even address copy */
*t++ = *f++;
cnt &= 1;
if (cnt) { /* odd len */
from = (u_char *)f;
to = (u_char *)t;
*to = *from;
}
}
while ((int)cnt-- > 0) /* one of the address(es) must be odd */
*to++ = *from++;
}
/*
* Process an ioctl request.
*/
enpioctl(ifp, cmd, data)
register struct ifnet *ifp;
int cmd;
caddr_t data;
{
register struct ifaddr *ifa = (struct ifaddr *)data;
struct enpdevice *addr;
int s = splimp(), error = 0;
switch (cmd) {
case SIOCSIFADDR:
ifp->if_flags |= IFF_UP;
switch (ifa->ifa_addr.sa_family) {
#ifdef INET
case AF_INET:
enpinit(ifp->if_unit);
((struct arpcom *)ifp)->ac_ipaddr =
IA_SIN(ifa)->sin_addr;
arpwhohas((struct arpcom *)ifp, &IA_SIN(ifa)->sin_addr);
break;
#endif
#ifdef NS
case AF_NS: {
struct ns_addr *ina = &IA_SNS(ifa)->sns_addr;
struct enp_softc *es = &enp_softc[ifp->if_unit];
if (!ns_nullhost(*ina)) {
ifp->if_flags &= ~IFF_RUNNING;
addr = (struct enpdevice *)
enpinfo[ifp->if_unit]->ui_addr;
enpsetaddr(ifp->if_unit, addr,
ina->x_host.c_host);
} else
ina->x_host = *(union ns_host *)es->es_addr;
enpinit(ifp->if_unit);
break;
}
#endif
default:
enpinit(ifp->if_unit);
break;
}
break;
case SIOCSIFFLAGS:
if ((ifp->if_flags&IFF_UP) == 0 && ifp->if_flags&IFF_RUNNING) {
enpinit(ifp->if_unit); /* reset board */
ifp->if_flags &= ~IFF_RUNNING;
} else if (ifp->if_flags&IFF_UP &&
(ifp->if_flags&IFF_RUNNING) == 0)
enpinit(ifp->if_unit);
break;
default:
error = EINVAL;
}
splx(s);
return (error);
}
enpsetaddr(unit, addr, enaddr)
int unit;
struct enpdevice *addr;
u_char *enaddr;
{
enpcopy(enaddr, addr->enp_addr.e_baseaddr.ea_addr,
sizeof (struct ether_addr));
enpinit(unit);
enpgetaddr(unit, addr);
}
enpgetaddr(unit, addr)
int unit;
struct enpdevice *addr;
{
struct enp_softc *es = &enp_softc[unit];
enpcopy(addr->enp_addr.e_baseaddr.ea_addr, es->es_addr,
sizeof (struct ether_addr));
printf("enp%d: hardware address %s\n",
unit, ether_sprintf(es->es_addr));
}
/*
* Routines to synchronize enp and host.
*/
#ifdef notdef
static
ringinit(rp, size)
register RING *rp;
{
rp->r_rdidx = rp->r_wrtidx = 0;
rp->r_size = size;
}
static
ringfull(rp)
register RING *rp;
{
register short idx;
idx = (rp->r_wrtidx + 1) & (rp->r_size-1);
return (idx == rp->r_rdidx);
}
static
fir(rp)
register RING *rp;
{
return (rp->r_rdidx != rp->r_wrtidx ? rp->r_slot[rp->r_rdidx] : 0);
}
#endif
static
ringempty(rp)
register RING *rp;
{
return (rp->r_rdidx == rp->r_wrtidx);
}
static
ringput(rp, v)
register RING *rp;
BCB *v;
{
register int idx;
idx = (rp->r_wrtidx + 1) & (rp->r_size-1);
if (idx != rp->r_rdidx) {
ENPSETLONG(&rp->r_slot[rp->r_wrtidx], v);
rp->r_wrtidx = idx;
if ((idx -= rp->r_rdidx) < 0)
idx += rp->r_size;
return (idx); /* num ring entries */
}
return (0);
}
static
ringget(rp)
register RING *rp;
{
register int i = 0;
if (rp->r_rdidx != rp->r_wrtidx) {
i = ENPGETLONG(&rp->r_slot[rp->r_rdidx]);
rp->r_rdidx = (++rp->r_rdidx) & (rp->r_size-1);
}
return (i);
}
/*
* ENP Ram device.
*/
enpr_open(dev)
dev_t dev;
{
register int unit = ENPUNIT(dev);
struct vba_device *ui;
struct enpdevice *addr;
if (unit >= NENP || (ui = enpinfo[unit]) == 0 || ui->ui_alive == 0 ||
(addr = (struct enpdevice *)ui->ui_addr) == 0)
return (ENODEV);
if (addr->enp_state != S_ENPRESET)
return (EACCES); /* enp is not in reset state, don't open */
return (0);
}
/*ARGSUSED*/
enpr_close(dev)
dev_t dev;
{
return (0);
}
enpr_read(dev, uio)
dev_t dev;
register struct uio *uio;
{
register struct iovec *iov;
struct enpdevice *addr;
if (uio->uio_offset > RAM_SIZE)
return (ENODEV);
iov = uio->uio_iov;
if (uio->uio_offset + iov->iov_len > RAM_SIZE)
iov->iov_len = RAM_SIZE - uio->uio_offset;
addr = (struct enpdevice *)enpinfo[ENPUNIT(dev)]->ui_addr;
if (useracc(iov->iov_base, (unsigned)iov->iov_len, 0) == 0)
return (EFAULT);
enpcopy((u_char *)&addr->enp_ram[uio->uio_offset],
(u_char *)iov->iov_base, (u_int)iov->iov_len);
uio->uio_resid -= iov->iov_len;
iov->iov_len = 0;
return (0);
}
enpr_write(dev, uio)
dev_t dev;
register struct uio *uio;
{
register struct enpdevice *addr;
register struct iovec *iov;
addr = (struct enpdevice *)enpinfo[ENPUNIT(dev)]->ui_addr;
iov = uio->uio_iov;
if (uio->uio_offset > RAM_SIZE)
return (ENODEV);
if (uio->uio_offset + iov->iov_len > RAM_SIZE)
iov->iov_len = RAM_SIZE - uio->uio_offset;
if (useracc(iov->iov_base, (unsigned)iov->iov_len, 1) == 0)
return (EFAULT);
enpcopy((u_char *)iov->iov_base,
(u_char *)&addr->enp_ram[uio->uio_offset], (u_int)iov->iov_len);
uio->uio_resid -= iov->iov_len;
iov->iov_len = 0;
return (0);
}
/*ARGSUSED*/
enpr_ioctl(dev, cmd, data)
dev_t dev;
caddr_t data;
{
register unit = ENPUNIT(dev);
struct enpdevice *addr;
addr = (struct enpdevice *)enpinfo[unit]->ui_addr;
switch(cmd) {
case ENPIOGO:
ENPSETLONG(&addr->enp_base, addr);
addr->enp_intrvec = enp_softc[unit].es_ivec;
ENP_GO(addr, ENPSTART);
DELAY(200000);
enpinit(unit);
/*
* Fetch Ethernet address after link level
* is booted (firmware copies manufacturer's
* address from on-board ROM).
*/
enpgetaddr(unit, addr);
addr->enp_state = S_ENPRUN;
break;
case ENPIORESET:
RESET_ENP(addr);
addr->enp_state = S_ENPRESET;
DELAY(100000);
break;
default:
return (EINVAL);
}
return (0);
}
#endif