[BACK]Return to qec.c CVS log [TXT][DIR] Up to [local] / sys / arch / sparc / dev

File: [local] / sys / arch / sparc / dev / qec.c (download)

Revision 1.1, Tue Mar 4 16:08:05 2008 UTC (16 years, 2 months ago) by nbrk
Branch point for: MAIN

Initial revision

/*	$OpenBSD: qec.c,v 1.17 2006/06/02 20:00:54 miod Exp $	*/

/*
 * Copyright (c) 1998 Theo de Raadt and Jason L. Wright.
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 AUTHORS 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.
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/buf.h>
#include <sys/proc.h>
#include <sys/user.h>
#include <sys/mbuf.h>
#include <sys/socket.h>

#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/netisr.h>
#include <net/if_media.h>

#ifdef INET
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/in_var.h>
#include <netinet/ip.h>
#include <netinet/if_ether.h>
#endif

#include <sparc/autoconf.h>
#include <sparc/cpu.h>

#include <sparc/dev/sbusvar.h>
#include <sparc/dev/dmareg.h>
#include <sparc/dev/qecreg.h>
#include <sparc/dev/qecvar.h>

int	qecprint(void *, const char *);
int	qecmatch(struct device *, void *, void *);
void	qecattach(struct device *, struct device *, void *);
void	qec_fix_range(struct qec_softc *, struct sbus_softc *);
void	qec_translate(struct qec_softc *, struct confargs *);

struct cfattach qec_ca = {
	sizeof(struct qec_softc), qecmatch, qecattach
};

struct cfdriver qec_cd = {
	NULL, "qec", DV_DULL
};

int
qecprint(aux, name)
	void *aux;
	const char *name;
{
	register struct confargs *ca = aux;

	if (name)
		printf("%s at %s", ca->ca_ra.ra_name, name);
	printf(" offset 0x%x", ca->ca_offset);
	return (UNCONF);
}

/*
 * match a QEC device in a slot capable of DMA
 */
int
qecmatch(parent, vcf, aux)
	struct device *parent;
	void *vcf, *aux;
{
	struct cfdata *cf = vcf;
	struct confargs *ca = aux;
	struct romaux *ra = &ca->ca_ra;

	if (strcmp(cf->cf_driver->cd_name, ra->ra_name))
		return (0);

	if (!sbus_testdma((struct sbus_softc *)parent, ca))
		return (0);

	return (1);
}

/*
 * Attach all the sub-devices we can find
 */
void
qecattach(parent, self, aux)
	struct device *parent, *self;
	void *aux;
{
	register struct confargs *ca = aux;
	struct qec_softc *sc = (void *)self;
	int node;
	struct confargs oca;
	char *name;
	int sbusburst;

	/*
	 * The first i/o space is the qec global registers, and
	 * the second is a buffer used by the qec channels internally.
	 * (It's not necessary to map the second i/o space, but knowing
	 * its size is necessary).
	 */
	sc->sc_regs = mapiodev(&ca->ca_ra.ra_reg[0], 0,
	    sizeof(struct qecregs));
	sc->sc_bufsiz = ca->ca_ra.ra_reg[1].rr_len;
	sc->sc_paddr = ca->ca_ra.ra_reg[0].rr_paddr;

	/*
	 * On qec+qe, the qec has the interrupt priority, but we
	 * need to pass that down so that the qe's can handle them.
	 */
	if (ca->ca_ra.ra_nintr == 1)
		sc->sc_pri = ca->ca_ra.ra_intr[0].int_pri;

	node = sc->sc_node = ca->ca_ra.ra_node;

	qec_fix_range(sc, (struct sbus_softc *)parent);

	/*
	 * Get transfer burst size from PROM
	 */
	sbusburst = ((struct sbus_softc *)parent)->sc_burst;
	if (sbusburst == 0)
		sbusburst = SBUS_BURST_32 - 1; /* 1->16 */

	sc->sc_nchannels = getpropint(ca->ca_ra.ra_node, "#channels", -1);
	if (sc->sc_nchannels == -1) {
		printf(": no channels\n");
		return;
	}
	else if (sc->sc_nchannels < 1 || sc->sc_nchannels > 4) {
		printf(": invalid number of channels: %d\n", sc->sc_nchannels);
		return;
	}

	sc->sc_burst = getpropint(ca->ca_ra.ra_node, "burst-sizes", -1);
	if (sc->sc_burst == -1)
		/* take SBus burst sizes */
		sc->sc_burst = sbusburst;

	/* Clamp at parent's burst sizes */
	sc->sc_burst &= sbusburst;

	printf(": %dK memory %d channel%s",
	    sc->sc_bufsiz / 1024, sc->sc_nchannels,
	    (sc->sc_nchannels == 1) ? "" : "s");

	node = sc->sc_node = ca->ca_ra.ra_node;

	/* Propagate bootpath */
	if (ca->ca_ra.ra_bp != NULL)
		oca.ca_ra.ra_bp = ca->ca_ra.ra_bp + 1;
	else
		oca.ca_ra.ra_bp = NULL;

	printf("\n");

	qec_reset(sc);

	/* search through children */
	for (node = firstchild(node); node; node = nextsibling(node)) {
		name = getpropstring(node, "name");
		if (!romprop(&oca.ca_ra, name, node))
			continue;

		qec_translate(sc, &oca);
		oca.ca_bustype = BUS_SBUS;
		(void) config_found(&sc->sc_dev, (void *)&oca, qecprint);
	}
}

void
qec_fix_range(sc, sbp)
	struct qec_softc *sc;
	struct sbus_softc *sbp;
{
	int rlen, i, j;

	rlen = getproplen(sc->sc_node, "ranges");
	sc->sc_range =
		(struct rom_range *)malloc(rlen, M_DEVBUF, M_NOWAIT);
	if (sc->sc_range == NULL) {
		printf("%s: PROM ranges too large: %d\n",
				sc->sc_dev.dv_xname, rlen);
		return;
	}
	sc->sc_nrange = rlen / sizeof(struct rom_range);
	(void)getprop(sc->sc_node, "ranges", sc->sc_range, rlen);

	for (i = 0; i < sc->sc_nrange; i++) {
		for (j = 0; j < sbp->sc_nrange; j++) {
			if (sc->sc_range[i].pspace == sbp->sc_range[j].cspace) {
				sc->sc_range[i].poffset +=
				    sbp->sc_range[j].poffset;
				sc->sc_range[i].pspace =
				    sbp->sc_range[j].pspace;
				break;
			}
		}
	}
}

/*
 * Translate the register addresses of our children 
 */
void
qec_translate(sc, ca)
	struct qec_softc *sc;
	struct confargs *ca;
{
	register int i;

	ca->ca_slot = ca->ca_ra.ra_iospace;
	ca->ca_offset = sc->sc_range[ca->ca_slot].poffset - (long)sc->sc_paddr;

	/* Translate into parent address spaces */
	for (i = 0; i < ca->ca_ra.ra_nreg; i++) {
		int j, cspace = ca->ca_ra.ra_reg[i].rr_iospace;

		for (j = 0; j < sc->sc_nrange; j++) {
			if (sc->sc_range[j].cspace == cspace) {
				(int)ca->ca_ra.ra_reg[i].rr_paddr +=
					sc->sc_range[j].poffset;
				(int)ca->ca_ra.ra_reg[i].rr_iospace =
					sc->sc_range[j].pspace;
				break;
			}
		}
	}
}

/*
 * Reset the QEC and initialize its global registers.
 */
void
qec_reset(sc)
	struct qec_softc *sc;
{
	struct qecregs *qr = sc->sc_regs;
	int i = 200;

	qr->ctrl = QEC_CTRL_RESET;
	while (--i) {
		if ((qr->ctrl & QEC_CTRL_RESET) == 0)
			break;
		DELAY(20);
	}
	if (i == 0) {
		printf("%s: reset failed.\n", sc->sc_dev.dv_xname);
		return;
	}

	qr->msize = sc->sc_bufsiz / sc->sc_nchannels;
	sc->sc_msize = qr->msize;

	qr->rsize = sc->sc_bufsiz / (sc->sc_nchannels * 2);
	sc->sc_rsize = qr->rsize;

	qr->tsize = sc->sc_bufsiz / (sc->sc_nchannels * 2);

	qr->psize = QEC_PSIZE_2048;

        if (sc->sc_burst & SBUS_BURST_64)
		i = QEC_CTRL_B64;
	else if (sc->sc_burst & SBUS_BURST_32)
		i = QEC_CTRL_B32;
	else
		i = QEC_CTRL_B16;

	qr->ctrl = (qr->ctrl & QEC_CTRL_MODEMASK) | i;
}

/*
 * Routine to copy from mbuf chain to transmit buffer in
 * network buffer memory.
 */
int
qec_put(buf, m0)
	u_int8_t *buf;
	struct mbuf *m0;
{
	struct mbuf *m;
	int len, tlen = 0;

	for (m = m0; m != NULL; m = m->m_next) {
		len = m->m_len;
		bcopy(mtod(m, caddr_t), buf, len);
		buf += len;
		tlen += len;
	}
	m_freem(m0);
	return (tlen);
}

/*
 * Pull data off an interface.
 * Len is the length of data, with local net header stripped.
 * We copy the data into mbufs.  When full cluster sized units are present,
 * we copy into clusters.
 */
struct mbuf *
qec_get(ifp, buf, totlen)
	struct ifnet *ifp;
	u_int8_t *buf;
	int totlen;
{
	struct mbuf *m, *top, **mp;
	int len, pad;

	MGETHDR(m, M_DONTWAIT, MT_DATA);
	if (m == NULL)
		return (NULL);
	m->m_pkthdr.rcvif = ifp;
	m->m_pkthdr.len = totlen;
	pad = ALIGN(sizeof(struct ether_header)) - sizeof(struct ether_header);
	len = MHLEN;
	if (totlen >= MINCLSIZE) {
		MCLGET(m, M_DONTWAIT);
		if (m->m_flags & M_EXT)
			len = MCLBYTES;
	}
	m->m_data += pad;
	len -= pad;
	top = NULL;
	mp = &top;

	while (totlen > 0) {
		if (top) {
			MGET(m, M_DONTWAIT, MT_DATA);
			if (m == NULL) {
				m_freem(top);
				return NULL;
			}
			len = MLEN;
		}
		if (top && totlen >= MINCLSIZE) {
			MCLGET(m, M_DONTWAIT);
			if (m->m_flags & M_EXT)
				len = MCLBYTES;
		}
		m->m_len = len = min(totlen, len);
		bcopy(buf, mtod(m, caddr_t), len);
		buf += len;
		totlen -= len;
		*mp = m;
		mp = &m->m_next;
	}

	return (top);
}