root/src/add-ons/kernel/drivers/network/wlan/marvell88w8335/dev/malo/if_malo.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2008 Weongyo Jeong <weongyo@freebsd.org>
 * Copyright (c) 2007 Marvell Semiconductor, Inc.
 * Copyright (c) 2007 Sam Leffler, Errno Consulting
 * 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,
 *    without modification.
 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
 *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
 *    redistribution must be conditioned upon including a substantially
 *    similar Disclaimer requirement for further binary redistribution.
 *
 * NO WARRANTY
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
 */

#include <sys/cdefs.h>
#ifdef __FreeBSD__
#endif

#include "opt_malo.h"

#include <sys/param.h>
#include <sys/endian.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/sysctl.h>
#include <sys/taskqueue.h>

#include <machine/bus.h>
#include <sys/bus.h>

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

#include <net80211/ieee80211_var.h>
#include <net80211/ieee80211_regdomain.h>

#include <net/bpf.h>

#include <dev/malo/if_malo.h>

SYSCTL_NODE(_hw, OID_AUTO, malo, CTLFLAG_RD | CTLFLAG_MPSAFE, 0,
    "Marvell 88w8335 driver parameters");

static  int malo_txcoalesce = 8;        /* # tx pkts to q before poking f/w*/
SYSCTL_INT(_hw_malo, OID_AUTO, txcoalesce, CTLFLAG_RWTUN, &malo_txcoalesce,
            0, "tx buffers to send at once");
static  int malo_rxbuf = MALO_RXBUF;            /* # rx buffers to allocate */
SYSCTL_INT(_hw_malo, OID_AUTO, rxbuf, CTLFLAG_RWTUN, &malo_rxbuf,
            0, "rx buffers allocated");
static  int malo_rxquota = MALO_RXBUF;          /* # max buffers to process */
SYSCTL_INT(_hw_malo, OID_AUTO, rxquota, CTLFLAG_RWTUN, &malo_rxquota,
            0, "max rx buffers to process per interrupt");
static  int malo_txbuf = MALO_TXBUF;            /* # tx buffers to allocate */
SYSCTL_INT(_hw_malo, OID_AUTO, txbuf, CTLFLAG_RWTUN, &malo_txbuf,
            0, "tx buffers allocated");

#ifdef MALO_DEBUG
static  int malo_debug = 0;
SYSCTL_INT(_hw_malo, OID_AUTO, debug, CTLFLAG_RWTUN, &malo_debug,
            0, "control debugging printfs");
enum {
        MALO_DEBUG_XMIT         = 0x00000001,   /* basic xmit operation */
        MALO_DEBUG_XMIT_DESC    = 0x00000002,   /* xmit descriptors */
        MALO_DEBUG_RECV         = 0x00000004,   /* basic recv operation */
        MALO_DEBUG_RECV_DESC    = 0x00000008,   /* recv descriptors */
        MALO_DEBUG_RESET        = 0x00000010,   /* reset processing */
        MALO_DEBUG_INTR         = 0x00000040,   /* ISR */
        MALO_DEBUG_TX_PROC      = 0x00000080,   /* tx ISR proc */
        MALO_DEBUG_RX_PROC      = 0x00000100,   /* rx ISR proc */
        MALO_DEBUG_STATE        = 0x00000400,   /* 802.11 state transitions */
        MALO_DEBUG_NODE         = 0x00000800,   /* node management */
        MALO_DEBUG_RECV_ALL     = 0x00001000,   /* trace all frames (beacons) */
        MALO_DEBUG_FW           = 0x00008000,   /* firmware */
        MALO_DEBUG_ANY          = 0xffffffff
};
#define IFF_DUMPPKTS_RECV(sc, wh)                                       \
        (((sc->malo_debug & MALO_DEBUG_RECV) &&                         \
          ((sc->malo_debug & MALO_DEBUG_RECV_ALL) || !IEEE80211_IS_MGMT_BEACON(wh))))
#define IFF_DUMPPKTS_XMIT(sc)                                           \
        (sc->malo_debug & MALO_DEBUG_XMIT)
#define DPRINTF(sc, m, fmt, ...) do {                           \
        if (sc->malo_debug & (m))                               \
                printf(fmt, __VA_ARGS__);                       \
} while (0)
#else
#define DPRINTF(sc, m, fmt, ...) do {                           \
        (void) sc;                                              \
} while (0)
#endif

static MALLOC_DEFINE(M_MALODEV, "malodev", "malo driver dma buffers");

static struct ieee80211vap *malo_vap_create(struct ieee80211com *,
                    const char [IFNAMSIZ], int, enum ieee80211_opmode, int,
                    const uint8_t [IEEE80211_ADDR_LEN],
                    const uint8_t [IEEE80211_ADDR_LEN]);
static  void    malo_vap_delete(struct ieee80211vap *);
static  int     malo_dma_setup(struct malo_softc *);
static  int     malo_setup_hwdma(struct malo_softc *);
static  void    malo_txq_init(struct malo_softc *, struct malo_txq *, int);
static  void    malo_tx_cleanupq(struct malo_softc *, struct malo_txq *);
static  void    malo_parent(struct ieee80211com *);
static  int     malo_transmit(struct ieee80211com *, struct mbuf *);
static  void    malo_start(struct malo_softc *);
static  void    malo_watchdog(void *);
static  void    malo_updateslot(struct ieee80211com *);
static  int     malo_newstate(struct ieee80211vap *, enum ieee80211_state, int);
static  void    malo_scan_start(struct ieee80211com *);
static  void    malo_scan_end(struct ieee80211com *);
static  void    malo_set_channel(struct ieee80211com *);
static  int     malo_raw_xmit(struct ieee80211_node *, struct mbuf *,
                    const struct ieee80211_bpf_params *);
static  void    malo_sysctlattach(struct malo_softc *);
static  void    malo_announce(struct malo_softc *);
static  void    malo_dma_cleanup(struct malo_softc *);
static  void    malo_stop(struct malo_softc *);
static  int     malo_chan_set(struct malo_softc *, struct ieee80211_channel *);
static  int     malo_mode_init(struct malo_softc *);
static  void    malo_tx_proc(void *, int);
static  void    malo_rx_proc(void *, int);
static  void    malo_init(void *);

/*
 * Read/Write shorthands for accesses to BAR 0.  Note that all BAR 1
 * operations are done in the "hal" except getting H/W MAC address at
 * malo_attach and there should be no reference to them here.
 */
static uint32_t
malo_bar0_read4(struct malo_softc *sc, bus_size_t off)
{
        return bus_space_read_4(sc->malo_io0t, sc->malo_io0h, off);
}

static void
malo_bar0_write4(struct malo_softc *sc, bus_size_t off, uint32_t val)
{
        DPRINTF(sc, MALO_DEBUG_FW, "%s: off 0x%jx val 0x%x\n",
            __func__, (uintmax_t)off, val);

        bus_space_write_4(sc->malo_io0t, sc->malo_io0h, off, val);
}

int
malo_attach(uint16_t devid, struct malo_softc *sc)
{
        struct ieee80211com *ic = &sc->malo_ic;
        struct malo_hal *mh;
        int error;
        uint8_t bands[IEEE80211_MODE_BYTES];

        MALO_LOCK_INIT(sc);
        callout_init_mtx(&sc->malo_watchdog_timer, &sc->malo_mtx, 0);
        mbufq_init(&sc->malo_snd, ifqmaxlen);

        mh = malo_hal_attach(sc->malo_dev, devid,
            sc->malo_io1h, sc->malo_io1t, sc->malo_dmat);
        if (mh == NULL) {
                device_printf(sc->malo_dev, "unable to attach HAL\n");
                error = EIO;
                goto bad;
        }
        sc->malo_mh = mh;

        /*
         * Load firmware so we can get setup.  We arbitrarily pick station
         * firmware; we'll re-load firmware as needed so setting up
         * the wrong mode isn't a big deal.
         */
        error = malo_hal_fwload(mh, "malo8335-h", "malo8335-m");
        if (error != 0) {
                device_printf(sc->malo_dev, "unable to setup firmware\n");
                goto bad1;
        }
        /* XXX gethwspecs() extracts correct informations?  not maybe!  */
        error = malo_hal_gethwspecs(mh, &sc->malo_hwspecs);
        if (error != 0) {
                device_printf(sc->malo_dev, "unable to fetch h/w specs\n");
                goto bad1;
        }

        DPRINTF(sc, MALO_DEBUG_FW,
            "malo_hal_gethwspecs: hwversion 0x%x hostif 0x%x"
            "maxnum_wcb 0x%x maxnum_mcaddr 0x%x maxnum_tx_wcb 0x%x"
            "regioncode 0x%x num_antenna 0x%x fw_releasenum 0x%x"
            "wcbbase0 0x%x rxdesc_read 0x%x rxdesc_write 0x%x"
            "ul_fw_awakecookie 0x%x w[4] = %x %x %x %x",
            sc->malo_hwspecs.hwversion,
            sc->malo_hwspecs.hostinterface, sc->malo_hwspecs.maxnum_wcb,
            sc->malo_hwspecs.maxnum_mcaddr, sc->malo_hwspecs.maxnum_tx_wcb,
            sc->malo_hwspecs.regioncode, sc->malo_hwspecs.num_antenna,
            sc->malo_hwspecs.fw_releasenum, sc->malo_hwspecs.wcbbase0,
            sc->malo_hwspecs.rxdesc_read, sc->malo_hwspecs.rxdesc_write,
            sc->malo_hwspecs.ul_fw_awakecookie,
            sc->malo_hwspecs.wcbbase[0], sc->malo_hwspecs.wcbbase[1],
            sc->malo_hwspecs.wcbbase[2], sc->malo_hwspecs.wcbbase[3]);

        /* NB: firmware looks that it does not export regdomain info API.  */
        memset(bands, 0, sizeof(bands));
        setbit(bands, IEEE80211_MODE_11B);
        setbit(bands, IEEE80211_MODE_11G);
        ieee80211_init_channels(ic, NULL, bands);

        sc->malo_txantenna = 0x2;       /* h/w default */
        sc->malo_rxantenna = 0xffff;    /* h/w default */

        /*
         * Allocate tx + rx descriptors and populate the lists.
         * We immediately push the information to the firmware
         * as otherwise it gets upset.
         */
        error = malo_dma_setup(sc);
        if (error != 0) {
                device_printf(sc->malo_dev,
                    "failed to setup descriptors: %d\n", error);
                goto bad1;
        }
        error = malo_setup_hwdma(sc);   /* push to firmware */
        if (error != 0)                 /* NB: malo_setupdma prints msg */
                goto bad2;

        sc->malo_tq = taskqueue_create_fast("malo_taskq", M_NOWAIT,
                taskqueue_thread_enqueue, &sc->malo_tq);
        taskqueue_start_threads(&sc->malo_tq, 1, PI_NET,
                "%s taskq", device_get_nameunit(sc->malo_dev));

        NET_TASK_INIT(&sc->malo_rxtask, 0, malo_rx_proc, sc);
        TASK_INIT(&sc->malo_txtask, 0, malo_tx_proc, sc);

        ic->ic_softc = sc;
        ic->ic_name = device_get_nameunit(sc->malo_dev);
        /* XXX not right but it's not used anywhere important */
        ic->ic_phytype = IEEE80211_T_OFDM;
        ic->ic_opmode = IEEE80211_M_STA;
        ic->ic_caps =
              IEEE80211_C_STA                   /* station mode supported */
            | IEEE80211_C_BGSCAN                /* capable of bg scanning */
            | IEEE80211_C_MONITOR               /* monitor mode */
            | IEEE80211_C_SHPREAMBLE            /* short preamble supported */
            | IEEE80211_C_SHSLOT                /* short slot time supported */
            | IEEE80211_C_TXPMGT                /* capable of txpow mgt */
            | IEEE80211_C_WPA                   /* capable of WPA1+WPA2 */
            ;
        IEEE80211_ADDR_COPY(ic->ic_macaddr, sc->malo_hwspecs.macaddr);

        /*
         * Transmit requires space in the packet for a special format transmit
         * record and optional padding between this record and the payload.
         * Ask the net80211 layer to arrange this when encapsulating
         * packets so we can add it efficiently. 
         */
        ic->ic_headroom = sizeof(struct malo_txrec) -
                sizeof(struct ieee80211_frame);

        /* call MI attach routine. */
        ieee80211_ifattach(ic);
        /* override default methods */
        ic->ic_vap_create = malo_vap_create;
        ic->ic_vap_delete = malo_vap_delete;
        ic->ic_raw_xmit = malo_raw_xmit;
        ic->ic_updateslot = malo_updateslot;
        ic->ic_scan_start = malo_scan_start;
        ic->ic_scan_end = malo_scan_end;
        ic->ic_set_channel = malo_set_channel;
        ic->ic_parent = malo_parent;
        ic->ic_transmit = malo_transmit;

        sc->malo_invalid = 0;           /* ready to go, enable int handling */

        ieee80211_radiotap_attach(ic,
            &sc->malo_tx_th.wt_ihdr, sizeof(sc->malo_tx_th),
                MALO_TX_RADIOTAP_PRESENT,
            &sc->malo_rx_th.wr_ihdr, sizeof(sc->malo_rx_th),
                MALO_RX_RADIOTAP_PRESENT);

        /*
         * Setup dynamic sysctl's.
         */
        malo_sysctlattach(sc);

        if (bootverbose)
                ieee80211_announce(ic);
        malo_announce(sc);

        return 0;
bad2:
        malo_dma_cleanup(sc);
bad1:
        malo_hal_detach(mh);
bad:
        sc->malo_invalid = 1;

        return error;
}

static struct ieee80211vap *
malo_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit,
    enum ieee80211_opmode opmode, int flags,
    const uint8_t bssid[IEEE80211_ADDR_LEN],
    const uint8_t mac[IEEE80211_ADDR_LEN])
{
        struct malo_softc *sc = ic->ic_softc;
        struct malo_vap *mvp;
        struct ieee80211vap *vap;

        if (!TAILQ_EMPTY(&ic->ic_vaps)) {
                device_printf(sc->malo_dev, "multiple vaps not supported\n");
                return NULL;
        }
        switch (opmode) {
        case IEEE80211_M_STA:
                if (opmode == IEEE80211_M_STA)
                        flags |= IEEE80211_CLONE_NOBEACONS;
                /* fall thru... */
        case IEEE80211_M_MONITOR:
                break;
        default:
                device_printf(sc->malo_dev, "%s mode not supported\n",
                    ieee80211_opmode_name[opmode]);
                return NULL;            /* unsupported */
        }
        mvp = malloc(sizeof(struct malo_vap), M_80211_VAP, M_WAITOK | M_ZERO);
        vap = &mvp->malo_vap;
        ieee80211_vap_setup(ic, vap, name, unit, opmode, flags, bssid);

        /* override state transition machine */
        mvp->malo_newstate = vap->iv_newstate;
        vap->iv_newstate = malo_newstate;

        /* complete setup */
        ieee80211_vap_attach(vap,
            ieee80211_media_change, ieee80211_media_status, mac);
        ic->ic_opmode = opmode;
        return vap;
}

static void
malo_vap_delete(struct ieee80211vap *vap)
{
        struct malo_vap *mvp = MALO_VAP(vap);

        ieee80211_vap_detach(vap);
        free(mvp, M_80211_VAP);
}

int
malo_intr(void *arg)
{
        struct malo_softc *sc = arg;
        struct malo_hal *mh = sc->malo_mh;
        uint32_t status;

        if (sc->malo_invalid) {
                /*
                 * The hardware is not ready/present, don't touch anything.
                 * Note this can happen early on if the IRQ is shared.
                 */
                DPRINTF(sc, MALO_DEBUG_ANY, "%s: invalid; ignored\n", __func__);
                return (FILTER_STRAY);
        }

        /*
         * Figure out the reason(s) for the interrupt.
         */
        malo_hal_getisr(mh, &status);           /* NB: clears ISR too */
        if (status == 0)                        /* must be a shared irq */
                return (FILTER_STRAY);

        DPRINTF(sc, MALO_DEBUG_INTR, "%s: status 0x%x imask 0x%x\n",
            __func__, status, sc->malo_imask);

        if (status & MALO_A2HRIC_BIT_RX_RDY)
                taskqueue_enqueue(sc->malo_tq, &sc->malo_rxtask);
        if (status & MALO_A2HRIC_BIT_TX_DONE)
                taskqueue_enqueue(sc->malo_tq, &sc->malo_txtask);
        if (status & MALO_A2HRIC_BIT_OPC_DONE)
                malo_hal_cmddone(mh);
        if (status & MALO_A2HRIC_BIT_MAC_EVENT)
                ;
        if (status & MALO_A2HRIC_BIT_RX_PROBLEM)
                ;
        if (status & MALO_A2HRIC_BIT_ICV_ERROR) {
                /* TKIP ICV error */
                sc->malo_stats.mst_rx_badtkipicv++;
        }
#ifdef MALO_DEBUG
        if (((status | sc->malo_imask) ^ sc->malo_imask) != 0)
                DPRINTF(sc, MALO_DEBUG_INTR,
                    "%s: can't handle interrupt status 0x%x\n",
                    __func__, status);
#endif
        return (FILTER_HANDLED);
}

static void
malo_load_cb(void *arg, bus_dma_segment_t *segs, int nsegs, int error)
{
        bus_addr_t *paddr = (bus_addr_t*) arg;

        KASSERT(error == 0, ("error %u on bus_dma callback", error));

        *paddr = segs->ds_addr;
}

static int
malo_desc_setup(struct malo_softc *sc, const char *name,
    struct malo_descdma *dd,
    int nbuf, size_t bufsize, int ndesc, size_t descsize)
{
        int error;
        uint8_t *ds;

        DPRINTF(sc, MALO_DEBUG_RESET,
            "%s: %s DMA: %u bufs (%ju) %u desc/buf (%ju)\n",
            __func__, name, nbuf, (uintmax_t) bufsize,
            ndesc, (uintmax_t) descsize);
        
        dd->dd_name = name;
        dd->dd_desc_len = nbuf * ndesc * descsize;

        /*
         * Setup DMA descriptor area.
         */
        error = bus_dma_tag_create(bus_get_dma_tag(sc->malo_dev),/* parent */
                       PAGE_SIZE, 0,            /* alignment, bounds */
                       BUS_SPACE_MAXADDR_32BIT, /* lowaddr */
                       BUS_SPACE_MAXADDR,       /* highaddr */
                       NULL, NULL,              /* filter, filterarg */
                       dd->dd_desc_len,         /* maxsize */
                       1,                       /* nsegments */
                       dd->dd_desc_len,         /* maxsegsize */
                       BUS_DMA_ALLOCNOW,        /* flags */
                       NULL,                    /* lockfunc */
                       NULL,                    /* lockarg */
                       &dd->dd_dmat);
        if (error != 0) {
                device_printf(sc->malo_dev, "cannot allocate %s DMA tag\n",
                    dd->dd_name);
                return error;
        }
        
        /* allocate descriptors */
        error = bus_dmamem_alloc(dd->dd_dmat, (void**) &dd->dd_desc,
            BUS_DMA_NOWAIT | BUS_DMA_COHERENT, &dd->dd_dmamap);
        if (error != 0) {
                device_printf(sc->malo_dev,
                    "unable to alloc memory for %u %s descriptors, "
                    "error %u\n", nbuf * ndesc, dd->dd_name, error);
                goto fail1;
        }

        error = bus_dmamap_load(dd->dd_dmat, dd->dd_dmamap,
            dd->dd_desc, dd->dd_desc_len,
            malo_load_cb, &dd->dd_desc_paddr, BUS_DMA_NOWAIT);
        if (error != 0) {
                device_printf(sc->malo_dev,
                    "unable to map %s descriptors, error %u\n",
                    dd->dd_name, error);
                goto fail2;
        }
        
        ds = dd->dd_desc;
        memset(ds, 0, dd->dd_desc_len);
        DPRINTF(sc, MALO_DEBUG_RESET,
            "%s: %s DMA map: %p (%lu) -> 0x%jx (%lu)\n",
            __func__, dd->dd_name, ds, (u_long) dd->dd_desc_len,
            (uintmax_t) dd->dd_desc_paddr, /*XXX*/ (u_long) dd->dd_desc_len);

        return 0;
fail2:
        bus_dmamem_free(dd->dd_dmat, dd->dd_desc, dd->dd_dmamap);
fail1:
        bus_dma_tag_destroy(dd->dd_dmat);
        memset(dd, 0, sizeof(*dd));
        return error;
}

#define DS2PHYS(_dd, _ds) \
        ((_dd)->dd_desc_paddr + ((caddr_t)(_ds) - (caddr_t)(_dd)->dd_desc))

static int
malo_rxdma_setup(struct malo_softc *sc)
{
        int error, bsize, i;
        struct malo_rxbuf *bf;
        struct malo_rxdesc *ds;

        error = malo_desc_setup(sc, "rx", &sc->malo_rxdma,
            malo_rxbuf, sizeof(struct malo_rxbuf),
            1, sizeof(struct malo_rxdesc));
        if (error != 0)
                return error;

        /*
         * Allocate rx buffers and set them up.
         */
        bsize = malo_rxbuf * sizeof(struct malo_rxbuf);
        bf = malloc(bsize, M_MALODEV, M_NOWAIT | M_ZERO);
        if (bf == NULL) {
                device_printf(sc->malo_dev,
                    "malloc of %u rx buffers failed\n", bsize);
                return error;
        }
        sc->malo_rxdma.dd_bufptr = bf;
        
        STAILQ_INIT(&sc->malo_rxbuf);
        ds = sc->malo_rxdma.dd_desc;
        for (i = 0; i < malo_rxbuf; i++, bf++, ds++) {
                bf->bf_desc = ds;
                bf->bf_daddr = DS2PHYS(&sc->malo_rxdma, ds);
                error = bus_dmamap_create(sc->malo_dmat, BUS_DMA_NOWAIT,
                    &bf->bf_dmamap);
                if (error != 0) {
                        device_printf(sc->malo_dev,
                            "%s: unable to dmamap for rx buffer, error %d\n",
                            __func__, error);
                        return error;
                }
                /* NB: tail is intentional to preserve descriptor order */
                STAILQ_INSERT_TAIL(&sc->malo_rxbuf, bf, bf_list);
        }
        return 0;
}

static int
malo_txdma_setup(struct malo_softc *sc, struct malo_txq *txq)
{
        int error, bsize, i;
        struct malo_txbuf *bf;
        struct malo_txdesc *ds;

        error = malo_desc_setup(sc, "tx", &txq->dma,
            malo_txbuf, sizeof(struct malo_txbuf),
            MALO_TXDESC, sizeof(struct malo_txdesc));
        if (error != 0)
                return error;
        
        /* allocate and setup tx buffers */
        bsize = malo_txbuf * sizeof(struct malo_txbuf);
        bf = malloc(bsize, M_MALODEV, M_NOWAIT | M_ZERO);
        if (bf == NULL) {
                device_printf(sc->malo_dev, "malloc of %u tx buffers failed\n",
                    malo_txbuf);
                return ENOMEM;
        }
        txq->dma.dd_bufptr = bf;
        
        STAILQ_INIT(&txq->free);
        txq->nfree = 0;
        ds = txq->dma.dd_desc;
        for (i = 0; i < malo_txbuf; i++, bf++, ds += MALO_TXDESC) {
                bf->bf_desc = ds;
                bf->bf_daddr = DS2PHYS(&txq->dma, ds);
                error = bus_dmamap_create(sc->malo_dmat, BUS_DMA_NOWAIT,
                    &bf->bf_dmamap);
                if (error != 0) {
                        device_printf(sc->malo_dev,
                            "unable to create dmamap for tx "
                            "buffer %u, error %u\n", i, error);
                        return error;
                }
                STAILQ_INSERT_TAIL(&txq->free, bf, bf_list);
                txq->nfree++;
        }

        return 0;
}

static void
malo_desc_cleanup(struct malo_softc *sc, struct malo_descdma *dd)
{
        bus_dmamap_unload(dd->dd_dmat, dd->dd_dmamap);
        bus_dmamem_free(dd->dd_dmat, dd->dd_desc, dd->dd_dmamap);
        bus_dma_tag_destroy(dd->dd_dmat);

        memset(dd, 0, sizeof(*dd));
}

static void
malo_rxdma_cleanup(struct malo_softc *sc)
{
        struct malo_rxbuf *bf;

        STAILQ_FOREACH(bf, &sc->malo_rxbuf, bf_list) {
                if (bf->bf_m != NULL) {
                        m_freem(bf->bf_m);
                        bf->bf_m = NULL;
                }
                if (bf->bf_dmamap != NULL) {
                        bus_dmamap_destroy(sc->malo_dmat, bf->bf_dmamap);
                        bf->bf_dmamap = NULL;
                }
        }
        STAILQ_INIT(&sc->malo_rxbuf);
        if (sc->malo_rxdma.dd_bufptr != NULL) {
                free(sc->malo_rxdma.dd_bufptr, M_MALODEV);
                sc->malo_rxdma.dd_bufptr = NULL;
        }
        if (sc->malo_rxdma.dd_desc_len != 0)
                malo_desc_cleanup(sc, &sc->malo_rxdma);
}

static void
malo_txdma_cleanup(struct malo_softc *sc, struct malo_txq *txq)
{
        struct malo_txbuf *bf;
        struct ieee80211_node *ni;

        STAILQ_FOREACH(bf, &txq->free, bf_list) {
                if (bf->bf_m != NULL) {
                        m_freem(bf->bf_m);
                        bf->bf_m = NULL;
                }
                ni = bf->bf_node;
                bf->bf_node = NULL;
                if (ni != NULL) {
                        /*
                         * Reclaim node reference.
                         */
                        ieee80211_free_node(ni);
                }
                if (bf->bf_dmamap != NULL) {
                        bus_dmamap_destroy(sc->malo_dmat, bf->bf_dmamap);
                        bf->bf_dmamap = NULL;
                }
        }
        STAILQ_INIT(&txq->free);
        txq->nfree = 0;
        if (txq->dma.dd_bufptr != NULL) {
                free(txq->dma.dd_bufptr, M_MALODEV);
                txq->dma.dd_bufptr = NULL;
        }
        if (txq->dma.dd_desc_len != 0)
                malo_desc_cleanup(sc, &txq->dma);
}

static void
malo_dma_cleanup(struct malo_softc *sc)
{
        int i;

        for (i = 0; i < MALO_NUM_TX_QUEUES; i++)
                malo_txdma_cleanup(sc, &sc->malo_txq[i]);

        malo_rxdma_cleanup(sc);
}

static int
malo_dma_setup(struct malo_softc *sc)
{
        int error, i;

        /* rxdma initializing.  */
        error = malo_rxdma_setup(sc);
        if (error != 0)
                return error;

        /* NB: we just have 1 tx queue now.  */
        for (i = 0; i < MALO_NUM_TX_QUEUES; i++) {
                error = malo_txdma_setup(sc, &sc->malo_txq[i]);
                if (error != 0) {
                        malo_dma_cleanup(sc);

                        return error;
                }

                malo_txq_init(sc, &sc->malo_txq[i], i);
        }

        return 0;
}

static void
malo_hal_set_rxtxdma(struct malo_softc *sc)
{
        int i;

        malo_bar0_write4(sc, sc->malo_hwspecs.rxdesc_read,
            sc->malo_hwdma.rxdesc_read);
        malo_bar0_write4(sc, sc->malo_hwspecs.rxdesc_write,
            sc->malo_hwdma.rxdesc_read);

        for (i = 0; i < MALO_NUM_TX_QUEUES; i++) {
                malo_bar0_write4(sc,
                    sc->malo_hwspecs.wcbbase[i], sc->malo_hwdma.wcbbase[i]);
        }
}

/*
 * Inform firmware of our tx/rx dma setup.  The BAR 0 writes below are
 * for compatibility with older firmware.  For current firmware we send
 * this information with a cmd block via malo_hal_sethwdma.
 */
static int
malo_setup_hwdma(struct malo_softc *sc)
{
        int i;
        struct malo_txq *txq;

        sc->malo_hwdma.rxdesc_read = sc->malo_rxdma.dd_desc_paddr;

        for (i = 0; i < MALO_NUM_TX_QUEUES; i++) {
                txq = &sc->malo_txq[i];
                sc->malo_hwdma.wcbbase[i] = txq->dma.dd_desc_paddr;
        }
        sc->malo_hwdma.maxnum_txwcb = malo_txbuf;
        sc->malo_hwdma.maxnum_wcb = MALO_NUM_TX_QUEUES;

        malo_hal_set_rxtxdma(sc);

        return 0;
}

static void
malo_txq_init(struct malo_softc *sc, struct malo_txq *txq, int qnum)
{
        struct malo_txbuf *bf, *bn;
        struct malo_txdesc *ds;

        MALO_TXQ_LOCK_INIT(sc, txq);
        txq->qnum = qnum;
        txq->txpri = 0; /* XXX */

        STAILQ_FOREACH(bf, &txq->free, bf_list) {
                bf->bf_txq = txq;

                ds = bf->bf_desc;
                bn = STAILQ_NEXT(bf, bf_list);
                if (bn == NULL)
                        bn = STAILQ_FIRST(&txq->free);
                ds->physnext = htole32(bn->bf_daddr);
        }
        STAILQ_INIT(&txq->active);
}

/*
 * Reclaim resources for a setup queue.
 */
static void
malo_tx_cleanupq(struct malo_softc *sc, struct malo_txq *txq)
{
        /* XXX hal work? */
        MALO_TXQ_LOCK_DESTROY(txq);
}

/*
 * Allocate a tx buffer for sending a frame.
 */
static struct malo_txbuf *
malo_getbuf(struct malo_softc *sc, struct malo_txq *txq)
{
        struct malo_txbuf *bf;

        MALO_TXQ_LOCK(txq);
        bf = STAILQ_FIRST(&txq->free);
        if (bf != NULL) {
                STAILQ_REMOVE_HEAD(&txq->free, bf_list);
                txq->nfree--;
        }
        MALO_TXQ_UNLOCK(txq);
        if (bf == NULL) {
                DPRINTF(sc, MALO_DEBUG_XMIT,
                    "%s: out of xmit buffers on q %d\n", __func__, txq->qnum);
                sc->malo_stats.mst_tx_qstop++;
        }
        return bf;
}

static int
malo_tx_dmasetup(struct malo_softc *sc, struct malo_txbuf *bf, struct mbuf *m0)
{
        struct mbuf *m;
        int error;

        /*
         * Load the DMA map so any coalescing is done.  This also calculates
         * the number of descriptors we need.
         */
        error = bus_dmamap_load_mbuf_sg(sc->malo_dmat, bf->bf_dmamap, m0,
                                     bf->bf_segs, &bf->bf_nseg,
                                     BUS_DMA_NOWAIT);
        if (error == EFBIG) {
                /* XXX packet requires too many descriptors */
                bf->bf_nseg = MALO_TXDESC + 1;
        } else if (error != 0) {
                sc->malo_stats.mst_tx_busdma++;
                m_freem(m0);
                return error;
        }
        /*
         * Discard null packets and check for packets that require too many
         * TX descriptors.  We try to convert the latter to a cluster.
         */
        if (error == EFBIG) {           /* too many desc's, linearize */
                sc->malo_stats.mst_tx_linear++;
                m = m_defrag(m0, M_NOWAIT);
                if (m == NULL) {
                        m_freem(m0);
                        sc->malo_stats.mst_tx_nombuf++;
                        return ENOMEM;
                }
                m0 = m;
                error = bus_dmamap_load_mbuf_sg(sc->malo_dmat, bf->bf_dmamap, m0,
                                             bf->bf_segs, &bf->bf_nseg,
                                             BUS_DMA_NOWAIT);
                if (error != 0) {
                        sc->malo_stats.mst_tx_busdma++;
                        m_freem(m0);
                        return error;
                }
                KASSERT(bf->bf_nseg <= MALO_TXDESC,
                    ("too many segments after defrag; nseg %u", bf->bf_nseg));
        } else if (bf->bf_nseg == 0) {          /* null packet, discard */
                sc->malo_stats.mst_tx_nodata++;
                m_freem(m0);
                return EIO;
        }
        DPRINTF(sc, MALO_DEBUG_XMIT, "%s: m %p len %u\n",
                __func__, m0, m0->m_pkthdr.len);
        bus_dmamap_sync(sc->malo_dmat, bf->bf_dmamap, BUS_DMASYNC_PREWRITE);
        bf->bf_m = m0;

        return 0;
}

#ifdef MALO_DEBUG
static void
malo_printrxbuf(const struct malo_rxbuf *bf, u_int ix)
{
        const struct malo_rxdesc *ds = bf->bf_desc;
        uint32_t status = le32toh(ds->status);
        
        printf("R[%2u] (DS.V:%p DS.P:0x%jx) NEXT:%08x DATA:%08x RC:%02x%s\n"
            "      STAT:%02x LEN:%04x SNR:%02x NF:%02x CHAN:%02x"
            " RATE:%02x QOS:%04x\n", ix, ds, (uintmax_t)bf->bf_daddr,
            le32toh(ds->physnext), le32toh(ds->physbuffdata),
            ds->rxcontrol, 
            ds->rxcontrol != MALO_RXD_CTRL_DRIVER_OWN ?
                "" : (status & MALO_RXD_STATUS_OK) ? " *" : " !",
            ds->status, le16toh(ds->pktlen), ds->snr, ds->nf, ds->channel,
            ds->rate, le16toh(ds->qosctrl));
}

static void
malo_printtxbuf(const struct malo_txbuf *bf, u_int qnum, u_int ix)
{
        const struct malo_txdesc *ds = bf->bf_desc;
        uint32_t status = le32toh(ds->status);
        
        printf("Q%u[%3u]", qnum, ix);
        printf(" (DS.V:%p DS.P:0x%jx)\n", ds, (uintmax_t)bf->bf_daddr);
        printf("    NEXT:%08x DATA:%08x LEN:%04x STAT:%08x%s\n",
            le32toh(ds->physnext),
            le32toh(ds->pktptr), le16toh(ds->pktlen), status,
            status & MALO_TXD_STATUS_USED ?
            "" : (status & 3) != 0 ? " *" : " !");
        printf("    RATE:%02x PRI:%x QOS:%04x SAP:%08x FORMAT:%04x\n",
            ds->datarate, ds->txpriority, le16toh(ds->qosctrl),
            le32toh(ds->sap_pktinfo), le16toh(ds->format));
#if 0
        {
                const uint8_t *cp = (const uint8_t *) ds;
                int i;
                for (i = 0; i < sizeof(struct malo_txdesc); i++) {
                        printf("%02x ", cp[i]);
                        if (((i+1) % 16) == 0)
                                printf("\n");
                }
                printf("\n");
        }
#endif
}
#endif /* MALO_DEBUG */

static __inline void
malo_updatetxrate(struct ieee80211_node *ni, int rix)
{
        static const int ieeerates[] =
            { 2, 4, 11, 22, 44, 12, 18, 24, 36, 48, 96, 108 };
        if (rix < nitems(ieeerates))
                ieee80211_node_set_txrate_dot11rate(ni, ieeerates[rix]);
}

static int
malo_fix2rate(int fix_rate)
{
        static const int rates[] =
            { 2, 4, 11, 22, 12, 18, 24, 36, 48, 96, 108 };
        return (fix_rate < nitems(rates) ? rates[fix_rate] : 0);
}

/*
 * Process completed xmit descriptors from the specified queue.
 */
static int
malo_tx_processq(struct malo_softc *sc, struct malo_txq *txq)
{
        struct malo_txbuf *bf;
        struct malo_txdesc *ds;
        struct ieee80211_node *ni;
        int nreaped;
        uint32_t status;

        DPRINTF(sc, MALO_DEBUG_TX_PROC, "%s: tx queue %u\n",
            __func__, txq->qnum);
        for (nreaped = 0;; nreaped++) {
                MALO_TXQ_LOCK(txq);
                bf = STAILQ_FIRST(&txq->active);
                if (bf == NULL) {
                        MALO_TXQ_UNLOCK(txq);
                        break;
                }
                ds = bf->bf_desc;
                MALO_TXDESC_SYNC(txq, ds,
                    BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
                if (ds->status & htole32(MALO_TXD_STATUS_FW_OWNED)) {
                        MALO_TXQ_UNLOCK(txq);
                        break;
                }
                STAILQ_REMOVE_HEAD(&txq->active, bf_list);
                MALO_TXQ_UNLOCK(txq);

#ifdef MALO_DEBUG
                if (sc->malo_debug & MALO_DEBUG_XMIT_DESC)
                        malo_printtxbuf(bf, txq->qnum, nreaped);
#endif
                ni = bf->bf_node;
                if (ni != NULL) {
                        status = le32toh(ds->status);
                        if (status & MALO_TXD_STATUS_OK) {
                                uint16_t format = le16toh(ds->format);
                                uint8_t txant =_IEEE80211_MASKSHIFT(
                                    format, MALO_TXD_ANTENNA);

                                sc->malo_stats.mst_ant_tx[txant]++;
                                if (status & MALO_TXD_STATUS_OK_RETRY)
                                        sc->malo_stats.mst_tx_retries++;
                                if (status & MALO_TXD_STATUS_OK_MORE_RETRY)
                                        sc->malo_stats.mst_tx_mretries++;
                                malo_updatetxrate(ni, ds->datarate);
                                sc->malo_stats.mst_tx_rate = ds->datarate;
                        } else {
                                if (status & MALO_TXD_STATUS_FAILED_LINK_ERROR)
                                        sc->malo_stats.mst_tx_linkerror++;
                                if (status & MALO_TXD_STATUS_FAILED_XRETRY)
                                        sc->malo_stats.mst_tx_xretries++;
                                if (status & MALO_TXD_STATUS_FAILED_AGING)
                                        sc->malo_stats.mst_tx_aging++;
                        }
                        /* XXX strip fw len in case header inspected */
                        m_adj(bf->bf_m, sizeof(uint16_t));
                        ieee80211_tx_complete(ni, bf->bf_m, 
                            (status & MALO_TXD_STATUS_OK) == 0);
                } else
                        m_freem(bf->bf_m);

                ds->status = htole32(MALO_TXD_STATUS_IDLE);
                ds->pktlen = htole32(0);

                bus_dmamap_sync(sc->malo_dmat, bf->bf_dmamap,
                    BUS_DMASYNC_POSTWRITE);
                bus_dmamap_unload(sc->malo_dmat, bf->bf_dmamap);
                bf->bf_m = NULL;
                bf->bf_node = NULL;

                MALO_TXQ_LOCK(txq);
                STAILQ_INSERT_TAIL(&txq->free, bf, bf_list);
                txq->nfree++;
                MALO_TXQ_UNLOCK(txq);
        }
        return nreaped;
}

/*
 * Deferred processing of transmit interrupt.
 */
static void
malo_tx_proc(void *arg, int npending)
{
        struct malo_softc *sc = arg;
        int i, nreaped;

        /*
         * Process each active queue.
         */
        nreaped = 0;
        MALO_LOCK(sc);
        for (i = 0; i < MALO_NUM_TX_QUEUES; i++) {
                if (!STAILQ_EMPTY(&sc->malo_txq[i].active))
                        nreaped += malo_tx_processq(sc, &sc->malo_txq[i]);
        }

        if (nreaped != 0) {
                sc->malo_timer = 0;
                malo_start(sc);
        }
        MALO_UNLOCK(sc);
}

static int
malo_tx_start(struct malo_softc *sc, struct ieee80211_node *ni,
    struct malo_txbuf *bf, struct mbuf *m0)
{
        int error, iswep;
        int hdrlen, pktlen;
        struct ieee80211_frame *wh;
        struct ieee80211com *ic = &sc->malo_ic;
        struct ieee80211vap *vap = ni->ni_vap;
        struct malo_txdesc *ds;
        struct malo_txrec *tr;
        struct malo_txq *txq;
        uint16_t qos;

        wh = mtod(m0, struct ieee80211_frame *);
        iswep = wh->i_fc[1] & IEEE80211_FC1_PROTECTED;
        hdrlen = ieee80211_anyhdrsize(wh);
        pktlen = m0->m_pkthdr.len;
        if (IEEE80211_QOS_HAS_SEQ(wh)) {
                qos = *(uint16_t *)ieee80211_getqos(wh);
        } else
                qos = 0;

        if (iswep) {
                struct ieee80211_key *k;

                /*
                 * Construct the 802.11 header+trailer for an encrypted
                 * frame. The only reason this can fail is because of an
                 * unknown or unsupported cipher/key type.
                 *
                 * NB: we do this even though the firmware will ignore
                 *     what we've done for WEP and TKIP as we need the
                 *     ExtIV filled in for CCMP and this also adjusts
                 *     the headers which simplifies our work below.
                 */
                k = ieee80211_crypto_encap(ni, m0);
                if (k == NULL) {
                        /*
                         * This can happen when the key is yanked after the
                         * frame was queued.  Just discard the frame; the
                         * 802.11 layer counts failures and provides
                         * debugging/diagnostics.
                         */
                        m_freem(m0);
                        return EIO;
                }

                /*
                 * Adjust the packet length for the crypto additions
                 * done during encap and any other bits that the f/w
                 * will add later on.
                 */
                pktlen = m0->m_pkthdr.len;

                /* packet header may have moved, reset our local pointer */
                wh = mtod(m0, struct ieee80211_frame *);
        }

        if (ieee80211_radiotap_active_vap(vap)) {
                sc->malo_tx_th.wt_flags = 0;    /* XXX */
                if (iswep)
                        sc->malo_tx_th.wt_flags |= IEEE80211_RADIOTAP_F_WEP;
                sc->malo_tx_th.wt_txpower = ni->ni_txpower;
                sc->malo_tx_th.wt_antenna = sc->malo_txantenna;

                ieee80211_radiotap_tx(vap, m0);
        }

        /*
         * Copy up/down the 802.11 header; the firmware requires
         * we present a 2-byte payload length followed by a
         * 4-address header (w/o QoS), followed (optionally) by
         * any WEP/ExtIV header (but only filled in for CCMP).
         * We are assured the mbuf has sufficient headroom to
         * prepend in-place by the setup of ic_headroom in
         * malo_attach.
         */
        if (hdrlen < sizeof(struct malo_txrec)) {
                const int space = sizeof(struct malo_txrec) - hdrlen;
                if (M_LEADINGSPACE(m0) < space) {
                        /* NB: should never happen */
                        device_printf(sc->malo_dev,
                            "not enough headroom, need %d found %zd, "
                            "m_flags 0x%x m_len %d\n",
                            space, M_LEADINGSPACE(m0), m0->m_flags, m0->m_len);
                        ieee80211_dump_pkt(ic,
                            mtod(m0, const uint8_t *), m0->m_len, 0, -1);
                        m_freem(m0);
                        /* XXX stat */
                        return EIO;
                }
                M_PREPEND(m0, space, M_NOWAIT);
        }
        tr = mtod(m0, struct malo_txrec *);
        if (wh != (struct ieee80211_frame *) &tr->wh)
                ovbcopy(wh, &tr->wh, hdrlen);
        /*
         * Note: the "firmware length" is actually the length of the fully
         * formed "802.11 payload".  That is, it's everything except for
         * the 802.11 header.  In particular this includes all crypto
         * material including the MIC!
         */
        tr->fwlen = htole16(pktlen - hdrlen);

        /*
         * Load the DMA map so any coalescing is done.  This
         * also calculates the number of descriptors we need.
         */
        error = malo_tx_dmasetup(sc, bf, m0);
        if (error != 0)
                return error;
        bf->bf_node = ni;                       /* NB: held reference */
        m0 = bf->bf_m;                          /* NB: may have changed */
        tr = mtod(m0, struct malo_txrec *);
        wh = (struct ieee80211_frame *)&tr->wh;

        /*
         * Formulate tx descriptor.
         */
        ds = bf->bf_desc;
        txq = bf->bf_txq;

        ds->qosctrl = qos;                      /* NB: already little-endian */
        ds->pktptr = htole32(bf->bf_segs[0].ds_addr);
        ds->pktlen = htole16(bf->bf_segs[0].ds_len);
        /* NB: pPhysNext setup once, don't touch */
        ds->datarate = IEEE80211_IS_DATA(wh) ? 1 : 0;
        ds->sap_pktinfo = 0;
        ds->format = 0;

        /*
         * Select transmit rate.
         */
        switch (wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) {
        case IEEE80211_FC0_TYPE_MGT:
                sc->malo_stats.mst_tx_mgmt++;
                /* fall thru... */
        case IEEE80211_FC0_TYPE_CTL:
                ds->txpriority = 1;
                break;
        case IEEE80211_FC0_TYPE_DATA:
                ds->txpriority = txq->qnum;
                break;
        default:
                device_printf(sc->malo_dev, "bogus frame type 0x%x (%s)\n",
                        wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK, __func__);
                /* XXX statistic */
                m_freem(m0);
                return EIO;
        }

#ifdef MALO_DEBUG
        if (IFF_DUMPPKTS_XMIT(sc))
                ieee80211_dump_pkt(ic,
                    mtod(m0, const uint8_t *)+sizeof(uint16_t),
                    m0->m_len - sizeof(uint16_t), ds->datarate, -1);
#endif

        MALO_TXQ_LOCK(txq);
        if (!IEEE80211_IS_DATA(wh))
                ds->status |= htole32(1);
        ds->status |= htole32(MALO_TXD_STATUS_FW_OWNED);
        STAILQ_INSERT_TAIL(&txq->active, bf, bf_list);
        MALO_TXDESC_SYNC(txq, ds, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);

        sc->malo_timer = 5;
        MALO_TXQ_UNLOCK(txq);
        return 0;
}

static int
malo_transmit(struct ieee80211com *ic, struct mbuf *m)
{
        struct malo_softc *sc = ic->ic_softc;
        int error;

        MALO_LOCK(sc);
        if (!sc->malo_running) {
                MALO_UNLOCK(sc);
                return (ENXIO);
        }
        error = mbufq_enqueue(&sc->malo_snd, m);
        if (error) {
                MALO_UNLOCK(sc);
                return (error);
        }
        malo_start(sc);
        MALO_UNLOCK(sc);
        return (0);
}

static void
malo_start(struct malo_softc *sc)
{
        struct ieee80211_node *ni;
        struct malo_txq *txq = &sc->malo_txq[0];
        struct malo_txbuf *bf = NULL;
        struct mbuf *m;
        int nqueued = 0;

        MALO_LOCK_ASSERT(sc);

        if (!sc->malo_running || sc->malo_invalid)
                return;

        while ((m = mbufq_dequeue(&sc->malo_snd)) != NULL) {
                ni = (struct ieee80211_node *) m->m_pkthdr.rcvif;
                bf = malo_getbuf(sc, txq);
                if (bf == NULL) {
                        mbufq_prepend(&sc->malo_snd, m);
                        sc->malo_stats.mst_tx_qstop++;
                        break;
                }
                /*
                 * Pass the frame to the h/w for transmission.
                 */
                if (malo_tx_start(sc, ni, bf, m)) {
                        if_inc_counter(ni->ni_vap->iv_ifp,
                            IFCOUNTER_OERRORS, 1);
                        if (bf != NULL) {
                                bf->bf_m = NULL;
                                bf->bf_node = NULL;
                                MALO_TXQ_LOCK(txq);
                                STAILQ_INSERT_HEAD(&txq->free, bf, bf_list);
                                MALO_TXQ_UNLOCK(txq);
                        }
                        ieee80211_free_node(ni);
                        continue;
                }
                nqueued++;

                if (nqueued >= malo_txcoalesce) {
                        /*
                         * Poke the firmware to process queued frames;
                         * see below about (lack of) locking.
                         */
                        nqueued = 0;
                        malo_hal_txstart(sc->malo_mh, 0/*XXX*/);
                }
        }

        if (nqueued) {
                /*
                 * NB: We don't need to lock against tx done because
                 * this just prods the firmware to check the transmit
                 * descriptors.  The firmware will also start fetching
                 * descriptors by itself if it notices new ones are
                 * present when it goes to deliver a tx done interrupt
                 * to the host. So if we race with tx done processing
                 * it's ok.  Delivering the kick here rather than in
                 * malo_tx_start is an optimization to avoid poking the
                 * firmware for each packet.
                 *
                 * NB: the queue id isn't used so 0 is ok.
                 */
                malo_hal_txstart(sc->malo_mh, 0/*XXX*/);
        }
}

static void
malo_watchdog(void *arg)
{
        struct malo_softc *sc = arg;

        callout_reset(&sc->malo_watchdog_timer, hz, malo_watchdog, sc);
        if (sc->malo_timer == 0 || --sc->malo_timer > 0)
                return;

        if (sc->malo_running && !sc->malo_invalid) {
                device_printf(sc->malo_dev, "watchdog timeout\n");

                /* XXX no way to reset h/w. now  */

                counter_u64_add(sc->malo_ic.ic_oerrors, 1);
                sc->malo_stats.mst_watchdog++;
        }
}

static int
malo_hal_reset(struct malo_softc *sc)
{
        static int first = 0;
        struct ieee80211com *ic = &sc->malo_ic;
        struct malo_hal *mh = sc->malo_mh;

        if (first == 0) {
                /*
                 * NB: when the device firstly is initialized, sometimes
                 * firmware could override rx/tx dma registers so we re-set
                 * these values once.
                 */
                malo_hal_set_rxtxdma(sc);
                first = 1;
        }

        malo_hal_setantenna(mh, MHA_ANTENNATYPE_RX, sc->malo_rxantenna);
        malo_hal_setantenna(mh, MHA_ANTENNATYPE_TX, sc->malo_txantenna);
        malo_hal_setradio(mh, 1, MHP_AUTO_PREAMBLE);
        malo_chan_set(sc, ic->ic_curchan);

        /* XXX needs other stuffs?  */

        return 1;
}

static __inline struct mbuf *
malo_getrxmbuf(struct malo_softc *sc, struct malo_rxbuf *bf)
{
        struct mbuf *m;
        bus_addr_t paddr;
        int error;

        /* XXX don't need mbuf, just dma buffer */
        m = m_getjcl(M_NOWAIT, MT_DATA, M_PKTHDR, MJUMPAGESIZE);
        if (m == NULL) {
                sc->malo_stats.mst_rx_nombuf++; /* XXX */
                return NULL;
        }
        error = bus_dmamap_load(sc->malo_dmat, bf->bf_dmamap,
            mtod(m, caddr_t), MJUMPAGESIZE,
            malo_load_cb, &paddr, BUS_DMA_NOWAIT);
        if (error != 0) {
                device_printf(sc->malo_dev,
                    "%s: bus_dmamap_load failed, error %d\n", __func__, error);
                m_freem(m);
                return NULL;
        }
        bf->bf_data = paddr;
        bus_dmamap_sync(sc->malo_dmat, bf->bf_dmamap, BUS_DMASYNC_PREWRITE);

        return m;
}

static int
malo_rxbuf_init(struct malo_softc *sc, struct malo_rxbuf *bf)
{
        struct malo_rxdesc *ds;

        ds = bf->bf_desc;
        if (bf->bf_m == NULL) {
                bf->bf_m = malo_getrxmbuf(sc, bf);
                if (bf->bf_m == NULL) {
                        /* mark descriptor to be skipped */
                        ds->rxcontrol = MALO_RXD_CTRL_OS_OWN;
                        /* NB: don't need PREREAD */
                        MALO_RXDESC_SYNC(sc, ds, BUS_DMASYNC_PREWRITE);
                        return ENOMEM;
                }
        }

        /*
         * Setup descriptor.
         */
        ds->qosctrl = 0;
        ds->snr = 0;
        ds->status = MALO_RXD_STATUS_IDLE;
        ds->channel = 0;
        ds->pktlen = htole16(MALO_RXSIZE);
        ds->nf = 0;
        ds->physbuffdata = htole32(bf->bf_data);
        /* NB: don't touch pPhysNext, set once */
        ds->rxcontrol = MALO_RXD_CTRL_DRIVER_OWN;
        MALO_RXDESC_SYNC(sc, ds, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);

        return 0;
}

/*
 * Setup the rx data structures.  This should only be done once or we may get
 * out of sync with the firmware.
 */
static int
malo_startrecv(struct malo_softc *sc)
{
        struct malo_rxbuf *bf, *prev;
        struct malo_rxdesc *ds;
        
        if (sc->malo_recvsetup == 1) {
                malo_mode_init(sc);             /* set filters, etc. */
                return 0;
        }
        
        prev = NULL;
        STAILQ_FOREACH(bf, &sc->malo_rxbuf, bf_list) {
                int error = malo_rxbuf_init(sc, bf);
                if (error != 0) {
                        DPRINTF(sc, MALO_DEBUG_RECV,
                            "%s: malo_rxbuf_init failed %d\n",
                            __func__, error);
                        return error;
                }
                if (prev != NULL) {
                        ds = prev->bf_desc;
                        ds->physnext = htole32(bf->bf_daddr);
                }
                prev = bf;
        }
        if (prev != NULL) {
                ds = prev->bf_desc;
                ds->physnext =
                    htole32(STAILQ_FIRST(&sc->malo_rxbuf)->bf_daddr);
        }

        sc->malo_recvsetup = 1;

        malo_mode_init(sc);             /* set filters, etc. */
        
        return 0;
}

static void
malo_init_locked(struct malo_softc *sc)
{
        struct malo_hal *mh = sc->malo_mh;
        int error;
        
        MALO_LOCK_ASSERT(sc);
        
        /*
         * Stop anything previously setup.  This is safe whether this is
         * the first time through or not.
         */
        malo_stop(sc);

        /*
         * Push state to the firmware.
         */
        if (!malo_hal_reset(sc)) {
                device_printf(sc->malo_dev,
                    "%s: unable to reset hardware\n", __func__);
                return;
        }

        /*
         * Setup recv (once); transmit is already good to go.
         */
        error = malo_startrecv(sc);
        if (error != 0) {
                device_printf(sc->malo_dev,
                    "%s: unable to start recv logic, error %d\n",
                    __func__, error);
                return;
        }

        /*
         * Enable interrupts.
         */
        sc->malo_imask = MALO_A2HRIC_BIT_RX_RDY
            | MALO_A2HRIC_BIT_TX_DONE
            | MALO_A2HRIC_BIT_OPC_DONE
            | MALO_A2HRIC_BIT_MAC_EVENT
            | MALO_A2HRIC_BIT_RX_PROBLEM
            | MALO_A2HRIC_BIT_ICV_ERROR
            | MALO_A2HRIC_BIT_RADAR_DETECT
            | MALO_A2HRIC_BIT_CHAN_SWITCH;

        sc->malo_running = 1;
        malo_hal_intrset(mh, sc->malo_imask);
        callout_reset(&sc->malo_watchdog_timer, hz, malo_watchdog, sc);
}

static void
malo_init(void *arg)
{
        struct malo_softc *sc = (struct malo_softc *) arg;
        struct ieee80211com *ic = &sc->malo_ic;
        
        MALO_LOCK(sc);
        malo_init_locked(sc);
        MALO_UNLOCK(sc);

        if (sc->malo_running)
                ieee80211_start_all(ic);        /* start all vap's */
}

struct malo_copy_maddr_ctx {
        uint8_t macs[IEEE80211_ADDR_LEN * MALO_HAL_MCAST_MAX];
        int nmc;
};

static u_int
malo_copy_maddr(void *arg, struct sockaddr_dl *sdl, u_int nmc)
{
        struct malo_copy_maddr_ctx *ctx = arg;

        if (ctx->nmc == MALO_HAL_MCAST_MAX)
                return (0);

        IEEE80211_ADDR_COPY(ctx->macs + (ctx->nmc * IEEE80211_ADDR_LEN),
            LLADDR(sdl));
        ctx->nmc++;

        return (1);
}

/*
 * Set the multicast filter contents into the hardware.
 */
static void
malo_setmcastfilter(struct malo_softc *sc)
{
        struct malo_copy_maddr_ctx ctx;
        struct ieee80211com *ic = &sc->malo_ic;
        struct ieee80211vap *vap;


        if (ic->ic_opmode == IEEE80211_M_MONITOR || ic->ic_allmulti > 0 ||
            ic->ic_promisc > 0)
                goto all;

        ctx.nmc = 0;
        TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next)
                if_foreach_llmaddr(vap->iv_ifp, malo_copy_maddr, &ctx);

        malo_hal_setmcast(sc->malo_mh, ctx.nmc, ctx.macs);

all:
        /*
         * XXX we don't know how to set the f/w for supporting
         * IFF_ALLMULTI | IFF_PROMISC cases
         */
        return;
}

static int
malo_mode_init(struct malo_softc *sc)
{
        struct ieee80211com *ic = &sc->malo_ic;
        struct malo_hal *mh = sc->malo_mh;

        malo_hal_setpromisc(mh, ic->ic_promisc > 0);
        malo_setmcastfilter(sc);

        return ENXIO;
}

static void
malo_tx_draintxq(struct malo_softc *sc, struct malo_txq *txq)
{
        struct ieee80211_node *ni;
        struct malo_txbuf *bf;
        u_int ix __unused;
        
        /*
         * NB: this assumes output has been stopped and
         *     we do not need to block malo_tx_tasklet
         */
        for (ix = 0;; ix++) {
                MALO_TXQ_LOCK(txq);
                bf = STAILQ_FIRST(&txq->active);
                if (bf == NULL) {
                        MALO_TXQ_UNLOCK(txq);
                        break;
                }
                STAILQ_REMOVE_HEAD(&txq->active, bf_list);
                MALO_TXQ_UNLOCK(txq);
#ifdef MALO_DEBUG
                if (sc->malo_debug & MALO_DEBUG_RESET) {
                        struct ieee80211com *ic = &sc->malo_ic;
                        const struct malo_txrec *tr =
                            mtod(bf->bf_m, const struct malo_txrec *);
                        malo_printtxbuf(bf, txq->qnum, ix);
                        ieee80211_dump_pkt(ic, (const uint8_t *)&tr->wh,
                            bf->bf_m->m_len - sizeof(tr->fwlen), 0, -1);
                }
#endif /* MALO_DEBUG */
                bus_dmamap_unload(sc->malo_dmat, bf->bf_dmamap);
                ni = bf->bf_node;
                bf->bf_node = NULL;
                if (ni != NULL) {
                        /*
                         * Reclaim node reference.
                         */
                        ieee80211_free_node(ni);
                }
                m_freem(bf->bf_m);
                bf->bf_m = NULL;
                
                MALO_TXQ_LOCK(txq);
                STAILQ_INSERT_TAIL(&txq->free, bf, bf_list);
                txq->nfree++;
                MALO_TXQ_UNLOCK(txq);
        }
}

static void
malo_stop(struct malo_softc *sc)
{
        struct malo_hal *mh = sc->malo_mh;
        int i;

        DPRINTF(sc, MALO_DEBUG_ANY, "%s: invalid %u running %u\n",
            __func__, sc->malo_invalid, sc->malo_running);

        MALO_LOCK_ASSERT(sc);

        if (!sc->malo_running)
                return;

        /*
         * Shutdown the hardware and driver:
         *    disable interrupts
         *    turn off the radio
         *    drain and release tx queues
         *
         * Note that some of this work is not possible if the hardware
         * is gone (invalid).
         */
        sc->malo_running = 0;
        callout_stop(&sc->malo_watchdog_timer);
        sc->malo_timer = 0;
        /* disable interrupt.  */
        malo_hal_intrset(mh, 0);
        /* turn off the radio.  */
        malo_hal_setradio(mh, 0, MHP_AUTO_PREAMBLE);

        /* drain and release tx queues.  */
        for (i = 0; i < MALO_NUM_TX_QUEUES; i++)
                malo_tx_draintxq(sc, &sc->malo_txq[i]);
}

static void
malo_parent(struct ieee80211com *ic)
{
        struct malo_softc *sc = ic->ic_softc;
        int startall = 0;

        MALO_LOCK(sc);
        if (ic->ic_nrunning > 0) {
                /*
                 * Beware of being called during attach/detach
                 * to reset promiscuous mode.  In that case we
                 * will still be marked UP but not RUNNING.
                 * However trying to re-init the interface
                 * is the wrong thing to do as we've already
                 * torn down much of our state.  There's
                 * probably a better way to deal with this.
                 */
                if (!sc->malo_running && !sc->malo_invalid) {
                        malo_init(sc);
                        startall = 1;
                }
                /*
                 * To avoid rescanning another access point,
                 * do not call malo_init() here.  Instead,
                 * only reflect promisc mode settings.
                 */
                malo_mode_init(sc);
        } else if (sc->malo_running)
                malo_stop(sc);
        MALO_UNLOCK(sc);
        if (startall)
                ieee80211_start_all(ic);
}

/*
 * Callback from the 802.11 layer to update the slot time
 * based on the current setting.  We use it to notify the
 * firmware of ERP changes and the f/w takes care of things
 * like slot time and preamble.
 */
static void
malo_updateslot(struct ieee80211com *ic)
{
        struct malo_softc *sc = ic->ic_softc;
        struct malo_hal *mh = sc->malo_mh;
        int error;
        
        /* NB: can be called early; suppress needless cmds */
        if (!sc->malo_running)
                return;

        DPRINTF(sc, MALO_DEBUG_RESET,
            "%s: chan %u MHz/flags 0x%x %s slot, (ic_flags 0x%x)\n",
            __func__, ic->ic_curchan->ic_freq, ic->ic_curchan->ic_flags,
            ic->ic_flags & IEEE80211_F_SHSLOT ? "short" : "long", ic->ic_flags);

        if (ic->ic_flags & IEEE80211_F_SHSLOT)
                error = malo_hal_set_slot(mh, 1);
        else
                error = malo_hal_set_slot(mh, 0);

        if (error != 0)
                device_printf(sc->malo_dev, "setting %s slot failed\n",
                        ic->ic_flags & IEEE80211_F_SHSLOT ? "short" : "long");
}

static int
malo_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
{
        struct ieee80211com *ic = vap->iv_ic;
        struct malo_softc *sc = ic->ic_softc;
        struct malo_hal *mh = sc->malo_mh;
        int error;

        DPRINTF(sc, MALO_DEBUG_STATE, "%s: %s -> %s\n", __func__,
            ieee80211_state_name[vap->iv_state],
            ieee80211_state_name[nstate]);

        /*
         * Invoke the net80211 layer first so iv_bss is setup.
         */
        error = MALO_VAP(vap)->malo_newstate(vap, nstate, arg);
        if (error != 0)
                return error;

        if (nstate == IEEE80211_S_RUN && vap->iv_state != IEEE80211_S_RUN) {
                struct ieee80211_node *ni = vap->iv_bss;
                enum ieee80211_phymode mode = ieee80211_chan2mode(ni->ni_chan);
                const struct ieee80211_txparam *tp = &vap->iv_txparms[mode];

                DPRINTF(sc, MALO_DEBUG_STATE,
                    "%s: %s(RUN): iv_flags 0x%08x bintvl %d bssid %s "
                    "capinfo 0x%04x chan %d associd 0x%x mode %d rate %d\n",
                    if_name(vap->iv_ifp), __func__, vap->iv_flags,
                    ni->ni_intval, ether_sprintf(ni->ni_bssid), ni->ni_capinfo,
                    ieee80211_chan2ieee(ic, ic->ic_curchan),
                    ni->ni_associd, mode, tp->ucastrate);

                malo_hal_setradio(mh, 1,
                    (ic->ic_flags & IEEE80211_F_SHPREAMBLE) ?
                        MHP_SHORT_PREAMBLE : MHP_LONG_PREAMBLE);
                malo_hal_setassocid(sc->malo_mh, ni->ni_bssid, ni->ni_associd);
                malo_hal_set_rate(mh, mode, 
                   tp->ucastrate == IEEE80211_FIXED_RATE_NONE ?
                       0 : malo_fix2rate(tp->ucastrate));
        }
        return 0;
}

static int
malo_raw_xmit(struct ieee80211_node *ni, struct mbuf *m,
        const struct ieee80211_bpf_params *params)
{
        struct ieee80211com *ic = ni->ni_ic;
        struct malo_softc *sc = ic->ic_softc;
        struct malo_txbuf *bf;
        struct malo_txq *txq;

        if (!sc->malo_running || sc->malo_invalid) {
                m_freem(m);
                return ENETDOWN;
        }

        /*
         * Grab a TX buffer and associated resources.  Note that we depend
         * on the classification by the 802.11 layer to get to the right h/w
         * queue.  Management frames must ALWAYS go on queue 1 but we
         * cannot just force that here because we may receive non-mgt frames.
         */
        txq = &sc->malo_txq[0];
        bf = malo_getbuf(sc, txq);
        if (bf == NULL) {
                m_freem(m);
                return ENOBUFS;
        }

        /*
         * Pass the frame to the h/w for transmission.
         */
        if (malo_tx_start(sc, ni, bf, m) != 0) {
                bf->bf_m = NULL;
                bf->bf_node = NULL;
                MALO_TXQ_LOCK(txq);
                STAILQ_INSERT_HEAD(&txq->free, bf, bf_list);
                txq->nfree++;
                MALO_TXQ_UNLOCK(txq);

                return EIO;             /* XXX */
        }

        /*
         * NB: We don't need to lock against tx done because this just
         * prods the firmware to check the transmit descriptors.  The firmware
         * will also start fetching descriptors by itself if it notices
         * new ones are present when it goes to deliver a tx done interrupt
         * to the host. So if we race with tx done processing it's ok.
         * Delivering the kick here rather than in malo_tx_start is
         * an optimization to avoid poking the firmware for each packet.
         *
         * NB: the queue id isn't used so 0 is ok.
         */
        malo_hal_txstart(sc->malo_mh, 0/*XXX*/);

        return 0;
}

static void
malo_sysctlattach(struct malo_softc *sc)
{
#ifdef  MALO_DEBUG
        struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(sc->malo_dev);
        struct sysctl_oid *tree = device_get_sysctl_tree(sc->malo_dev);

        sc->malo_debug = malo_debug;
        SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
                "debug", CTLFLAG_RW, &sc->malo_debug, 0,
                "control debugging printfs");
#endif
}

static void
malo_announce(struct malo_softc *sc)
{

        device_printf(sc->malo_dev,
                "versions [hw %d fw %d.%d.%d.%d] (regioncode %d)\n",
                sc->malo_hwspecs.hwversion,
                (sc->malo_hwspecs.fw_releasenum >> 24) & 0xff,
                (sc->malo_hwspecs.fw_releasenum >> 16) & 0xff,
                (sc->malo_hwspecs.fw_releasenum >> 8) & 0xff,
                (sc->malo_hwspecs.fw_releasenum >> 0) & 0xff,
                sc->malo_hwspecs.regioncode);

        if (bootverbose || malo_rxbuf != MALO_RXBUF)
                device_printf(sc->malo_dev,
                    "using %u rx buffers\n", malo_rxbuf);
        if (bootverbose || malo_txbuf != MALO_TXBUF)
                device_printf(sc->malo_dev,
                    "using %u tx buffers\n", malo_txbuf);
}

/*
 * Convert net80211 channel to a HAL channel.
 */
static void
malo_mapchan(struct malo_hal_channel *hc, const struct ieee80211_channel *chan)
{
        hc->channel = chan->ic_ieee;

        *(uint32_t *)&hc->flags = 0;
        if (IEEE80211_IS_CHAN_2GHZ(chan))
                hc->flags.freqband = MALO_FREQ_BAND_2DOT4GHZ;
}

/*
 * Set/change channels.  If the channel is really being changed,
 * it's done by reseting the chip.  To accomplish this we must
 * first cleanup any pending DMA, then restart stuff after a la
 * malo_init.
 */
static int
malo_chan_set(struct malo_softc *sc, struct ieee80211_channel *chan)
{
        struct malo_hal *mh = sc->malo_mh;
        struct malo_hal_channel hchan;

        DPRINTF(sc, MALO_DEBUG_RESET, "%s: chan %u MHz/flags 0x%x\n",
            __func__, chan->ic_freq, chan->ic_flags);

        /*
         * Convert to a HAL channel description with the flags constrained
         * to reflect the current operating mode.
         */
        malo_mapchan(&hchan, chan);
        malo_hal_intrset(mh, 0);                /* disable interrupts */
        malo_hal_setchannel(mh, &hchan);
        malo_hal_settxpower(mh, &hchan);

        /*
         * Update internal state.
         */
        sc->malo_tx_th.wt_chan_freq = htole16(chan->ic_freq);
        sc->malo_rx_th.wr_chan_freq = htole16(chan->ic_freq);
        if (IEEE80211_IS_CHAN_ANYG(chan)) {
                sc->malo_tx_th.wt_chan_flags = htole16(IEEE80211_CHAN_G);
                sc->malo_rx_th.wr_chan_flags = htole16(IEEE80211_CHAN_G);
        } else {
                sc->malo_tx_th.wt_chan_flags = htole16(IEEE80211_CHAN_B);
                sc->malo_rx_th.wr_chan_flags = htole16(IEEE80211_CHAN_B);
        }
        sc->malo_curchan = hchan;
        malo_hal_intrset(mh, sc->malo_imask);

        return 0;
}

static void
malo_scan_start(struct ieee80211com *ic)
{
        struct malo_softc *sc = ic->ic_softc;

        DPRINTF(sc, MALO_DEBUG_STATE, "%s\n", __func__);
}

static void
malo_scan_end(struct ieee80211com *ic)
{
        struct malo_softc *sc = ic->ic_softc;

        DPRINTF(sc, MALO_DEBUG_STATE, "%s\n", __func__);
}

static void
malo_set_channel(struct ieee80211com *ic)
{
        struct malo_softc *sc = ic->ic_softc;

        (void) malo_chan_set(sc, ic->ic_curchan);
}

static void
malo_rx_proc(void *arg, int npending)
{
        struct malo_softc *sc = arg;
        struct ieee80211com *ic = &sc->malo_ic;
        struct malo_rxbuf *bf;
        struct malo_rxdesc *ds;
        struct mbuf *m, *mnew;
        struct ieee80211_qosframe *wh;
        struct ieee80211_node *ni;
        int off, len, hdrlen, pktlen, rssi, ntodo;
        uint8_t *data, status;
        uint32_t readptr, writeptr;

        DPRINTF(sc, MALO_DEBUG_RX_PROC,
            "%s: pending %u rdptr(0x%x) 0x%x wrptr(0x%x) 0x%x\n",
            __func__, npending,
            sc->malo_hwspecs.rxdesc_read,
            malo_bar0_read4(sc, sc->malo_hwspecs.rxdesc_read),
            sc->malo_hwspecs.rxdesc_write,
            malo_bar0_read4(sc, sc->malo_hwspecs.rxdesc_write));

        readptr = malo_bar0_read4(sc, sc->malo_hwspecs.rxdesc_read);
        writeptr = malo_bar0_read4(sc, sc->malo_hwspecs.rxdesc_write);
        if (readptr == writeptr)
                return;

        bf = sc->malo_rxnext;
        for (ntodo = malo_rxquota; ntodo > 0 && readptr != writeptr; ntodo--) {
                if (bf == NULL) {
                        bf = STAILQ_FIRST(&sc->malo_rxbuf);
                        break;
                }
                ds = bf->bf_desc;
                if (bf->bf_m == NULL) {
                        /*
                         * If data allocation failed previously there
                         * will be no buffer; try again to re-populate it.
                         * Note the firmware will not advance to the next
                         * descriptor with a dma buffer so we must mimic
                         * this or we'll get out of sync.
                         */ 
                        DPRINTF(sc, MALO_DEBUG_ANY,
                            "%s: rx buf w/o dma memory\n", __func__);
                        (void)malo_rxbuf_init(sc, bf);
                        break;
                }
                MALO_RXDESC_SYNC(sc, ds,
                    BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
                if (ds->rxcontrol != MALO_RXD_CTRL_DMA_OWN)
                        break;

                readptr = le32toh(ds->physnext);

#ifdef MALO_DEBUG
                if (sc->malo_debug & MALO_DEBUG_RECV_DESC)
                        malo_printrxbuf(bf, 0);
#endif
                status = ds->status;
                if (status & MALO_RXD_STATUS_DECRYPT_ERR_MASK) {
                        counter_u64_add(ic->ic_ierrors, 1);
                        goto rx_next;
                }
                /*
                 * Sync the data buffer.
                 */
                len = le16toh(ds->pktlen);
                bus_dmamap_sync(sc->malo_dmat, bf->bf_dmamap,
                    BUS_DMASYNC_POSTREAD);
                /*
                 * The 802.11 header is provided all or in part at the front;
                 * use it to calculate the true size of the header that we'll
                 * construct below.  We use this to figure out where to copy
                 * payload prior to constructing the header.
                 */
                m = bf->bf_m;
                data = mtod(m, uint8_t *);
                hdrlen = ieee80211_anyhdrsize(data + sizeof(uint16_t));
                off = sizeof(uint16_t) + sizeof(struct ieee80211_frame_addr4);

                /*
                 * Calculate RSSI. XXX wrong
                 */
                rssi = 2 * ((int) ds->snr - ds->nf);    /* NB: .5 dBm  */
                if (rssi > 100)
                        rssi = 100;

                pktlen = hdrlen + (len - off);
                /*
                 * NB: we know our frame is at least as large as
                 * IEEE80211_MIN_LEN because there is a 4-address frame at
                 * the front.  Hence there's no need to vet the packet length.
                 * If the frame in fact is too small it should be discarded
                 * at the net80211 layer.
                 */

                /* XXX don't need mbuf, just dma buffer */
                mnew = malo_getrxmbuf(sc, bf);
                if (mnew == NULL) {
                        counter_u64_add(ic->ic_ierrors, 1);
                        goto rx_next;
                }
                /*
                 * Attach the dma buffer to the mbuf; malo_rxbuf_init will
                 * re-setup the rx descriptor using the replacement dma
                 * buffer we just installed above.
                 */
                bf->bf_m = mnew;
                m->m_data += off - hdrlen;
                m->m_pkthdr.len = m->m_len = pktlen;

                /*
                 * Piece 802.11 header together.
                 */
                wh = mtod(m, struct ieee80211_qosframe *);
                /* NB: don't need to do this sometimes but ... */
                /* XXX special case so we can memcpy after m_devget? */
                ovbcopy(data + sizeof(uint16_t), wh, hdrlen);
                if (IEEE80211_QOS_HAS_SEQ(wh))
                        *(uint16_t *)ieee80211_getqos(wh) = ds->qosctrl;
                if (ieee80211_radiotap_active(ic)) {
                        sc->malo_rx_th.wr_flags = 0;
                        sc->malo_rx_th.wr_rate = ds->rate;
                        sc->malo_rx_th.wr_antsignal = rssi;
                        sc->malo_rx_th.wr_antnoise = ds->nf;
                }
#ifdef MALO_DEBUG
                if (IFF_DUMPPKTS_RECV(sc, wh)) {
                        ieee80211_dump_pkt(ic, mtod(m, caddr_t),
                            len, ds->rate, rssi);
                }
#endif
                /* dispatch */
                ni = ieee80211_find_rxnode(ic,
                    (struct ieee80211_frame_min *)wh);
                if (ni != NULL) {
                        (void) ieee80211_input(ni, m, rssi, ds->nf);
                        ieee80211_free_node(ni);
                } else
                        (void) ieee80211_input_all(ic, m, rssi, ds->nf);
rx_next:
                /* NB: ignore ENOMEM so we process more descriptors */
                (void) malo_rxbuf_init(sc, bf);
                bf = STAILQ_NEXT(bf, bf_list);
        }
        
        malo_bar0_write4(sc, sc->malo_hwspecs.rxdesc_read, readptr);
        sc->malo_rxnext = bf;

        if (mbufq_first(&sc->malo_snd) != NULL)
                malo_start(sc);
}

/*
 * Reclaim all tx queue resources.
 */
static void
malo_tx_cleanup(struct malo_softc *sc)
{
        int i;

        for (i = 0; i < MALO_NUM_TX_QUEUES; i++)
                malo_tx_cleanupq(sc, &sc->malo_txq[i]);
}

int
malo_detach(struct malo_softc *sc)
{
        struct ieee80211com *ic = &sc->malo_ic;

        malo_stop(sc);

        if (sc->malo_tq != NULL) {
                taskqueue_drain(sc->malo_tq, &sc->malo_rxtask);
                taskqueue_drain(sc->malo_tq, &sc->malo_txtask);
                taskqueue_free(sc->malo_tq);
                sc->malo_tq = NULL;
        }

        /*
         * NB: the order of these is important:
         * o call the 802.11 layer before detaching the hal to
         *   insure callbacks into the driver to delete global
         *   key cache entries can be handled
         * o reclaim the tx queue data structures after calling
         *   the 802.11 layer as we'll get called back to reclaim
         *   node state and potentially want to use them
         * o to cleanup the tx queues the hal is called, so detach
         *   it last
         * Other than that, it's straightforward...
         */
        ieee80211_ifdetach(ic);
        callout_drain(&sc->malo_watchdog_timer);
        malo_dma_cleanup(sc);
        malo_tx_cleanup(sc);
        malo_hal_detach(sc->malo_mh);
        mbufq_drain(&sc->malo_snd);
        MALO_LOCK_DESTROY(sc);

        return 0;
}

void
malo_shutdown(struct malo_softc *sc)
{

        malo_stop(sc);
}

void
malo_suspend(struct malo_softc *sc)
{

        malo_stop(sc);
}

void
malo_resume(struct malo_softc *sc)
{

        if (sc->malo_ic.ic_nrunning > 0)
                malo_init(sc);
}