root/sys/dev/ic/acx.c
/*      $OpenBSD: acx.c,v 1.128 2023/11/10 15:51:20 bluhm Exp $ */

/*
 * Copyright (c) 2006 Jonathan Gray <jsg@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * Copyright (c) 2006 The DragonFly Project.  All rights reserved.
 *
 * This code is derived from software contributed to The DragonFly Project
 * by Sepherosa Ziehau <sepherosa@gmail.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name of The DragonFly Project nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific, prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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
 * COPYRIGHT HOLDERS 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.
 */

/*
 * Copyright (c) 2003-2004 wlan.kewl.org Project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *
 *    This product includes software developed by the wlan.kewl.org Project.
 *
 * 4. Neither the name of the wlan.kewl.org Project nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``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 wlan.kewl.org Project BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "bpfilter.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/ioctl.h>
#include <sys/errno.h>
#include <sys/device.h>
#include <sys/endian.h>

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

#include <net/if.h>
#include <net/if_media.h>

#if NBPFILTER > 0
#include <net/bpf.h>
#endif

#include <netinet/in.h>
#include <netinet/if_ether.h>

#include <net80211/ieee80211_var.h>
#include <net80211/ieee80211_amrr.h>
#include <net80211/ieee80211_radiotap.h>

#include <dev/ic/acxvar.h>
#include <dev/ic/acxreg.h>

#ifdef ACX_DEBUG
int acxdebug = 0;
#endif

int      acx_attach(struct acx_softc *);
int      acx_detach(void *);

int      acx_init(struct ifnet *);
int      acx_stop(struct acx_softc *);
void     acx_init_info_reg(struct acx_softc *);
int      acx_config(struct acx_softc *);
int      acx_read_config(struct acx_softc *, struct acx_config *);
int      acx_write_config(struct acx_softc *, struct acx_config *);
int      acx_rx_config(struct acx_softc *);
int      acx_set_crypt_keys(struct acx_softc *);
void     acx_next_scan(void *);

void     acx_start(struct ifnet *);
void     acx_watchdog(struct ifnet *);

int      acx_ioctl(struct ifnet *, u_long, caddr_t);

int      acx_intr(void *);
void     acx_disable_intr(struct acx_softc *);
void     acx_enable_intr(struct acx_softc *);
void     acx_txeof(struct acx_softc *);
void     acx_txerr(struct acx_softc *, uint8_t);
void     acx_rxeof(struct acx_softc *);

int      acx_dma_alloc(struct acx_softc *);
void     acx_dma_free(struct acx_softc *);
void     acx_init_tx_ring(struct acx_softc *);
int      acx_init_rx_ring(struct acx_softc *);
int      acx_newbuf(struct acx_softc *, struct acx_rxbuf *, int);
int      acx_encap(struct acx_softc *, struct acx_txbuf *,
             struct mbuf *, struct ieee80211_node *, int);

int      acx_reset(struct acx_softc *);

int      acx_set_null_tmplt(struct acx_softc *);
int      acx_set_probe_req_tmplt(struct acx_softc *, const char *, int);
#ifndef IEEE80211_STA_ONLY
int      acx_set_probe_resp_tmplt(struct acx_softc *, struct ieee80211_node *);
int      acx_beacon_locate(struct mbuf *, u_int8_t);
int      acx_set_beacon_tmplt(struct acx_softc *, struct ieee80211_node *);
#endif

int      acx_read_eeprom(struct acx_softc *, uint32_t, uint8_t *);
int      acx_read_phyreg(struct acx_softc *, uint32_t, uint8_t *);
const char *    acx_get_rf(int);
int      acx_get_maxrssi(int);

int      acx_load_firmware(struct acx_softc *, uint32_t,
             const uint8_t *, int);
int      acx_load_radio_firmware(struct acx_softc *, const char *);
int      acx_load_base_firmware(struct acx_softc *, const char *);

struct ieee80211_node
        *acx_node_alloc(struct ieee80211com *);
int      acx_newstate(struct ieee80211com *, enum ieee80211_state, int);

void     acx_init_cmd_reg(struct acx_softc *);
int      acx_join_bss(struct acx_softc *, uint8_t, struct ieee80211_node *);
int      acx_set_channel(struct acx_softc *, uint8_t);
int      acx_init_radio(struct acx_softc *, uint32_t, uint32_t);

void     acx_iter_func(void *, struct ieee80211_node *);
void     acx_amrr_timeout(void *);
void     acx_newassoc(struct ieee80211com *, struct ieee80211_node *, int);
#ifndef IEEE80211_STA_ONLY
void     acx_set_tim(struct ieee80211com *, int, int);
#endif

int             acx_beacon_intvl = 100; /* 100 TU */

/*
 * Possible values for the second parameter of acx_join_bss()
 */
#define ACX_MODE_ADHOC  0
#define ACX_MODE_UNUSED 1
#define ACX_MODE_STA    2
#define ACX_MODE_AP     3

struct cfdriver acx_cd = {
        NULL, "acx", DV_IFNET
};

int
acx_attach(struct acx_softc *sc)
{
        struct ieee80211com *ic = &sc->sc_ic;
        struct ifnet *ifp = &sc->sc_ic.ic_if;
        int i, error;

        /* Initialize channel scanning timer */
        timeout_set(&sc->sc_chanscan_timer, acx_next_scan, sc);

        /* Allocate busdma stuffs */
        error = acx_dma_alloc(sc);
        if (error) {
                printf("%s: attach failed, could not allocate DMA!\n",
                    sc->sc_dev.dv_xname);
                return (error);
        }

        /* Reset Hardware */
        error = acx_reset(sc);
        if (error) {
                printf("%s: attach failed, could not reset device!\n",
                    sc->sc_dev.dv_xname);
                return (error);
        }

        /* Disable interrupts before firmware is loaded */
        acx_disable_intr(sc);

        /* Get radio type and form factor */
#define EEINFO_RETRY_MAX        50
        for (i = 0; i < EEINFO_RETRY_MAX; ++i) {
                uint16_t ee_info;

                ee_info = CSR_READ_2(sc, ACXREG_EEPROM_INFO);
                if (ACX_EEINFO_HAS_RADIO_TYPE(ee_info)) {
                        sc->sc_form_factor = ACX_EEINFO_FORM_FACTOR(ee_info);
                        sc->sc_radio_type = ACX_EEINFO_RADIO_TYPE(ee_info);
                        break;
                }
                DELAY(10000);
        }
        if (i == EEINFO_RETRY_MAX) {
                printf("%s: attach failed, could not get radio type!\n",
                    sc->sc_dev.dv_xname);
                return (ENXIO);
        }
#undef EEINFO_RETRY_MAX

#ifdef DUMP_EEPROM
        for (i = 0; i < 0x40; ++i) {
                uint8_t val;

                error = acx_read_eeprom(sc, i, &val);
                if (error)
                        return (error);
                if (i % 10 == 0)
                        printf("\n");
                printf("%02x ", val);
        }
        printf("\n");
#endif  /* DUMP_EEPROM */

        /* Get EEPROM version */
        error = acx_read_eeprom(sc, ACX_EE_VERSION_OFS, &sc->sc_eeprom_ver);
        if (error) {
                printf("%s: attach failed, could not get EEPROM version!\n",
                    sc->sc_dev.dv_xname);
                return (error);
        }

        ifp->if_softc = sc;
        ifp->if_ioctl = acx_ioctl;
        ifp->if_start = acx_start;
        ifp->if_watchdog = acx_watchdog;
        ifp->if_flags = IFF_SIMPLEX | IFF_BROADCAST | IFF_MULTICAST;
        strlcpy(ifp->if_xname, sc->sc_dev.dv_xname, IFNAMSIZ);
        ifq_init_maxlen(&ifp->if_snd, IFQ_MAXLEN);

        /* Set channels */
        for (i = 1; i <= 14; ++i) {
                ic->ic_channels[i].ic_freq =
                    ieee80211_ieee2mhz(i, IEEE80211_CHAN_2GHZ);
                ic->ic_channels[i].ic_flags = sc->chip_chan_flags;
        }

        ic->ic_opmode = IEEE80211_M_STA;
        ic->ic_state = IEEE80211_S_INIT;

        /*
         * NOTE: Don't overwrite ic_caps set by chip specific code
         */
        ic->ic_caps =
            IEEE80211_C_WEP |                   /* WEP */
            IEEE80211_C_MONITOR |               /* Monitor mode */
#ifndef IEEE80211_STA_ONLY
            IEEE80211_C_IBSS |                  /* IBSS mode */
            IEEE80211_C_HOSTAP |                /* Access Point */
            IEEE80211_C_APPMGT |                /* AP Power Mgmt */
#endif
            IEEE80211_C_SHPREAMBLE;             /* Short preamble */

        /* Get station id */
        for (i = 0; i < IEEE80211_ADDR_LEN; ++i) {
                error = acx_read_eeprom(sc, sc->chip_ee_eaddr_ofs - i,
                    &ic->ic_myaddr[i]);
                if (error) {
                        printf("%s: attach failed, could not get station id\n",
                            sc->sc_dev.dv_xname);
                        return error;
                }
        }

        printf("%s: %s, radio %s (0x%02x), EEPROM ver %u, address %s\n",
            sc->sc_dev.dv_xname,
            (sc->sc_flags & ACX_FLAG_ACX111) ? "ACX111" : "ACX100",
            acx_get_rf(sc->sc_radio_type), sc->sc_radio_type,
            sc->sc_eeprom_ver, ether_sprintf(ic->ic_myaddr));

        if_attach(ifp);
        ieee80211_ifattach(ifp);

        /* Override node alloc */
        ic->ic_node_alloc = acx_node_alloc;
        ic->ic_newassoc = acx_newassoc;

#ifndef IEEE80211_STA_ONLY
        /* Override set TIM */
        ic->ic_set_tim = acx_set_tim;
#endif

        /* Override newstate */
        sc->sc_newstate = ic->ic_newstate;
        ic->ic_newstate = acx_newstate;

        /* Set maximal rssi */
        ic->ic_max_rssi = acx_get_maxrssi(sc->sc_radio_type);

        ieee80211_media_init(ifp, ieee80211_media_change,
            ieee80211_media_status);

        /* AMRR rate control */
        sc->amrr.amrr_min_success_threshold = 1;
        sc->amrr.amrr_max_success_threshold = 15;
        timeout_set(&sc->amrr_ch, acx_amrr_timeout, sc);

        sc->sc_long_retry_limit = 4;
        sc->sc_short_retry_limit = 7;
        sc->sc_msdu_lifetime = 4096;

#if NBPFILTER > 0
        bpfattach(&sc->sc_drvbpf, ifp, DLT_IEEE802_11_RADIO,
            sizeof(struct ieee80211_frame) + 64);

        sc->sc_rxtap_len = sizeof(sc->sc_rxtapu);
        sc->sc_rxtap.wr_ihdr.it_len = htole16(sc->sc_rxtap_len);
        sc->sc_rxtap.wr_ihdr.it_present = htole32(ACX_RX_RADIOTAP_PRESENT);

        sc->sc_txtap_len = sizeof(sc->sc_txtapu);
        sc->sc_txtap.wt_ihdr.it_len = htole16(sc->sc_txtap_len);
        sc->sc_txtap.wt_ihdr.it_present = htole32(ACX_TX_RADIOTAP_PRESENT);
#endif

        return (0);
}

int
acx_detach(void *xsc)
{
        struct acx_softc *sc = xsc;
        struct ieee80211com *ic = &sc->sc_ic;
        struct ifnet *ifp = &ic->ic_if;

        acx_stop(sc);
        ieee80211_ifdetach(ifp);
        if_detach(ifp);

        acx_dma_free(sc);

        return (0);
}

int
acx_init(struct ifnet *ifp)
{
        struct acx_softc *sc = ifp->if_softc;
        struct ieee80211com *ic = &sc->sc_ic;
        char fname[] = "tiacx111c16";
        int error, combined = 0;

        error = acx_stop(sc);
        if (error)
                return (EIO);

        /* enable card if possible */
        if (sc->sc_enable != NULL) {
                error = (*sc->sc_enable)(sc);
                if (error)
                        return (EIO);
        }

        acx_init_tx_ring(sc);

        error = acx_init_rx_ring(sc);
        if (error) {
                printf("%s: can't initialize RX ring\n",
                    sc->sc_dev.dv_xname);
                goto back;
        }

        if (sc->sc_flags & ACX_FLAG_ACX111) {
                snprintf(fname, sizeof(fname), "tiacx111c%02X",
                    sc->sc_radio_type);
                error = acx_load_base_firmware(sc, fname);

                if (!error)
                        combined = 1;
        }

        if (!combined) {
                snprintf(fname, sizeof(fname), "tiacx%s",
                    (sc->sc_flags & ACX_FLAG_ACX111) ? "111" : "100");
                error = acx_load_base_firmware(sc, fname);
        }

        if (error)
                goto back;

        /*
         * Initialize command and information registers
         * NOTE: This should be done after base firmware is loaded
         */
        acx_init_cmd_reg(sc);
        acx_init_info_reg(sc);

        sc->sc_flags |= ACX_FLAG_FW_LOADED;

        if (!combined) {
                snprintf(fname, sizeof(fname), "tiacx%sr%02X",
                    (sc->sc_flags & ACX_FLAG_ACX111) ? "111" : "100",
                    sc->sc_radio_type);
                error = acx_load_radio_firmware(sc, fname);

                if (error)
                        goto back;
        }

        error = sc->chip_init(sc);
        if (error)
                goto back;

        /* Get and set device various configuration */
        error = acx_config(sc);
        if (error)
                goto back;

        /* Setup crypto stuffs */
        if (sc->sc_ic.ic_flags & IEEE80211_F_WEPON) {
                error = acx_set_crypt_keys(sc);
                if (error)
                        goto back;
        }

        /* Turn on power led */
        CSR_CLRB_2(sc, ACXREG_GPIO_OUT, sc->chip_gpio_pled);

        acx_enable_intr(sc);

        ifp->if_flags |= IFF_RUNNING;
        ifq_clr_oactive(&ifp->if_snd);

        if (ic->ic_opmode != IEEE80211_M_MONITOR)
                /* start background scanning */
                ieee80211_new_state(ic, IEEE80211_S_SCAN, -1);
        else
                /* in monitor mode change directly into run state */
                ieee80211_new_state(ic, IEEE80211_S_RUN, -1);

        return (0);
back:
        acx_stop(sc);
        return (error);
}

void
acx_init_info_reg(struct acx_softc *sc)
{
        sc->sc_info = CSR_READ_4(sc, ACXREG_INFO_REG_OFFSET);
        sc->sc_info_param = sc->sc_info + ACX_INFO_REG_SIZE;
}

int
acx_set_crypt_keys(struct acx_softc *sc)
{
        struct ieee80211com *ic = &sc->sc_ic;
        struct acx_conf_wep_txkey wep_txkey;
        int i, error, got_wk = 0;

        for (i = 0; i < IEEE80211_WEP_NKID; ++i) {
                struct ieee80211_key *k = &ic->ic_nw_keys[i];

                if (k->k_len == 0)
                        continue;

                if (sc->chip_hw_crypt) {
                        error = sc->chip_set_wepkey(sc, k, i);
                        if (error)
                                return (error);
                        got_wk = 1;
                }
        }

        if (!got_wk)
                return (0);

        /* Set current WEP key index */
        wep_txkey.wep_txkey = ic->ic_wep_txkey;
        if (acx_set_conf(sc, ACX_CONF_WEP_TXKEY, &wep_txkey,
            sizeof(wep_txkey)) != 0) {
                printf("%s: set WEP txkey failed\n", sc->sc_dev.dv_xname);
                return (ENXIO);
        }

        return (0);
}

void
acx_next_scan(void *arg)
{
        struct acx_softc *sc = arg;
        struct ieee80211com *ic = &sc->sc_ic;
        struct ifnet *ifp = &ic->ic_if;

        if (ic->ic_state == IEEE80211_S_SCAN)
                ieee80211_next_scan(ifp);
}

int
acx_stop(struct acx_softc *sc)
{
        struct ieee80211com *ic = &sc->sc_ic;
        struct ifnet *ifp = &ic->ic_if;
        struct acx_buf_data *bd = &sc->sc_buf_data;
        struct acx_ring_data *rd = &sc->sc_ring_data;
        int i, error;

        sc->sc_firmware_ver = 0;
        sc->sc_hardware_id = 0;

        /* Reset hardware */
        error = acx_reset(sc);
        if (error)
                return (error);

        /* Firmware no longer functions after hardware reset */
        sc->sc_flags &= ~ACX_FLAG_FW_LOADED;

        acx_disable_intr(sc);

        /* Stop background scanning */
        timeout_del(&sc->sc_chanscan_timer);

        /* Turn off power led */
        CSR_SETB_2(sc, ACXREG_GPIO_OUT, sc->chip_gpio_pled);

        /* Free TX mbuf */
        for (i = 0; i < ACX_TX_DESC_CNT; ++i) {
                struct acx_txbuf *buf;
                struct ieee80211_node *ni;

                buf = &bd->tx_buf[i];

                if (buf->tb_mbuf != NULL) {
                        bus_dmamap_unload(sc->sc_dmat, buf->tb_mbuf_dmamap);
                        m_freem(buf->tb_mbuf);
                        buf->tb_mbuf = NULL;
                }

                ni = (struct ieee80211_node *)buf->tb_node;
                if (ni != NULL)
                        ieee80211_release_node(ic, ni);
                buf->tb_node = NULL;
        }

        /* Clear TX host descriptors */
        bzero(rd->tx_ring, ACX_TX_RING_SIZE);

        /* Free RX mbuf */
        for (i = 0; i < ACX_RX_DESC_CNT; ++i) {
                if (bd->rx_buf[i].rb_mbuf != NULL) {
                        bus_dmamap_unload(sc->sc_dmat,
                            bd->rx_buf[i].rb_mbuf_dmamap);
                        m_freem(bd->rx_buf[i].rb_mbuf);
                        bd->rx_buf[i].rb_mbuf = NULL;
                }
        }

        /* Clear RX host descriptors */
        bzero(rd->rx_ring, ACX_RX_RING_SIZE);

        sc->sc_txtimer = 0;
        ifp->if_timer = 0;
        ifp->if_flags &= ~IFF_RUNNING;
        ifq_clr_oactive(&ifp->if_snd);
        ieee80211_new_state(&sc->sc_ic, IEEE80211_S_INIT, -1);

        /* disable card if possible */
        if (sc->sc_disable != NULL)
                (*sc->sc_disable)(sc);

        return (0);
}

int
acx_config(struct acx_softc *sc)
{
        struct acx_config conf;
        int error;

        error = acx_read_config(sc, &conf);
        if (error)
                return (error);

        error = acx_write_config(sc, &conf);
        if (error)
                return (error);

        error = acx_rx_config(sc);
        if (error)
                return (error);

        if (acx_set_probe_req_tmplt(sc, "", 0) != 0) {
                printf("%s: can't set probe req template "
                    "(empty ssid)\n", sc->sc_dev.dv_xname);
                return (ENXIO);
        }

        /* XXX for PM?? */
        if (acx_set_null_tmplt(sc) != 0) {
                printf("%s: can't set null data template\n",
                    sc->sc_dev.dv_xname);
                return (ENXIO);
        }

        return (0);
}

int
acx_read_config(struct acx_softc *sc, struct acx_config *conf)
{
        struct acx_conf_regdom reg_dom;
        struct acx_conf_antenna ant;
        struct acx_conf_fwrev fw_rev;
        uint32_t fw_rev_no;
        uint8_t sen;
        int error;

        /* Get region domain */
        if (acx_get_conf(sc, ACX_CONF_REGDOM, &reg_dom, sizeof(reg_dom)) != 0) {
                printf("%s: can't get region domain\n", sc->sc_dev.dv_xname);
                return (ENXIO);
        }
        conf->regdom = reg_dom.regdom;
        DPRINTF(("%s: regdom %02x\n", sc->sc_dev.dv_xname, reg_dom.regdom));

        /* Get antenna */
        if (acx_get_conf(sc, ACX_CONF_ANTENNA, &ant, sizeof(ant)) != 0) {
                printf("%s: can't get antenna\n", sc->sc_dev.dv_xname);
                return (ENXIO);
        }
        conf->antenna = ant.antenna;
        DPRINTF(("%s: antenna %02x\n", sc->sc_dev.dv_xname, ant.antenna));

        /* Get sensitivity XXX not used */
        if (sc->sc_radio_type == ACX_RADIO_TYPE_MAXIM ||
            sc->sc_radio_type == ACX_RADIO_TYPE_RFMD ||
            sc->sc_radio_type == ACX_RADIO_TYPE_RALINK) {
                error = acx_read_phyreg(sc, ACXRV_PHYREG_SENSITIVITY, &sen);
                if (error) {
                        printf("%s: can't get sensitivity\n",
                            sc->sc_dev.dv_xname);
                        return (error);
                }
        } else
                sen = 0;
        DPRINTF(("%s: sensitivity %02x\n", sc->sc_dev.dv_xname, sen));

        /* Get firmware revision */
        if (acx_get_conf(sc, ACX_CONF_FWREV, &fw_rev, sizeof(fw_rev)) != 0) {
                printf("%s: can't get firmware revision\n",
                    sc->sc_dev.dv_xname);
                return (ENXIO);
        }

        if (strncmp(fw_rev.fw_rev, "Rev ", 4) != 0) {
                printf("%s: strange revision string -- %s\n",
                    sc->sc_dev.dv_xname, fw_rev.fw_rev);
                fw_rev_no = 0x01090407;
        } else {
                /*
                 *  01234
                 * "Rev xx.xx.xx.xx"
                 *      ^ Start from here
                 */
                fw_rev_no  = fw_rev.fw_rev[0] << 24;
                fw_rev_no |= fw_rev.fw_rev[1] << 16;
                fw_rev_no |= fw_rev.fw_rev[2] <<  8;
                fw_rev_no |= fw_rev.fw_rev[3];
        }
        sc->sc_firmware_ver = fw_rev_no;
        sc->sc_hardware_id = letoh32(fw_rev.hw_id);
        DPRINTF(("%s: fw rev %08x, hw id %08x\n",
            sc->sc_dev.dv_xname, sc->sc_firmware_ver, sc->sc_hardware_id));

        if (sc->chip_read_config != NULL) {
                error = sc->chip_read_config(sc, conf);
                if (error)
                        return (error);
        }

        return (0);
}

int
acx_write_config(struct acx_softc *sc, struct acx_config *conf)
{
        struct acx_conf_nretry_short sretry;
        struct acx_conf_nretry_long lretry;
        struct acx_conf_msdu_lifetime msdu_lifetime;
        struct acx_conf_rate_fallback rate_fb;
        struct acx_conf_antenna ant;
        struct acx_conf_regdom reg_dom;
        struct ifnet *ifp = &sc->sc_ic.ic_if;
        int error;

        /* Set number of long/short retry */
        sretry.nretry = sc->sc_short_retry_limit;
        if (acx_set_conf(sc, ACX_CONF_NRETRY_SHORT, &sretry,
            sizeof(sretry)) != 0) {
                printf("%s: can't set short retry limit\n", ifp->if_xname);
                return (ENXIO);
        }

        lretry.nretry = sc->sc_long_retry_limit;
        if (acx_set_conf(sc, ACX_CONF_NRETRY_LONG, &lretry,
            sizeof(lretry)) != 0) {
                printf("%s: can't set long retry limit\n", ifp->if_xname);
                return (ENXIO);
        }

        /* Set MSDU lifetime */
        msdu_lifetime.lifetime = htole32(sc->sc_msdu_lifetime);
        if (acx_set_conf(sc, ACX_CONF_MSDU_LIFETIME, &msdu_lifetime,
            sizeof(msdu_lifetime)) != 0) {
                printf("%s: can't set MSDU lifetime\n", ifp->if_xname);
                return (ENXIO);
        }

        /* Enable rate fallback */
        rate_fb.ratefb_enable = 1;
        if (acx_set_conf(sc, ACX_CONF_RATE_FALLBACK, &rate_fb,
            sizeof(rate_fb)) != 0) {
                printf("%s: can't enable rate fallback\n", ifp->if_xname);
                return (ENXIO);
        }

        /* Set antenna */
        ant.antenna = conf->antenna;
        if (acx_set_conf(sc, ACX_CONF_ANTENNA, &ant, sizeof(ant)) != 0) {
                printf("%s: can't set antenna\n", ifp->if_xname);
                return (ENXIO);
        }

        /* Set region domain */
        reg_dom.regdom = conf->regdom;
        if (acx_set_conf(sc, ACX_CONF_REGDOM, &reg_dom, sizeof(reg_dom)) != 0) {
                printf("%s: can't set region domain\n", ifp->if_xname);
                return (ENXIO);
        }

        if (sc->chip_write_config != NULL) {
                error = sc->chip_write_config(sc, conf);
                if (error)
                        return (error);
        }

        return (0);
}

int
acx_rx_config(struct acx_softc *sc)
{
        struct ieee80211com *ic = &sc->sc_ic;
        struct acx_conf_rxopt rx_opt;

        /* tell the RX receiver what frames we want to have */
        rx_opt.opt1 = htole16(RXOPT1_INCL_RXBUF_HDR);
        rx_opt.opt2 = htole16(
            RXOPT2_RECV_ASSOC_REQ |
            RXOPT2_RECV_AUTH |
            RXOPT2_RECV_BEACON |
            RXOPT2_RECV_CF |
            RXOPT2_RECV_CTRL |
            RXOPT2_RECV_DATA |
            RXOPT2_RECV_MGMT |
            RXOPT2_RECV_PROBE_REQ |
            RXOPT2_RECV_PROBE_RESP |
            RXOPT2_RECV_OTHER);

        /* in monitor mode go promiscuous */
        if (ic->ic_opmode == IEEE80211_M_MONITOR) {
                rx_opt.opt1 |= RXOPT1_PROMISC;
                rx_opt.opt2 |= RXOPT2_RECV_BROKEN | RXOPT2_RECV_ACK;
        } else
                rx_opt.opt1 |= RXOPT1_FILT_FDEST;

        /* finally set the RX options */
        if (acx_set_conf(sc, ACX_CONF_RXOPT, &rx_opt, sizeof(rx_opt)) != 0) {
                printf("%s: can not set RX options!\n", sc->sc_dev.dv_xname);
                return (ENXIO);
        }

        return (0);
}

int
acx_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
{
        struct acx_softc *sc = ifp->if_softc;
        struct ieee80211com *ic = &sc->sc_ic;
        int s, error = 0;
        uint8_t chan;

        s = splnet();

        switch (cmd) {
        case SIOCSIFADDR:
                ifp->if_flags |= IFF_UP;
                /* FALLTHROUGH */
        case SIOCSIFFLAGS:
                if (ifp->if_flags & IFF_UP) {
                        if ((ifp->if_flags & IFF_RUNNING) == 0)
                                error = acx_init(ifp);
                } else {
                        if (ifp->if_flags & IFF_RUNNING)
                                error = acx_stop(sc);
                }
                break;
        case SIOCS80211CHANNEL:
                /* allow fast channel switching in monitor mode */
                error = ieee80211_ioctl(ifp, cmd, data);
                if (error == ENETRESET &&
                    ic->ic_opmode == IEEE80211_M_MONITOR) {
                        if ((ifp->if_flags & (IFF_UP | IFF_RUNNING)) ==
                            (IFF_UP | IFF_RUNNING)) {
                                ic->ic_bss->ni_chan = ic->ic_ibss_chan;
                                chan = ieee80211_chan2ieee(ic,
                                    ic->ic_bss->ni_chan);
                                (void)acx_set_channel(sc, chan);
                        }
                        error = 0;
                }
                break;
        default:
                error = ieee80211_ioctl(ifp, cmd, data);
                break;
        }

        if (error == ENETRESET) {
                if ((ifp->if_flags & (IFF_RUNNING | IFF_UP)) ==
                    (IFF_RUNNING | IFF_UP))
                        error = acx_init(ifp);
                else
                        error = 0;
        }

        splx(s);

        return (error);
}

void
acx_start(struct ifnet *ifp)
{
        struct acx_softc *sc = ifp->if_softc;
        struct ieee80211com *ic = &sc->sc_ic;
        struct acx_buf_data *bd = &sc->sc_buf_data;
        struct acx_txbuf *buf;
        int trans, idx;

        if ((sc->sc_flags & ACX_FLAG_FW_LOADED) == 0 ||
            (ifp->if_flags & IFF_RUNNING) == 0 ||
            ifq_is_oactive(&ifp->if_snd))
                return;

        /*
         * NOTE:
         * We can't start from a random position that TX descriptor
         * is free, since hardware will be confused by that.
         * We have to follow the order of the TX ring.
         */
        idx = bd->tx_free_start;
        trans = 0;
        for (buf = &bd->tx_buf[idx]; buf->tb_mbuf == NULL;
             buf = &bd->tx_buf[idx]) {
                struct ieee80211_frame *wh;
                struct ieee80211_node *ni = NULL;
                struct mbuf *m;
                int rate;

                m = mq_dequeue(&ic->ic_mgtq);
                /* first dequeue management frames */
                if (m != NULL) {
                        ni = m->m_pkthdr.ph_cookie;

                        /*
                         * probe response mgmt frames are handled by the
                         * firmware already.  So, don't send them twice.
                         */
                        wh = mtod(m, struct ieee80211_frame *);
                        if ((wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) ==
                            IEEE80211_FC0_SUBTYPE_PROBE_RESP) {
                                if (ni != NULL)
                                        ieee80211_release_node(ic, ni);
                                m_freem(m);
                                continue;
                        }

                        /*
                         * mgmt frames are sent at the lowest available
                         * bit-rate.
                         */
                        rate = ni->ni_rates.rs_rates[0];
                        rate &= IEEE80211_RATE_VAL;
                } else {
                        struct ether_header *eh;

                        /* then dequeue packets on the powersave queue */
                        m = mq_dequeue(&ic->ic_pwrsaveq);
                        if (m != NULL) {
                                ni = m->m_pkthdr.ph_cookie;
                                goto encapped;
                        } else {
                                m = ifq_dequeue(&ifp->if_snd);
                                if (m == NULL)
                                        break;
                        }
                        if (ic->ic_state != IEEE80211_S_RUN) {
                                DPRINTF(("%s: data packet dropped due to "
                                    "not RUN.  Current state %d\n",
                                    ifp->if_xname, ic->ic_state));
                                m_freem(m);
                                break;
                        }

                        if (m->m_len < sizeof(struct ether_header)) {
                                m = m_pullup(m, sizeof(struct ether_header));
                                if (m == NULL) {
                                        ifp->if_oerrors++;
                                        continue;
                                }
                        }
                        eh = mtod(m, struct ether_header *);

                        /* TODO power save */

#if NBPFILTER > 0
                        if (ifp->if_bpf != NULL)
                                bpf_mtap(ifp->if_bpf, m, BPF_DIRECTION_OUT);
#endif

                        if ((m = ieee80211_encap(ifp, m, &ni)) == NULL) {
                                ifp->if_oerrors++;
                                continue;
                        }
encapped:
                        if (ic->ic_fixed_rate != -1) {
                                rate = ic->ic_sup_rates[ic->ic_curmode].
                                    rs_rates[ic->ic_fixed_rate];
                        } else
                                rate = ni->ni_rates.rs_rates[ni->ni_txrate];
                        rate &= IEEE80211_RATE_VAL;
                } 

#if NBPFILTER > 0
                if (ic->ic_rawbpf != NULL)
                        bpf_mtap(ic->ic_rawbpf, m, BPF_DIRECTION_OUT);
#endif

                wh = mtod(m, struct ieee80211_frame *);
                if ((wh->i_fc[1] & IEEE80211_FC1_WEP) && !sc->chip_hw_crypt) {
                        struct ieee80211_key *k;

                        k = ieee80211_get_txkey(ic, wh, ni);
                        if ((m = ieee80211_encrypt(ic, m, k)) == NULL) {
                                ieee80211_release_node(ic, ni);
                                ifp->if_oerrors++;
                                continue;
                        }
                }

#if NBPFILTER > 0
                if (sc->sc_drvbpf != NULL) {
                        struct mbuf mb;
                        struct acx_tx_radiotap_hdr *tap = &sc->sc_txtap;

                        tap->wt_flags = 0;
                        tap->wt_rate = rate;
                        tap->wt_chan_freq =
                            htole16(ic->ic_bss->ni_chan->ic_freq);
                        tap->wt_chan_flags =
                            htole16(ic->ic_bss->ni_chan->ic_flags);

                        mb.m_data = (caddr_t)tap;
                        mb.m_len = sc->sc_txtap_len;
                        mb.m_next = m;
                        mb.m_nextpkt = NULL;
                        mb.m_type = 0;
                        mb.m_flags = 0;
                        bpf_mtap(sc->sc_drvbpf, &mb, BPF_DIRECTION_OUT);
                }
#endif

                if (acx_encap(sc, buf, m, ni, rate) != 0) {
                        /*
                         * NOTE: `m' will be freed in acx_encap()
                         * if we reach here.
                         */
                        if (ni != NULL)
                                ieee80211_release_node(ic, ni);
                        ifp->if_oerrors++;
                        continue;
                }

                /*
                 * NOTE:
                 * 1) `m' should not be touched after acx_encap()
                 * 2) `node' will be used to do TX rate control during
                 *    acx_txeof(), so it is not freed here.  acx_txeof()
                 *    will free it for us
                 */
                trans++;
                bd->tx_used_count++;
                idx = (idx + 1) % ACX_TX_DESC_CNT;
        }
        bd->tx_free_start = idx;

        if (bd->tx_used_count == ACX_TX_DESC_CNT)
                ifq_set_oactive(&ifp->if_snd);

        if (trans && sc->sc_txtimer == 0)
                sc->sc_txtimer = 5;
        ifp->if_timer = 1;
}

void
acx_watchdog(struct ifnet *ifp)
{
        struct acx_softc *sc = ifp->if_softc;

        ifp->if_timer = 0;

        if ((ifp->if_flags & IFF_RUNNING) == 0)
                return;

        if (sc->sc_txtimer) {
                if (--sc->sc_txtimer == 0) {
                        printf("%s: watchdog timeout\n", ifp->if_xname);
                        acx_init(ifp);
                        ifp->if_oerrors++;
                        return;
                } else
                        ifp->if_timer = 1;
        }

        ieee80211_watchdog(ifp);
}

int
acx_intr(void *arg)
{
        struct acx_softc *sc = arg;
        uint16_t intr_status;

        if ((sc->sc_flags & ACX_FLAG_FW_LOADED) == 0)
                return (0);

        intr_status = CSR_READ_2(sc, ACXREG_INTR_STATUS_CLR);
        if (intr_status == ACXRV_INTR_ALL) {
                /* not our interrupt */
                return (0);
        }

        /* Acknowledge all interrupts */
        CSR_WRITE_2(sc, ACXREG_INTR_ACK, intr_status);

        intr_status &= sc->chip_intr_enable;
        if (intr_status == 0) {
                /* not interrupts we care about */
                return (1);
        }

#ifndef IEEE80211_STA_ONLY
        if (intr_status & ACXRV_INTR_DTIM)
                ieee80211_notify_dtim(&sc->sc_ic);
#endif

        if (intr_status & ACXRV_INTR_TX_FINI)
                acx_txeof(sc);

        if (intr_status & ACXRV_INTR_RX_FINI)
                acx_rxeof(sc);

        return (1);
}

void
acx_disable_intr(struct acx_softc *sc)
{
        CSR_WRITE_2(sc, ACXREG_INTR_MASK, sc->chip_intr_disable);
        CSR_WRITE_2(sc, ACXREG_EVENT_MASK, 0);
}

void
acx_enable_intr(struct acx_softc *sc)
{
        /* Mask out interrupts that are not in the enable set */
        CSR_WRITE_2(sc, ACXREG_INTR_MASK, ~sc->chip_intr_enable);
        CSR_WRITE_2(sc, ACXREG_EVENT_MASK, ACXRV_EVENT_DISABLE);
}

void
acx_txeof(struct acx_softc *sc)
{
        struct acx_buf_data *bd;
        struct acx_txbuf *buf;
        struct ifnet *ifp;
        int idx;

        ifp = &sc->sc_ic.ic_if;

        bd = &sc->sc_buf_data;
        idx = bd->tx_used_start;
        for (buf = &bd->tx_buf[idx]; buf->tb_mbuf != NULL;
             buf = &bd->tx_buf[idx]) {
                uint8_t ctrl, error;

                ctrl = FW_TXDESC_GETFIELD_1(sc, buf, f_tx_ctrl);
                if ((ctrl & (DESC_CTRL_HOSTOWN | DESC_CTRL_ACXDONE)) !=
                    (DESC_CTRL_HOSTOWN | DESC_CTRL_ACXDONE))
                        break;

                bus_dmamap_unload(sc->sc_dmat, buf->tb_mbuf_dmamap);
                m_freem(buf->tb_mbuf);
                buf->tb_mbuf = NULL;

                error = FW_TXDESC_GETFIELD_1(sc, buf, f_tx_error);
                if (error) {
                        acx_txerr(sc, error);
                        ifp->if_oerrors++;
                }

                /* Update rate control statistics for the node */
                if (buf->tb_node != NULL) {
                        struct ieee80211com *ic;
                        struct ieee80211_node *ni;
                        struct acx_node *wn;
                        int ntries;

                        ic = &sc->sc_ic;
                        ni = (struct ieee80211_node *)buf->tb_node;
                        wn = (struct acx_node *)ni;
                        ntries = FW_TXDESC_GETFIELD_1(sc, buf, f_tx_rts_fail) +
                            FW_TXDESC_GETFIELD_1(sc, buf, f_tx_ack_fail);

                        wn->amn.amn_txcnt++;
                        if (ntries > 0) {
                                DPRINTFN(2, ("%s: tx intr ntries %d\n",
                                    sc->sc_dev.dv_xname, ntries));
                                wn->amn.amn_retrycnt++;
                        }

                        ieee80211_release_node(ic, ni);
                        buf->tb_node = NULL;
                }

                FW_TXDESC_SETFIELD_1(sc, buf, f_tx_ctrl, DESC_CTRL_HOSTOWN);

                bd->tx_used_count--;

                idx = (idx + 1) % ACX_TX_DESC_CNT;
        }
        bd->tx_used_start = idx;

        sc->sc_txtimer = bd->tx_used_count == 0 ? 0 : 5;

        if (bd->tx_used_count != ACX_TX_DESC_CNT) {
                ifq_clr_oactive(&ifp->if_snd);
                acx_start(ifp);
        }
}

void
acx_txerr(struct acx_softc *sc, uint8_t err)
{
        struct ifnet *ifp = &sc->sc_ic.ic_if;
        struct acx_stats *stats = &sc->sc_stats;

        if (err == DESC_ERR_EXCESSIVE_RETRY) {
                /*
                 * This a common error (see comment below),
                 * so print it using DPRINTF().
                 */
                DPRINTF(("%s: TX failed -- excessive retry\n",
                    sc->sc_dev.dv_xname));
        } else
                printf("%s: TX failed -- ", ifp->if_xname);

        /*
         * Although `err' looks like bitmask, it never
         * has multiple bits set.
         */
        switch (err) {
#if 0
        case DESC_ERR_OTHER_FRAG:
                /* XXX what's this */
                printf("error in other fragment\n");
                stats->err_oth_frag++;
                break;
#endif
        case DESC_ERR_ABORT:
                printf("aborted\n");
                stats->err_abort++;
                break;
        case DESC_ERR_PARAM:
                printf("wrong parameters in descriptor\n");
                stats->err_param++;
                break;
        case DESC_ERR_NO_WEPKEY:
                printf("WEP key missing\n");
                stats->err_no_wepkey++;
                break;
        case DESC_ERR_MSDU_TIMEOUT:
                printf("MSDU life timeout\n");
                stats->err_msdu_timeout++;
                break;
        case DESC_ERR_EXCESSIVE_RETRY:
                /*
                 * Possible causes:
                 * 1) Distance is too long
                 * 2) Transmit failed (e.g. no MAC level ACK)
                 * 3) Chip overheated (this should be rare)
                 */
                stats->err_ex_retry++;
                break;
        case DESC_ERR_BUF_OVERFLOW:
                printf("buffer overflow\n");
                stats->err_buf_oflow++;
                break;
        case DESC_ERR_DMA:
                printf("DMA error\n");
                stats->err_dma++;
                break;
        default:
                printf("unknown error %d\n", err);
                stats->err_unkn++;
                break;
        }
}

void
acx_rxeof(struct acx_softc *sc)
{
        struct ieee80211com *ic = &sc->sc_ic;
        struct acx_ring_data *rd = &sc->sc_ring_data;
        struct acx_buf_data *bd = &sc->sc_buf_data;
        struct ifnet *ifp = &ic->ic_if;
        int idx, ready;

        bus_dmamap_sync(sc->sc_dmat, rd->rx_ring_dmamap, 0,
            rd->rx_ring_dmamap->dm_mapsize, BUS_DMASYNC_POSTREAD);

        /*
         * Locate first "ready" rx buffer,
         * start from last stopped position.
         */
        idx = bd->rx_scan_start;
        ready = 0;
        do {
                struct acx_rxbuf *buf;

                buf = &bd->rx_buf[idx];
                if ((buf->rb_desc->h_ctrl & htole16(DESC_CTRL_HOSTOWN)) &&
                    (buf->rb_desc->h_status & htole32(DESC_STATUS_FULL))) {
                        ready = 1;
                        break;
                }
                idx = (idx + 1) % ACX_RX_DESC_CNT;
        } while (idx != bd->rx_scan_start);

        if (!ready)
                return;

        /*
         * NOTE: don't mess up `idx' here, it will
         * be used in the following code.
         */
        do {
                struct acx_rxbuf_hdr *head;
                struct acx_rxbuf *buf;
                struct mbuf *m;
                struct ieee80211_rxinfo rxi;
                uint32_t desc_status;
                uint16_t desc_ctrl;
                int len, error;

                buf = &bd->rx_buf[idx];

                desc_ctrl = letoh16(buf->rb_desc->h_ctrl);
                desc_status = letoh32(buf->rb_desc->h_status);
                if (!(desc_ctrl & DESC_CTRL_HOSTOWN) ||
                    !(desc_status & DESC_STATUS_FULL))
                        break;

                bus_dmamap_sync(sc->sc_dmat, buf->rb_mbuf_dmamap, 0,
                    buf->rb_mbuf_dmamap->dm_mapsize, BUS_DMASYNC_POSTREAD);

                m = buf->rb_mbuf;

                error = acx_newbuf(sc, buf, 0);
                if (error) {
                        ifp->if_ierrors++;
                        goto next;
                }

                head = mtod(m, struct acx_rxbuf_hdr *);

                len = letoh16(head->rbh_len) & ACX_RXBUF_LEN_MASK;
                if (len >= sizeof(struct ieee80211_frame_min) &&
                    len < MCLBYTES) {
                        struct ieee80211_frame *wh;
                        struct ieee80211_node *ni;

                        m_adj(m, sizeof(struct acx_rxbuf_hdr) +
                            sc->chip_rxbuf_exhdr);
                        wh = mtod(m, struct ieee80211_frame *);

                        memset(&rxi, 0, sizeof(rxi));
                        if ((wh->i_fc[1] & IEEE80211_FC1_WEP) &&
                            sc->chip_hw_crypt) {
                                /* Short circuit software WEP */
                                wh->i_fc[1] &= ~IEEE80211_FC1_WEP;

                                /* Do chip specific RX buffer processing */
                                if (sc->chip_proc_wep_rxbuf != NULL) {
                                        sc->chip_proc_wep_rxbuf(sc, m, &len);
                                        wh = mtod(m, struct ieee80211_frame *);
                                }
                                rxi.rxi_flags |= IEEE80211_RXI_HWDEC;
                        }

                        m->m_len = m->m_pkthdr.len = len;

#if NBPFILTER > 0
                        if (sc->sc_drvbpf != NULL) {
                                struct mbuf mb;
                                struct acx_rx_radiotap_hdr *tap = &sc->sc_rxtap;

                                tap->wr_flags = 0;
                                tap->wr_chan_freq =
                                    htole16(ic->ic_bss->ni_chan->ic_freq);
                                tap->wr_chan_flags =
                                    htole16(ic->ic_bss->ni_chan->ic_flags);
                                tap->wr_rssi = head->rbh_level;
                                tap->wr_max_rssi = ic->ic_max_rssi;

                                mb.m_data = (caddr_t)tap;
                                mb.m_len = sc->sc_rxtap_len;
                                mb.m_next = m;
                                mb.m_nextpkt = NULL;
                                mb.m_type = 0;
                                mb.m_flags = 0;
                                bpf_mtap(sc->sc_drvbpf, &mb, BPF_DIRECTION_IN);
                        }
#endif

                        ni = ieee80211_find_rxnode(ic, wh);

                        rxi.rxi_rssi = head->rbh_level;
                        rxi.rxi_tstamp = letoh32(head->rbh_time);
                        ieee80211_input(ifp, m, ni, &rxi);

                        ieee80211_release_node(ic, ni);
                } else {
                        m_freem(m);
                        ifp->if_ierrors++;
                }

next:
                buf->rb_desc->h_ctrl = htole16(desc_ctrl & ~DESC_CTRL_HOSTOWN);
                buf->rb_desc->h_status = 0;
                bus_dmamap_sync(sc->sc_dmat, rd->rx_ring_dmamap, 0,
                    rd->rx_ring_dmamap->dm_mapsize, BUS_DMASYNC_PREWRITE);

                idx = (idx + 1) % ACX_RX_DESC_CNT;
        } while (idx != bd->rx_scan_start);

        /*
         * Record the position so that next
         * time we can start from it.
         */
        bd->rx_scan_start = idx;
}

int
acx_reset(struct acx_softc *sc)
{
        uint16_t reg;

        /* Halt ECPU */
        CSR_SETB_2(sc, ACXREG_ECPU_CTRL, ACXRV_ECPU_HALT);

        /* Software reset */
        reg = CSR_READ_2(sc, ACXREG_SOFT_RESET);
        CSR_WRITE_2(sc, ACXREG_SOFT_RESET, reg | ACXRV_SOFT_RESET);
        DELAY(100);
        CSR_WRITE_2(sc, ACXREG_SOFT_RESET, reg);

        /* Initialize EEPROM */
        CSR_SETB_2(sc, ACXREG_EEPROM_INIT, ACXRV_EEPROM_INIT);
        DELAY(50000);

        /* Test whether ECPU is stopped */
        reg = CSR_READ_2(sc, ACXREG_ECPU_CTRL);
        if (!(reg & ACXRV_ECPU_HALT)) {
                printf("%s: can't halt ECPU\n", sc->sc_dev.dv_xname);
                return (ENXIO);
        }

        return (0);
}

int
acx_read_eeprom(struct acx_softc *sc, uint32_t offset, uint8_t *val)
{
        int i;

        CSR_WRITE_4(sc, ACXREG_EEPROM_CONF, 0);
        CSR_WRITE_4(sc, ACXREG_EEPROM_ADDR, offset);
        CSR_WRITE_4(sc, ACXREG_EEPROM_CTRL, ACXRV_EEPROM_READ);

#define EE_READ_RETRY_MAX       100
        for (i = 0; i < EE_READ_RETRY_MAX; ++i) {
                if (CSR_READ_2(sc, ACXREG_EEPROM_CTRL) == 0)
                        break;
                DELAY(10000);
        }
        if (i == EE_READ_RETRY_MAX) {
                printf("%s: can't read EEPROM offset %x (timeout)\n",
                    sc->sc_dev.dv_xname, offset);
                return (ETIMEDOUT);
        }
#undef EE_READ_RETRY_MAX

        *val = CSR_READ_1(sc, ACXREG_EEPROM_DATA);

        return (0);
}

int
acx_read_phyreg(struct acx_softc *sc, uint32_t reg, uint8_t *val)
{
        struct ifnet *ifp = &sc->sc_ic.ic_if;
        int i;

        CSR_WRITE_4(sc, ACXREG_PHY_ADDR, reg);
        CSR_WRITE_4(sc, ACXREG_PHY_CTRL, ACXRV_PHY_READ);

#define PHY_READ_RETRY_MAX      100
        for (i = 0; i < PHY_READ_RETRY_MAX; ++i) {
                if (CSR_READ_4(sc, ACXREG_PHY_CTRL) == 0)
                        break;
                DELAY(10000);
        }
        if (i == PHY_READ_RETRY_MAX) {
                printf("%s: can't read phy reg %x (timeout)\n",
                    ifp->if_xname, reg);
                return (ETIMEDOUT);
        }
#undef PHY_READ_RETRY_MAX

        *val = CSR_READ_1(sc, ACXREG_PHY_DATA);

        return (0);
}

void
acx_write_phyreg(struct acx_softc *sc, uint32_t reg, uint8_t val)
{
        CSR_WRITE_4(sc, ACXREG_PHY_DATA, val);
        CSR_WRITE_4(sc, ACXREG_PHY_ADDR, reg);
        CSR_WRITE_4(sc, ACXREG_PHY_CTRL, ACXRV_PHY_WRITE);
}

int
acx_load_base_firmware(struct acx_softc *sc, const char *name)
{
        struct ifnet *ifp = &sc->sc_ic.ic_if;
        int i, error;
        uint8_t *ucode;
        size_t size;

        error = loadfirmware(name, &ucode, &size);

        if (error != 0) {
                printf("%s: error %d, could not read firmware %s\n",
                    ifp->if_xname, error, name);
                return (EIO);
        }

        /* Load base firmware */
        error = acx_load_firmware(sc, 0, ucode, size);

        free(ucode, M_DEVBUF, size);

        if (error) {
                printf("%s: can't load base firmware\n", ifp->if_xname);
                return error;
        }
        DPRINTF(("%s: base firmware loaded\n", sc->sc_dev.dv_xname));

        /* Start ECPU */
        CSR_WRITE_2(sc, ACXREG_ECPU_CTRL, ACXRV_ECPU_START);

        /* Wait for ECPU to be up */
        for (i = 0; i < 500; ++i) {
                uint16_t reg;

                reg = CSR_READ_2(sc, ACXREG_INTR_STATUS);
                if (reg & ACXRV_INTR_FCS_THRESH) {
                        CSR_WRITE_2(sc, ACXREG_INTR_ACK, ACXRV_INTR_FCS_THRESH);
                        return (0);
                }
                DELAY(10000);
        }

        printf("%s: can't initialize ECPU (timeout)\n", ifp->if_xname);

        return (ENXIO);
}

int
acx_load_radio_firmware(struct acx_softc *sc, const char *name)
{
        struct ifnet *ifp = &sc->sc_ic.ic_if;
        struct acx_conf_mmap mem_map;
        uint32_t radio_fw_ofs;
        int error;
        uint8_t *ucode;
        size_t size;

        error = loadfirmware(name, &ucode, &size);

        if (error != 0) {
                printf("%s: error %d, could not read firmware %s\n",
                    ifp->if_xname, error, name);
                return (EIO);
        }

        /*
         * Get the position, where base firmware is loaded, so that
         * radio firmware can be loaded after it.
         */
        if (acx_get_conf(sc, ACX_CONF_MMAP, &mem_map, sizeof(mem_map)) != 0) {
                free(ucode, M_DEVBUF, size);
                return (ENXIO);
        }
        radio_fw_ofs = letoh32(mem_map.code_end);

        /* Put ECPU into sleeping state, before loading radio firmware */
        if (acx_exec_command(sc, ACXCMD_SLEEP, NULL, 0, NULL, 0) != 0) {
                free(ucode, M_DEVBUF, size);
                return (ENXIO);
        }

        /* Load radio firmware */
        error = acx_load_firmware(sc, radio_fw_ofs, ucode, size);

        free(ucode, M_DEVBUF, size);

        if (error) {
                printf("%s: can't load radio firmware\n", ifp->if_xname);
                return (ENXIO);
        }
        DPRINTF(("%s: radio firmware loaded\n", sc->sc_dev.dv_xname));

        /* Wake up sleeping ECPU, after radio firmware is loaded */
        if (acx_exec_command(sc, ACXCMD_WAKEUP, NULL, 0, NULL, 0) != 0)
                return (ENXIO);

        /* Initialize radio */
        if (acx_init_radio(sc, radio_fw_ofs, size) != 0)
                return (ENXIO);

        /* Verify radio firmware's loading position */
        if (acx_get_conf(sc, ACX_CONF_MMAP, &mem_map, sizeof(mem_map)) != 0)
                return (ENXIO);

        if (letoh32(mem_map.code_end) != radio_fw_ofs + size) {
                printf("%s: loaded radio firmware position mismatch\n",
                    ifp->if_xname);
                return (ENXIO);
        }

        DPRINTF(("%s: radio firmware initialized\n", sc->sc_dev.dv_xname));

        return (0);
}

int
acx_load_firmware(struct acx_softc *sc, uint32_t offset, const uint8_t *data,
    int data_len)
{
        struct ifnet *ifp = &sc->sc_ic.ic_if;
        const uint32_t *fw;
        u_int32_t csum = 0;
        int i, fw_len;

        for (i = 4; i < data_len; i++)
                csum += data[i];

        fw = (const uint32_t *)data;

        if (*fw != htole32(csum)) {
                printf("%s: firmware checksum 0x%x does not match 0x%x!\n",
                    ifp->if_xname, *fw, htole32(csum));
                return (ENXIO);
        }

        /* skip csum + length */
        data += 8;
        data_len -= 8;

        fw = (const uint32_t *)data;
        fw_len = data_len / sizeof(uint32_t);

        /*
         * LOADFW_AUTO_INC only works with some older firmware:
         * 1) acx100's firmware
         * 2) acx111's firmware whose rev is 0x00010011
         */

        /* Load firmware */
        CSR_WRITE_4(sc, ACXREG_FWMEM_START, ACXRV_FWMEM_START_OP);
#ifndef LOADFW_AUTO_INC
        CSR_WRITE_4(sc, ACXREG_FWMEM_CTRL, 0);
#else
        CSR_WRITE_4(sc, ACXREG_FWMEM_CTRL, ACXRV_FWMEM_ADDR_AUTOINC);
        CSR_WRITE_4(sc, ACXREG_FWMEM_ADDR, offset);
#endif

        for (i = 0; i < fw_len; ++i) {
#ifndef LOADFW_AUTO_INC
                CSR_WRITE_4(sc, ACXREG_FWMEM_ADDR, offset + (i * 4));
#endif
                CSR_WRITE_4(sc, ACXREG_FWMEM_DATA, betoh32(fw[i]));
        }

        /* Verify firmware */
        CSR_WRITE_4(sc, ACXREG_FWMEM_START, ACXRV_FWMEM_START_OP);
#ifndef LOADFW_AUTO_INC
        CSR_WRITE_4(sc, ACXREG_FWMEM_CTRL, 0);
#else
        CSR_WRITE_4(sc, ACXREG_FWMEM_CTRL, ACXRV_FWMEM_ADDR_AUTOINC);
        CSR_WRITE_4(sc, ACXREG_FWMEM_ADDR, offset);
#endif

        for (i = 0; i < fw_len; ++i) {
                uint32_t val;

#ifndef LOADFW_AUTO_INC
                CSR_WRITE_4(sc, ACXREG_FWMEM_ADDR, offset + (i * 4));
#endif
                val = CSR_READ_4(sc, ACXREG_FWMEM_DATA);
                if (betoh32(fw[i]) != val) {
                        printf("%s: firmware mismatch fw %08x  loaded %08x\n",
                            ifp->if_xname, fw[i], val);
                        return (ENXIO);
                }
        }

        return (0);
}

struct ieee80211_node *
acx_node_alloc(struct ieee80211com *ic)
{
        struct acx_node *wn;

        wn = malloc(sizeof(*wn), M_DEVBUF, M_NOWAIT | M_ZERO);
        if (wn == NULL)
                return (NULL);

        return ((struct ieee80211_node *)wn);
}

int
acx_newstate(struct ieee80211com *ic, enum ieee80211_state nstate, int arg)
{
        struct acx_softc *sc = ic->ic_if.if_softc;
        struct ifnet *ifp = &ic->ic_if;
        int error = 0;

        timeout_del(&sc->amrr_ch);

        switch (nstate) {
        case IEEE80211_S_INIT:
                break;
        case IEEE80211_S_SCAN: {
                        uint8_t chan;

                        chan = ieee80211_chan2ieee(ic, ic->ic_bss->ni_chan);
                        if (acx_set_channel(sc, chan) != 0) {
                                error = 1;
                                goto back;
                        }

                        /* 200ms => 5 channels per second */
                        timeout_add_msec(&sc->sc_chanscan_timer, 200);
                }
                break;
        case IEEE80211_S_AUTH:
                if (ic->ic_opmode == IEEE80211_M_STA) {
                        struct ieee80211_node *ni;
#ifdef ACX_DEBUG
                        int i;
#endif

                        ni = ic->ic_bss;

                        if (acx_join_bss(sc, ACX_MODE_STA, ni) != 0) {
                                printf("%s: join BSS failed\n", ifp->if_xname);
                                error = 1;
                                goto back;
                        }

                        DPRINTF(("%s: join BSS\n", sc->sc_dev.dv_xname));
                        if (ic->ic_state == IEEE80211_S_ASSOC) {
                                DPRINTF(("%s: change from assoc to run\n",
                                    sc->sc_dev.dv_xname));
                                ic->ic_state = IEEE80211_S_RUN;
                        }

#ifdef ACX_DEBUG
                        printf("%s: AP rates: ", sc->sc_dev.dv_xname);
                        for (i = 0; i < ni->ni_rates.rs_nrates; ++i)
                                printf("%d ", ni->ni_rates.rs_rates[i]);
                        ieee80211_print_essid(ni->ni_essid, ni->ni_esslen);
                        printf(" %s\n", ether_sprintf(ni->ni_bssid));
#endif
                }
                break;
        case IEEE80211_S_RUN:
#ifndef IEEE80211_STA_ONLY
                if (ic->ic_opmode == IEEE80211_M_IBSS ||
                    ic->ic_opmode == IEEE80211_M_HOSTAP) {
                        struct ieee80211_node *ni;
                        uint8_t chan;

                        ni = ic->ic_bss;
                        chan = ieee80211_chan2ieee(ic, ni->ni_chan);

                        error = 1;

                        if (acx_set_channel(sc, chan) != 0)
                                goto back;

                        if (acx_set_beacon_tmplt(sc, ni) != 0) {
                                printf("%s: set beacon template failed\n",
                                    ifp->if_xname);
                                goto back;
                        }

                        if (acx_set_probe_resp_tmplt(sc, ni) != 0) {
                                printf("%s: set probe response template "
                                    "failed\n", ifp->if_xname);
                                goto back;
                        }

                        if (ic->ic_opmode == IEEE80211_M_IBSS) {
                                if (acx_join_bss(sc, ACX_MODE_ADHOC, ni) != 0) {
                                        printf("%s: join IBSS failed\n",
                                            ifp->if_xname);
                                        goto back;
                                }
                        } else {
                                if (acx_join_bss(sc, ACX_MODE_AP, ni) != 0) {
                                        printf("%s: join HOSTAP failed\n",
                                            ifp->if_xname);
                                        goto back;
                                }
                        }

                        DPRINTF(("%s: join IBSS\n", sc->sc_dev.dv_xname));
                        error = 0;
                }
#endif
                /* fake a join to init the tx rate */
                if (ic->ic_opmode == IEEE80211_M_STA)
                        acx_newassoc(ic, ic->ic_bss, 1);

                /* start automatic rate control timer */
                if (ic->ic_fixed_rate == -1)
                        timeout_add_msec(&sc->amrr_ch, 500);
                break;
        default:
                break;
        }

back:
        if (error) {
                /* XXX */
                nstate = IEEE80211_S_INIT;
                arg = -1;
        }

        return (sc->sc_newstate(ic, nstate, arg));
}

int
acx_init_tmplt_ordered(struct acx_softc *sc)
{
        union {
                struct acx_tmplt_beacon         beacon;
                struct acx_tmplt_null_data      null;
                struct acx_tmplt_probe_req      preq;
                struct acx_tmplt_probe_resp     presp;
                struct acx_tmplt_tim            tim;
        } data;

        bzero(&data, sizeof(data));
        /*
         * NOTE:
         * Order of templates initialization:
         * 1) Probe request
         * 2) NULL data
         * 3) Beacon
         * 4) TIM
         * 5) Probe response
         * Above order is critical to get a correct memory map.
         */
        if (acx_set_tmplt(sc, ACXCMD_TMPLT_PROBE_REQ, &data.preq,
            sizeof(data.preq)) != 0)
                return (1);

        if (acx_set_tmplt(sc, ACXCMD_TMPLT_NULL_DATA, &data.null,
            sizeof(data.null)) != 0)
                return (1);

        if (acx_set_tmplt(sc, ACXCMD_TMPLT_BEACON, &data.beacon,
            sizeof(data.beacon)) != 0)
                return (1);

        if (acx_set_tmplt(sc, ACXCMD_TMPLT_TIM, &data.tim,
            sizeof(data.tim)) != 0)
                return (1);

        if (acx_set_tmplt(sc, ACXCMD_TMPLT_PROBE_RESP, &data.presp,
            sizeof(data.presp)) != 0)
                return (1);

        return (0);
}

int
acx_dma_alloc(struct acx_softc *sc)
{
        struct acx_ring_data *rd = &sc->sc_ring_data;
        struct acx_buf_data *bd = &sc->sc_buf_data;
        int i, error, nsegs;

        /* Allocate DMA stuffs for RX descriptors  */
        error = bus_dmamap_create(sc->sc_dmat, ACX_RX_RING_SIZE, 1,
            ACX_RX_RING_SIZE, 0, BUS_DMA_NOWAIT, &rd->rx_ring_dmamap);

        if (error) {
                printf("%s: can't create rx ring dma tag\n",
                    sc->sc_dev.dv_xname);
                return (error);
        }

        error = bus_dmamem_alloc(sc->sc_dmat, ACX_RX_RING_SIZE, PAGE_SIZE,
            0, &rd->rx_ring_seg, 1, &nsegs, BUS_DMA_NOWAIT);

        if (error != 0) {
                printf("%s: can't allocate rx ring dma memory\n",
                    sc->sc_dev.dv_xname);
                return (error);
        }

        error = bus_dmamem_map(sc->sc_dmat, &rd->rx_ring_seg, nsegs,
            ACX_RX_RING_SIZE, (caddr_t *)&rd->rx_ring,
            BUS_DMA_NOWAIT);

        if (error != 0) {
                printf("%s: can't map rx desc DMA memory\n",
                    sc->sc_dev.dv_xname);
                return (error);
        }

        error = bus_dmamap_load(sc->sc_dmat, rd->rx_ring_dmamap,
            rd->rx_ring, ACX_RX_RING_SIZE, NULL, BUS_DMA_WAITOK);

        if (error) {
                printf("%s: can't get rx ring dma address\n",
                    sc->sc_dev.dv_xname);
                bus_dmamem_free(sc->sc_dmat, &rd->rx_ring_seg, 1);
                return (error);
        }

        rd->rx_ring_paddr = rd->rx_ring_dmamap->dm_segs[0].ds_addr;

        /* Allocate DMA stuffs for TX descriptors */
        error = bus_dmamap_create(sc->sc_dmat, ACX_TX_RING_SIZE, 1,
            ACX_TX_RING_SIZE, 0, BUS_DMA_NOWAIT, &rd->tx_ring_dmamap);

        if (error) {
                printf("%s: can't create tx ring dma tag\n",
                    sc->sc_dev.dv_xname);
                return (error);
        }

        error = bus_dmamem_alloc(sc->sc_dmat, ACX_TX_RING_SIZE, PAGE_SIZE,
            0, &rd->tx_ring_seg, 1, &nsegs, BUS_DMA_NOWAIT);

        if (error) {
                printf("%s: can't allocate tx ring dma memory\n",
                    sc->sc_dev.dv_xname);
                return (error);
        }

        error = bus_dmamem_map(sc->sc_dmat, &rd->tx_ring_seg, nsegs,
            ACX_TX_RING_SIZE, (caddr_t *)&rd->tx_ring, BUS_DMA_NOWAIT);

        if (error != 0) {
                printf("%s: can't map tx desc DMA memory\n",
                    sc->sc_dev.dv_xname);
                return (error);
        }

        error = bus_dmamap_load(sc->sc_dmat, rd->tx_ring_dmamap,
            rd->tx_ring, ACX_TX_RING_SIZE, NULL, BUS_DMA_WAITOK);

        if (error) {
                printf("%s: can't get tx ring dma address\n",
                    sc->sc_dev.dv_xname);
                bus_dmamem_free(sc->sc_dmat, &rd->tx_ring_seg, 1);
                return (error);
        }

        rd->tx_ring_paddr = rd->tx_ring_dmamap->dm_segs[0].ds_addr;

        /* Create a spare RX DMA map */
        error = bus_dmamap_create(sc->sc_dmat, MCLBYTES, 1, MCLBYTES,
            0, 0, &bd->mbuf_tmp_dmamap);

        if (error) {
                printf("%s: can't create tmp mbuf dma map\n",
                    sc->sc_dev.dv_xname);
                return (error);
        }

        /* Create DMA map for RX mbufs */
        for (i = 0; i < ACX_RX_DESC_CNT; ++i) {
                error = bus_dmamap_create(sc->sc_dmat, MCLBYTES, 1,
                    MCLBYTES, 0, 0, &bd->rx_buf[i].rb_mbuf_dmamap);
                if (error) {
                        printf("%s: can't create rx mbuf dma map (%d)\n",
                            sc->sc_dev.dv_xname, i);
                        return (error);
                }
                bd->rx_buf[i].rb_desc = &rd->rx_ring[i];
        }

        /* Create DMA map for TX mbufs */
        for (i = 0; i < ACX_TX_DESC_CNT; ++i) {
                error = bus_dmamap_create(sc->sc_dmat, MCLBYTES, 1,
                    MCLBYTES, 0, 0, &bd->tx_buf[i].tb_mbuf_dmamap);
                if (error) {
                        printf("%s: can't create tx mbuf dma map (%d)\n",
                            sc->sc_dev.dv_xname, i);
                        return (error);
                }
                bd->tx_buf[i].tb_desc1 = &rd->tx_ring[i * 2];
                bd->tx_buf[i].tb_desc2 = &rd->tx_ring[(i * 2) + 1];
        }

        return (0);
}

void
acx_dma_free(struct acx_softc *sc)
{
        struct acx_ring_data *rd = &sc->sc_ring_data;
        struct acx_buf_data *bd = &sc->sc_buf_data;
        int i;

        if (rd->rx_ring != NULL) {
                bus_dmamap_unload(sc->sc_dmat, rd->rx_ring_dmamap);
                bus_dmamem_free(sc->sc_dmat, &rd->rx_ring_seg, 1);
        }

        if (rd->tx_ring != NULL) {
                bus_dmamap_unload(sc->sc_dmat, rd->tx_ring_dmamap);
                bus_dmamem_free(sc->sc_dmat, &rd->tx_ring_seg, 1);
        }

        for (i = 0; i < ACX_RX_DESC_CNT; ++i) {
                if (bd->rx_buf[i].rb_desc != NULL) {
                        if (bd->rx_buf[i].rb_mbuf != NULL) {
                                bus_dmamap_unload(sc->sc_dmat,
                                    bd->rx_buf[i].rb_mbuf_dmamap);
                                m_freem(bd->rx_buf[i].rb_mbuf);
                        }
                        bus_dmamap_destroy(sc->sc_dmat,
                            bd->rx_buf[i].rb_mbuf_dmamap);
                }
        }

        for (i = 0; i < ACX_TX_DESC_CNT; ++i) {
                if (bd->tx_buf[i].tb_desc1 != NULL) {
                        if (bd->tx_buf[i].tb_mbuf != NULL) {
                                bus_dmamap_unload(sc->sc_dmat,
                                    bd->tx_buf[i].tb_mbuf_dmamap);
                                m_freem(bd->tx_buf[i].tb_mbuf);
                        }
                        bus_dmamap_destroy(sc->sc_dmat,
                            bd->tx_buf[i].tb_mbuf_dmamap);
                }
        }

        if (bd->mbuf_tmp_dmamap != NULL)
                bus_dmamap_destroy(sc->sc_dmat, bd->mbuf_tmp_dmamap);
}

void
acx_init_tx_ring(struct acx_softc *sc)
{
        struct acx_ring_data *rd;
        struct acx_buf_data *bd;
        uint32_t paddr;
        int i;

        rd = &sc->sc_ring_data;
        paddr = rd->tx_ring_paddr;
        for (i = 0; i < (ACX_TX_DESC_CNT * 2) - 1; ++i) {
                paddr += sizeof(struct acx_host_desc);

                bzero(&rd->tx_ring[i], sizeof(struct acx_host_desc));
                rd->tx_ring[i].h_ctrl = htole16(DESC_CTRL_HOSTOWN);

                if (i == (ACX_TX_DESC_CNT * 2) - 1)
                        rd->tx_ring[i].h_next_desc = htole32(rd->tx_ring_paddr);
                else
                        rd->tx_ring[i].h_next_desc = htole32(paddr);
        }

        bus_dmamap_sync(sc->sc_dmat, rd->tx_ring_dmamap, 0,
            rd->tx_ring_dmamap->dm_mapsize, BUS_DMASYNC_PREWRITE);

        bd = &sc->sc_buf_data;
        bd->tx_free_start = 0;
        bd->tx_used_start = 0;
        bd->tx_used_count = 0;
}

int
acx_init_rx_ring(struct acx_softc *sc)
{
        struct acx_ring_data *rd;
        struct acx_buf_data *bd;
        uint32_t paddr;
        int i;

        bd = &sc->sc_buf_data;
        rd = &sc->sc_ring_data;
        paddr = rd->rx_ring_paddr;

        for (i = 0; i < ACX_RX_DESC_CNT; ++i) {
                int error;

                paddr += sizeof(struct acx_host_desc);
                bzero(&rd->rx_ring[i], sizeof(struct acx_host_desc));

                error = acx_newbuf(sc, &bd->rx_buf[i], 1);
                if (error)
                        return (error);

                if (i == ACX_RX_DESC_CNT - 1)
                        rd->rx_ring[i].h_next_desc = htole32(rd->rx_ring_paddr);
                else
                        rd->rx_ring[i].h_next_desc = htole32(paddr);
        }

        bus_dmamap_sync(sc->sc_dmat, rd->rx_ring_dmamap, 0,
            rd->rx_ring_dmamap->dm_mapsize, BUS_DMASYNC_PREWRITE);

        bd->rx_scan_start = 0;

        return (0);
}

int
acx_newbuf(struct acx_softc *sc, struct acx_rxbuf *rb, int wait)
{
        struct acx_buf_data *bd;
        struct mbuf *m;
        bus_dmamap_t map;
        uint32_t paddr;
        int error;

        bd = &sc->sc_buf_data;

        MGETHDR(m, wait ? M_WAITOK : M_DONTWAIT, MT_DATA);
        if (m == NULL)
                return (ENOBUFS);

        MCLGET(m, wait ? M_WAITOK : M_DONTWAIT);
        if (!(m->m_flags & M_EXT)) {
                m_freem(m);
                return (ENOBUFS);
        }

        m->m_len = m->m_pkthdr.len = MCLBYTES;

        error = bus_dmamap_load_mbuf(sc->sc_dmat, bd->mbuf_tmp_dmamap, m,
            wait ? BUS_DMA_WAITOK : BUS_DMA_NOWAIT);
        if (error) {
                m_freem(m);
                printf("%s: can't map rx mbuf %d\n",
                    sc->sc_dev.dv_xname, error);
                return (error);
        }

        /* Unload originally mapped mbuf */
        if (rb->rb_mbuf != NULL)
                bus_dmamap_unload(sc->sc_dmat, rb->rb_mbuf_dmamap);

        /* Swap this dmamap with tmp dmamap */
        map = rb->rb_mbuf_dmamap;
        rb->rb_mbuf_dmamap = bd->mbuf_tmp_dmamap;
        bd->mbuf_tmp_dmamap = map;
        paddr = rb->rb_mbuf_dmamap->dm_segs[0].ds_addr;

        rb->rb_mbuf = m;
        rb->rb_desc->h_data_paddr = htole32(paddr);
        rb->rb_desc->h_data_len = htole16(m->m_len);

        bus_dmamap_sync(sc->sc_dmat, rb->rb_mbuf_dmamap, 0,
            rb->rb_mbuf_dmamap->dm_mapsize, BUS_DMASYNC_PREREAD);

        return (0);
}

int
acx_encap(struct acx_softc *sc, struct acx_txbuf *txbuf, struct mbuf *m,
    struct ieee80211_node *ni, int rate)
{
        struct acx_ring_data *rd = &sc->sc_ring_data;
        struct acx_node *node = (struct acx_node *)ni;
        struct ifnet *ifp = &sc->sc_ic.ic_if;
        uint32_t paddr;
        uint8_t ctrl;
        int error;

        if (txbuf->tb_mbuf != NULL)
                panic("free TX buf has mbuf installed");

        if (m->m_pkthdr.len > MCLBYTES) {
                printf("%s: mbuf too big\n", ifp->if_xname);
                error = E2BIG;
                goto back;
        } else if (m->m_pkthdr.len < ACX_FRAME_HDRLEN) {
                printf("%s: mbuf too small\n", ifp->if_xname);
                error = EINVAL;
                goto back;
        }

        error = bus_dmamap_load_mbuf(sc->sc_dmat, txbuf->tb_mbuf_dmamap, m,
            BUS_DMA_NOWAIT);

        if (error && error != EFBIG) {
                printf("%s: can't map tx mbuf1 %d\n",
                    sc->sc_dev.dv_xname, error);
                goto back;
        }

        if (error) {    /* error == EFBIG */
                /* too many fragments, linearize */
                if (m_defrag(m, M_DONTWAIT)) {
                        printf("%s: can't defrag tx mbuf\n", ifp->if_xname);
                        goto back;
                }
                error = bus_dmamap_load_mbuf(sc->sc_dmat,
                    txbuf->tb_mbuf_dmamap, m, BUS_DMA_NOWAIT);
                if (error) {
                        printf("%s: can't map tx mbuf2 %d\n",
                            sc->sc_dev.dv_xname, error);
                        goto back;
                }
        }

        error = 0;

        bus_dmamap_sync(sc->sc_dmat, txbuf->tb_mbuf_dmamap, 0,
            txbuf->tb_mbuf_dmamap->dm_mapsize, BUS_DMASYNC_PREWRITE);

        txbuf->tb_mbuf = m;
        txbuf->tb_node = node;
        txbuf->tb_rate = rate;

        /*
         * TX buffers are accessed in following way:
         * acx_fw_txdesc -> acx_host_desc -> buffer
         *
         * It is quite strange that acx also queries acx_host_desc next to
         * the one we have assigned to acx_fw_txdesc even if first one's
         * acx_host_desc.h_data_len == acx_fw_txdesc.f_tx_len
         *
         * So we allocate two acx_host_desc for one acx_fw_txdesc and
         * assign the first acx_host_desc to acx_fw_txdesc
         *
         * For acx111
         * host_desc1.h_data_len = buffer_len
         * host_desc2.h_data_len = buffer_len - mac_header_len
         *
         * For acx100
         * host_desc1.h_data_len = mac_header_len
         * host_desc2.h_data_len = buffer_len - mac_header_len
         */
        paddr = txbuf->tb_mbuf_dmamap->dm_segs[0].ds_addr;
        txbuf->tb_desc1->h_data_paddr = htole32(paddr);
        txbuf->tb_desc2->h_data_paddr = htole32(paddr + ACX_FRAME_HDRLEN);

        txbuf->tb_desc1->h_data_len =
            htole16(sc->chip_txdesc1_len ? sc->chip_txdesc1_len
            : m->m_pkthdr.len);
        txbuf->tb_desc2->h_data_len =
            htole16(m->m_pkthdr.len - ACX_FRAME_HDRLEN);

        /*
         * NOTE:
         * We can't simply assign f_tx_ctrl, we will first read it back
         * and change it bit by bit
         */
        ctrl = FW_TXDESC_GETFIELD_1(sc, txbuf, f_tx_ctrl);
        ctrl |= sc->chip_fw_txdesc_ctrl; /* extra chip specific flags */
        ctrl &= ~(DESC_CTRL_HOSTOWN | DESC_CTRL_ACXDONE);

        FW_TXDESC_SETFIELD_2(sc, txbuf, f_tx_len, m->m_pkthdr.len);
        FW_TXDESC_SETFIELD_1(sc, txbuf, f_tx_error, 0);
        FW_TXDESC_SETFIELD_1(sc, txbuf, f_tx_ack_fail, 0);
        FW_TXDESC_SETFIELD_1(sc, txbuf, f_tx_rts_fail, 0);
        FW_TXDESC_SETFIELD_1(sc, txbuf, f_tx_rts_ok, 0);
        sc->chip_set_fw_txdesc_rate(sc, txbuf, rate);

        txbuf->tb_desc1->h_ctrl = 0;
        txbuf->tb_desc2->h_ctrl = 0;
        bus_dmamap_sync(sc->sc_dmat, rd->tx_ring_dmamap, 0,
            rd->tx_ring_dmamap->dm_mapsize, BUS_DMASYNC_PREWRITE);

        FW_TXDESC_SETFIELD_1(sc, txbuf, f_tx_ctrl2, 0);
        FW_TXDESC_SETFIELD_1(sc, txbuf, f_tx_ctrl, ctrl);

        /* Tell chip to inform us about TX completion */
        CSR_WRITE_2(sc, ACXREG_INTR_TRIG, ACXRV_TRIG_TX_FINI);
back:
        if (error)
                m_freem(m);

        return (error);
}

int
acx_set_null_tmplt(struct acx_softc *sc)
{
        struct ieee80211com *ic = &sc->sc_ic;
        struct acx_tmplt_null_data n;
        struct ieee80211_frame *wh;

        bzero(&n, sizeof(n));

        wh = &n.data;
        wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_DATA |
            IEEE80211_FC0_SUBTYPE_NODATA;
        wh->i_fc[1] = IEEE80211_FC1_DIR_NODS;
        IEEE80211_ADDR_COPY(wh->i_addr1, etherbroadcastaddr);
        IEEE80211_ADDR_COPY(wh->i_addr2, ic->ic_myaddr);
        IEEE80211_ADDR_COPY(wh->i_addr3, etherbroadcastaddr);

        return (acx_set_tmplt(sc, ACXCMD_TMPLT_NULL_DATA, &n, sizeof(n)));
}

int
acx_set_probe_req_tmplt(struct acx_softc *sc, const char *ssid, int ssid_len)
{
        struct ieee80211com *ic = &sc->sc_ic;
        struct acx_tmplt_probe_req req;
        struct ieee80211_frame *wh;
        struct ieee80211_rateset *rs;
        uint8_t *frm;
        int len;

        bzero(&req, sizeof(req));

        wh = &req.data.u_data.f;
        wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_MGT |
            IEEE80211_FC0_SUBTYPE_PROBE_REQ;
        wh->i_fc[1] = IEEE80211_FC1_DIR_NODS;
        IEEE80211_ADDR_COPY(wh->i_addr1, etherbroadcastaddr);
        IEEE80211_ADDR_COPY(wh->i_addr2, ic->ic_myaddr);
        IEEE80211_ADDR_COPY(wh->i_addr3, etherbroadcastaddr);

        frm = req.data.u_data.var;
        frm = ieee80211_add_ssid(frm, ssid, ssid_len);
        rs = &ic->ic_sup_rates[sc->chip_phymode];
        frm = ieee80211_add_rates(frm, rs);
        if (rs->rs_nrates > IEEE80211_RATE_SIZE)
                frm = ieee80211_add_xrates(frm, rs);
        len = frm - req.data.u_data.var;

        return (acx_set_tmplt(sc, ACXCMD_TMPLT_PROBE_REQ, &req,
            ACX_TMPLT_PROBE_REQ_SIZ(len)));
}

#ifndef IEEE80211_STA_ONLY
struct mbuf *ieee80211_get_probe_resp(struct ieee80211com *,
    struct ieee80211_node *);

int
acx_set_probe_resp_tmplt(struct acx_softc *sc, struct ieee80211_node *ni)
{
        struct ieee80211com *ic = &sc->sc_ic;
        struct acx_tmplt_probe_resp resp;
        struct ieee80211_frame *wh;
        struct mbuf *m;
        int len;

        bzero(&resp, sizeof(resp));

        m = ieee80211_get_probe_resp(ic, ni);
        if (m == NULL)
                return (1);
        M_PREPEND(m, sizeof(struct ieee80211_frame), M_DONTWAIT);
        if (m == NULL)
                return (1);
        wh = mtod(m, struct ieee80211_frame *);
        wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_MGT |
            IEEE80211_FC0_SUBTYPE_PROBE_RESP;
        wh->i_fc[1] = IEEE80211_FC1_DIR_NODS;
        *(u_int16_t *)&wh->i_dur[0] = 0;
        IEEE80211_ADDR_COPY(wh->i_addr1, ni->ni_macaddr);
        IEEE80211_ADDR_COPY(wh->i_addr2, ic->ic_myaddr);
        IEEE80211_ADDR_COPY(wh->i_addr3, ni->ni_bssid);
        *(u_int16_t *)wh->i_seq = 0;

        m_copydata(m, 0, m->m_pkthdr.len, &resp.data);
        len = m->m_pkthdr.len + sizeof(resp.size);
        m_freem(m); 

        return (acx_set_tmplt(sc, ACXCMD_TMPLT_PROBE_RESP, &resp, len));
}

int
acx_beacon_locate(struct mbuf *m, u_int8_t type)
{
        int off;
        u_int8_t *frm;
        /*
         * beacon frame format
         *      [8] time stamp
         *      [2] beacon interval
         *      [2] capability information
         *      from here on [tlv] values
         */

        if (m->m_len != m->m_pkthdr.len)
                panic("beacon not in contiguous mbuf");

        off = sizeof(struct ieee80211_frame) + 8 + 2 + 2;
        frm = mtod(m, u_int8_t *);
        for (; off + 1 < m->m_len; off += frm[off + 1] + 2) {
                if (frm[off] == type)
                        return (off);
        }
        return (-1);
}

int
acx_set_beacon_tmplt(struct acx_softc *sc, struct ieee80211_node *ni)
{
        struct ieee80211com *ic = &sc->sc_ic;
        struct acx_tmplt_beacon beacon;
        struct acx_tmplt_tim tim;
        struct mbuf *m;
        int len, off;

        bzero(&beacon, sizeof(beacon));
        bzero(&tim, sizeof(tim));

        m = ieee80211_beacon_alloc(ic, ni);
        if (m == NULL)
                return (1);

        off = acx_beacon_locate(m, IEEE80211_ELEMID_TIM);
        if (off < 0) {
                m_free(m);
                return (1);
        }

        m_copydata(m, 0, off, &beacon.data);
        len = off + sizeof(beacon.size);

        if (acx_set_tmplt(sc, ACXCMD_TMPLT_BEACON, &beacon, len) != 0) {
                m_freem(m);
                return (1);
        }

        len = m->m_pkthdr.len - off;
        if (len == 0) {
                /* no TIM field */
                m_freem(m);
                return (0);
        }

        m_copydata(m, off, len, &tim.data);
        len += sizeof(beacon.size);
        m_freem(m);

        return (acx_set_tmplt(sc, ACXCMD_TMPLT_TIM, &tim, len));
}
#endif  /* IEEE80211_STA_ONLY */

void
acx_init_cmd_reg(struct acx_softc *sc)
{
        sc->sc_cmd = CSR_READ_4(sc, ACXREG_CMD_REG_OFFSET);
        sc->sc_cmd_param = sc->sc_cmd + ACX_CMD_REG_SIZE;

        /* Clear command & status */
        CMD_WRITE_4(sc, 0);
}

int
acx_join_bss(struct acx_softc *sc, uint8_t mode, struct ieee80211_node *node)
{
        uint8_t bj_buf[BSS_JOIN_BUFLEN];
        struct bss_join_hdr *bj;
        int i, dtim_intvl;

        bzero(bj_buf, sizeof(bj_buf));
        bj = (struct bss_join_hdr *)bj_buf;

        for (i = 0; i < IEEE80211_ADDR_LEN; ++i)
                bj->bssid[i] = node->ni_bssid[IEEE80211_ADDR_LEN - i - 1];

        bj->beacon_intvl = htole16(acx_beacon_intvl);

        /* TODO tunable */
#ifndef IEEE80211_STA_ONLY
        if (sc->sc_ic.ic_opmode == IEEE80211_M_IBSS)
                dtim_intvl = 1;
        else
#endif
                dtim_intvl = 10;
        sc->chip_set_bss_join_param(sc, bj->chip_spec, dtim_intvl);

        bj->ndata_txrate = ACX_NDATA_TXRATE_1;
        bj->ndata_txopt = 0;
        bj->mode = mode;
        bj->channel = ieee80211_chan2ieee(&sc->sc_ic, node->ni_chan);
        bj->esslen = node->ni_esslen;
        bcopy(node->ni_essid, bj->essid, node->ni_esslen);

        DPRINTF(("%s: join BSS/IBSS on channel %d\n", sc->sc_dev.dv_xname,
            bj->channel));
        return (acx_exec_command(sc, ACXCMD_JOIN_BSS,
            bj, BSS_JOIN_PARAM_SIZE(bj), NULL, 0));
}

int
acx_set_channel(struct acx_softc *sc, uint8_t chan)
{
        if (acx_exec_command(sc, ACXCMD_ENABLE_TXCHAN, &chan, sizeof(chan),
            NULL, 0) != 0) {
                DPRINTF(("%s: setting TX channel %d failed\n",
                    sc->sc_dev.dv_xname, chan));
                return (ENXIO);
        }

        if (acx_exec_command(sc, ACXCMD_ENABLE_RXCHAN, &chan, sizeof(chan),
            NULL, 0) != 0) {
                DPRINTF(("%s: setting RX channel %d failed\n",
                    sc->sc_dev.dv_xname, chan));
                return (ENXIO);
        }

        return (0);
}

int
acx_get_conf(struct acx_softc *sc, uint16_t conf_id, void *conf,
    uint16_t conf_len)
{
        struct acx_conf *confcom;

        if (conf_len < sizeof(*confcom)) {
                printf("%s: %s configure data is too short\n",
                    sc->sc_dev.dv_xname, __func__);
                return (1);
        }

        confcom = conf;
        confcom->conf_id = htole16(conf_id);
        confcom->conf_data_len = htole16(conf_len - sizeof(*confcom));

        return (acx_exec_command(sc, ACXCMD_GET_CONF, confcom, sizeof(*confcom),
            conf, conf_len));
}

int
acx_set_conf(struct acx_softc *sc, uint16_t conf_id, void *conf,
    uint16_t conf_len)
{
        struct acx_conf *confcom;

        if (conf_len < sizeof(*confcom)) {
                printf("%s: %s configure data is too short\n",
                    sc->sc_dev.dv_xname, __func__);
                return (1);
        }

        confcom = conf;
        confcom->conf_id = htole16(conf_id);
        confcom->conf_data_len = htole16(conf_len - sizeof(*confcom));

        return (acx_exec_command(sc, ACXCMD_SET_CONF, conf, conf_len, NULL, 0));
}

int
acx_set_tmplt(struct acx_softc *sc, uint16_t cmd, void *tmplt,
    uint16_t tmplt_len)
{
        uint16_t *size;

        if (tmplt_len < sizeof(*size)) {
                printf("%s: %s template is too short\n",
                    sc->sc_dev.dv_xname, __func__);
                return (1);
        }

        size = tmplt;
        *size = htole16(tmplt_len - sizeof(*size));

        return (acx_exec_command(sc, cmd, tmplt, tmplt_len, NULL, 0));
}

int
acx_init_radio(struct acx_softc *sc, uint32_t radio_ofs, uint32_t radio_len)
{
        struct radio_init r;

        r.radio_ofs = htole32(radio_ofs);
        r.radio_len = htole32(radio_len);

        return (acx_exec_command(sc, ACXCMD_INIT_RADIO, &r, sizeof(r), NULL,
            0));
}

int
acx_exec_command(struct acx_softc *sc, uint16_t cmd, void *param,
    uint16_t param_len, void *result, uint16_t result_len)
{
        uint16_t status;
        int i, ret;

        if ((sc->sc_flags & ACX_FLAG_FW_LOADED) == 0) {
                printf("%s: cmd 0x%04x failed (base firmware not loaded)\n",
                    sc->sc_dev.dv_xname, cmd);
                return (1);
        }

        ret = 0;

        if (param != NULL && param_len != 0) {
                /* Set command param */
                CMDPRM_WRITE_REGION_1(sc, param, param_len);
        }

        /* Set command */
        CMD_WRITE_4(sc, cmd);

        /* Exec command */
        CSR_WRITE_2(sc, ACXREG_INTR_TRIG, ACXRV_TRIG_CMD_FINI);
        DELAY(50);

        /* Wait for command to complete */
        if (cmd == ACXCMD_INIT_RADIO) {
                /* radio initialization is extremely long */
                tsleep_nsec(&cmd, 0, "rdinit", MSEC_TO_NSEC(300));
        }

#define CMDWAIT_RETRY_MAX       1000
        for (i = 0; i < CMDWAIT_RETRY_MAX; ++i) {
                uint16_t reg;

                reg = CSR_READ_2(sc, ACXREG_INTR_STATUS);
                if (reg & ACXRV_INTR_CMD_FINI) {
                        CSR_WRITE_2(sc, ACXREG_INTR_ACK, ACXRV_INTR_CMD_FINI);
                        break;
                }
                DELAY(50);
        }
        if (i == CMDWAIT_RETRY_MAX) {
                printf("%s: cmd %04x failed (timeout)\n",
                    sc->sc_dev.dv_xname, cmd);
                ret = 1;
                goto back;
        }
#undef CMDWAIT_RETRY_MAX

        /* Get command exec status */
        status = (CMD_READ_4(sc) >> ACX_CMD_STATUS_SHIFT);
        if (status != ACX_CMD_STATUS_OK) {
                DPRINTF(("%s: cmd %04x failed\n", sc->sc_dev.dv_xname, cmd));
                ret = 1;
                goto back;
        }

        if (result != NULL && result_len != 0) {
                /* Get command result */
                CMDPRM_READ_REGION_1(sc, result, result_len);
        }

back:
        CMD_WRITE_4(sc, 0);

        return (ret);
}

const char *
acx_get_rf(int rev)
{
        switch (rev) {
        case ACX_RADIO_TYPE_MAXIM:      return "MAX2820";
        case ACX_RADIO_TYPE_RFMD:       return "RFMD";
        case ACX_RADIO_TYPE_RALINK:     return "Ralink";
        case ACX_RADIO_TYPE_RADIA:      return "Radia";
        default:                        return "unknown";
        }
}

int
acx_get_maxrssi(int radio)
{
        switch (radio) {
        case ACX_RADIO_TYPE_MAXIM:      return ACX_RADIO_RSSI_MAXIM;
        case ACX_RADIO_TYPE_RFMD:       return ACX_RADIO_RSSI_RFMD;
        case ACX_RADIO_TYPE_RALINK:     return ACX_RADIO_RSSI_RALINK;
        case ACX_RADIO_TYPE_RADIA:      return ACX_RADIO_RSSI_RADIA;
        default:                        return ACX_RADIO_RSSI_UNKN;
        }
}

void
acx_iter_func(void *arg, struct ieee80211_node *ni)
{
        struct acx_softc *sc = arg;
        struct acx_node *wn = (struct acx_node *)ni;

        ieee80211_amrr_choose(&sc->amrr, ni, &wn->amn);
}

void
acx_amrr_timeout(void *arg)
{
        struct acx_softc *sc = arg;
        struct ieee80211com *ic = &sc->sc_ic;

        if (ic->ic_opmode == IEEE80211_M_STA)
                acx_iter_func(sc, ic->ic_bss);
        else
                ieee80211_iterate_nodes(ic, acx_iter_func, sc);

        timeout_add_msec(&sc->amrr_ch, 500);
}

void
acx_newassoc(struct ieee80211com *ic, struct ieee80211_node *ni, int isnew)
{
        struct acx_softc *sc = ic->ic_if.if_softc;
        int i;

        ieee80211_amrr_node_init(&sc->amrr, &((struct acx_node *)ni)->amn);

        /* set rate to some reasonable initial value */
        for (i = ni->ni_rates.rs_nrates - 1;
            i > 0 && (ni->ni_rates.rs_rates[i] & IEEE80211_RATE_VAL) > 72;
            i--);
        ni->ni_txrate = i;
}

#ifndef IEEE80211_STA_ONLY
void
acx_set_tim(struct ieee80211com *ic, int aid, int set)
{
        struct acx_softc *sc = ic->ic_if.if_softc;
        struct acx_tmplt_tim tim;
        u_int8_t *ep;

        ieee80211_set_tim(ic, aid, set);

        bzero(&tim, sizeof(tim));
        ep = ieee80211_add_tim(tim.data.u_mem, ic);

        acx_set_tmplt(sc, ACXCMD_TMPLT_TIM, &tim, ep - (u_int8_t *)&tim);
}
#endif