Source to i386/isa/if_apx.c


Enter a symbol's name here to quickly find it.

/*
 * Copyright (c) 1982, 1990, 1993
 *	The Regents of the University of California.  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 the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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.
 *
 *	@(#)if_apx.c	8.1 (Berkeley) 6/11/93
 */

/*
 * Driver for SGS-THOMSON MK5025 based Link level controller.
 * The chip will do LAPB in hardware, although this driver only
 * attempts to use it for HDLC framing.
 *
 * Driver written by Keith Sklower, based on lance AMD7990 
 * driver by Van Jacobsen, and information graciously supplied
 * by the ADAX corporation of Berkeley, CA.
 */

#include "apx.h"
#if NAPX > 0

#include <sys/param.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/errno.h>
#include <sys/syslog.h>

#include <net/if.h>
#include <net/netisr.h>
#include <net/if_types.h>
#ifdef CCITT
#include <netccitt/x25.h>
int x25_rtrequest(), x25_ifoutput();
#endif

#include <i386/isa/if_apxreg.h>

int	apxprobe(), apxattach(), apxstart(), apx_uprim(), apx_meminit();
int	apxinit(), apxoutput(), apxioctl(), apxreset(), apxdebug = 0;
void	apx_ifattach(), apxtest(), apxinput(), apxintr(), apxtint(), apxrint();

struct apx_softc {
	struct	ifnet apx_if;
	caddr_t	apx_device;		/* e.g. isa_device, vme_device, etc. */
	struct	apc_reg *apx_reg;	/* control regs for both subunits */
	struct	apc_mem *apx_hmem;	/* Host addr for shared memory */
	struct	apc_mem *apx_dmem;	/* Device (chip) addr for shared mem */
	struct	sgcp *apx_sgcp;		/* IO control port for this subunit */
	int	apx_flags;		/* Flags specific to this driver */
#define APXF_CHIPHERE	0x01		/* mk5025 present */
	int	apx_rxnum;		/* Last receiver dx we looked at */
	int	apx_txnum;		/* Last tranmistter dx we stomped on */
	int	apx_txcnt;		/* Number of packets queued for tx*/
	u_int	apx_msize;
	struct	sgae apx_csr23;		/* 24 bit init addr, as seen by chip */
	u_short	apx_csr4;		/* byte gender, set in mach dep code */
	struct	apc_modes apx_modes;	/* Parameters, as amended by ioctls */
} apx_softc[2 * NAPX];

struct apxstat {
	int	rxnull;			/* no rx bufs ready this interrupt */
	int	rxnrdy;			/* expected rx buf not ready */
	int	rx2big;			/* expected rx buf not ready */
	int	txnull;
	int	pint;			/* new primitive available interrupt */
	int	rint;			/* receive interrupts */
	int	tint;			/* transmit interrupts */
	int	anyint;			/* note all interrupts */
	int	queued;			/* got through apxinput */
	int	nxpctd;			/* received while if was down */
	int	rstfld;			/* reset didn't work */
} apxstat;

/* default operating paramters for devices */
struct	apc_modes apx_default_modes = {
 { 1,		/* apm_sgob.lsaddr; */
   3,		/* apm_sgob.rsaddr; */
   -SGMTU,	/* apm_sgob.n1; */
   ((-10)<<8),	/* apm_sgob.n2_scale; */
   -1250,	/* apm_sgob.t1; */
   -10000,	/* apm_sgob.t3; */
   -80,		/* apm_sgob.tp; */
 },
 2,		/* apm_txwin; */
 1,		/* apm_apxmode: RS_232 connector and modem clock; */
 0,		/* apm_apxaltmode: enable dtr, disable X.21 connector; */
 IFT_X25,	/* apm_iftype; */
};

/* Begin bus & endian dependence */

#include <i386/isa/isa_device.h>

struct	isa_driver apxdriver = {
	apxprobe, apxattach, "apx",
};

#define SG_RCSR(apx, csrnum) \
	 (outw(&(apx->apx_sgcp->sgcp_rap), csrnum << 1), \
	  inw(&(apx->apx_sgcp->sgcp_rdp)))

#define SG_WCSR(apx, csrnum, data) \
	 (outw(&(apx->apx_sgcp->sgcp_rap), csrnum << 1), \
	  outw(&(apx->apx_sgcp->sgcp_rdp), data))

#define APX_RCSR(apx, csrname) inb(&(apx->apx_reg->csrname))
#define APX_WCSR(apx, csrname, data) outb(&(apx->apx_reg->csrname), data)

#define TIMO 10000 /* used in apx_uprim */

apxprobe(id)
	register struct	isa_device *id;
{
	int	moffset = 0, nchips = 2, unit = id->id_unit << 1, subunit;
	struct	apc_reg *reg = (struct apc_reg *)id->id_iobase;
	register struct	apx_softc *apx = apx_softc + unit;

	/*
	 * Probing for the second MK5025 on all ISA/EISA adax boards
	 * manufactured prior to July 1992 (and some time following)
	 * will hang the bus and the system.  Thus, it is essential
	 * not to probe for the second mk5025 if it is known not to be there.
	 * As the current config scheme for 386BSD does not have a flags
	 * field, we adopt the convention of using the low order bit of
	 * the memsize to warn us that we have a single chip board.
	 */
	if (id->id_msize & 1)
		nchips = 1;
	for (subunit = 0; subunit < nchips; subunit++) {
		apx->apx_msize	= id->id_msize >> 1;
		apx->apx_hmem	= (struct apc_mem *) (id->id_maddr + moffset);
		apx->apx_dmem	= (struct apc_mem *) moffset;
		apx->apx_device	= (caddr_t) id;
		apx->apx_reg	= reg;
		apx->apx_sgcp	= reg->axr_sgcp + subunit;
		apx->apx_csr4	= 0x0210;	/* no byte swapping for PC-AT */
		apx->apx_modes	= apx_default_modes;
		apx->apx_if.if_unit = unit++;
		moffset = apx->apx_msize;
		apxtest(apx++);
	}
	return 1;
}

apxattach(id)
	struct	isa_device *id;
{
	register struct	apx_softc *apx = apx_softc + (id->id_unit << 1);

	apx_ifattach(&((apx++)->apx_if));
	apx_ifattach(&(apx->apx_if));
	return 0;
}
/* End bus & endian dependence */

/*
 * Interface exists: make available by filling in network interface
 * record.  System will initialize the interface when it is ready
 * to accept packets.
 */
void
apx_ifattach(ifp)
	register struct ifnet *ifp;
{
	/*
	 * Initialize ifnet structure
	 */
	ifp->if_name	= "apc";
	ifp->if_mtu	= SGMTU;
	ifp->if_init	= apxinit;
	ifp->if_output	= apxoutput;
	ifp->if_start	= apxstart;
	ifp->if_ioctl	= apxioctl;
	ifp->if_reset	= apxreset;
	ifp->if_type	= apx_default_modes.apm_iftype;
	ifp->if_hdrlen	= 5;
	ifp->if_addrlen	= 8;
	if_attach(ifp);
}
/*
 * Initialization of interface
 */
apxinit(unit)
	int unit;
{
	struct ifnet *ifp = &apx_softc[unit].apx_if;
	int s = splimp();

	ifp->if_flags &= ~(IFF_RUNNING|IFF_OACTIVE);
	if (apxreset(unit) && (ifp->if_flags & IFF_UP)) {
		ifp->if_flags |= IFF_RUNNING;
		(void)apxstart(ifp);
	}
	splx(s);
	return 0;
}

apxctr(apx)
	register struct apx_softc *apx;
{
	APX_WCSR(apx, axr_ccr, 0xB0); /* select ctr 2, write lsb+msb, mode 0 */
	APX_WCSR(apx, axr_cnt2, 0x1);
	APX_WCSR(apx, axr_cnt2, 0x0);
	DELAY(50);
	APX_WCSR(apx, axr_ccr, 0xE8); /* latch status, ctr 2; */
	return (APX_RCSR(apx, axr_cnt2));
}

void
apxtest(apx)
	register struct apx_softc *apx;
{
	int i =  0;

	if ((apx->apx_if.if_unit & 1) == 0 && (i = apxctr(apx)) == 0)
		apxerror(apx, "no response from timer chip", 0);
	if (SG_RCSR(apx, 1) & 0x8000)
		SG_WCSR(apx, 1, 0x8040);
	SG_WCSR(apx, 4, apx->apx_csr4);
	SG_WCSR(apx, 5, 0x08);		/* Set DTR mode in SGS thompson chip */
	if (((i = SG_RCSR(apx, 5)) & 0xff08) != 0x08)
		apxerror(apx, "no mk5025, csr5 high bits are", i);
	else
		apx->apx_flags |= APXF_CHIPHERE;
	(void) apx_uprim(apx, SG_STOP, "stop after probing");
}

apxreset(unit)
	int	unit;
{
	register struct apx_softc *apx = &apx_softc[unit ^ 1];
	u_char apm_apxmode = 0, apm_apxaltmode = 0;
#define MODE(m) (m |= apx->apx_modes.m << ((apx->apx_if.if_unit & 1) ? 1 : 0))

	MODE(apm_apxmode);
	MODE(apm_apxaltmode);
	apx = apx_softc + unit;
	MODE(apm_apxmode);
	MODE(apm_apxaltmode);
	APX_WCSR(apx, axr_mode, apm_apxmode);
	APX_WCSR(apx, axr_altmode, apm_apxaltmode);
	(void) apxctr(apx);
	(void) apx_uprim(apx, SG_STOP, "stop to reset");
	if ((apx->apx_if.if_flags & IFF_UP) == 0)
		return 0;
	apx_meminit(apx->apx_hmem, apx);
	SG_WCSR(apx, 4, apx->apx_csr4);
	SG_WCSR(apx, 2, apx->apx_csr23.f_hi);
	SG_WCSR(apx, 3, apx->apx_csr23.lo);
	if (apx_uprim(apx, SG_INIT, "init request") ||
	    apx_uprim(apx, SG_STAT, "status request") ||
	    apx_uprim(apx, SG_TRANS, "transparent mode"))
		return 0;
	SG_WCSR(apx, 0, SG_INEA);
	return 1;
}

apx_uprim(apx, request, ident)
	register struct apx_softc *apx;
	char *ident;
{
	register int timo = 0;
	int reply;

	if ((apx->apx_flags & APXF_CHIPHERE) == 0)
		return 1;	/* maybe even should panic . . . */
	if ((reply = SG_RCSR(apx, 1)) & 0x8040)
		SG_WCSR(apx, 1, 0x8040); /* Magic! */
	if (request == SG_STOP && (SG_RCSR(apx, 0) & SG_STOPPED))
		return 0;
	SG_WCSR(apx, 1, request | SG_UAV);
	do {
		reply = SG_RCSR(apx, 1);
		if (timo++ >= TIMO || (reply & 0x8000)) {
				apxerror(apx, ident, reply);
				return 1;
		}
	} while (reply & SG_UAV);
	return 0;
}

apx_meminit(apc, apx)
	register struct apc_mem *apc;
	struct apx_softc *apx;
{
	register struct apc_mem *apcbase = apx->apx_dmem;
	register int i;
#define LOWADDR(e) (((u_long)&(apcbase->e)) & 0xffff)
#define HIADDR(e) ((((u_long)&(apcbase->e)) >> 16) & 0xff)
#define SET_SGAE(d, f, a) {(d).lo = LOWADDR(a); (d).f_hi = (f) | HIADDR(a);}
#define SET_SGDX(d, f, a, b) \
	{SET_SGAE((d).sgdx_ae, f, a); (d).sgdx_mcnt = (d).sgdx_bcnt = (b);}

	apx->apx_txnum = apx->apx_rxnum = apx->apx_txcnt = 0;
	bzero((caddr_t)apc, ((caddr_t)(&apc->apc_rxmd[0])) - (caddr_t)apc);
	apc->apc_mode = 0x0108;	/* 2 flag spacing, leave addr & ctl, do CRC16 */
	apc->apc_sgop = apx->apx_modes.apm_sgop;
	SET_SGAE(apx->apx_csr23, SG_UIE | SG_PROM, apc_mode);
	SET_SGAE(apc->apc_rxdd, SG_RLEN, apc_rxmd[0]);
	i = SG_TLEN | ((apx->apx_modes.apm_txwin)<< 8);
	SET_SGAE(apc->apc_txdd, i, apc_txmd[0]);
	SET_SGAE(apc->apc_stdd, 0, apc_sgsb);
	SET_SGDX(apc->apc_rxtid, SG_OWN, apc_rxidbuf[0], -SGMTU);
	SET_SGDX(apc->apc_txtid, 0, apc_txidbuf[0], 0);
	for (i = 0; i < SGRBUF; i++)
		 SET_SGDX(apc->apc_rxmd[i], SG_OWN, apc_rbuf[i][0], -SGMTU)
	for (i = 0; i < SGTBUF; i++)
		 SET_SGDX(apc->apc_txmd[i], 0, apc_tbuf[i][0], 0)
}

/*
 * Start output on interface.  Get another datagram to send
 * off of the interface queue, and copy it to the interface
 * before starting the output.
 */
apxstart(ifp)
	struct ifnet *ifp;
{
	register struct apx_softc *apx = &apx_softc[ifp->if_unit];
	register struct sgdx *dx;
	struct apc_mem *apc = apx->apx_hmem;
	struct mbuf *m;
	int len;

	if ((ifp->if_flags & IFF_RUNNING) == 0)
		return (0);
	do {
		dx = apc->apc_txmd + apx->apx_txnum;
		if (dx->sgdx_flags & SG_OWN)
			return (0);
		IF_DEQUEUE(&ifp->if_snd, m);
		if (m == 0)
			return (0);
		len = min(m->m_pkthdr.len, SGMTU);
		m_copydata(m, 0, len, apc->apc_tbuf[apx->apx_txnum]);
		m_freem(m);
		dx->sgdx_mcnt = -len;
		dx->sgdx_flags = (SG_OWN|SG_TUI|SG_SLF|SG_ELF) |
			(0xff & dx->sgdx_flags);
		SG_WCSR(apx, 0, SG_INEA | SG_TDMD);
		DELAY(20);
		if (++apx->apx_txnum >= SGTBUF)
			apx->apx_txnum = 0;
	} while (++apx->apx_txcnt < SGTBUF);
	apx->apx_txcnt = SGTBUF; /* in case txcnt > SGTBUF by mistake */
	ifp->if_flags |= IFF_OACTIVE;
	return (0);
}

void
apxintr()
{
	register struct apx_softc *apx;
	int reply;

	apxstat.anyint++;
	for (apx = apx_softc + NAPX + NAPX; --apx >= apx_softc;) {
		if (apx->apx_flags & APXF_CHIPHERE)
		    /* Try to turn off interrupt cause */
		    while ((reply = SG_RCSR(apx, 0)) & 0xff) {
			SG_WCSR(apx, 0, SG_INEA | 0xfe);
			if (reply & (SG_MERR|SG_TUR|SG_ROR)) {
				apxerror(apx, "mem, rx, or tx error", reply);
				apxinit(apx->apx_if.if_unit);
				break;
			}
			if (reply & SG_RINT)
				apxrint(apx);
			if (reply & SG_TINT)
				apxtint(apx);
			if (reply & SG_PINT)
				apxstat.pint++;
		}
	}
}

void
apxtint(apx)
	register struct apx_softc *apx;
{
	register struct apc_mem *apc = apx->apx_hmem;
	int i, loopcount = 0;

	apxstat.tint++;
	do {
		if ((i = apx->apx_txnum - apx->apx_txcnt) < 0)
			i += SGTBUF;
		if (apc->apc_txmd[i].sgdx_flags & SG_OWN) {
			if (loopcount)
				break;
			apxstat.txnull++;
			return;
		}
		loopcount++;
		apx->apx_if.if_flags &= ~IFF_OACTIVE;
	} while (--apx->apx_txcnt > 0);
	apxstart(&apx->apx_if);
}

void
apxrint(apx)
	register struct apx_softc *apx;
{
	register struct apc_mem *apc = apx->apx_hmem;
	register struct sgdx *dx = apc->apc_rxmd + apx->apx_rxnum;
	int i = 0;
#define SGNEXTRXMD \
dx = ++apx->apx_rxnum == SGRBUF ? &apc->apc_rxmd[apx->apx_rxnum = 0] : dx + 1;

	apxstat.rint++;
	/*
	 * Out of sync with hardware, should never happen?
	 */
	while (dx->sgdx_flags & SG_OWN) {
		apxstat.rxnrdy++;
		if (++i == SGRBUF) {
			apxstat.rxnull++;
			return;
		}
		SGNEXTRXMD;
	}
	/*
	 * Process all buffers with valid data
	 */
	while ((dx->sgdx_flags & SG_OWN) == 0) {
		if ((dx->sgdx_flags & (SG_SLF|SG_ELF)) != (SG_SLF|SG_ELF)) {
			/*
			 * Find the end of the packet so we synch up.
			 * We throw the data away.
			 */
			apxerror(apx, "chained buffer", dx->sgdx_flags);
			do {
				apxstat.rx2big++;
				dx->sgdx_bcnt = 0;
				dx->sgdx_flags = SG_OWN | (0xff&dx->sgdx_flags);
				SGNEXTRXMD;
			} while (!(dx->sgdx_flags & (SG_OWN|SG_SLF|SG_ELF)));
			/*
			 * If search terminated without successful completion
			 * we reset the hardware (conservative).
			 */
			if ((dx->sgdx_flags & (SG_OWN|SG_SLF|SG_ELF)) !=
			    SG_ELF) {
				apxreset(apx->apx_if.if_unit);
				return;
			}
		} else
			apxinput(&apx->apx_if, apc->apc_rbuf[apx->apx_rxnum],
					-dx->sgdx_mcnt);
		dx->sgdx_bcnt = 0;
		dx->sgdx_flags = SG_OWN | (0xff & dx->sgdx_flags);
		SGNEXTRXMD;
	}
}

void
apxinput(ifp, buffer, len)
	register struct ifnet *ifp;
	caddr_t buffer;
{
	extern struct ifqueue hdintrq, ipintrq;
	register struct ifqueue *inq;
	register u_char *cp = (u_char *)buffer;
	struct mbuf *m, *m_devget();
	int isr;

	ifp->if_ipackets++;
	if ((ifp->if_flags & IFF_UP) == 0) {
		apxstat.nxpctd++;
		return;
	}
	if (cp[0] == 0xff && cp[1] == 0x3) {
		/* This is a UI HDLC Packet, so we'll assume PPP
		   protocol.  for now, IP only. */
		buffer += 4;
		len -= 4;
		inq = &ipintrq;
		isr = NETISR_IP;
	} else {
#ifdef CCITT
		inq = &hdintrq;
		isr = NETISR_CCITT;
	}
	if (len <= 0) {
#endif
		return;
	}
	m = m_devget(buffer, len, 0, ifp, (void (*)())0);
	if (m == 0)
		return;
	if(IF_QFULL(inq)) {
		IF_DROP(inq);
		m_freem(m);
	} else {
		apxstat.queued++;
		IF_ENQUEUE(inq, m);
		schednetisr(isr);
	}
}

/*
 * Process an ioctl request.
 */
apxioctl(ifp, cmd, data)
	register struct ifnet *ifp;
	int cmd;
	caddr_t data;
{
	register struct ifaddr *ifa = (struct ifaddr *)data;
	int s = splimp(), error = 0;
	struct apx_softc *apx = &apx_softc[ifp->if_unit];

	switch (cmd) {

	case SIOCSIFADDR:
#ifdef CCITT
		ifa->ifa_rtrequest = x25_rtrequest;
		break;

	case SIOCSIFCONF_X25:
		ifp->if_output = x25_ifoutput;
		ifp->if_flags |= IFF_UP;
		error = hd_ctlinput(PRC_IFUP, ifa->ifa_addr);
		if (error == 0)
			apxinit(ifp->if_unit);
#endif
		break;

	case SIOCSIFFLAGS:
		if (((ifp->if_flags & IFF_UP) == 0 &&
		     (ifp->if_flags & IFF_RUNNING)) ||
		    (ifp->if_flags & IFF_UP) &&
		     (ifp->if_flags & IFF_RUNNING) == 0)
			apxinit(ifp->if_unit);
		break;

	case SIOCSIFMODE:
		if ((ifp->if_flags & IFF_UP) == 0)
			apx->apx_modes = *(struct apc_modes *)data;
		else
	default:
			error = EINVAL;

	}
	splx(s);
	return (error);
}

apxerror(apx, msg, data)
	register struct	apx_softc *apx;	
	char	*msg;
{
	log(LOG_WARNING, "apc%d: %s, stat=0x%x\n",
		apx->apx_if.if_unit, msg, data);
}

/*
 * For debugging loopback activity.
 */
apxoutput(ifp, m, dst, rt)
register struct ifnet *ifp;
register struct mbuf *m;
struct sockaddr *dst;
struct rtentry *rt;
{
	int s = splimp(), error = 0;
	static char pppheader[4] = { -1, 3, 0, 0x21 };
	/*
	 * Queue message on interface, and start output if interface
	 * not yet active.
	 */
	ifp->if_opackets++;
	M_PREPEND(m, sizeof pppheader, M_DONTWAIT);
	if (m == 0) {
		splx(s);
		return ENOBUFS;
	}
	bcopy(pppheader, mtod(m, caddr_t), sizeof pppheader);
	if (IF_QFULL(&ifp->if_snd)) {
		IF_DROP(&ifp->if_snd);
		m_freem(m);
		error = ENOBUFS;
	} else {
		IF_ENQUEUE(&ifp->if_snd, m);
		if ((ifp->if_flags & IFF_OACTIVE) == 0)
			(*ifp->if_start)(ifp);
	}
	splx(s);
	return (error);
}
#endif /* NAPX */