ed6a66ca6c
the knowledge of mbuf layout, and in particular constants such as M_EXT, MLEN, MHLEN, and so on, in mbuf consumers by unifying various alignment utility functions (M_ALIGN(), MH_ALIGN(), MEXT_ALIGN() in a single M_ALIGN() macro, implemented by a now-inlined m_align() function: - Move m_align() from uipc_mbuf.c to mbuf.h; mark as __inline. - Reimplement M_ALIGN(), MH_ALIGN(), and MEXT_ALIGN() using m_align(). - Update consumers around the tree to simply use M_ALIGN(). This change eliminates a number of cases where mbuf consumers must be aware of whether or not mbufs returned by the allocator use external storage, but also assumptions about the size of the returned mbuf. This will make it easier to introduce changes in how we use external storage, as well as features such as variable-size mbufs. Differential Revision: https://reviews.freebsd.org/D1436 Reviewed by: glebius, trasz, gnn, bz Sponsored by: EMC / Isilon Storage Division
3092 lines
72 KiB
C
3092 lines
72 KiB
C
/*-
|
|
* Copyright (c) 2001-2003
|
|
* Fraunhofer Institute for Open Communication Systems (FhG Fokus).
|
|
* 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 AUTHOR 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 AUTHOR 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.
|
|
*
|
|
* Author: Hartmut Brandt <harti@freebsd.org>
|
|
*
|
|
* Fore PCA200E driver for NATM
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include "opt_inet.h"
|
|
#include "opt_natm.h"
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/module.h>
|
|
#include <sys/queue.h>
|
|
#include <sys/syslog.h>
|
|
#include <sys/endian.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/condvar.h>
|
|
#include <vm/uma.h>
|
|
|
|
#include <sys/sockio.h>
|
|
#include <sys/mbuf.h>
|
|
#include <sys/socket.h>
|
|
|
|
#include <net/if.h>
|
|
#include <net/if_var.h>
|
|
#include <net/if_media.h>
|
|
#include <net/if_types.h>
|
|
#include <net/if_atm.h>
|
|
#include <net/route.h>
|
|
#ifdef ENABLE_BPF
|
|
#include <net/bpf.h>
|
|
#endif
|
|
#ifdef INET
|
|
#include <netinet/in.h>
|
|
#include <netinet/if_atm.h>
|
|
#endif
|
|
|
|
#include <machine/bus.h>
|
|
#include <machine/resource.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/rman.h>
|
|
#include <dev/pci/pcireg.h>
|
|
#include <dev/pci/pcivar.h>
|
|
|
|
#include <dev/utopia/utopia.h>
|
|
|
|
#include <dev/fatm/if_fatmreg.h>
|
|
#include <dev/fatm/if_fatmvar.h>
|
|
|
|
#include <dev/fatm/firmware.h>
|
|
|
|
devclass_t fatm_devclass;
|
|
|
|
static const struct {
|
|
uint16_t vid;
|
|
uint16_t did;
|
|
const char *name;
|
|
} fatm_devs[] = {
|
|
{ 0x1127, 0x300,
|
|
"FORE PCA200E" },
|
|
{ 0, 0, NULL }
|
|
};
|
|
|
|
static const struct rate {
|
|
uint32_t ratio;
|
|
uint32_t cell_rate;
|
|
} rate_table[] = {
|
|
#include <dev/fatm/if_fatm_rate.h>
|
|
};
|
|
#define RATE_TABLE_SIZE (sizeof(rate_table) / sizeof(rate_table[0]))
|
|
|
|
SYSCTL_DECL(_hw_atm);
|
|
|
|
MODULE_DEPEND(fatm, utopia, 1, 1, 1);
|
|
|
|
static int fatm_utopia_readregs(struct ifatm *, u_int, uint8_t *, u_int *);
|
|
static int fatm_utopia_writereg(struct ifatm *, u_int, u_int, u_int);
|
|
|
|
static const struct utopia_methods fatm_utopia_methods = {
|
|
fatm_utopia_readregs,
|
|
fatm_utopia_writereg
|
|
};
|
|
|
|
#define VC_OK(SC, VPI, VCI) \
|
|
(((VPI) & ~((1 << IFP2IFATM((SC)->ifp)->mib.vpi_bits) - 1)) == 0 && \
|
|
(VCI) != 0 && ((VCI) & ~((1 << IFP2IFATM((SC)->ifp)->mib.vci_bits) - 1)) == 0)
|
|
|
|
static int fatm_load_vc(struct fatm_softc *sc, struct card_vcc *vc);
|
|
|
|
/*
|
|
* Probing is easy: step trough the list of known vendor and device
|
|
* ids and compare. If one is found - it's our.
|
|
*/
|
|
static int
|
|
fatm_probe(device_t dev)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; fatm_devs[i].name; i++)
|
|
if (pci_get_vendor(dev) == fatm_devs[i].vid &&
|
|
pci_get_device(dev) == fatm_devs[i].did) {
|
|
device_set_desc(dev, fatm_devs[i].name);
|
|
return (BUS_PROBE_DEFAULT);
|
|
}
|
|
return (ENXIO);
|
|
}
|
|
|
|
/*
|
|
* Function called at completion of a SUNI writeregs/readregs command.
|
|
* This is called from the interrupt handler while holding the softc lock.
|
|
* We use the queue entry as the randevouze point.
|
|
*/
|
|
static void
|
|
fatm_utopia_writeregs_complete(struct fatm_softc *sc, struct cmdqueue *q)
|
|
{
|
|
|
|
H_SYNCSTAT_POSTREAD(sc, q->q.statp);
|
|
if(H_GETSTAT(q->q.statp) & FATM_STAT_ERROR) {
|
|
sc->istats.suni_reg_errors++;
|
|
q->error = EIO;
|
|
}
|
|
wakeup(q);
|
|
}
|
|
|
|
/*
|
|
* Write a SUNI register. The bits that are 1 in mask are written from val
|
|
* into register reg. We wait for the command to complete by sleeping on
|
|
* the register memory.
|
|
*
|
|
* We assume, that we already hold the softc mutex.
|
|
*/
|
|
static int
|
|
fatm_utopia_writereg(struct ifatm *ifatm, u_int reg, u_int mask, u_int val)
|
|
{
|
|
int error;
|
|
struct cmdqueue *q;
|
|
struct fatm_softc *sc;
|
|
|
|
sc = ifatm->ifp->if_softc;
|
|
FATM_CHECKLOCK(sc);
|
|
if (!(ifatm->ifp->if_drv_flags & IFF_DRV_RUNNING))
|
|
return (EIO);
|
|
|
|
/* get queue element and fill it */
|
|
q = GET_QUEUE(sc->cmdqueue, struct cmdqueue, sc->cmdqueue.head);
|
|
|
|
H_SYNCSTAT_POSTREAD(sc, q->q.statp);
|
|
if (!(H_GETSTAT(q->q.statp) & FATM_STAT_FREE)) {
|
|
sc->istats.cmd_queue_full++;
|
|
return (EIO);
|
|
}
|
|
NEXT_QUEUE_ENTRY(sc->cmdqueue.head, FATM_CMD_QLEN);
|
|
|
|
q->error = 0;
|
|
q->cb = fatm_utopia_writeregs_complete;
|
|
H_SETSTAT(q->q.statp, FATM_STAT_PENDING);
|
|
H_SYNCSTAT_PREWRITE(sc, q->q.statp);
|
|
|
|
WRITE4(sc, q->q.card + FATMOC_GETOC3_BUF, 0);
|
|
BARRIER_W(sc);
|
|
WRITE4(sc, q->q.card + FATMOC_OP,
|
|
FATM_MAKE_SETOC3(reg, val, mask) | FATM_OP_INTERRUPT_SEL);
|
|
BARRIER_W(sc);
|
|
|
|
/*
|
|
* Wait for the command to complete
|
|
*/
|
|
error = msleep(q, &sc->mtx, PZERO | PCATCH, "fatm_setreg", hz);
|
|
|
|
switch(error) {
|
|
|
|
case EWOULDBLOCK:
|
|
error = EIO;
|
|
break;
|
|
|
|
case ERESTART:
|
|
error = EINTR;
|
|
break;
|
|
|
|
case 0:
|
|
error = q->error;
|
|
break;
|
|
}
|
|
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Function called at completion of a SUNI readregs command.
|
|
* This is called from the interrupt handler while holding the softc lock.
|
|
* We use reg_mem as the randevouze point.
|
|
*/
|
|
static void
|
|
fatm_utopia_readregs_complete(struct fatm_softc *sc, struct cmdqueue *q)
|
|
{
|
|
|
|
H_SYNCSTAT_POSTREAD(sc, q->q.statp);
|
|
if (H_GETSTAT(q->q.statp) & FATM_STAT_ERROR) {
|
|
sc->istats.suni_reg_errors++;
|
|
q->error = EIO;
|
|
}
|
|
wakeup(&sc->reg_mem);
|
|
}
|
|
|
|
/*
|
|
* Read SUNI registers
|
|
*
|
|
* We use a preallocated buffer to read the registers. Therefor we need
|
|
* to protect against multiple threads trying to read registers. We do this
|
|
* with a condition variable and a flag. We wait for the command to complete by sleeping on
|
|
* the register memory.
|
|
*
|
|
* We assume, that we already hold the softc mutex.
|
|
*/
|
|
static int
|
|
fatm_utopia_readregs_internal(struct fatm_softc *sc)
|
|
{
|
|
int error, i;
|
|
uint32_t *ptr;
|
|
struct cmdqueue *q;
|
|
|
|
/* get the buffer */
|
|
for (;;) {
|
|
if (!(sc->ifp->if_drv_flags & IFF_DRV_RUNNING))
|
|
return (EIO);
|
|
if (!(sc->flags & FATM_REGS_INUSE))
|
|
break;
|
|
cv_wait(&sc->cv_regs, &sc->mtx);
|
|
}
|
|
sc->flags |= FATM_REGS_INUSE;
|
|
|
|
q = GET_QUEUE(sc->cmdqueue, struct cmdqueue, sc->cmdqueue.head);
|
|
|
|
H_SYNCSTAT_POSTREAD(sc, q->q.statp);
|
|
if (!(H_GETSTAT(q->q.statp) & FATM_STAT_FREE)) {
|
|
sc->istats.cmd_queue_full++;
|
|
return (EIO);
|
|
}
|
|
NEXT_QUEUE_ENTRY(sc->cmdqueue.head, FATM_CMD_QLEN);
|
|
|
|
q->error = 0;
|
|
q->cb = fatm_utopia_readregs_complete;
|
|
H_SETSTAT(q->q.statp, FATM_STAT_PENDING);
|
|
H_SYNCSTAT_PREWRITE(sc, q->q.statp);
|
|
|
|
bus_dmamap_sync(sc->reg_mem.dmat, sc->reg_mem.map, BUS_DMASYNC_PREREAD);
|
|
|
|
WRITE4(sc, q->q.card + FATMOC_GETOC3_BUF, sc->reg_mem.paddr);
|
|
BARRIER_W(sc);
|
|
WRITE4(sc, q->q.card + FATMOC_OP,
|
|
FATM_OP_OC3_GET_REG | FATM_OP_INTERRUPT_SEL);
|
|
BARRIER_W(sc);
|
|
|
|
/*
|
|
* Wait for the command to complete
|
|
*/
|
|
error = msleep(&sc->reg_mem, &sc->mtx, PZERO | PCATCH,
|
|
"fatm_getreg", hz);
|
|
|
|
switch(error) {
|
|
|
|
case EWOULDBLOCK:
|
|
error = EIO;
|
|
break;
|
|
|
|
case ERESTART:
|
|
error = EINTR;
|
|
break;
|
|
|
|
case 0:
|
|
bus_dmamap_sync(sc->reg_mem.dmat, sc->reg_mem.map,
|
|
BUS_DMASYNC_POSTREAD);
|
|
error = q->error;
|
|
break;
|
|
}
|
|
|
|
if (error != 0) {
|
|
/* declare buffer to be free */
|
|
sc->flags &= ~FATM_REGS_INUSE;
|
|
cv_signal(&sc->cv_regs);
|
|
return (error);
|
|
}
|
|
|
|
/* swap if needed */
|
|
ptr = (uint32_t *)sc->reg_mem.mem;
|
|
for (i = 0; i < FATM_NREGS; i++)
|
|
ptr[i] = le32toh(ptr[i]) & 0xff;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Read SUNI registers for the SUNI module.
|
|
*
|
|
* We assume, that we already hold the mutex.
|
|
*/
|
|
static int
|
|
fatm_utopia_readregs(struct ifatm *ifatm, u_int reg, uint8_t *valp, u_int *np)
|
|
{
|
|
int err;
|
|
int i;
|
|
struct fatm_softc *sc;
|
|
|
|
if (reg >= FATM_NREGS)
|
|
return (EINVAL);
|
|
if (reg + *np > FATM_NREGS)
|
|
*np = FATM_NREGS - reg;
|
|
sc = ifatm->ifp->if_softc;
|
|
FATM_CHECKLOCK(sc);
|
|
|
|
err = fatm_utopia_readregs_internal(sc);
|
|
if (err != 0)
|
|
return (err);
|
|
|
|
for (i = 0; i < *np; i++)
|
|
valp[i] = ((uint32_t *)sc->reg_mem.mem)[reg + i];
|
|
|
|
/* declare buffer to be free */
|
|
sc->flags &= ~FATM_REGS_INUSE;
|
|
cv_signal(&sc->cv_regs);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Check whether the hard is beating. We remember the last heart beat and
|
|
* compare it to the current one. If it appears stuck for 10 times, we have
|
|
* a problem.
|
|
*
|
|
* Assume we hold the lock.
|
|
*/
|
|
static void
|
|
fatm_check_heartbeat(struct fatm_softc *sc)
|
|
{
|
|
uint32_t h;
|
|
|
|
FATM_CHECKLOCK(sc);
|
|
|
|
h = READ4(sc, FATMO_HEARTBEAT);
|
|
DBG(sc, BEAT, ("heartbeat %08x", h));
|
|
|
|
if (sc->stop_cnt == 10)
|
|
return;
|
|
|
|
if (h == sc->heartbeat) {
|
|
if (++sc->stop_cnt == 10) {
|
|
log(LOG_ERR, "i960 stopped???\n");
|
|
WRITE4(sc, FATMO_HIMR, 1);
|
|
}
|
|
return;
|
|
}
|
|
|
|
sc->stop_cnt = 0;
|
|
sc->heartbeat = h;
|
|
}
|
|
|
|
/*
|
|
* Ensure that the heart is still beating.
|
|
*/
|
|
static void
|
|
fatm_watchdog(void *arg)
|
|
{
|
|
struct fatm_softc *sc;
|
|
|
|
sc = arg;
|
|
FATM_CHECKLOCK(sc);
|
|
fatm_check_heartbeat(sc);
|
|
callout_reset(&sc->watchdog_timer, hz * 5, fatm_watchdog, sc);
|
|
}
|
|
|
|
/*
|
|
* Hard reset the i960 on the board. This is done by initializing registers,
|
|
* clearing interrupts and waiting for the selftest to finish. Not sure,
|
|
* whether all these barriers are actually needed.
|
|
*
|
|
* Assumes that we hold the lock.
|
|
*/
|
|
static int
|
|
fatm_reset(struct fatm_softc *sc)
|
|
{
|
|
int w;
|
|
uint32_t val;
|
|
|
|
FATM_CHECKLOCK(sc);
|
|
|
|
WRITE4(sc, FATMO_APP_BASE, FATMO_COMMON_ORIGIN);
|
|
BARRIER_W(sc);
|
|
|
|
WRITE4(sc, FATMO_UART_TO_960, XMIT_READY);
|
|
BARRIER_W(sc);
|
|
|
|
WRITE4(sc, FATMO_UART_TO_HOST, XMIT_READY);
|
|
BARRIER_W(sc);
|
|
|
|
WRITE4(sc, FATMO_BOOT_STATUS, COLD_START);
|
|
BARRIER_W(sc);
|
|
|
|
WRITE1(sc, FATMO_HCR, FATM_HCR_RESET);
|
|
BARRIER_W(sc);
|
|
|
|
DELAY(1000);
|
|
|
|
WRITE1(sc, FATMO_HCR, 0);
|
|
BARRIER_RW(sc);
|
|
|
|
DELAY(1000);
|
|
|
|
for (w = 100; w; w--) {
|
|
BARRIER_R(sc);
|
|
val = READ4(sc, FATMO_BOOT_STATUS);
|
|
switch (val) {
|
|
case SELF_TEST_OK:
|
|
return (0);
|
|
case SELF_TEST_FAIL:
|
|
return (EIO);
|
|
}
|
|
DELAY(1000);
|
|
}
|
|
return (EIO);
|
|
}
|
|
|
|
/*
|
|
* Stop the card. Must be called WITH the lock held
|
|
* Reset, free transmit and receive buffers. Wakeup everybody who may sleep.
|
|
*/
|
|
static void
|
|
fatm_stop(struct fatm_softc *sc)
|
|
{
|
|
int i;
|
|
struct cmdqueue *q;
|
|
struct rbuf *rb;
|
|
struct txqueue *tx;
|
|
uint32_t stat;
|
|
|
|
FATM_CHECKLOCK(sc);
|
|
|
|
/* Stop the board */
|
|
utopia_stop(&sc->utopia);
|
|
(void)fatm_reset(sc);
|
|
|
|
/* stop watchdog */
|
|
callout_stop(&sc->watchdog_timer);
|
|
|
|
if (sc->ifp->if_drv_flags & IFF_DRV_RUNNING) {
|
|
sc->ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE);
|
|
ATMEV_SEND_IFSTATE_CHANGED(IFP2IFATM(sc->ifp),
|
|
sc->utopia.carrier == UTP_CARR_OK);
|
|
|
|
/*
|
|
* Collect transmit mbufs, partial receive mbufs and
|
|
* supplied mbufs
|
|
*/
|
|
for (i = 0; i < FATM_TX_QLEN; i++) {
|
|
tx = GET_QUEUE(sc->txqueue, struct txqueue, i);
|
|
if (tx->m) {
|
|
bus_dmamap_unload(sc->tx_tag, tx->map);
|
|
m_freem(tx->m);
|
|
tx->m = NULL;
|
|
}
|
|
}
|
|
|
|
/* Collect supplied mbufs */
|
|
while ((rb = LIST_FIRST(&sc->rbuf_used)) != NULL) {
|
|
LIST_REMOVE(rb, link);
|
|
bus_dmamap_unload(sc->rbuf_tag, rb->map);
|
|
m_free(rb->m);
|
|
rb->m = NULL;
|
|
LIST_INSERT_HEAD(&sc->rbuf_free, rb, link);
|
|
}
|
|
|
|
/* Unwait any waiters */
|
|
wakeup(&sc->sadi_mem);
|
|
|
|
/* wakeup all threads waiting for STAT or REG buffers */
|
|
cv_broadcast(&sc->cv_stat);
|
|
cv_broadcast(&sc->cv_regs);
|
|
|
|
sc->flags &= ~(FATM_STAT_INUSE | FATM_REGS_INUSE);
|
|
|
|
/* wakeup all threads waiting on commands */
|
|
for (i = 0; i < FATM_CMD_QLEN; i++) {
|
|
q = GET_QUEUE(sc->cmdqueue, struct cmdqueue, i);
|
|
|
|
H_SYNCSTAT_POSTREAD(sc, q->q.statp);
|
|
if ((stat = H_GETSTAT(q->q.statp)) != FATM_STAT_FREE) {
|
|
H_SETSTAT(q->q.statp, stat | FATM_STAT_ERROR);
|
|
H_SYNCSTAT_PREWRITE(sc, q->q.statp);
|
|
wakeup(q);
|
|
}
|
|
}
|
|
utopia_reset_media(&sc->utopia);
|
|
}
|
|
sc->small_cnt = sc->large_cnt = 0;
|
|
|
|
/* Reset vcc info */
|
|
if (sc->vccs != NULL) {
|
|
sc->open_vccs = 0;
|
|
for (i = 0; i < FORE_MAX_VCC + 1; i++) {
|
|
if (sc->vccs[i] != NULL) {
|
|
if ((sc->vccs[i]->vflags & (FATM_VCC_OPEN |
|
|
FATM_VCC_TRY_OPEN)) == 0) {
|
|
uma_zfree(sc->vcc_zone, sc->vccs[i]);
|
|
sc->vccs[i] = NULL;
|
|
} else {
|
|
sc->vccs[i]->vflags = 0;
|
|
sc->open_vccs++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* Load the firmware into the board and save the entry point.
|
|
*/
|
|
static uint32_t
|
|
firmware_load(struct fatm_softc *sc)
|
|
{
|
|
struct firmware *fw = (struct firmware *)firmware;
|
|
|
|
DBG(sc, INIT, ("loading - entry=%x", fw->entry));
|
|
bus_space_write_region_4(sc->memt, sc->memh, fw->offset, firmware,
|
|
sizeof(firmware) / sizeof(firmware[0]));
|
|
BARRIER_RW(sc);
|
|
|
|
return (fw->entry);
|
|
}
|
|
|
|
/*
|
|
* Read a character from the virtual UART. The availability of a character
|
|
* is signaled by a non-null value of the 32 bit register. The eating of
|
|
* the character by us is signalled to the card by setting that register
|
|
* to zero.
|
|
*/
|
|
static int
|
|
rx_getc(struct fatm_softc *sc)
|
|
{
|
|
int w = 50;
|
|
int c;
|
|
|
|
while (w--) {
|
|
c = READ4(sc, FATMO_UART_TO_HOST);
|
|
BARRIER_RW(sc);
|
|
if (c != 0) {
|
|
WRITE4(sc, FATMO_UART_TO_HOST, 0);
|
|
DBGC(sc, UART, ("%c", c & 0xff));
|
|
return (c & 0xff);
|
|
}
|
|
DELAY(1000);
|
|
}
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* Eat up characters from the board and stuff them in the bit-bucket.
|
|
*/
|
|
static void
|
|
rx_flush(struct fatm_softc *sc)
|
|
{
|
|
int w = 10000;
|
|
|
|
while (w-- && rx_getc(sc) >= 0)
|
|
;
|
|
}
|
|
|
|
/*
|
|
* Write a character to the card. The UART is available if the register
|
|
* is zero.
|
|
*/
|
|
static int
|
|
tx_putc(struct fatm_softc *sc, u_char c)
|
|
{
|
|
int w = 10;
|
|
int c1;
|
|
|
|
while (w--) {
|
|
c1 = READ4(sc, FATMO_UART_TO_960);
|
|
BARRIER_RW(sc);
|
|
if (c1 == 0) {
|
|
WRITE4(sc, FATMO_UART_TO_960, c | CHAR_AVAIL);
|
|
DBGC(sc, UART, ("%c", c & 0xff));
|
|
return (0);
|
|
}
|
|
DELAY(1000);
|
|
}
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* Start the firmware. This is doing by issuing a 'go' command with
|
|
* the hex entry address of the firmware. Then we wait for the self-test to
|
|
* succeed.
|
|
*/
|
|
static int
|
|
fatm_start_firmware(struct fatm_softc *sc, uint32_t start)
|
|
{
|
|
static char hex[] = "0123456789abcdef";
|
|
u_int w, val;
|
|
|
|
DBG(sc, INIT, ("starting"));
|
|
rx_flush(sc);
|
|
tx_putc(sc, '\r');
|
|
DELAY(1000);
|
|
|
|
rx_flush(sc);
|
|
|
|
tx_putc(sc, 'g');
|
|
(void)rx_getc(sc);
|
|
tx_putc(sc, 'o');
|
|
(void)rx_getc(sc);
|
|
tx_putc(sc, ' ');
|
|
(void)rx_getc(sc);
|
|
|
|
tx_putc(sc, hex[(start >> 12) & 0xf]);
|
|
(void)rx_getc(sc);
|
|
tx_putc(sc, hex[(start >> 8) & 0xf]);
|
|
(void)rx_getc(sc);
|
|
tx_putc(sc, hex[(start >> 4) & 0xf]);
|
|
(void)rx_getc(sc);
|
|
tx_putc(sc, hex[(start >> 0) & 0xf]);
|
|
(void)rx_getc(sc);
|
|
|
|
tx_putc(sc, '\r');
|
|
rx_flush(sc);
|
|
|
|
for (w = 100; w; w--) {
|
|
BARRIER_R(sc);
|
|
val = READ4(sc, FATMO_BOOT_STATUS);
|
|
switch (val) {
|
|
case CP_RUNNING:
|
|
return (0);
|
|
case SELF_TEST_FAIL:
|
|
return (EIO);
|
|
}
|
|
DELAY(1000);
|
|
}
|
|
return (EIO);
|
|
}
|
|
|
|
/*
|
|
* Initialize one card and host queue.
|
|
*/
|
|
static void
|
|
init_card_queue(struct fatm_softc *sc, struct fqueue *queue, int qlen,
|
|
size_t qel_size, size_t desc_size, cardoff_t off,
|
|
u_char **statpp, uint32_t *cardstat, u_char *descp, uint32_t carddesc)
|
|
{
|
|
struct fqelem *el = queue->chunk;
|
|
|
|
while (qlen--) {
|
|
el->card = off;
|
|
off += 8; /* size of card entry */
|
|
|
|
el->statp = (uint32_t *)(*statpp);
|
|
(*statpp) += sizeof(uint32_t);
|
|
H_SETSTAT(el->statp, FATM_STAT_FREE);
|
|
H_SYNCSTAT_PREWRITE(sc, el->statp);
|
|
|
|
WRITE4(sc, el->card + FATMOS_STATP, (*cardstat));
|
|
(*cardstat) += sizeof(uint32_t);
|
|
|
|
el->ioblk = descp;
|
|
descp += desc_size;
|
|
el->card_ioblk = carddesc;
|
|
carddesc += desc_size;
|
|
|
|
el = (struct fqelem *)((u_char *)el + qel_size);
|
|
}
|
|
queue->tail = queue->head = 0;
|
|
}
|
|
|
|
/*
|
|
* Issue the initialize operation to the card, wait for completion and
|
|
* initialize the on-board and host queue structures with offsets and
|
|
* addresses.
|
|
*/
|
|
static int
|
|
fatm_init_cmd(struct fatm_softc *sc)
|
|
{
|
|
int w, c;
|
|
u_char *statp;
|
|
uint32_t card_stat;
|
|
u_int cnt;
|
|
struct fqelem *el;
|
|
cardoff_t off;
|
|
|
|
DBG(sc, INIT, ("command"));
|
|
WRITE4(sc, FATMO_ISTAT, 0);
|
|
WRITE4(sc, FATMO_IMASK, 1);
|
|
WRITE4(sc, FATMO_HLOGGER, 0);
|
|
|
|
WRITE4(sc, FATMO_INIT + FATMOI_RECEIVE_TRESHOLD, 0);
|
|
WRITE4(sc, FATMO_INIT + FATMOI_NUM_CONNECT, FORE_MAX_VCC);
|
|
WRITE4(sc, FATMO_INIT + FATMOI_CQUEUE_LEN, FATM_CMD_QLEN);
|
|
WRITE4(sc, FATMO_INIT + FATMOI_TQUEUE_LEN, FATM_TX_QLEN);
|
|
WRITE4(sc, FATMO_INIT + FATMOI_RQUEUE_LEN, FATM_RX_QLEN);
|
|
WRITE4(sc, FATMO_INIT + FATMOI_RPD_EXTENSION, RPD_EXTENSIONS);
|
|
WRITE4(sc, FATMO_INIT + FATMOI_TPD_EXTENSION, TPD_EXTENSIONS);
|
|
|
|
/*
|
|
* initialize buffer descriptors
|
|
*/
|
|
WRITE4(sc, FATMO_INIT + FATMOI_SMALL_B1 + FATMOB_QUEUE_LENGTH,
|
|
SMALL_SUPPLY_QLEN);
|
|
WRITE4(sc, FATMO_INIT + FATMOI_SMALL_B1 + FATMOB_BUFFER_SIZE,
|
|
SMALL_BUFFER_LEN);
|
|
WRITE4(sc, FATMO_INIT + FATMOI_SMALL_B1 + FATMOB_POOL_SIZE,
|
|
SMALL_POOL_SIZE);
|
|
WRITE4(sc, FATMO_INIT + FATMOI_SMALL_B1 + FATMOB_SUPPLY_BLKSIZE,
|
|
SMALL_SUPPLY_BLKSIZE);
|
|
|
|
WRITE4(sc, FATMO_INIT + FATMOI_LARGE_B1 + FATMOB_QUEUE_LENGTH,
|
|
LARGE_SUPPLY_QLEN);
|
|
WRITE4(sc, FATMO_INIT + FATMOI_LARGE_B1 + FATMOB_BUFFER_SIZE,
|
|
LARGE_BUFFER_LEN);
|
|
WRITE4(sc, FATMO_INIT + FATMOI_LARGE_B1 + FATMOB_POOL_SIZE,
|
|
LARGE_POOL_SIZE);
|
|
WRITE4(sc, FATMO_INIT + FATMOI_LARGE_B1 + FATMOB_SUPPLY_BLKSIZE,
|
|
LARGE_SUPPLY_BLKSIZE);
|
|
|
|
WRITE4(sc, FATMO_INIT + FATMOI_SMALL_B2 + FATMOB_QUEUE_LENGTH, 0);
|
|
WRITE4(sc, FATMO_INIT + FATMOI_SMALL_B2 + FATMOB_BUFFER_SIZE, 0);
|
|
WRITE4(sc, FATMO_INIT + FATMOI_SMALL_B2 + FATMOB_POOL_SIZE, 0);
|
|
WRITE4(sc, FATMO_INIT + FATMOI_SMALL_B2 + FATMOB_SUPPLY_BLKSIZE, 0);
|
|
|
|
WRITE4(sc, FATMO_INIT + FATMOI_LARGE_B2 + FATMOB_QUEUE_LENGTH, 0);
|
|
WRITE4(sc, FATMO_INIT + FATMOI_LARGE_B2 + FATMOB_BUFFER_SIZE, 0);
|
|
WRITE4(sc, FATMO_INIT + FATMOI_LARGE_B2 + FATMOB_POOL_SIZE, 0);
|
|
WRITE4(sc, FATMO_INIT + FATMOI_LARGE_B2 + FATMOB_SUPPLY_BLKSIZE, 0);
|
|
|
|
/*
|
|
* Start the command
|
|
*/
|
|
BARRIER_W(sc);
|
|
WRITE4(sc, FATMO_INIT + FATMOI_STATUS, FATM_STAT_PENDING);
|
|
BARRIER_W(sc);
|
|
WRITE4(sc, FATMO_INIT + FATMOI_OP, FATM_OP_INITIALIZE);
|
|
BARRIER_W(sc);
|
|
|
|
/*
|
|
* Busy wait for completion
|
|
*/
|
|
w = 100;
|
|
while (w--) {
|
|
c = READ4(sc, FATMO_INIT + FATMOI_STATUS);
|
|
BARRIER_R(sc);
|
|
if (c & FATM_STAT_COMPLETE)
|
|
break;
|
|
DELAY(1000);
|
|
}
|
|
|
|
if (c & FATM_STAT_ERROR)
|
|
return (EIO);
|
|
|
|
/*
|
|
* Initialize the queues
|
|
*/
|
|
statp = sc->stat_mem.mem;
|
|
card_stat = sc->stat_mem.paddr;
|
|
|
|
/*
|
|
* Command queue. This is special in that it's on the card.
|
|
*/
|
|
el = sc->cmdqueue.chunk;
|
|
off = READ4(sc, FATMO_COMMAND_QUEUE);
|
|
DBG(sc, INIT, ("cmd queue=%x", off));
|
|
for (cnt = 0; cnt < FATM_CMD_QLEN; cnt++) {
|
|
el = &((struct cmdqueue *)sc->cmdqueue.chunk + cnt)->q;
|
|
|
|
el->card = off;
|
|
off += 32; /* size of card structure */
|
|
|
|
el->statp = (uint32_t *)statp;
|
|
statp += sizeof(uint32_t);
|
|
H_SETSTAT(el->statp, FATM_STAT_FREE);
|
|
H_SYNCSTAT_PREWRITE(sc, el->statp);
|
|
|
|
WRITE4(sc, el->card + FATMOC_STATP, card_stat);
|
|
card_stat += sizeof(uint32_t);
|
|
}
|
|
sc->cmdqueue.tail = sc->cmdqueue.head = 0;
|
|
|
|
/*
|
|
* Now the other queues. These are in memory
|
|
*/
|
|
init_card_queue(sc, &sc->txqueue, FATM_TX_QLEN,
|
|
sizeof(struct txqueue), TPD_SIZE,
|
|
READ4(sc, FATMO_TRANSMIT_QUEUE),
|
|
&statp, &card_stat, sc->txq_mem.mem, sc->txq_mem.paddr);
|
|
|
|
init_card_queue(sc, &sc->rxqueue, FATM_RX_QLEN,
|
|
sizeof(struct rxqueue), RPD_SIZE,
|
|
READ4(sc, FATMO_RECEIVE_QUEUE),
|
|
&statp, &card_stat, sc->rxq_mem.mem, sc->rxq_mem.paddr);
|
|
|
|
init_card_queue(sc, &sc->s1queue, SMALL_SUPPLY_QLEN,
|
|
sizeof(struct supqueue), BSUP_BLK2SIZE(SMALL_SUPPLY_BLKSIZE),
|
|
READ4(sc, FATMO_SMALL_B1_QUEUE),
|
|
&statp, &card_stat, sc->s1q_mem.mem, sc->s1q_mem.paddr);
|
|
|
|
init_card_queue(sc, &sc->l1queue, LARGE_SUPPLY_QLEN,
|
|
sizeof(struct supqueue), BSUP_BLK2SIZE(LARGE_SUPPLY_BLKSIZE),
|
|
READ4(sc, FATMO_LARGE_B1_QUEUE),
|
|
&statp, &card_stat, sc->l1q_mem.mem, sc->l1q_mem.paddr);
|
|
|
|
sc->txcnt = 0;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Read PROM. Called only from attach code. Here we spin because the interrupt
|
|
* handler is not yet set up.
|
|
*/
|
|
static int
|
|
fatm_getprom(struct fatm_softc *sc)
|
|
{
|
|
int i;
|
|
struct prom *prom;
|
|
struct cmdqueue *q;
|
|
|
|
DBG(sc, INIT, ("reading prom"));
|
|
q = GET_QUEUE(sc->cmdqueue, struct cmdqueue, sc->cmdqueue.head);
|
|
NEXT_QUEUE_ENTRY(sc->cmdqueue.head, FATM_CMD_QLEN);
|
|
|
|
q->error = 0;
|
|
q->cb = NULL;
|
|
H_SETSTAT(q->q.statp, FATM_STAT_PENDING);
|
|
H_SYNCSTAT_PREWRITE(sc, q->q.statp);
|
|
|
|
bus_dmamap_sync(sc->prom_mem.dmat, sc->prom_mem.map,
|
|
BUS_DMASYNC_PREREAD);
|
|
|
|
WRITE4(sc, q->q.card + FATMOC_GPROM_BUF, sc->prom_mem.paddr);
|
|
BARRIER_W(sc);
|
|
WRITE4(sc, q->q.card + FATMOC_OP, FATM_OP_GET_PROM_DATA);
|
|
BARRIER_W(sc);
|
|
|
|
for (i = 0; i < 1000; i++) {
|
|
H_SYNCSTAT_POSTREAD(sc, q->q.statp);
|
|
if (H_GETSTAT(q->q.statp) &
|
|
(FATM_STAT_COMPLETE | FATM_STAT_ERROR))
|
|
break;
|
|
DELAY(1000);
|
|
}
|
|
if (i == 1000) {
|
|
if_printf(sc->ifp, "getprom timeout\n");
|
|
return (EIO);
|
|
}
|
|
H_SYNCSTAT_POSTREAD(sc, q->q.statp);
|
|
if (H_GETSTAT(q->q.statp) & FATM_STAT_ERROR) {
|
|
if_printf(sc->ifp, "getprom error\n");
|
|
return (EIO);
|
|
}
|
|
H_SETSTAT(q->q.statp, FATM_STAT_FREE);
|
|
H_SYNCSTAT_PREWRITE(sc, q->q.statp);
|
|
NEXT_QUEUE_ENTRY(sc->cmdqueue.tail, FATM_CMD_QLEN);
|
|
|
|
bus_dmamap_sync(sc->prom_mem.dmat, sc->prom_mem.map,
|
|
BUS_DMASYNC_POSTREAD);
|
|
|
|
|
|
#ifdef notdef
|
|
{
|
|
u_int i;
|
|
|
|
printf("PROM: ");
|
|
u_char *ptr = (u_char *)sc->prom_mem.mem;
|
|
for (i = 0; i < sizeof(struct prom); i++)
|
|
printf("%02x ", *ptr++);
|
|
printf("\n");
|
|
}
|
|
#endif
|
|
|
|
prom = (struct prom *)sc->prom_mem.mem;
|
|
|
|
bcopy(prom->mac + 2, IFP2IFATM(sc->ifp)->mib.esi, 6);
|
|
IFP2IFATM(sc->ifp)->mib.serial = le32toh(prom->serial);
|
|
IFP2IFATM(sc->ifp)->mib.hw_version = le32toh(prom->version);
|
|
IFP2IFATM(sc->ifp)->mib.sw_version = READ4(sc, FATMO_FIRMWARE_RELEASE);
|
|
|
|
if_printf(sc->ifp, "ESI=%02x:%02x:%02x:%02x:%02x:%02x "
|
|
"serial=%u hw=0x%x sw=0x%x\n", IFP2IFATM(sc->ifp)->mib.esi[0],
|
|
IFP2IFATM(sc->ifp)->mib.esi[1], IFP2IFATM(sc->ifp)->mib.esi[2], IFP2IFATM(sc->ifp)->mib.esi[3],
|
|
IFP2IFATM(sc->ifp)->mib.esi[4], IFP2IFATM(sc->ifp)->mib.esi[5], IFP2IFATM(sc->ifp)->mib.serial,
|
|
IFP2IFATM(sc->ifp)->mib.hw_version, IFP2IFATM(sc->ifp)->mib.sw_version);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* This is the callback function for bus_dmamap_load. We assume, that we
|
|
* have a 32-bit bus and so have always one segment.
|
|
*/
|
|
static void
|
|
dmaload_helper(void *arg, bus_dma_segment_t *segs, int nsegs, int error)
|
|
{
|
|
bus_addr_t *ptr = (bus_addr_t *)arg;
|
|
|
|
if (error != 0) {
|
|
printf("%s: error=%d\n", __func__, error);
|
|
return;
|
|
}
|
|
KASSERT(nsegs == 1, ("too many DMA segments"));
|
|
KASSERT(segs[0].ds_addr <= 0xffffffff, ("DMA address too large %lx",
|
|
(u_long)segs[0].ds_addr));
|
|
|
|
*ptr = segs[0].ds_addr;
|
|
}
|
|
|
|
/*
|
|
* Allocate a chunk of DMA-able memory and map it.
|
|
*/
|
|
static int
|
|
alloc_dma_memory(struct fatm_softc *sc, const char *nm, struct fatm_mem *mem)
|
|
{
|
|
int error;
|
|
|
|
mem->mem = NULL;
|
|
|
|
if (bus_dma_tag_create(sc->parent_dmat, mem->align, 0,
|
|
BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR,
|
|
NULL, NULL, mem->size, 1, BUS_SPACE_MAXSIZE_32BIT,
|
|
BUS_DMA_ALLOCNOW, NULL, NULL, &mem->dmat)) {
|
|
if_printf(sc->ifp, "could not allocate %s DMA tag\n",
|
|
nm);
|
|
return (ENOMEM);
|
|
}
|
|
|
|
error = bus_dmamem_alloc(mem->dmat, &mem->mem, 0, &mem->map);
|
|
if (error) {
|
|
if_printf(sc->ifp, "could not allocate %s DMA memory: "
|
|
"%d\n", nm, error);
|
|
bus_dma_tag_destroy(mem->dmat);
|
|
mem->mem = NULL;
|
|
return (error);
|
|
}
|
|
|
|
error = bus_dmamap_load(mem->dmat, mem->map, mem->mem, mem->size,
|
|
dmaload_helper, &mem->paddr, BUS_DMA_NOWAIT);
|
|
if (error) {
|
|
if_printf(sc->ifp, "could not load %s DMA memory: "
|
|
"%d\n", nm, error);
|
|
bus_dmamem_free(mem->dmat, mem->mem, mem->map);
|
|
bus_dma_tag_destroy(mem->dmat);
|
|
mem->mem = NULL;
|
|
return (error);
|
|
}
|
|
|
|
DBG(sc, DMA, ("DMA %s V/P/S/Z %p/%lx/%x/%x", nm, mem->mem,
|
|
(u_long)mem->paddr, mem->size, mem->align));
|
|
|
|
return (0);
|
|
}
|
|
|
|
#ifdef TEST_DMA_SYNC
|
|
static int
|
|
alloc_dma_memoryX(struct fatm_softc *sc, const char *nm, struct fatm_mem *mem)
|
|
{
|
|
int error;
|
|
|
|
mem->mem = NULL;
|
|
|
|
if (bus_dma_tag_create(NULL, mem->align, 0,
|
|
BUS_SPACE_MAXADDR_24BIT, BUS_SPACE_MAXADDR,
|
|
NULL, NULL, mem->size, 1, mem->size,
|
|
BUS_DMA_ALLOCNOW, NULL, NULL, &mem->dmat)) {
|
|
if_printf(sc->ifp, "could not allocate %s DMA tag\n",
|
|
nm);
|
|
return (ENOMEM);
|
|
}
|
|
|
|
mem->mem = contigmalloc(mem->size, M_DEVBUF, M_WAITOK,
|
|
BUS_SPACE_MAXADDR_24BIT, BUS_SPACE_MAXADDR_32BIT, mem->align, 0);
|
|
|
|
error = bus_dmamap_create(mem->dmat, 0, &mem->map);
|
|
if (error) {
|
|
if_printf(sc->ifp, "could not allocate %s DMA map: "
|
|
"%d\n", nm, error);
|
|
contigfree(mem->mem, mem->size, M_DEVBUF);
|
|
bus_dma_tag_destroy(mem->dmat);
|
|
mem->mem = NULL;
|
|
return (error);
|
|
}
|
|
|
|
error = bus_dmamap_load(mem->dmat, mem->map, mem->mem, mem->size,
|
|
dmaload_helper, &mem->paddr, BUS_DMA_NOWAIT);
|
|
if (error) {
|
|
if_printf(sc->ifp, "could not load %s DMA memory: "
|
|
"%d\n", nm, error);
|
|
bus_dmamap_destroy(mem->dmat, mem->map);
|
|
contigfree(mem->mem, mem->size, M_DEVBUF);
|
|
bus_dma_tag_destroy(mem->dmat);
|
|
mem->mem = NULL;
|
|
return (error);
|
|
}
|
|
|
|
DBG(sc, DMA, ("DMAX %s V/P/S/Z %p/%lx/%x/%x", nm, mem->mem,
|
|
(u_long)mem->paddr, mem->size, mem->align));
|
|
|
|
printf("DMAX: %s V/P/S/Z %p/%lx/%x/%x", nm, mem->mem,
|
|
(u_long)mem->paddr, mem->size, mem->align);
|
|
|
|
return (0);
|
|
}
|
|
#endif /* TEST_DMA_SYNC */
|
|
|
|
/*
|
|
* Destroy all resources of an dma-able memory chunk
|
|
*/
|
|
static void
|
|
destroy_dma_memory(struct fatm_mem *mem)
|
|
{
|
|
if (mem->mem != NULL) {
|
|
bus_dmamap_unload(mem->dmat, mem->map);
|
|
bus_dmamem_free(mem->dmat, mem->mem, mem->map);
|
|
bus_dma_tag_destroy(mem->dmat);
|
|
mem->mem = NULL;
|
|
}
|
|
}
|
|
#ifdef TEST_DMA_SYNC
|
|
static void
|
|
destroy_dma_memoryX(struct fatm_mem *mem)
|
|
{
|
|
if (mem->mem != NULL) {
|
|
bus_dmamap_unload(mem->dmat, mem->map);
|
|
bus_dmamap_destroy(mem->dmat, mem->map);
|
|
contigfree(mem->mem, mem->size, M_DEVBUF);
|
|
bus_dma_tag_destroy(mem->dmat);
|
|
mem->mem = NULL;
|
|
}
|
|
}
|
|
#endif /* TEST_DMA_SYNC */
|
|
|
|
/*
|
|
* Try to supply buffers to the card if there are free entries in the queues
|
|
*/
|
|
static void
|
|
fatm_supply_small_buffers(struct fatm_softc *sc)
|
|
{
|
|
int nblocks, nbufs;
|
|
struct supqueue *q;
|
|
struct rbd *bd;
|
|
int i, j, error, cnt;
|
|
struct mbuf *m;
|
|
struct rbuf *rb;
|
|
bus_addr_t phys;
|
|
|
|
nbufs = max(4 * sc->open_vccs, 32);
|
|
nbufs = min(nbufs, SMALL_POOL_SIZE);
|
|
nbufs -= sc->small_cnt;
|
|
|
|
nblocks = (nbufs + SMALL_SUPPLY_BLKSIZE - 1) / SMALL_SUPPLY_BLKSIZE;
|
|
for (cnt = 0; cnt < nblocks; cnt++) {
|
|
q = GET_QUEUE(sc->s1queue, struct supqueue, sc->s1queue.head);
|
|
|
|
H_SYNCSTAT_POSTREAD(sc, q->q.statp);
|
|
if (H_GETSTAT(q->q.statp) != FATM_STAT_FREE)
|
|
break;
|
|
|
|
bd = (struct rbd *)q->q.ioblk;
|
|
|
|
for (i = 0; i < SMALL_SUPPLY_BLKSIZE; i++) {
|
|
if ((rb = LIST_FIRST(&sc->rbuf_free)) == NULL) {
|
|
if_printf(sc->ifp, "out of rbufs\n");
|
|
break;
|
|
}
|
|
MGETHDR(m, M_NOWAIT, MT_DATA);
|
|
if (m == NULL) {
|
|
LIST_INSERT_HEAD(&sc->rbuf_free, rb, link);
|
|
break;
|
|
}
|
|
M_ALIGN(m, SMALL_BUFFER_LEN);
|
|
error = bus_dmamap_load(sc->rbuf_tag, rb->map,
|
|
m->m_data, SMALL_BUFFER_LEN, dmaload_helper,
|
|
&phys, BUS_DMA_NOWAIT);
|
|
if (error) {
|
|
if_printf(sc->ifp,
|
|
"dmamap_load mbuf failed %d", error);
|
|
m_freem(m);
|
|
LIST_INSERT_HEAD(&sc->rbuf_free, rb, link);
|
|
break;
|
|
}
|
|
bus_dmamap_sync(sc->rbuf_tag, rb->map,
|
|
BUS_DMASYNC_PREREAD);
|
|
|
|
LIST_REMOVE(rb, link);
|
|
LIST_INSERT_HEAD(&sc->rbuf_used, rb, link);
|
|
|
|
rb->m = m;
|
|
bd[i].handle = rb - sc->rbufs;
|
|
H_SETDESC(bd[i].buffer, phys);
|
|
}
|
|
|
|
if (i < SMALL_SUPPLY_BLKSIZE) {
|
|
for (j = 0; j < i; j++) {
|
|
rb = sc->rbufs + bd[j].handle;
|
|
bus_dmamap_unload(sc->rbuf_tag, rb->map);
|
|
m_free(rb->m);
|
|
rb->m = NULL;
|
|
|
|
LIST_REMOVE(rb, link);
|
|
LIST_INSERT_HEAD(&sc->rbuf_free, rb, link);
|
|
}
|
|
break;
|
|
}
|
|
H_SYNCQ_PREWRITE(&sc->s1q_mem, bd,
|
|
sizeof(struct rbd) * SMALL_SUPPLY_BLKSIZE);
|
|
|
|
H_SETSTAT(q->q.statp, FATM_STAT_PENDING);
|
|
H_SYNCSTAT_PREWRITE(sc, q->q.statp);
|
|
|
|
WRITE4(sc, q->q.card, q->q.card_ioblk);
|
|
BARRIER_W(sc);
|
|
|
|
sc->small_cnt += SMALL_SUPPLY_BLKSIZE;
|
|
|
|
NEXT_QUEUE_ENTRY(sc->s1queue.head, SMALL_SUPPLY_QLEN);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Try to supply buffers to the card if there are free entries in the queues
|
|
* We assume that all buffers are within the address space accessible by the
|
|
* card (32-bit), so we don't need bounce buffers.
|
|
*/
|
|
static void
|
|
fatm_supply_large_buffers(struct fatm_softc *sc)
|
|
{
|
|
int nbufs, nblocks, cnt;
|
|
struct supqueue *q;
|
|
struct rbd *bd;
|
|
int i, j, error;
|
|
struct mbuf *m;
|
|
struct rbuf *rb;
|
|
bus_addr_t phys;
|
|
|
|
nbufs = max(4 * sc->open_vccs, 32);
|
|
nbufs = min(nbufs, LARGE_POOL_SIZE);
|
|
nbufs -= sc->large_cnt;
|
|
|
|
nblocks = (nbufs + LARGE_SUPPLY_BLKSIZE - 1) / LARGE_SUPPLY_BLKSIZE;
|
|
|
|
for (cnt = 0; cnt < nblocks; cnt++) {
|
|
q = GET_QUEUE(sc->l1queue, struct supqueue, sc->l1queue.head);
|
|
|
|
H_SYNCSTAT_POSTREAD(sc, q->q.statp);
|
|
if (H_GETSTAT(q->q.statp) != FATM_STAT_FREE)
|
|
break;
|
|
|
|
bd = (struct rbd *)q->q.ioblk;
|
|
|
|
for (i = 0; i < LARGE_SUPPLY_BLKSIZE; i++) {
|
|
if ((rb = LIST_FIRST(&sc->rbuf_free)) == NULL) {
|
|
if_printf(sc->ifp, "out of rbufs\n");
|
|
break;
|
|
}
|
|
if ((m = m_getcl(M_NOWAIT, MT_DATA,
|
|
M_PKTHDR)) == NULL) {
|
|
LIST_INSERT_HEAD(&sc->rbuf_free, rb, link);
|
|
break;
|
|
}
|
|
/* No MEXT_ALIGN */
|
|
m->m_data += MCLBYTES - LARGE_BUFFER_LEN;
|
|
error = bus_dmamap_load(sc->rbuf_tag, rb->map,
|
|
m->m_data, LARGE_BUFFER_LEN, dmaload_helper,
|
|
&phys, BUS_DMA_NOWAIT);
|
|
if (error) {
|
|
if_printf(sc->ifp,
|
|
"dmamap_load mbuf failed %d", error);
|
|
m_freem(m);
|
|
LIST_INSERT_HEAD(&sc->rbuf_free, rb, link);
|
|
break;
|
|
}
|
|
|
|
bus_dmamap_sync(sc->rbuf_tag, rb->map,
|
|
BUS_DMASYNC_PREREAD);
|
|
|
|
LIST_REMOVE(rb, link);
|
|
LIST_INSERT_HEAD(&sc->rbuf_used, rb, link);
|
|
|
|
rb->m = m;
|
|
bd[i].handle = rb - sc->rbufs;
|
|
H_SETDESC(bd[i].buffer, phys);
|
|
}
|
|
|
|
if (i < LARGE_SUPPLY_BLKSIZE) {
|
|
for (j = 0; j < i; j++) {
|
|
rb = sc->rbufs + bd[j].handle;
|
|
bus_dmamap_unload(sc->rbuf_tag, rb->map);
|
|
m_free(rb->m);
|
|
rb->m = NULL;
|
|
|
|
LIST_REMOVE(rb, link);
|
|
LIST_INSERT_HEAD(&sc->rbuf_free, rb, link);
|
|
}
|
|
break;
|
|
}
|
|
H_SYNCQ_PREWRITE(&sc->l1q_mem, bd,
|
|
sizeof(struct rbd) * LARGE_SUPPLY_BLKSIZE);
|
|
|
|
H_SETSTAT(q->q.statp, FATM_STAT_PENDING);
|
|
H_SYNCSTAT_PREWRITE(sc, q->q.statp);
|
|
WRITE4(sc, q->q.card, q->q.card_ioblk);
|
|
BARRIER_W(sc);
|
|
|
|
sc->large_cnt += LARGE_SUPPLY_BLKSIZE;
|
|
|
|
NEXT_QUEUE_ENTRY(sc->l1queue.head, LARGE_SUPPLY_QLEN);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Actually start the card. The lock must be held here.
|
|
* Reset, load the firmware, start it, initializes queues, read the PROM
|
|
* and supply receive buffers to the card.
|
|
*/
|
|
static void
|
|
fatm_init_locked(struct fatm_softc *sc)
|
|
{
|
|
struct rxqueue *q;
|
|
int i, c, error;
|
|
uint32_t start;
|
|
|
|
DBG(sc, INIT, ("initialize"));
|
|
if (sc->ifp->if_drv_flags & IFF_DRV_RUNNING)
|
|
fatm_stop(sc);
|
|
|
|
/*
|
|
* Hard reset the board
|
|
*/
|
|
if (fatm_reset(sc))
|
|
return;
|
|
|
|
start = firmware_load(sc);
|
|
if (fatm_start_firmware(sc, start) || fatm_init_cmd(sc) ||
|
|
fatm_getprom(sc)) {
|
|
fatm_reset(sc);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Handle media
|
|
*/
|
|
c = READ4(sc, FATMO_MEDIA_TYPE);
|
|
switch (c) {
|
|
|
|
case FORE_MT_TAXI_100:
|
|
IFP2IFATM(sc->ifp)->mib.media = IFM_ATM_TAXI_100;
|
|
IFP2IFATM(sc->ifp)->mib.pcr = 227273;
|
|
break;
|
|
|
|
case FORE_MT_TAXI_140:
|
|
IFP2IFATM(sc->ifp)->mib.media = IFM_ATM_TAXI_140;
|
|
IFP2IFATM(sc->ifp)->mib.pcr = 318181;
|
|
break;
|
|
|
|
case FORE_MT_UTP_SONET:
|
|
IFP2IFATM(sc->ifp)->mib.media = IFM_ATM_UTP_155;
|
|
IFP2IFATM(sc->ifp)->mib.pcr = 353207;
|
|
break;
|
|
|
|
case FORE_MT_MM_OC3_ST:
|
|
case FORE_MT_MM_OC3_SC:
|
|
IFP2IFATM(sc->ifp)->mib.media = IFM_ATM_MM_155;
|
|
IFP2IFATM(sc->ifp)->mib.pcr = 353207;
|
|
break;
|
|
|
|
case FORE_MT_SM_OC3_ST:
|
|
case FORE_MT_SM_OC3_SC:
|
|
IFP2IFATM(sc->ifp)->mib.media = IFM_ATM_SM_155;
|
|
IFP2IFATM(sc->ifp)->mib.pcr = 353207;
|
|
break;
|
|
|
|
default:
|
|
log(LOG_ERR, "fatm: unknown media type %d\n", c);
|
|
IFP2IFATM(sc->ifp)->mib.media = IFM_ATM_UNKNOWN;
|
|
IFP2IFATM(sc->ifp)->mib.pcr = 353207;
|
|
break;
|
|
}
|
|
sc->ifp->if_baudrate = 53 * 8 * IFP2IFATM(sc->ifp)->mib.pcr;
|
|
utopia_init_media(&sc->utopia);
|
|
|
|
/*
|
|
* Initialize the RBDs
|
|
*/
|
|
for (i = 0; i < FATM_RX_QLEN; i++) {
|
|
q = GET_QUEUE(sc->rxqueue, struct rxqueue, i);
|
|
WRITE4(sc, q->q.card + 0, q->q.card_ioblk);
|
|
}
|
|
BARRIER_W(sc);
|
|
|
|
/*
|
|
* Supply buffers to the card
|
|
*/
|
|
fatm_supply_small_buffers(sc);
|
|
fatm_supply_large_buffers(sc);
|
|
|
|
/*
|
|
* Now set flags, that we are ready
|
|
*/
|
|
sc->ifp->if_drv_flags |= IFF_DRV_RUNNING;
|
|
|
|
/*
|
|
* Start the watchdog timer
|
|
*/
|
|
callout_reset(&sc->watchdog_timer, hz * 5, fatm_watchdog, sc);
|
|
|
|
/* start SUNI */
|
|
utopia_start(&sc->utopia);
|
|
|
|
ATMEV_SEND_IFSTATE_CHANGED(IFP2IFATM(sc->ifp),
|
|
sc->utopia.carrier == UTP_CARR_OK);
|
|
|
|
/* start all channels */
|
|
for (i = 0; i < FORE_MAX_VCC + 1; i++)
|
|
if (sc->vccs[i] != NULL) {
|
|
sc->vccs[i]->vflags |= FATM_VCC_REOPEN;
|
|
error = fatm_load_vc(sc, sc->vccs[i]);
|
|
if (error != 0) {
|
|
if_printf(sc->ifp, "reopening %u "
|
|
"failed: %d\n", i, error);
|
|
sc->vccs[i]->vflags &= ~FATM_VCC_REOPEN;
|
|
}
|
|
}
|
|
|
|
DBG(sc, INIT, ("done"));
|
|
}
|
|
|
|
/*
|
|
* This is the exported as initialisation function.
|
|
*/
|
|
static void
|
|
fatm_init(void *p)
|
|
{
|
|
struct fatm_softc *sc = p;
|
|
|
|
FATM_LOCK(sc);
|
|
fatm_init_locked(sc);
|
|
FATM_UNLOCK(sc);
|
|
}
|
|
|
|
/************************************************************/
|
|
/*
|
|
* The INTERRUPT handling
|
|
*/
|
|
/*
|
|
* Check the command queue. If a command was completed, call the completion
|
|
* function for that command.
|
|
*/
|
|
static void
|
|
fatm_intr_drain_cmd(struct fatm_softc *sc)
|
|
{
|
|
struct cmdqueue *q;
|
|
int stat;
|
|
|
|
/*
|
|
* Drain command queue
|
|
*/
|
|
for (;;) {
|
|
q = GET_QUEUE(sc->cmdqueue, struct cmdqueue, sc->cmdqueue.tail);
|
|
|
|
H_SYNCSTAT_POSTREAD(sc, q->q.statp);
|
|
stat = H_GETSTAT(q->q.statp);
|
|
|
|
if (stat != FATM_STAT_COMPLETE &&
|
|
stat != (FATM_STAT_COMPLETE | FATM_STAT_ERROR) &&
|
|
stat != FATM_STAT_ERROR)
|
|
break;
|
|
|
|
(*q->cb)(sc, q);
|
|
|
|
H_SETSTAT(q->q.statp, FATM_STAT_FREE);
|
|
H_SYNCSTAT_PREWRITE(sc, q->q.statp);
|
|
|
|
NEXT_QUEUE_ENTRY(sc->cmdqueue.tail, FATM_CMD_QLEN);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Drain the small buffer supply queue.
|
|
*/
|
|
static void
|
|
fatm_intr_drain_small_buffers(struct fatm_softc *sc)
|
|
{
|
|
struct supqueue *q;
|
|
int stat;
|
|
|
|
for (;;) {
|
|
q = GET_QUEUE(sc->s1queue, struct supqueue, sc->s1queue.tail);
|
|
|
|
H_SYNCSTAT_POSTREAD(sc, q->q.statp);
|
|
stat = H_GETSTAT(q->q.statp);
|
|
|
|
if ((stat & FATM_STAT_COMPLETE) == 0)
|
|
break;
|
|
if (stat & FATM_STAT_ERROR)
|
|
log(LOG_ERR, "%s: status %x\n", __func__, stat);
|
|
|
|
H_SETSTAT(q->q.statp, FATM_STAT_FREE);
|
|
H_SYNCSTAT_PREWRITE(sc, q->q.statp);
|
|
|
|
NEXT_QUEUE_ENTRY(sc->s1queue.tail, SMALL_SUPPLY_QLEN);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Drain the large buffer supply queue.
|
|
*/
|
|
static void
|
|
fatm_intr_drain_large_buffers(struct fatm_softc *sc)
|
|
{
|
|
struct supqueue *q;
|
|
int stat;
|
|
|
|
for (;;) {
|
|
q = GET_QUEUE(sc->l1queue, struct supqueue, sc->l1queue.tail);
|
|
|
|
H_SYNCSTAT_POSTREAD(sc, q->q.statp);
|
|
stat = H_GETSTAT(q->q.statp);
|
|
|
|
if ((stat & FATM_STAT_COMPLETE) == 0)
|
|
break;
|
|
if (stat & FATM_STAT_ERROR)
|
|
log(LOG_ERR, "%s status %x\n", __func__, stat);
|
|
|
|
H_SETSTAT(q->q.statp, FATM_STAT_FREE);
|
|
H_SYNCSTAT_PREWRITE(sc, q->q.statp);
|
|
|
|
NEXT_QUEUE_ENTRY(sc->l1queue.tail, LARGE_SUPPLY_QLEN);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check the receive queue. Send any received PDU up the protocol stack
|
|
* (except when there was an error or the VCI appears to be closed. In this
|
|
* case discard the PDU).
|
|
*/
|
|
static void
|
|
fatm_intr_drain_rx(struct fatm_softc *sc)
|
|
{
|
|
struct rxqueue *q;
|
|
int stat, mlen;
|
|
u_int i;
|
|
uint32_t h;
|
|
struct mbuf *last, *m0;
|
|
struct rpd *rpd;
|
|
struct rbuf *rb;
|
|
u_int vci, vpi, pt;
|
|
struct atm_pseudohdr aph;
|
|
struct ifnet *ifp;
|
|
struct card_vcc *vc;
|
|
|
|
for (;;) {
|
|
q = GET_QUEUE(sc->rxqueue, struct rxqueue, sc->rxqueue.tail);
|
|
|
|
H_SYNCSTAT_POSTREAD(sc, q->q.statp);
|
|
stat = H_GETSTAT(q->q.statp);
|
|
|
|
if ((stat & FATM_STAT_COMPLETE) == 0)
|
|
break;
|
|
|
|
rpd = (struct rpd *)q->q.ioblk;
|
|
H_SYNCQ_POSTREAD(&sc->rxq_mem, rpd, RPD_SIZE);
|
|
|
|
rpd->nseg = le32toh(rpd->nseg);
|
|
mlen = 0;
|
|
m0 = last = 0;
|
|
for (i = 0; i < rpd->nseg; i++) {
|
|
rb = sc->rbufs + rpd->segment[i].handle;
|
|
if (m0 == NULL) {
|
|
m0 = last = rb->m;
|
|
} else {
|
|
last->m_next = rb->m;
|
|
last = rb->m;
|
|
}
|
|
last->m_next = NULL;
|
|
if (last->m_flags & M_EXT)
|
|
sc->large_cnt--;
|
|
else
|
|
sc->small_cnt--;
|
|
bus_dmamap_sync(sc->rbuf_tag, rb->map,
|
|
BUS_DMASYNC_POSTREAD);
|
|
bus_dmamap_unload(sc->rbuf_tag, rb->map);
|
|
rb->m = NULL;
|
|
|
|
LIST_REMOVE(rb, link);
|
|
LIST_INSERT_HEAD(&sc->rbuf_free, rb, link);
|
|
|
|
last->m_len = le32toh(rpd->segment[i].length);
|
|
mlen += last->m_len;
|
|
}
|
|
|
|
m0->m_pkthdr.len = mlen;
|
|
m0->m_pkthdr.rcvif = sc->ifp;
|
|
|
|
h = le32toh(rpd->atm_header);
|
|
vpi = (h >> 20) & 0xff;
|
|
vci = (h >> 4 ) & 0xffff;
|
|
pt = (h >> 1 ) & 0x7;
|
|
|
|
/*
|
|
* Locate the VCC this packet belongs to
|
|
*/
|
|
if (!VC_OK(sc, vpi, vci))
|
|
vc = NULL;
|
|
else if ((vc = sc->vccs[vci]) == NULL ||
|
|
!(sc->vccs[vci]->vflags & FATM_VCC_OPEN)) {
|
|
sc->istats.rx_closed++;
|
|
vc = NULL;
|
|
}
|
|
|
|
DBG(sc, RCV, ("RCV: vc=%u.%u pt=%u mlen=%d %s", vpi, vci,
|
|
pt, mlen, vc == NULL ? "dropped" : ""));
|
|
|
|
if (vc == NULL) {
|
|
m_freem(m0);
|
|
} else {
|
|
#ifdef ENABLE_BPF
|
|
if (!(vc->param.flags & ATMIO_FLAG_NG) &&
|
|
vc->param.aal == ATMIO_AAL_5 &&
|
|
(vc->param.flags & ATM_PH_LLCSNAP))
|
|
BPF_MTAP(sc->ifp, m0);
|
|
#endif
|
|
|
|
ATM_PH_FLAGS(&aph) = vc->param.flags;
|
|
ATM_PH_VPI(&aph) = vpi;
|
|
ATM_PH_SETVCI(&aph, vci);
|
|
|
|
ifp = sc->ifp;
|
|
if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1);
|
|
|
|
vc->ipackets++;
|
|
vc->ibytes += m0->m_pkthdr.len;
|
|
|
|
atm_input(ifp, &aph, m0, vc->rxhand);
|
|
}
|
|
|
|
H_SETSTAT(q->q.statp, FATM_STAT_FREE);
|
|
H_SYNCSTAT_PREWRITE(sc, q->q.statp);
|
|
|
|
WRITE4(sc, q->q.card, q->q.card_ioblk);
|
|
BARRIER_W(sc);
|
|
|
|
NEXT_QUEUE_ENTRY(sc->rxqueue.tail, FATM_RX_QLEN);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check the transmit queue. Free the mbuf chains that we were transmitting.
|
|
*/
|
|
static void
|
|
fatm_intr_drain_tx(struct fatm_softc *sc)
|
|
{
|
|
struct txqueue *q;
|
|
int stat;
|
|
|
|
/*
|
|
* Drain tx queue
|
|
*/
|
|
for (;;) {
|
|
q = GET_QUEUE(sc->txqueue, struct txqueue, sc->txqueue.tail);
|
|
|
|
H_SYNCSTAT_POSTREAD(sc, q->q.statp);
|
|
stat = H_GETSTAT(q->q.statp);
|
|
|
|
if (stat != FATM_STAT_COMPLETE &&
|
|
stat != (FATM_STAT_COMPLETE | FATM_STAT_ERROR) &&
|
|
stat != FATM_STAT_ERROR)
|
|
break;
|
|
|
|
H_SETSTAT(q->q.statp, FATM_STAT_FREE);
|
|
H_SYNCSTAT_PREWRITE(sc, q->q.statp);
|
|
|
|
bus_dmamap_sync(sc->tx_tag, q->map, BUS_DMASYNC_POSTWRITE);
|
|
bus_dmamap_unload(sc->tx_tag, q->map);
|
|
|
|
m_freem(q->m);
|
|
q->m = NULL;
|
|
sc->txcnt--;
|
|
|
|
NEXT_QUEUE_ENTRY(sc->txqueue.tail, FATM_TX_QLEN);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Interrupt handler
|
|
*/
|
|
static void
|
|
fatm_intr(void *p)
|
|
{
|
|
struct fatm_softc *sc = (struct fatm_softc *)p;
|
|
|
|
FATM_LOCK(sc);
|
|
if (!READ4(sc, FATMO_PSR)) {
|
|
FATM_UNLOCK(sc);
|
|
return;
|
|
}
|
|
WRITE4(sc, FATMO_HCR, FATM_HCR_CLRIRQ);
|
|
|
|
if (!(sc->ifp->if_drv_flags & IFF_DRV_RUNNING)) {
|
|
FATM_UNLOCK(sc);
|
|
return;
|
|
}
|
|
fatm_intr_drain_cmd(sc);
|
|
fatm_intr_drain_rx(sc);
|
|
fatm_intr_drain_tx(sc);
|
|
fatm_intr_drain_small_buffers(sc);
|
|
fatm_intr_drain_large_buffers(sc);
|
|
fatm_supply_small_buffers(sc);
|
|
fatm_supply_large_buffers(sc);
|
|
|
|
FATM_UNLOCK(sc);
|
|
|
|
if (sc->retry_tx && _IF_QLEN(&sc->ifp->if_snd))
|
|
(*sc->ifp->if_start)(sc->ifp);
|
|
}
|
|
|
|
/*
|
|
* Get device statistics. This must be called with the softc locked.
|
|
* We use a preallocated buffer, so we need to protect this buffer.
|
|
* We do this by using a condition variable and a flag. If the flag is set
|
|
* the buffer is in use by one thread (one thread is executing a GETSTAT
|
|
* card command). In this case all other threads that are trying to get
|
|
* statistics block on that condition variable. When the thread finishes
|
|
* using the buffer it resets the flag and signals the condition variable. This
|
|
* will wakeup the next thread that is waiting for the buffer. If the interface
|
|
* is stopped the stopping function will broadcast the cv. All threads will
|
|
* find that the interface has been stopped and return.
|
|
*
|
|
* Aquiring of the buffer is done by the fatm_getstat() function. The freeing
|
|
* must be done by the caller when he has finished using the buffer.
|
|
*/
|
|
static void
|
|
fatm_getstat_complete(struct fatm_softc *sc, struct cmdqueue *q)
|
|
{
|
|
|
|
H_SYNCSTAT_POSTREAD(sc, q->q.statp);
|
|
if (H_GETSTAT(q->q.statp) & FATM_STAT_ERROR) {
|
|
sc->istats.get_stat_errors++;
|
|
q->error = EIO;
|
|
}
|
|
wakeup(&sc->sadi_mem);
|
|
}
|
|
static int
|
|
fatm_getstat(struct fatm_softc *sc)
|
|
{
|
|
int error;
|
|
struct cmdqueue *q;
|
|
|
|
/*
|
|
* Wait until either the interface is stopped or we can get the
|
|
* statistics buffer
|
|
*/
|
|
for (;;) {
|
|
if (!(sc->ifp->if_drv_flags & IFF_DRV_RUNNING))
|
|
return (EIO);
|
|
if (!(sc->flags & FATM_STAT_INUSE))
|
|
break;
|
|
cv_wait(&sc->cv_stat, &sc->mtx);
|
|
}
|
|
sc->flags |= FATM_STAT_INUSE;
|
|
|
|
q = GET_QUEUE(sc->cmdqueue, struct cmdqueue, sc->cmdqueue.head);
|
|
|
|
H_SYNCSTAT_POSTREAD(sc, q->q.statp);
|
|
if (!(H_GETSTAT(q->q.statp) & FATM_STAT_FREE)) {
|
|
sc->istats.cmd_queue_full++;
|
|
return (EIO);
|
|
}
|
|
NEXT_QUEUE_ENTRY(sc->cmdqueue.head, FATM_CMD_QLEN);
|
|
|
|
q->error = 0;
|
|
q->cb = fatm_getstat_complete;
|
|
H_SETSTAT(q->q.statp, FATM_STAT_PENDING);
|
|
H_SYNCSTAT_PREWRITE(sc, q->q.statp);
|
|
|
|
bus_dmamap_sync(sc->sadi_mem.dmat, sc->sadi_mem.map,
|
|
BUS_DMASYNC_PREREAD);
|
|
|
|
WRITE4(sc, q->q.card + FATMOC_GSTAT_BUF,
|
|
sc->sadi_mem.paddr);
|
|
BARRIER_W(sc);
|
|
WRITE4(sc, q->q.card + FATMOC_OP,
|
|
FATM_OP_REQUEST_STATS | FATM_OP_INTERRUPT_SEL);
|
|
BARRIER_W(sc);
|
|
|
|
/*
|
|
* Wait for the command to complete
|
|
*/
|
|
error = msleep(&sc->sadi_mem, &sc->mtx, PZERO | PCATCH,
|
|
"fatm_stat", hz);
|
|
|
|
switch (error) {
|
|
|
|
case EWOULDBLOCK:
|
|
error = EIO;
|
|
break;
|
|
|
|
case ERESTART:
|
|
error = EINTR;
|
|
break;
|
|
|
|
case 0:
|
|
bus_dmamap_sync(sc->sadi_mem.dmat, sc->sadi_mem.map,
|
|
BUS_DMASYNC_POSTREAD);
|
|
error = q->error;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Swap statistics
|
|
*/
|
|
if (q->error == 0) {
|
|
u_int i;
|
|
uint32_t *p = (uint32_t *)sc->sadi_mem.mem;
|
|
|
|
for (i = 0; i < sizeof(struct fatm_stats) / sizeof(uint32_t);
|
|
i++, p++)
|
|
*p = be32toh(*p);
|
|
}
|
|
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Create a copy of a single mbuf. It can have either internal or
|
|
* external data, it may have a packet header. External data is really
|
|
* copied, so the new buffer is writeable.
|
|
*/
|
|
static struct mbuf *
|
|
copy_mbuf(struct mbuf *m)
|
|
{
|
|
struct mbuf *new;
|
|
|
|
MGET(new, M_NOWAIT, MT_DATA);
|
|
if (new == NULL)
|
|
return (NULL);
|
|
|
|
if (m->m_flags & M_PKTHDR) {
|
|
M_MOVE_PKTHDR(new, m);
|
|
if (m->m_len > MHLEN)
|
|
MCLGET(new, M_WAITOK);
|
|
} else {
|
|
if (m->m_len > MLEN)
|
|
MCLGET(new, M_WAITOK);
|
|
}
|
|
|
|
bcopy(m->m_data, new->m_data, m->m_len);
|
|
new->m_len = m->m_len;
|
|
new->m_flags &= ~M_RDONLY;
|
|
|
|
return (new);
|
|
}
|
|
|
|
/*
|
|
* All segments must have a four byte aligned buffer address and a four
|
|
* byte aligned length. Step through an mbuf chain and check these conditions.
|
|
* If the buffer address is not aligned and this is a normal mbuf, move
|
|
* the data down. Else make a copy of the mbuf with aligned data.
|
|
* If the buffer length is not aligned steel data from the next mbuf.
|
|
* We don't need to check whether this has more than one external reference,
|
|
* because steeling data doesn't change the external cluster.
|
|
* If the last mbuf is not aligned, fill with zeroes.
|
|
*
|
|
* Return packet length (well we should have this in the packet header),
|
|
* but be careful not to count the zero fill at the end.
|
|
*
|
|
* If fixing fails free the chain and zero the pointer.
|
|
*
|
|
* We assume, that aligning the virtual address also aligns the mapped bus
|
|
* address.
|
|
*/
|
|
static u_int
|
|
fatm_fix_chain(struct fatm_softc *sc, struct mbuf **mp)
|
|
{
|
|
struct mbuf *m = *mp, *prev = NULL, *next, *new;
|
|
u_int mlen = 0, fill = 0;
|
|
int first, off;
|
|
u_char *d, *cp;
|
|
|
|
do {
|
|
next = m->m_next;
|
|
|
|
if ((uintptr_t)mtod(m, void *) % 4 != 0 ||
|
|
(m->m_len % 4 != 0 && next)) {
|
|
/*
|
|
* Needs fixing
|
|
*/
|
|
first = (m == *mp);
|
|
|
|
d = mtod(m, u_char *);
|
|
if ((off = (uintptr_t)(void *)d % 4) != 0) {
|
|
if (M_WRITABLE(m)) {
|
|
sc->istats.fix_addr_copy++;
|
|
bcopy(d, d - off, m->m_len);
|
|
m->m_data = (caddr_t)(d - off);
|
|
} else {
|
|
if ((new = copy_mbuf(m)) == NULL) {
|
|
sc->istats.fix_addr_noext++;
|
|
goto fail;
|
|
}
|
|
sc->istats.fix_addr_ext++;
|
|
if (prev)
|
|
prev->m_next = new;
|
|
new->m_next = next;
|
|
m_free(m);
|
|
m = new;
|
|
}
|
|
}
|
|
|
|
if ((off = m->m_len % 4) != 0) {
|
|
if (!M_WRITABLE(m)) {
|
|
if ((new = copy_mbuf(m)) == NULL) {
|
|
sc->istats.fix_len_noext++;
|
|
goto fail;
|
|
}
|
|
sc->istats.fix_len_copy++;
|
|
if (prev)
|
|
prev->m_next = new;
|
|
new->m_next = next;
|
|
m_free(m);
|
|
m = new;
|
|
} else
|
|
sc->istats.fix_len++;
|
|
d = mtod(m, u_char *) + m->m_len;
|
|
off = 4 - off;
|
|
while (off) {
|
|
if (next == NULL) {
|
|
*d++ = 0;
|
|
fill++;
|
|
} else if (next->m_len == 0) {
|
|
sc->istats.fix_empty++;
|
|
next = m_free(next);
|
|
continue;
|
|
} else {
|
|
cp = mtod(next, u_char *);
|
|
*d++ = *cp++;
|
|
next->m_len--;
|
|
next->m_data = (caddr_t)cp;
|
|
}
|
|
off--;
|
|
m->m_len++;
|
|
}
|
|
}
|
|
|
|
if (first)
|
|
*mp = m;
|
|
}
|
|
|
|
mlen += m->m_len;
|
|
prev = m;
|
|
} while ((m = next) != NULL);
|
|
|
|
return (mlen - fill);
|
|
|
|
fail:
|
|
m_freem(*mp);
|
|
*mp = NULL;
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* The helper function is used to load the computed physical addresses
|
|
* into the transmit descriptor.
|
|
*/
|
|
static void
|
|
fatm_tpd_load(void *varg, bus_dma_segment_t *segs, int nsegs,
|
|
bus_size_t mapsize, int error)
|
|
{
|
|
struct tpd *tpd = varg;
|
|
|
|
if (error)
|
|
return;
|
|
|
|
KASSERT(nsegs <= TPD_EXTENSIONS + TXD_FIXED, ("too many segments"));
|
|
|
|
tpd->spec = 0;
|
|
while (nsegs--) {
|
|
H_SETDESC(tpd->segment[tpd->spec].buffer, segs->ds_addr);
|
|
H_SETDESC(tpd->segment[tpd->spec].length, segs->ds_len);
|
|
tpd->spec++;
|
|
segs++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Start output.
|
|
*
|
|
* Note, that we update the internal statistics without the lock here.
|
|
*/
|
|
static int
|
|
fatm_tx(struct fatm_softc *sc, struct mbuf *m, struct card_vcc *vc, u_int mlen)
|
|
{
|
|
struct txqueue *q;
|
|
u_int nblks;
|
|
int error, aal, nsegs;
|
|
struct tpd *tpd;
|
|
|
|
/*
|
|
* Get a queue element.
|
|
* If there isn't one - try to drain the transmit queue
|
|
* We used to sleep here if that doesn't help, but we
|
|
* should not sleep here, because we are called with locks.
|
|
*/
|
|
q = GET_QUEUE(sc->txqueue, struct txqueue, sc->txqueue.head);
|
|
|
|
H_SYNCSTAT_POSTREAD(sc, q->q.statp);
|
|
if (H_GETSTAT(q->q.statp) != FATM_STAT_FREE) {
|
|
fatm_intr_drain_tx(sc);
|
|
H_SYNCSTAT_POSTREAD(sc, q->q.statp);
|
|
if (H_GETSTAT(q->q.statp) != FATM_STAT_FREE) {
|
|
if (sc->retry_tx) {
|
|
sc->istats.tx_retry++;
|
|
IF_PREPEND(&sc->ifp->if_snd, m);
|
|
return (1);
|
|
}
|
|
sc->istats.tx_queue_full++;
|
|
m_freem(m);
|
|
return (0);
|
|
}
|
|
sc->istats.tx_queue_almost_full++;
|
|
}
|
|
|
|
tpd = q->q.ioblk;
|
|
|
|
m->m_data += sizeof(struct atm_pseudohdr);
|
|
m->m_len -= sizeof(struct atm_pseudohdr);
|
|
|
|
#ifdef ENABLE_BPF
|
|
if (!(vc->param.flags & ATMIO_FLAG_NG) &&
|
|
vc->param.aal == ATMIO_AAL_5 &&
|
|
(vc->param.flags & ATM_PH_LLCSNAP))
|
|
BPF_MTAP(sc->ifp, m);
|
|
#endif
|
|
|
|
/* map the mbuf */
|
|
error = bus_dmamap_load_mbuf(sc->tx_tag, q->map, m,
|
|
fatm_tpd_load, tpd, BUS_DMA_NOWAIT);
|
|
if(error) {
|
|
if_inc_counter(sc->ifp, IFCOUNTER_OERRORS, 1);
|
|
if_printf(sc->ifp, "mbuf loaded error=%d\n", error);
|
|
m_freem(m);
|
|
return (0);
|
|
}
|
|
nsegs = tpd->spec;
|
|
|
|
bus_dmamap_sync(sc->tx_tag, q->map, BUS_DMASYNC_PREWRITE);
|
|
|
|
/*
|
|
* OK. Now go and do it.
|
|
*/
|
|
aal = (vc->param.aal == ATMIO_AAL_5) ? 5 : 0;
|
|
|
|
H_SETSTAT(q->q.statp, FATM_STAT_PENDING);
|
|
H_SYNCSTAT_PREWRITE(sc, q->q.statp);
|
|
q->m = m;
|
|
|
|
/*
|
|
* If the transmit queue is almost full, schedule a
|
|
* transmit interrupt so that transmit descriptors can
|
|
* be recycled.
|
|
*/
|
|
H_SETDESC(tpd->spec, TDX_MKSPEC((sc->txcnt >=
|
|
(4 * FATM_TX_QLEN) / 5), aal, nsegs, mlen));
|
|
H_SETDESC(tpd->atm_header, TDX_MKHDR(vc->param.vpi,
|
|
vc->param.vci, 0, 0));
|
|
|
|
if (vc->param.traffic == ATMIO_TRAFFIC_UBR)
|
|
H_SETDESC(tpd->stream, 0);
|
|
else {
|
|
u_int i;
|
|
|
|
for (i = 0; i < RATE_TABLE_SIZE; i++)
|
|
if (rate_table[i].cell_rate < vc->param.tparam.pcr)
|
|
break;
|
|
if (i > 0)
|
|
i--;
|
|
H_SETDESC(tpd->stream, rate_table[i].ratio);
|
|
}
|
|
H_SYNCQ_PREWRITE(&sc->txq_mem, tpd, TPD_SIZE);
|
|
|
|
nblks = TDX_SEGS2BLKS(nsegs);
|
|
|
|
DBG(sc, XMIT, ("XMIT: mlen=%d spec=0x%x nsegs=%d blocks=%d",
|
|
mlen, le32toh(tpd->spec), nsegs, nblks));
|
|
|
|
WRITE4(sc, q->q.card + 0, q->q.card_ioblk | nblks);
|
|
BARRIER_W(sc);
|
|
|
|
sc->txcnt++;
|
|
if_inc_counter(sc->ifp, IFCOUNTER_OPACKETS, 1);
|
|
vc->obytes += m->m_pkthdr.len;
|
|
vc->opackets++;
|
|
|
|
NEXT_QUEUE_ENTRY(sc->txqueue.head, FATM_TX_QLEN);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
fatm_start(struct ifnet *ifp)
|
|
{
|
|
struct atm_pseudohdr aph;
|
|
struct fatm_softc *sc;
|
|
struct mbuf *m;
|
|
u_int mlen, vpi, vci;
|
|
struct card_vcc *vc;
|
|
|
|
sc = ifp->if_softc;
|
|
|
|
while (1) {
|
|
IF_DEQUEUE(&ifp->if_snd, m);
|
|
if (m == NULL)
|
|
break;
|
|
|
|
/*
|
|
* Loop through the mbuf chain and compute the total length
|
|
* of the packet. Check that all data pointer are
|
|
* 4 byte aligned. If they are not, call fatm_mfix to
|
|
* fix that problem. This comes more or less from the
|
|
* en driver.
|
|
*/
|
|
mlen = fatm_fix_chain(sc, &m);
|
|
if (m == NULL)
|
|
continue;
|
|
|
|
if (m->m_len < sizeof(struct atm_pseudohdr) &&
|
|
(m = m_pullup(m, sizeof(struct atm_pseudohdr))) == NULL)
|
|
continue;
|
|
|
|
aph = *mtod(m, struct atm_pseudohdr *);
|
|
mlen -= sizeof(struct atm_pseudohdr);
|
|
|
|
if (mlen == 0) {
|
|
m_freem(m);
|
|
continue;
|
|
}
|
|
if (mlen > FATM_MAXPDU) {
|
|
sc->istats.tx_pdu2big++;
|
|
m_freem(m);
|
|
continue;
|
|
}
|
|
|
|
vci = ATM_PH_VCI(&aph);
|
|
vpi = ATM_PH_VPI(&aph);
|
|
|
|
/*
|
|
* From here on we need the softc
|
|
*/
|
|
FATM_LOCK(sc);
|
|
if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) {
|
|
FATM_UNLOCK(sc);
|
|
m_freem(m);
|
|
break;
|
|
}
|
|
if (!VC_OK(sc, vpi, vci) || (vc = sc->vccs[vci]) == NULL ||
|
|
!(vc->vflags & FATM_VCC_OPEN)) {
|
|
FATM_UNLOCK(sc);
|
|
m_freem(m);
|
|
continue;
|
|
}
|
|
if (fatm_tx(sc, m, vc, mlen)) {
|
|
FATM_UNLOCK(sc);
|
|
break;
|
|
}
|
|
FATM_UNLOCK(sc);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* VCC managment
|
|
*
|
|
* This may seem complicated. The reason for this is, that we need an
|
|
* asynchronuous open/close for the NATM VCCs because our ioctl handler
|
|
* is called with the radix node head of the routing table locked. Therefor
|
|
* we cannot sleep there and wait for the open/close to succeed. For this
|
|
* reason we just initiate the operation from the ioctl.
|
|
*/
|
|
|
|
/*
|
|
* Command the card to open/close a VC.
|
|
* Return the queue entry for waiting if we are succesful.
|
|
*/
|
|
static struct cmdqueue *
|
|
fatm_start_vcc(struct fatm_softc *sc, u_int vpi, u_int vci, uint32_t cmd,
|
|
u_int mtu, void (*func)(struct fatm_softc *, struct cmdqueue *))
|
|
{
|
|
struct cmdqueue *q;
|
|
|
|
q = GET_QUEUE(sc->cmdqueue, struct cmdqueue, sc->cmdqueue.head);
|
|
|
|
H_SYNCSTAT_POSTREAD(sc, q->q.statp);
|
|
if (!(H_GETSTAT(q->q.statp) & FATM_STAT_FREE)) {
|
|
sc->istats.cmd_queue_full++;
|
|
return (NULL);
|
|
}
|
|
NEXT_QUEUE_ENTRY(sc->cmdqueue.head, FATM_CMD_QLEN);
|
|
|
|
q->error = 0;
|
|
q->cb = func;
|
|
H_SETSTAT(q->q.statp, FATM_STAT_PENDING);
|
|
H_SYNCSTAT_PREWRITE(sc, q->q.statp);
|
|
|
|
WRITE4(sc, q->q.card + FATMOC_ACTIN_VPVC, MKVPVC(vpi, vci));
|
|
BARRIER_W(sc);
|
|
WRITE4(sc, q->q.card + FATMOC_ACTIN_MTU, mtu);
|
|
BARRIER_W(sc);
|
|
WRITE4(sc, q->q.card + FATMOC_OP, cmd);
|
|
BARRIER_W(sc);
|
|
|
|
return (q);
|
|
}
|
|
|
|
/*
|
|
* The VC has been opened/closed and somebody has been waiting for this.
|
|
* Wake him up.
|
|
*/
|
|
static void
|
|
fatm_cmd_complete(struct fatm_softc *sc, struct cmdqueue *q)
|
|
{
|
|
|
|
H_SYNCSTAT_POSTREAD(sc, q->q.statp);
|
|
if (H_GETSTAT(q->q.statp) & FATM_STAT_ERROR) {
|
|
sc->istats.get_stat_errors++;
|
|
q->error = EIO;
|
|
}
|
|
wakeup(q);
|
|
}
|
|
|
|
/*
|
|
* Open complete
|
|
*/
|
|
static void
|
|
fatm_open_finish(struct fatm_softc *sc, struct card_vcc *vc)
|
|
{
|
|
vc->vflags &= ~FATM_VCC_TRY_OPEN;
|
|
vc->vflags |= FATM_VCC_OPEN;
|
|
|
|
if (vc->vflags & FATM_VCC_REOPEN) {
|
|
vc->vflags &= ~FATM_VCC_REOPEN;
|
|
return;
|
|
}
|
|
|
|
/* inform management if this is not an NG
|
|
* VCC or it's an NG PVC. */
|
|
if (!(vc->param.flags & ATMIO_FLAG_NG) ||
|
|
(vc->param.flags & ATMIO_FLAG_PVC))
|
|
ATMEV_SEND_VCC_CHANGED(IFP2IFATM(sc->ifp), 0, vc->param.vci, 1);
|
|
}
|
|
|
|
/*
|
|
* The VC that we have tried to open asynchronuosly has been opened.
|
|
*/
|
|
static void
|
|
fatm_open_complete(struct fatm_softc *sc, struct cmdqueue *q)
|
|
{
|
|
u_int vci;
|
|
struct card_vcc *vc;
|
|
|
|
vci = GETVCI(READ4(sc, q->q.card + FATMOC_ACTIN_VPVC));
|
|
vc = sc->vccs[vci];
|
|
H_SYNCSTAT_POSTREAD(sc, q->q.statp);
|
|
if (H_GETSTAT(q->q.statp) & FATM_STAT_ERROR) {
|
|
sc->istats.get_stat_errors++;
|
|
sc->vccs[vci] = NULL;
|
|
uma_zfree(sc->vcc_zone, vc);
|
|
if_printf(sc->ifp, "opening VCI %u failed\n", vci);
|
|
return;
|
|
}
|
|
fatm_open_finish(sc, vc);
|
|
}
|
|
|
|
/*
|
|
* Wait on the queue entry until the VCC is opened/closed.
|
|
*/
|
|
static int
|
|
fatm_waitvcc(struct fatm_softc *sc, struct cmdqueue *q)
|
|
{
|
|
int error;
|
|
|
|
/*
|
|
* Wait for the command to complete
|
|
*/
|
|
error = msleep(q, &sc->mtx, PZERO | PCATCH, "fatm_vci", hz);
|
|
|
|
if (error != 0)
|
|
return (error);
|
|
return (q->error);
|
|
}
|
|
|
|
/*
|
|
* Start to open a VCC. This just initiates the operation.
|
|
*/
|
|
static int
|
|
fatm_open_vcc(struct fatm_softc *sc, struct atmio_openvcc *op)
|
|
{
|
|
int error;
|
|
struct card_vcc *vc;
|
|
|
|
/*
|
|
* Check parameters
|
|
*/
|
|
if ((op->param.flags & ATMIO_FLAG_NOTX) &&
|
|
(op->param.flags & ATMIO_FLAG_NORX))
|
|
return (EINVAL);
|
|
|
|
if (!VC_OK(sc, op->param.vpi, op->param.vci))
|
|
return (EINVAL);
|
|
if (op->param.aal != ATMIO_AAL_0 && op->param.aal != ATMIO_AAL_5)
|
|
return (EINVAL);
|
|
|
|
vc = uma_zalloc(sc->vcc_zone, M_NOWAIT | M_ZERO);
|
|
if (vc == NULL)
|
|
return (ENOMEM);
|
|
|
|
error = 0;
|
|
|
|
FATM_LOCK(sc);
|
|
if (!(sc->ifp->if_drv_flags & IFF_DRV_RUNNING)) {
|
|
error = EIO;
|
|
goto done;
|
|
}
|
|
if (sc->vccs[op->param.vci] != NULL) {
|
|
error = EBUSY;
|
|
goto done;
|
|
}
|
|
vc->param = op->param;
|
|
vc->rxhand = op->rxhand;
|
|
|
|
switch (op->param.traffic) {
|
|
|
|
case ATMIO_TRAFFIC_UBR:
|
|
break;
|
|
|
|
case ATMIO_TRAFFIC_CBR:
|
|
if (op->param.tparam.pcr == 0 ||
|
|
op->param.tparam.pcr > IFP2IFATM(sc->ifp)->mib.pcr) {
|
|
error = EINVAL;
|
|
goto done;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
error = EINVAL;
|
|
goto done;
|
|
}
|
|
vc->ibytes = vc->obytes = 0;
|
|
vc->ipackets = vc->opackets = 0;
|
|
|
|
vc->vflags = FATM_VCC_TRY_OPEN;
|
|
sc->vccs[op->param.vci] = vc;
|
|
sc->open_vccs++;
|
|
|
|
error = fatm_load_vc(sc, vc);
|
|
if (error != 0) {
|
|
sc->vccs[op->param.vci] = NULL;
|
|
sc->open_vccs--;
|
|
goto done;
|
|
}
|
|
|
|
/* don't free below */
|
|
vc = NULL;
|
|
|
|
done:
|
|
FATM_UNLOCK(sc);
|
|
if (vc != NULL)
|
|
uma_zfree(sc->vcc_zone, vc);
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Try to initialize the given VC
|
|
*/
|
|
static int
|
|
fatm_load_vc(struct fatm_softc *sc, struct card_vcc *vc)
|
|
{
|
|
uint32_t cmd;
|
|
struct cmdqueue *q;
|
|
int error;
|
|
|
|
/* Command and buffer strategy */
|
|
cmd = FATM_OP_ACTIVATE_VCIN | FATM_OP_INTERRUPT_SEL | (0 << 16);
|
|
if (vc->param.aal == ATMIO_AAL_0)
|
|
cmd |= (0 << 8);
|
|
else
|
|
cmd |= (5 << 8);
|
|
|
|
q = fatm_start_vcc(sc, vc->param.vpi, vc->param.vci, cmd, 1,
|
|
(vc->param.flags & ATMIO_FLAG_ASYNC) ?
|
|
fatm_open_complete : fatm_cmd_complete);
|
|
if (q == NULL)
|
|
return (EIO);
|
|
|
|
if (!(vc->param.flags & ATMIO_FLAG_ASYNC)) {
|
|
error = fatm_waitvcc(sc, q);
|
|
if (error != 0)
|
|
return (error);
|
|
fatm_open_finish(sc, vc);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Finish close
|
|
*/
|
|
static void
|
|
fatm_close_finish(struct fatm_softc *sc, struct card_vcc *vc)
|
|
{
|
|
/* inform management of this is not an NG
|
|
* VCC or it's an NG PVC. */
|
|
if (!(vc->param.flags & ATMIO_FLAG_NG) ||
|
|
(vc->param.flags & ATMIO_FLAG_PVC))
|
|
ATMEV_SEND_VCC_CHANGED(IFP2IFATM(sc->ifp), 0, vc->param.vci, 0);
|
|
|
|
sc->vccs[vc->param.vci] = NULL;
|
|
sc->open_vccs--;
|
|
|
|
uma_zfree(sc->vcc_zone, vc);
|
|
}
|
|
|
|
/*
|
|
* The VC has been closed.
|
|
*/
|
|
static void
|
|
fatm_close_complete(struct fatm_softc *sc, struct cmdqueue *q)
|
|
{
|
|
u_int vci;
|
|
struct card_vcc *vc;
|
|
|
|
vci = GETVCI(READ4(sc, q->q.card + FATMOC_ACTIN_VPVC));
|
|
vc = sc->vccs[vci];
|
|
H_SYNCSTAT_POSTREAD(sc, q->q.statp);
|
|
if (H_GETSTAT(q->q.statp) & FATM_STAT_ERROR) {
|
|
sc->istats.get_stat_errors++;
|
|
/* keep the VCC in that state */
|
|
if_printf(sc->ifp, "closing VCI %u failed\n", vci);
|
|
return;
|
|
}
|
|
|
|
fatm_close_finish(sc, vc);
|
|
}
|
|
|
|
/*
|
|
* Initiate closing a VCC
|
|
*/
|
|
static int
|
|
fatm_close_vcc(struct fatm_softc *sc, struct atmio_closevcc *cl)
|
|
{
|
|
int error;
|
|
struct cmdqueue *q;
|
|
struct card_vcc *vc;
|
|
|
|
if (!VC_OK(sc, cl->vpi, cl->vci))
|
|
return (EINVAL);
|
|
|
|
error = 0;
|
|
|
|
FATM_LOCK(sc);
|
|
if (!(sc->ifp->if_drv_flags & IFF_DRV_RUNNING)) {
|
|
error = EIO;
|
|
goto done;
|
|
}
|
|
vc = sc->vccs[cl->vci];
|
|
if (vc == NULL || !(vc->vflags & (FATM_VCC_OPEN | FATM_VCC_TRY_OPEN))) {
|
|
error = ENOENT;
|
|
goto done;
|
|
}
|
|
|
|
q = fatm_start_vcc(sc, cl->vpi, cl->vci,
|
|
FATM_OP_DEACTIVATE_VCIN | FATM_OP_INTERRUPT_SEL, 1,
|
|
(vc->param.flags & ATMIO_FLAG_ASYNC) ?
|
|
fatm_close_complete : fatm_cmd_complete);
|
|
if (q == NULL) {
|
|
error = EIO;
|
|
goto done;
|
|
}
|
|
|
|
vc->vflags &= ~(FATM_VCC_OPEN | FATM_VCC_TRY_OPEN);
|
|
vc->vflags |= FATM_VCC_TRY_CLOSE;
|
|
|
|
if (!(vc->param.flags & ATMIO_FLAG_ASYNC)) {
|
|
error = fatm_waitvcc(sc, q);
|
|
if (error != 0)
|
|
goto done;
|
|
|
|
fatm_close_finish(sc, vc);
|
|
}
|
|
|
|
done:
|
|
FATM_UNLOCK(sc);
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* IOCTL handler
|
|
*/
|
|
static int
|
|
fatm_ioctl(struct ifnet *ifp, u_long cmd, caddr_t arg)
|
|
{
|
|
int error;
|
|
struct fatm_softc *sc = ifp->if_softc;
|
|
struct ifaddr *ifa = (struct ifaddr *)arg;
|
|
struct ifreq *ifr = (struct ifreq *)arg;
|
|
struct atmio_closevcc *cl = (struct atmio_closevcc *)arg;
|
|
struct atmio_openvcc *op = (struct atmio_openvcc *)arg;
|
|
struct atmio_vcctable *vtab;
|
|
|
|
error = 0;
|
|
switch (cmd) {
|
|
|
|
case SIOCATMOPENVCC: /* kernel internal use */
|
|
error = fatm_open_vcc(sc, op);
|
|
break;
|
|
|
|
case SIOCATMCLOSEVCC: /* kernel internal use */
|
|
error = fatm_close_vcc(sc, cl);
|
|
break;
|
|
|
|
case SIOCSIFADDR:
|
|
FATM_LOCK(sc);
|
|
ifp->if_flags |= IFF_UP;
|
|
if (!(ifp->if_drv_flags & IFF_DRV_RUNNING))
|
|
fatm_init_locked(sc);
|
|
switch (ifa->ifa_addr->sa_family) {
|
|
#ifdef INET
|
|
case AF_INET:
|
|
case AF_INET6:
|
|
ifa->ifa_rtrequest = atm_rtrequest;
|
|
break;
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
FATM_UNLOCK(sc);
|
|
break;
|
|
|
|
case SIOCSIFFLAGS:
|
|
FATM_LOCK(sc);
|
|
if (ifp->if_flags & IFF_UP) {
|
|
if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) {
|
|
fatm_init_locked(sc);
|
|
}
|
|
} else {
|
|
if (ifp->if_drv_flags & IFF_DRV_RUNNING) {
|
|
fatm_stop(sc);
|
|
}
|
|
}
|
|
FATM_UNLOCK(sc);
|
|
break;
|
|
|
|
case SIOCGIFMEDIA:
|
|
case SIOCSIFMEDIA:
|
|
if (ifp->if_drv_flags & IFF_DRV_RUNNING)
|
|
error = ifmedia_ioctl(ifp, ifr, &sc->media, cmd);
|
|
else
|
|
error = EINVAL;
|
|
break;
|
|
|
|
case SIOCATMGVCCS:
|
|
/* return vcc table */
|
|
vtab = atm_getvccs((struct atmio_vcc **)sc->vccs,
|
|
FORE_MAX_VCC + 1, sc->open_vccs, &sc->mtx, 1);
|
|
error = copyout(vtab, ifr->ifr_data, sizeof(*vtab) +
|
|
vtab->count * sizeof(vtab->vccs[0]));
|
|
free(vtab, M_DEVBUF);
|
|
break;
|
|
|
|
case SIOCATMGETVCCS: /* internal netgraph use */
|
|
vtab = atm_getvccs((struct atmio_vcc **)sc->vccs,
|
|
FORE_MAX_VCC + 1, sc->open_vccs, &sc->mtx, 0);
|
|
if (vtab == NULL) {
|
|
error = ENOMEM;
|
|
break;
|
|
}
|
|
*(void **)arg = vtab;
|
|
break;
|
|
|
|
default:
|
|
DBG(sc, IOCTL, ("+++ cmd=%08lx arg=%p", cmd, arg));
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Detach from the interface and free all resources allocated during
|
|
* initialisation and later.
|
|
*/
|
|
static int
|
|
fatm_detach(device_t dev)
|
|
{
|
|
u_int i;
|
|
struct rbuf *rb;
|
|
struct fatm_softc *sc;
|
|
struct txqueue *tx;
|
|
|
|
sc = device_get_softc(dev);
|
|
|
|
if (device_is_alive(dev)) {
|
|
FATM_LOCK(sc);
|
|
fatm_stop(sc);
|
|
utopia_detach(&sc->utopia);
|
|
FATM_UNLOCK(sc);
|
|
atm_ifdetach(sc->ifp); /* XXX race */
|
|
}
|
|
callout_drain(&sc->watchdog_timer);
|
|
|
|
if (sc->ih != NULL)
|
|
bus_teardown_intr(dev, sc->irqres, sc->ih);
|
|
|
|
while ((rb = LIST_FIRST(&sc->rbuf_used)) != NULL) {
|
|
if_printf(sc->ifp, "rbuf %p still in use!\n", rb);
|
|
bus_dmamap_unload(sc->rbuf_tag, rb->map);
|
|
m_freem(rb->m);
|
|
LIST_REMOVE(rb, link);
|
|
LIST_INSERT_HEAD(&sc->rbuf_free, rb, link);
|
|
}
|
|
|
|
if (sc->txqueue.chunk != NULL) {
|
|
for (i = 0; i < FATM_TX_QLEN; i++) {
|
|
tx = GET_QUEUE(sc->txqueue, struct txqueue, i);
|
|
bus_dmamap_destroy(sc->tx_tag, tx->map);
|
|
}
|
|
}
|
|
|
|
while ((rb = LIST_FIRST(&sc->rbuf_free)) != NULL) {
|
|
bus_dmamap_destroy(sc->rbuf_tag, rb->map);
|
|
LIST_REMOVE(rb, link);
|
|
}
|
|
|
|
if (sc->rbufs != NULL)
|
|
free(sc->rbufs, M_DEVBUF);
|
|
if (sc->vccs != NULL) {
|
|
for (i = 0; i < FORE_MAX_VCC + 1; i++)
|
|
if (sc->vccs[i] != NULL) {
|
|
uma_zfree(sc->vcc_zone, sc->vccs[i]);
|
|
sc->vccs[i] = NULL;
|
|
}
|
|
free(sc->vccs, M_DEVBUF);
|
|
}
|
|
if (sc->vcc_zone != NULL)
|
|
uma_zdestroy(sc->vcc_zone);
|
|
|
|
if (sc->l1queue.chunk != NULL)
|
|
free(sc->l1queue.chunk, M_DEVBUF);
|
|
if (sc->s1queue.chunk != NULL)
|
|
free(sc->s1queue.chunk, M_DEVBUF);
|
|
if (sc->rxqueue.chunk != NULL)
|
|
free(sc->rxqueue.chunk, M_DEVBUF);
|
|
if (sc->txqueue.chunk != NULL)
|
|
free(sc->txqueue.chunk, M_DEVBUF);
|
|
if (sc->cmdqueue.chunk != NULL)
|
|
free(sc->cmdqueue.chunk, M_DEVBUF);
|
|
|
|
destroy_dma_memory(&sc->reg_mem);
|
|
destroy_dma_memory(&sc->sadi_mem);
|
|
destroy_dma_memory(&sc->prom_mem);
|
|
#ifdef TEST_DMA_SYNC
|
|
destroy_dma_memoryX(&sc->s1q_mem);
|
|
destroy_dma_memoryX(&sc->l1q_mem);
|
|
destroy_dma_memoryX(&sc->rxq_mem);
|
|
destroy_dma_memoryX(&sc->txq_mem);
|
|
destroy_dma_memoryX(&sc->stat_mem);
|
|
#endif
|
|
|
|
if (sc->tx_tag != NULL)
|
|
if (bus_dma_tag_destroy(sc->tx_tag))
|
|
printf("tx DMA tag busy!\n");
|
|
|
|
if (sc->rbuf_tag != NULL)
|
|
if (bus_dma_tag_destroy(sc->rbuf_tag))
|
|
printf("rbuf DMA tag busy!\n");
|
|
|
|
if (sc->parent_dmat != NULL)
|
|
if (bus_dma_tag_destroy(sc->parent_dmat))
|
|
printf("parent DMA tag busy!\n");
|
|
|
|
if (sc->irqres != NULL)
|
|
bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irqres);
|
|
|
|
if (sc->memres != NULL)
|
|
bus_release_resource(dev, SYS_RES_MEMORY,
|
|
sc->memid, sc->memres);
|
|
|
|
(void)sysctl_ctx_free(&sc->sysctl_ctx);
|
|
|
|
cv_destroy(&sc->cv_stat);
|
|
cv_destroy(&sc->cv_regs);
|
|
|
|
mtx_destroy(&sc->mtx);
|
|
|
|
if_free(sc->ifp);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Sysctl handler
|
|
*/
|
|
static int
|
|
fatm_sysctl_istats(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
struct fatm_softc *sc = arg1;
|
|
u_long *ret;
|
|
int error;
|
|
|
|
ret = malloc(sizeof(sc->istats), M_TEMP, M_WAITOK);
|
|
|
|
FATM_LOCK(sc);
|
|
bcopy(&sc->istats, ret, sizeof(sc->istats));
|
|
FATM_UNLOCK(sc);
|
|
|
|
error = SYSCTL_OUT(req, ret, sizeof(sc->istats));
|
|
free(ret, M_TEMP);
|
|
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Sysctl handler for card statistics
|
|
* This is disable because it destroys the PHY statistics.
|
|
*/
|
|
static int
|
|
fatm_sysctl_stats(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
struct fatm_softc *sc = arg1;
|
|
int error;
|
|
const struct fatm_stats *s;
|
|
u_long *ret;
|
|
u_int i;
|
|
|
|
ret = malloc(sizeof(u_long) * FATM_NSTATS, M_TEMP, M_WAITOK);
|
|
|
|
FATM_LOCK(sc);
|
|
|
|
if ((error = fatm_getstat(sc)) == 0) {
|
|
s = sc->sadi_mem.mem;
|
|
i = 0;
|
|
ret[i++] = s->phy_4b5b.crc_header_errors;
|
|
ret[i++] = s->phy_4b5b.framing_errors;
|
|
ret[i++] = s->phy_oc3.section_bip8_errors;
|
|
ret[i++] = s->phy_oc3.path_bip8_errors;
|
|
ret[i++] = s->phy_oc3.line_bip24_errors;
|
|
ret[i++] = s->phy_oc3.line_febe_errors;
|
|
ret[i++] = s->phy_oc3.path_febe_errors;
|
|
ret[i++] = s->phy_oc3.corr_hcs_errors;
|
|
ret[i++] = s->phy_oc3.ucorr_hcs_errors;
|
|
ret[i++] = s->atm.cells_transmitted;
|
|
ret[i++] = s->atm.cells_received;
|
|
ret[i++] = s->atm.vpi_bad_range;
|
|
ret[i++] = s->atm.vpi_no_conn;
|
|
ret[i++] = s->atm.vci_bad_range;
|
|
ret[i++] = s->atm.vci_no_conn;
|
|
ret[i++] = s->aal0.cells_transmitted;
|
|
ret[i++] = s->aal0.cells_received;
|
|
ret[i++] = s->aal0.cells_dropped;
|
|
ret[i++] = s->aal4.cells_transmitted;
|
|
ret[i++] = s->aal4.cells_received;
|
|
ret[i++] = s->aal4.cells_crc_errors;
|
|
ret[i++] = s->aal4.cels_protocol_errors;
|
|
ret[i++] = s->aal4.cells_dropped;
|
|
ret[i++] = s->aal4.cspdus_transmitted;
|
|
ret[i++] = s->aal4.cspdus_received;
|
|
ret[i++] = s->aal4.cspdus_protocol_errors;
|
|
ret[i++] = s->aal4.cspdus_dropped;
|
|
ret[i++] = s->aal5.cells_transmitted;
|
|
ret[i++] = s->aal5.cells_received;
|
|
ret[i++] = s->aal5.congestion_experienced;
|
|
ret[i++] = s->aal5.cells_dropped;
|
|
ret[i++] = s->aal5.cspdus_transmitted;
|
|
ret[i++] = s->aal5.cspdus_received;
|
|
ret[i++] = s->aal5.cspdus_crc_errors;
|
|
ret[i++] = s->aal5.cspdus_protocol_errors;
|
|
ret[i++] = s->aal5.cspdus_dropped;
|
|
ret[i++] = s->aux.small_b1_failed;
|
|
ret[i++] = s->aux.large_b1_failed;
|
|
ret[i++] = s->aux.small_b2_failed;
|
|
ret[i++] = s->aux.large_b2_failed;
|
|
ret[i++] = s->aux.rpd_alloc_failed;
|
|
ret[i++] = s->aux.receive_carrier;
|
|
}
|
|
/* declare the buffer free */
|
|
sc->flags &= ~FATM_STAT_INUSE;
|
|
cv_signal(&sc->cv_stat);
|
|
|
|
FATM_UNLOCK(sc);
|
|
|
|
if (error == 0)
|
|
error = SYSCTL_OUT(req, ret, sizeof(u_long) * FATM_NSTATS);
|
|
free(ret, M_TEMP);
|
|
|
|
return (error);
|
|
}
|
|
|
|
#define MAXDMASEGS 32 /* maximum number of receive descriptors */
|
|
|
|
/*
|
|
* Attach to the device.
|
|
*
|
|
* We assume, that there is a global lock (Giant in this case) that protects
|
|
* multiple threads from entering this function. This makes sense, doesn't it?
|
|
*/
|
|
static int
|
|
fatm_attach(device_t dev)
|
|
{
|
|
struct ifnet *ifp;
|
|
struct fatm_softc *sc;
|
|
int unit;
|
|
uint16_t cfg;
|
|
int error = 0;
|
|
struct rbuf *rb;
|
|
u_int i;
|
|
struct txqueue *tx;
|
|
|
|
sc = device_get_softc(dev);
|
|
unit = device_get_unit(dev);
|
|
|
|
ifp = sc->ifp = if_alloc(IFT_ATM);
|
|
if (ifp == NULL) {
|
|
error = ENOSPC;
|
|
goto fail;
|
|
}
|
|
|
|
IFP2IFATM(sc->ifp)->mib.device = ATM_DEVICE_PCA200E;
|
|
IFP2IFATM(sc->ifp)->mib.serial = 0;
|
|
IFP2IFATM(sc->ifp)->mib.hw_version = 0;
|
|
IFP2IFATM(sc->ifp)->mib.sw_version = 0;
|
|
IFP2IFATM(sc->ifp)->mib.vpi_bits = 0;
|
|
IFP2IFATM(sc->ifp)->mib.vci_bits = FORE_VCIBITS;
|
|
IFP2IFATM(sc->ifp)->mib.max_vpcs = 0;
|
|
IFP2IFATM(sc->ifp)->mib.max_vccs = FORE_MAX_VCC;
|
|
IFP2IFATM(sc->ifp)->mib.media = IFM_ATM_UNKNOWN;
|
|
IFP2IFATM(sc->ifp)->phy = &sc->utopia;
|
|
|
|
LIST_INIT(&sc->rbuf_free);
|
|
LIST_INIT(&sc->rbuf_used);
|
|
|
|
/*
|
|
* Initialize mutex and condition variables.
|
|
*/
|
|
mtx_init(&sc->mtx, device_get_nameunit(dev),
|
|
MTX_NETWORK_LOCK, MTX_DEF);
|
|
|
|
cv_init(&sc->cv_stat, "fatm_stat");
|
|
cv_init(&sc->cv_regs, "fatm_regs");
|
|
|
|
sysctl_ctx_init(&sc->sysctl_ctx);
|
|
callout_init_mtx(&sc->watchdog_timer, &sc->mtx, 0);
|
|
|
|
/*
|
|
* Make the sysctl tree
|
|
*/
|
|
if ((sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx,
|
|
SYSCTL_STATIC_CHILDREN(_hw_atm), OID_AUTO,
|
|
device_get_nameunit(dev), CTLFLAG_RD, 0, "")) == NULL)
|
|
goto fail;
|
|
|
|
if (SYSCTL_ADD_PROC(&sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree),
|
|
OID_AUTO, "istats", CTLTYPE_ULONG | CTLFLAG_RD, sc, 0,
|
|
fatm_sysctl_istats, "LU", "internal statistics") == NULL)
|
|
goto fail;
|
|
|
|
if (SYSCTL_ADD_PROC(&sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree),
|
|
OID_AUTO, "stats", CTLTYPE_ULONG | CTLFLAG_RD, sc, 0,
|
|
fatm_sysctl_stats, "LU", "card statistics") == NULL)
|
|
goto fail;
|
|
|
|
if (SYSCTL_ADD_INT(&sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree),
|
|
OID_AUTO, "retry_tx", CTLFLAG_RW, &sc->retry_tx, 0,
|
|
"retry flag") == NULL)
|
|
goto fail;
|
|
|
|
#ifdef FATM_DEBUG
|
|
if (SYSCTL_ADD_UINT(&sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree),
|
|
OID_AUTO, "debug", CTLFLAG_RW, &sc->debug, 0, "debug flags")
|
|
== NULL)
|
|
goto fail;
|
|
sc->debug = FATM_DEBUG;
|
|
#endif
|
|
|
|
/*
|
|
* Network subsystem stuff
|
|
*/
|
|
ifp->if_softc = sc;
|
|
if_initname(ifp, device_get_name(dev), device_get_unit(dev));
|
|
ifp->if_flags = IFF_SIMPLEX;
|
|
ifp->if_ioctl = fatm_ioctl;
|
|
ifp->if_start = fatm_start;
|
|
ifp->if_init = fatm_init;
|
|
ifp->if_linkmib = &IFP2IFATM(sc->ifp)->mib;
|
|
ifp->if_linkmiblen = sizeof(IFP2IFATM(sc->ifp)->mib);
|
|
|
|
/*
|
|
* Enable busmaster
|
|
*/
|
|
pci_enable_busmaster(dev);
|
|
|
|
/*
|
|
* Map memory
|
|
*/
|
|
sc->memid = 0x10;
|
|
sc->memres = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->memid,
|
|
RF_ACTIVE);
|
|
if (sc->memres == NULL) {
|
|
if_printf(ifp, "could not map memory\n");
|
|
error = ENXIO;
|
|
goto fail;
|
|
}
|
|
sc->memh = rman_get_bushandle(sc->memres);
|
|
sc->memt = rman_get_bustag(sc->memres);
|
|
|
|
/*
|
|
* Convert endianess of slave access
|
|
*/
|
|
cfg = pci_read_config(dev, FATM_PCIR_MCTL, 1);
|
|
cfg |= FATM_PCIM_SWAB;
|
|
pci_write_config(dev, FATM_PCIR_MCTL, cfg, 1);
|
|
|
|
/*
|
|
* Allocate interrupt (activate at the end)
|
|
*/
|
|
sc->irqid = 0;
|
|
sc->irqres = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid,
|
|
RF_SHAREABLE | RF_ACTIVE);
|
|
if (sc->irqres == NULL) {
|
|
if_printf(ifp, "could not allocate irq\n");
|
|
error = ENXIO;
|
|
goto fail;
|
|
}
|
|
|
|
/*
|
|
* Allocate the parent DMA tag. This is used simply to hold overall
|
|
* restrictions for the controller (and PCI bus) and is never used
|
|
* to do anything.
|
|
*/
|
|
if (bus_dma_tag_create(bus_get_dma_tag(dev), 1, 0,
|
|
BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR,
|
|
NULL, NULL, BUS_SPACE_MAXSIZE_32BIT, MAXDMASEGS,
|
|
BUS_SPACE_MAXSIZE_32BIT, 0, NULL, NULL,
|
|
&sc->parent_dmat)) {
|
|
if_printf(ifp, "could not allocate parent DMA tag\n");
|
|
error = ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
/*
|
|
* Allocate the receive buffer DMA tag. This tag must map a maximum of
|
|
* a mbuf cluster.
|
|
*/
|
|
if (bus_dma_tag_create(sc->parent_dmat, 1, 0,
|
|
BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR,
|
|
NULL, NULL, MCLBYTES, 1, MCLBYTES, 0,
|
|
NULL, NULL, &sc->rbuf_tag)) {
|
|
if_printf(ifp, "could not allocate rbuf DMA tag\n");
|
|
error = ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
/*
|
|
* Allocate the transmission DMA tag. Must add 1, because
|
|
* rounded up PDU will be 65536 bytes long.
|
|
*/
|
|
if (bus_dma_tag_create(sc->parent_dmat, 1, 0,
|
|
BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR,
|
|
NULL, NULL,
|
|
FATM_MAXPDU + 1, TPD_EXTENSIONS + TXD_FIXED, MCLBYTES, 0,
|
|
NULL, NULL, &sc->tx_tag)) {
|
|
if_printf(ifp, "could not allocate tx DMA tag\n");
|
|
error = ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
/*
|
|
* Allocate DMAable memory.
|
|
*/
|
|
sc->stat_mem.size = sizeof(uint32_t) * (FATM_CMD_QLEN + FATM_TX_QLEN
|
|
+ FATM_RX_QLEN + SMALL_SUPPLY_QLEN + LARGE_SUPPLY_QLEN);
|
|
sc->stat_mem.align = 4;
|
|
|
|
sc->txq_mem.size = FATM_TX_QLEN * TPD_SIZE;
|
|
sc->txq_mem.align = 32;
|
|
|
|
sc->rxq_mem.size = FATM_RX_QLEN * RPD_SIZE;
|
|
sc->rxq_mem.align = 32;
|
|
|
|
sc->s1q_mem.size = SMALL_SUPPLY_QLEN *
|
|
BSUP_BLK2SIZE(SMALL_SUPPLY_BLKSIZE);
|
|
sc->s1q_mem.align = 32;
|
|
|
|
sc->l1q_mem.size = LARGE_SUPPLY_QLEN *
|
|
BSUP_BLK2SIZE(LARGE_SUPPLY_BLKSIZE);
|
|
sc->l1q_mem.align = 32;
|
|
|
|
#ifdef TEST_DMA_SYNC
|
|
if ((error = alloc_dma_memoryX(sc, "STATUS", &sc->stat_mem)) != 0 ||
|
|
(error = alloc_dma_memoryX(sc, "TXQ", &sc->txq_mem)) != 0 ||
|
|
(error = alloc_dma_memoryX(sc, "RXQ", &sc->rxq_mem)) != 0 ||
|
|
(error = alloc_dma_memoryX(sc, "S1Q", &sc->s1q_mem)) != 0 ||
|
|
(error = alloc_dma_memoryX(sc, "L1Q", &sc->l1q_mem)) != 0)
|
|
goto fail;
|
|
#else
|
|
if ((error = alloc_dma_memory(sc, "STATUS", &sc->stat_mem)) != 0 ||
|
|
(error = alloc_dma_memory(sc, "TXQ", &sc->txq_mem)) != 0 ||
|
|
(error = alloc_dma_memory(sc, "RXQ", &sc->rxq_mem)) != 0 ||
|
|
(error = alloc_dma_memory(sc, "S1Q", &sc->s1q_mem)) != 0 ||
|
|
(error = alloc_dma_memory(sc, "L1Q", &sc->l1q_mem)) != 0)
|
|
goto fail;
|
|
#endif
|
|
|
|
sc->prom_mem.size = sizeof(struct prom);
|
|
sc->prom_mem.align = 32;
|
|
if ((error = alloc_dma_memory(sc, "PROM", &sc->prom_mem)) != 0)
|
|
goto fail;
|
|
|
|
sc->sadi_mem.size = sizeof(struct fatm_stats);
|
|
sc->sadi_mem.align = 32;
|
|
if ((error = alloc_dma_memory(sc, "STATISTICS", &sc->sadi_mem)) != 0)
|
|
goto fail;
|
|
|
|
sc->reg_mem.size = sizeof(uint32_t) * FATM_NREGS;
|
|
sc->reg_mem.align = 32;
|
|
if ((error = alloc_dma_memory(sc, "REGISTERS", &sc->reg_mem)) != 0)
|
|
goto fail;
|
|
|
|
/*
|
|
* Allocate queues
|
|
*/
|
|
sc->cmdqueue.chunk = malloc(FATM_CMD_QLEN * sizeof(struct cmdqueue),
|
|
M_DEVBUF, M_ZERO | M_WAITOK);
|
|
sc->txqueue.chunk = malloc(FATM_TX_QLEN * sizeof(struct txqueue),
|
|
M_DEVBUF, M_ZERO | M_WAITOK);
|
|
sc->rxqueue.chunk = malloc(FATM_RX_QLEN * sizeof(struct rxqueue),
|
|
M_DEVBUF, M_ZERO | M_WAITOK);
|
|
sc->s1queue.chunk = malloc(SMALL_SUPPLY_QLEN * sizeof(struct supqueue),
|
|
M_DEVBUF, M_ZERO | M_WAITOK);
|
|
sc->l1queue.chunk = malloc(LARGE_SUPPLY_QLEN * sizeof(struct supqueue),
|
|
M_DEVBUF, M_ZERO | M_WAITOK);
|
|
|
|
sc->vccs = malloc((FORE_MAX_VCC + 1) * sizeof(sc->vccs[0]),
|
|
M_DEVBUF, M_ZERO | M_WAITOK);
|
|
sc->vcc_zone = uma_zcreate("FATM vccs", sizeof(struct card_vcc),
|
|
NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0);
|
|
if (sc->vcc_zone == NULL) {
|
|
error = ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
/*
|
|
* Allocate memory for the receive buffer headers. The total number
|
|
* of headers should probably also include the maximum number of
|
|
* buffers on the receive queue.
|
|
*/
|
|
sc->rbuf_total = SMALL_POOL_SIZE + LARGE_POOL_SIZE;
|
|
sc->rbufs = malloc(sc->rbuf_total * sizeof(struct rbuf),
|
|
M_DEVBUF, M_ZERO | M_WAITOK);
|
|
|
|
/*
|
|
* Put all rbuf headers on the free list and create DMA maps.
|
|
*/
|
|
for (rb = sc->rbufs, i = 0; i < sc->rbuf_total; i++, rb++) {
|
|
if ((error = bus_dmamap_create(sc->rbuf_tag, 0, &rb->map))) {
|
|
if_printf(sc->ifp, "creating rx map: %d\n",
|
|
error);
|
|
goto fail;
|
|
}
|
|
LIST_INSERT_HEAD(&sc->rbuf_free, rb, link);
|
|
}
|
|
|
|
/*
|
|
* Create dma maps for transmission. In case of an error, free the
|
|
* allocated DMA maps, because on some architectures maps are NULL
|
|
* and we cannot distinguish between a failure and a NULL map in
|
|
* the detach routine.
|
|
*/
|
|
for (i = 0; i < FATM_TX_QLEN; i++) {
|
|
tx = GET_QUEUE(sc->txqueue, struct txqueue, i);
|
|
if ((error = bus_dmamap_create(sc->tx_tag, 0, &tx->map))) {
|
|
if_printf(sc->ifp, "creating tx map: %d\n",
|
|
error);
|
|
while (i > 0) {
|
|
tx = GET_QUEUE(sc->txqueue, struct txqueue,
|
|
i - 1);
|
|
bus_dmamap_destroy(sc->tx_tag, tx->map);
|
|
i--;
|
|
}
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
utopia_attach(&sc->utopia, IFP2IFATM(sc->ifp), &sc->media, &sc->mtx,
|
|
&sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree),
|
|
&fatm_utopia_methods);
|
|
sc->utopia.flags |= UTP_FL_NORESET | UTP_FL_POLL_CARRIER;
|
|
|
|
/*
|
|
* Attach the interface
|
|
*/
|
|
atm_ifattach(ifp);
|
|
ifp->if_snd.ifq_maxlen = 512;
|
|
|
|
#ifdef ENABLE_BPF
|
|
bpfattach(ifp, DLT_ATM_RFC1483, sizeof(struct atmllc));
|
|
#endif
|
|
|
|
error = bus_setup_intr(dev, sc->irqres, INTR_TYPE_NET | INTR_MPSAFE,
|
|
NULL, fatm_intr, sc, &sc->ih);
|
|
if (error) {
|
|
if_printf(ifp, "couldn't setup irq\n");
|
|
goto fail;
|
|
}
|
|
|
|
fail:
|
|
if (error)
|
|
fatm_detach(dev);
|
|
|
|
return (error);
|
|
}
|
|
|
|
#if defined(FATM_DEBUG) && 0
|
|
static void
|
|
dump_s1_queue(struct fatm_softc *sc)
|
|
{
|
|
int i;
|
|
struct supqueue *q;
|
|
|
|
for(i = 0; i < SMALL_SUPPLY_QLEN; i++) {
|
|
q = GET_QUEUE(sc->s1queue, struct supqueue, i);
|
|
printf("%2d: card=%x(%x,%x) stat=%x\n", i,
|
|
q->q.card,
|
|
READ4(sc, q->q.card),
|
|
READ4(sc, q->q.card + 4),
|
|
*q->q.statp);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Driver infrastructure.
|
|
*/
|
|
static device_method_t fatm_methods[] = {
|
|
DEVMETHOD(device_probe, fatm_probe),
|
|
DEVMETHOD(device_attach, fatm_attach),
|
|
DEVMETHOD(device_detach, fatm_detach),
|
|
{ 0, 0 }
|
|
};
|
|
static driver_t fatm_driver = {
|
|
"fatm",
|
|
fatm_methods,
|
|
sizeof(struct fatm_softc),
|
|
};
|
|
|
|
DRIVER_MODULE(fatm, pci, fatm_driver, fatm_devclass, 0, 0);
|