root/sys/dev/ic/bwfm.c
/* $NetBSD: bwfm.c,v 1.37 2026/02/20 12:05:51 jmcneill Exp $ */
/* $OpenBSD: bwfm.c,v 1.5 2017/10/16 22:27:16 patrick Exp $ */
/*
 * Copyright (c) 2010-2016 Broadcom Corporation
 * Copyright (c) 2016,2017 Patrick Wildt <patrick@blueri.se>
 *
 * Permission to use, copy, modify, and/or 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.
 */

#include <sys/param.h>
#include <sys/types.h>

#include <sys/buf.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/kmem.h>
#include <sys/pool.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/systm.h>
#include <sys/workqueue.h>

#include <net/bpf.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_ether.h>
#include <net/if_media.h>

#include <netinet/in.h>

#include <net80211/ieee80211_var.h>

#include <dev/firmload.h>

#include <dev/ic/bwfmreg.h>
#include <dev/ic/bwfmvar.h>

/* #define BWFM_DEBUG */
#ifdef BWFM_DEBUG
#define DPRINTF(x)      do { if (bwfm_debug > 0) printf x; } while (0)
#define DPRINTFN(n, x)  do { if (bwfm_debug >= (n)) printf x; } while (0)
static int bwfm_debug = 1;
#else
#define DPRINTF(x)      do { ; } while (0)
#define DPRINTFN(n, x)  do { ; } while (0)
#endif

#define DEVNAME(sc)     device_xname((sc)->sc_dev)

void     bwfm_start(struct ifnet *);
int      bwfm_init(struct ifnet *);
void     bwfm_stop(struct ifnet *, int);
void     bwfm_watchdog(struct ifnet *);
int      bwfm_ioctl(struct ifnet *, u_long, void *);
int      bwfm_media_change(struct ifnet *);

int      bwfm_send_mgmt(struct ieee80211com *, struct ieee80211_node *,
             int, int);
void     bwfm_recv_mgmt(struct ieee80211com *, struct mbuf *,
             struct ieee80211_node *, int, int, uint32_t);
int      bwfm_key_set(struct ieee80211com *, const struct ieee80211_key *,
             const uint8_t[IEEE80211_ADDR_LEN]);
int      bwfm_key_delete(struct ieee80211com *, const struct ieee80211_key *);
int      bwfm_newstate(struct ieee80211com *, enum ieee80211_state, int);
void     bwfm_newstate_cb(struct bwfm_softc *, struct bwfm_cmd_newstate *);
void     bwfm_newassoc(struct ieee80211_node *, int);
void     bwfm_task(struct work *, void *);

int      bwfm_chip_attach(struct bwfm_softc *);
int      bwfm_chip_detach(struct bwfm_softc *, int);
struct bwfm_core *bwfm_chip_get_core(struct bwfm_softc *, int);
struct bwfm_core *bwfm_chip_get_pmu(struct bwfm_softc *);
int      bwfm_chip_ai_isup(struct bwfm_softc *, struct bwfm_core *);
void     bwfm_chip_ai_disable(struct bwfm_softc *, struct bwfm_core *,
             uint32_t, uint32_t);
void     bwfm_chip_ai_reset(struct bwfm_softc *, struct bwfm_core *,
             uint32_t, uint32_t, uint32_t);
void     bwfm_chip_dmp_erom_scan(struct bwfm_softc *);
int      bwfm_chip_dmp_get_regaddr(struct bwfm_softc *, uint32_t *,
             uint32_t *, uint32_t *);
int      bwfm_chip_cr4_set_active(struct bwfm_softc *, const uint32_t);
void     bwfm_chip_cr4_set_passive(struct bwfm_softc *);
int      bwfm_chip_ca7_set_active(struct bwfm_softc *, const uint32_t);
void     bwfm_chip_ca7_set_passive(struct bwfm_softc *);
int      bwfm_chip_cm3_set_active(struct bwfm_softc *);
void     bwfm_chip_cm3_set_passive(struct bwfm_softc *);
void     bwfm_chip_socram_ramsize(struct bwfm_softc *, struct bwfm_core *);
void     bwfm_chip_sysmem_ramsize(struct bwfm_softc *, struct bwfm_core *);
void     bwfm_chip_tcm_ramsize(struct bwfm_softc *, struct bwfm_core *);
void     bwfm_chip_tcm_rambase(struct bwfm_softc *);

void     bwfm_process_blob(struct bwfm_softc *, const char *, uint8_t **,
             size_t *);
int      bwfm_proto_bcdc_query_dcmd(struct bwfm_softc *, int,
             int, char *, size_t *);
int      bwfm_proto_bcdc_set_dcmd(struct bwfm_softc *, int,
             int, char *, size_t);

int      bwfm_fwvar_cmd_get_data(struct bwfm_softc *, int, void *, size_t);
int      bwfm_fwvar_cmd_set_data(struct bwfm_softc *, int, void *, size_t);
int      bwfm_fwvar_cmd_get_int(struct bwfm_softc *, int, uint32_t *);
int      bwfm_fwvar_cmd_set_int(struct bwfm_softc *, int, uint32_t);
int      bwfm_fwvar_var_get_data(struct bwfm_softc *, const char *, void *, size_t);
int      bwfm_fwvar_var_set_data(struct bwfm_softc *, const char *, void *, size_t);
int      bwfm_fwvar_var_get_int(struct bwfm_softc *, const char *, uint32_t *);
int      bwfm_fwvar_var_set_int(struct bwfm_softc *, const char *, uint32_t);

struct ieee80211_channel *bwfm_bss2chan(struct bwfm_softc *, struct bwfm_bss_info *);
void     bwfm_scan(struct bwfm_softc *);
void     bwfm_connect(struct bwfm_softc *);
void     bwfm_get_sta_info(struct bwfm_softc *, struct ifmediareq *);

void     bwfm_rx(struct bwfm_softc *, struct mbuf *);
void     bwfm_rx_event(struct bwfm_softc *, struct mbuf *);
void     bwfm_rx_event_cb(struct bwfm_softc *, struct mbuf *);
void     bwfm_scan_node(struct bwfm_softc *, struct bwfm_bss_info *, size_t);

static const uint8_t bwfm_2ghz_channels[] = {
        1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
};
static const uint8_t bwfm_5ghz_channels[] = {
        34, 36, 38, 40, 42, 44, 46, 48, 52, 56, 60, 64, 100, 104, 108, 112,
        116, 120, 124, 128, 132, 136, 140, 144, 149, 153, 157, 161, 165,
};

const struct bwfm_proto_ops bwfm_proto_bcdc_ops = {
        .proto_query_dcmd = bwfm_proto_bcdc_query_dcmd,
        .proto_set_dcmd = bwfm_proto_bcdc_set_dcmd,
};

static const struct {
        const char *suffix;
        const char *description;
} bwfm_firmware_filetypes[] = {
        [BWFM_FILETYPE_UCODE] = {
                .suffix = "bin",
                .description = "Firmware",
        },
        [BWFM_FILETYPE_NVRAM] = {
                .suffix = "txt",
                .description = "NVRAM",
        },
        [BWFM_FILETYPE_CLM] = {
                .suffix = "clm_blob",
                .description = "CLM",
        },
        [BWFM_FILETYPE_TXCAP] = {
                .suffix = "txcap_blob",
                .description = "TXCAP",
        },
        [BWFM_FILETYPE_CAL] = {
                .suffix = "cal_blob",
                .description = "CAL",
        },
};

static void
bwfm_firmware_read_file(struct bwfm_softc * const sc,
    const struct bwfm_firmware_selector * const fwp,
    struct bwfm_firmware_context * const ctx,
    unsigned int const which)
{
        firmware_handle_t fwh;
        char *names[2];
        int i, error;

        names[1] = kmem_asprintf("%s.%s", fwp->fwsel_basename,
            bwfm_firmware_filetypes[which].suffix);
        names[0] = ctx->ctx_model ? kmem_asprintf("%s.%s.%s",
            fwp->fwsel_basename, ctx->ctx_model,
            bwfm_firmware_filetypes[which].suffix) : NULL;

        aprint_verbose_dev(sc->sc_dev, "%s file default:    %s\n",
            bwfm_firmware_filetypes[which].description, names[1]);
        if (names[0]) {
                aprint_verbose_dev(sc->sc_dev, "%s file model-spec: %s\n",
                    bwfm_firmware_filetypes[which].description, names[0]);
        }

        for (i = 0; i < 2; i++) {
                if (names[i] == NULL)
                        continue;
                error = firmware_open("if_bwfm", names[i], &fwh);
                if (error == 0)
                        break;
        }
        if (i == 2)
                goto out;

        aprint_verbose_dev(sc->sc_dev, "Found %s file: %s\n",
            bwfm_firmware_filetypes[which].description, names[i]);

        size_t size = firmware_get_size(fwh);
        void *data = firmware_malloc(size);
        if (data == NULL) {
                aprint_error_dev(sc->sc_dev,
                    "unable to allocate %zu bytes for %s image\n", size,
                    bwfm_firmware_filetypes[which].description);
                firmware_close(fwh);
                goto out;
        }
        error = firmware_read(fwh, 0, data, size);
        firmware_close(fwh);
        if (error) {
                aprint_error_dev(sc->sc_dev,
                    "failed to read %s file, error %d\n",
                    bwfm_firmware_filetypes[which].description,
                    error);
                firmware_free(data, size);
                goto out;
        }

        ctx->ctx_file[which].ctx_f_data = data;
        ctx->ctx_file[which].ctx_f_size = size;
 out:
        for (i = 0; i < 2; i++) {
                if (names[i])
                        kmem_free(names[i], strlen(names[i])+1);
        }
}

void
bwfm_firmware_context_init(struct bwfm_firmware_context * const ctx,
    uint32_t const chip, uint32_t const chiprev, const char * const model,
    uint32_t req)
{
        memset(ctx, 0, sizeof(*ctx));
        ctx->ctx_chip = chip;
        ctx->ctx_chiprev = chiprev;
        ctx->ctx_model = model;

        /* all devices require ucode */
        ctx->ctx_req = req | BWFM_FWREQ(BWFM_FILETYPE_UCODE);
}

bool
bwfm_firmware_open(struct bwfm_softc * const sc,
    const struct bwfm_firmware_selector * const fwtab,
    struct bwfm_firmware_context * const ctx)
{
        const struct bwfm_firmware_selector *fwp;
        unsigned int i;

        KASSERT(fwtab != NULL);
        KASSERT(ctx != NULL);

        /* First locate the appropriate entry for this chip / rev. */
        for (fwp = fwtab; fwp->fwsel_basename != NULL; fwp++) {
                if (fwp->fwsel_chip == ctx->ctx_chip &&
                    fwp->fwsel_revmask & __BIT(ctx->ctx_chiprev))
                        break;
        }
        if (fwp->fwsel_basename == NULL) {
                aprint_error_dev(sc->sc_dev,
                    "No firmware entry for chip 0x%x/%u rev %u model %s\n",
                    ctx->ctx_chip, ctx->ctx_chip, ctx->ctx_chiprev,
                    ctx->ctx_model);
                return false;
        }

        bool rv = true;

        /*
         * Read in each file that the front-end has requested as
         * either required or optional.
         */
        for (i = 0; i < BWFM_NFILETYPES; i++) {
                if (ctx->ctx_req & (BWFM_FWREQ(i) | BWFM_FWOPT(i)))
                        bwfm_firmware_read_file(sc, fwp, ctx, i);
                if ((ctx->ctx_req & BWFM_FWREQ(i)) &&
                    ctx->ctx_file[i].ctx_f_data == NULL) {
                        aprint_error_dev(sc->sc_dev,
                            "%s file not available\n",
                            bwfm_firmware_filetypes[i].description);
                        rv = false;
                }
        }

        if (rv == false)
                bwfm_firmware_close(ctx);

        return rv;
}

void
bwfm_firmware_close(struct bwfm_firmware_context * const ctx)
{
        for (int i = 0; i < BWFM_NFILETYPES; i++) {
                if (ctx->ctx_file[i].ctx_f_data == NULL)
                        continue;
                firmware_free(ctx->ctx_file[i].ctx_f_data,
                              ctx->ctx_file[i].ctx_f_size);
                ctx->ctx_file[i].ctx_f_data = NULL;
        }
}

void *
bwfm_firmware_data(struct bwfm_firmware_context * const ctx,
    unsigned int const which, size_t *sizep)
{
        KASSERT(which < BWFM_NFILETYPES);
        KASSERT(sizep != NULL);

        *sizep = ctx->ctx_file[which].ctx_f_size;
        return ctx->ctx_file[which].ctx_f_data;
}

const char *
bwfm_firmware_description(unsigned int const which)
{
        KASSERT(which < BWFM_NFILETYPES);

        return bwfm_firmware_filetypes[which].description;
}

void
bwfm_attach(struct bwfm_softc *sc)
{
        struct ieee80211com *ic = &sc->sc_ic;
        struct ifnet *ifp = &sc->sc_if;
        char fw_version[BWFM_DCMD_SMLEN];
        uint32_t bandlist[3];
        int nmode, vhtmode;
        uint32_t tmp;
        int i, j, error;

        error = workqueue_create(&sc->sc_taskq, DEVNAME(sc),
            bwfm_task, sc, PRI_NONE, IPL_NET, 0);
        if (error != 0) {
                printf("%s: could not create workqueue\n", DEVNAME(sc));
                return;
        }
        sc->sc_freetask = pool_cache_init(sizeof(struct bwfm_task), 0, 0, 0,
            "bwfmtask", NULL, IPL_NET /* XXX IPL_SOFTNET? */,
            NULL, NULL, NULL);
        pool_cache_prime(sc->sc_freetask, BWFM_TASK_COUNT);

        /* Stop the device in case it was previously initialized */
        bwfm_fwvar_cmd_set_int(sc, BWFM_C_DOWN, 1);

        if (bwfm_fwvar_cmd_get_int(sc, BWFM_C_GET_VERSION, &tmp)) {
                printf("%s: could not read io type\n", DEVNAME(sc));
                return;
        } else
                sc->sc_io_type = tmp;
        if (bwfm_fwvar_var_get_data(sc, "cur_etheraddr", ic->ic_myaddr,
            sizeof(ic->ic_myaddr))) {
                printf("%s: could not read mac address\n", DEVNAME(sc));
                return;
        }

        printf("%s: address %s\n", DEVNAME(sc), ether_sprintf(ic->ic_myaddr));

        bwfm_process_blob(sc, "clmload", &sc->sc_clm, &sc->sc_clmsize);
        bwfm_process_blob(sc, "txcapload", &sc->sc_txcap, &sc->sc_txcapsize);
        bwfm_process_blob(sc, "calload", &sc->sc_cal, &sc->sc_calsize);

        memset(fw_version, 0, sizeof(fw_version));
        if (bwfm_fwvar_var_get_data(sc, "ver", fw_version, sizeof(fw_version)) == 0)
                printf("%s: %s", DEVNAME(sc), fw_version);

        ic->ic_ifp = ifp;
        ic->ic_phytype = IEEE80211_T_OFDM;
        ic->ic_opmode = IEEE80211_M_STA;
        ic->ic_state = IEEE80211_S_INIT;

        ic->ic_caps =
            IEEE80211_C_WEP |
            IEEE80211_C_TKIP |
            IEEE80211_C_AES |
            IEEE80211_C_AES_CCM |
            IEEE80211_C_PMGT |
#if notyet
            IEEE80211_C_MONITOR |               /* monitor mode supported */
            IEEE80211_C_IBSS |
            IEEE80211_C_TXPMGT |
            IEEE80211_C_WME |
#endif
            IEEE80211_C_SHSLOT |                /* short slot time supported */
            IEEE80211_C_SHPREAMBLE |            /* short preamble supported */
            IEEE80211_C_WPA |                   /* 802.11i */
            /* IEEE80211_C_WPA_4WAY */0;                /* WPA 4-way handshake in hw */

        /* IBSS channel undefined for now. */
        ic->ic_ibss_chan = &ic->ic_channels[0];

        if (bwfm_fwvar_var_get_int(sc, "nmode", &nmode))
                nmode = 0;
        if (bwfm_fwvar_var_get_int(sc, "vhtmode", &vhtmode))
                vhtmode = 0;
        if (bwfm_fwvar_cmd_get_data(sc, BWFM_C_GET_BANDLIST, bandlist,
            sizeof(bandlist))) {
                printf("%s: couldn't get supported band list\n", DEVNAME(sc));
                return;
        }
        const u_int nbands = le32toh(bandlist[0]);
        for (i = 1; i <= MIN(nbands, __arraycount(bandlist) - 1); i++) {
                switch (le32toh(bandlist[i])) {
                case BWFM_BAND_2G:
                        ic->ic_sup_rates[IEEE80211_MODE_11B] = ieee80211_std_rateset_11b;
                        ic->ic_sup_rates[IEEE80211_MODE_11G] = ieee80211_std_rateset_11g;

                        for (j = 0; j < __arraycount(bwfm_2ghz_channels); j++) {
                                uint8_t chan = bwfm_2ghz_channels[j];
                                ic->ic_channels[chan].ic_freq =
                                    ieee80211_ieee2mhz(chan, IEEE80211_CHAN_2GHZ);
                                ic->ic_channels[chan].ic_flags =
                                    IEEE80211_CHAN_CCK | IEEE80211_CHAN_OFDM |
                                    IEEE80211_CHAN_DYN | IEEE80211_CHAN_2GHZ;
                                if (nmode)
                                        ic->ic_channels[chan].ic_flags |=
                                            IEEE80211_CHAN_HT;
                        }
                        break;
                case BWFM_BAND_5G:
                        ic->ic_sup_rates[IEEE80211_MODE_11A] = ieee80211_std_rateset_11a;

                        for (j = 0; j < __arraycount(bwfm_5ghz_channels); j++) {
                                uint8_t chan = bwfm_5ghz_channels[j];
                                ic->ic_channels[chan].ic_freq =
                                    ieee80211_ieee2mhz(chan, IEEE80211_CHAN_5GHZ);
                                ic->ic_channels[chan].ic_flags =
                                    IEEE80211_CHAN_A;
                                if (nmode)
                                        ic->ic_channels[chan].ic_flags |=
                                            IEEE80211_CHAN_HT;
                                if (vhtmode)
                                        ic->ic_channels[chan].ic_flags |=
                                            IEEE80211_CHAN_VHT;
                        }
                        break;
                default:
                        printf("%s: unsupported band 0x%x\n", DEVNAME(sc),
                            le32toh(bandlist[i]));
                        break;
                }
        }

        ifp->if_softc = sc;
        ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
        ifp->if_init = bwfm_init;
        ifp->if_ioctl = bwfm_ioctl;
        ifp->if_start = bwfm_start;
        ifp->if_stop = bwfm_stop;
        ifp->if_watchdog = bwfm_watchdog;
        IFQ_SET_READY(&ifp->if_snd);
        memcpy(ifp->if_xname, DEVNAME(sc), IFNAMSIZ);

        if_initialize(ifp);
        ieee80211_ifattach(ic);
        sc->sc_newstate = ic->ic_newstate;
        ic->ic_newstate = bwfm_newstate;
        ic->ic_newassoc = bwfm_newassoc;
        ic->ic_send_mgmt = bwfm_send_mgmt;
        ic->ic_recv_mgmt = bwfm_recv_mgmt;
        ic->ic_crypto.cs_key_set = bwfm_key_set;
        ic->ic_crypto.cs_key_delete = bwfm_key_delete;

        ifp->if_percpuq = if_percpuq_create(ifp);
        if_deferred_start_init(ifp, NULL);
        if_register(ifp);
        ieee80211_media_init(ic, bwfm_media_change, ieee80211_media_status);

        ieee80211_announce(ic);

        sc->sc_if_attached = true;
}

int
bwfm_detach(struct bwfm_softc *sc, int flags)
{
        struct ieee80211com *ic = &sc->sc_ic;
        struct ifnet *ifp = ic->ic_ifp;

        if (sc->sc_if_attached) {
                bpf_detach(ifp);
                ieee80211_ifdetach(ic);
                if_detach(ifp);
        }

        if (sc->sc_taskq)
                workqueue_destroy(sc->sc_taskq);
        if (sc->sc_freetask)
                pool_cache_destroy(sc->sc_freetask);

        return 0;
}

void
bwfm_start(struct ifnet *ifp)
{
        struct bwfm_softc *sc = ifp->if_softc;
        struct ieee80211com *ic = &sc->sc_ic;
        struct mbuf *m;
        int error;

        if ((ifp->if_flags & (IFF_RUNNING | IFF_OACTIVE)) != IFF_RUNNING)
                return;

        /* TODO: return if no link? */

        if (sc->sc_setpm) {
                sc->sc_setpm = false;
                if (bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_PM, sc->sc_pm))
                        printf("%s: could not set power\n", DEVNAME(sc));
        }

        for (;;) {
                /* Discard management packets (fw handles this for us) */
                IF_DEQUEUE(&ic->ic_mgtq, m);
                if (m != NULL) {
                        m_freem(m);
                        continue;
                }

                if (sc->sc_bus_ops->bs_txcheck(sc)) {
                        ifp->if_flags |= IFF_OACTIVE;
                        break;
                }

                IFQ_DEQUEUE(&ifp->if_snd, m);
                if (m == NULL)
                        break;

                error = sc->sc_bus_ops->bs_txdata(sc, &m);
                if (error == ENOBUFS) {
                        IF_PREPEND(&ifp->if_snd, m);
                        ifp->if_flags |= IFF_OACTIVE;
                        break;
                }
                if (error != 0) {
                        if_statinc(ifp, if_oerrors);
                        m_freem(m);
                        continue;
                }

                bpf_mtap(ifp, m, BPF_D_OUT);
        }
}

int
bwfm_init(struct ifnet *ifp)
{
        struct bwfm_softc *sc = ifp->if_softc;
        struct ieee80211com *ic = &sc->sc_ic;
        uint8_t evmask[BWFM_EVENT_MASK_LEN];
        struct bwfm_join_pref_params join_pref[2];

        if (bwfm_fwvar_var_set_int(sc, "mpc", 1)) {
                printf("%s: could not set mpc\n", DEVNAME(sc));
                return EIO;
        }

        /* Select target by RSSI (boost on 5GHz) */
        join_pref[0].type = BWFM_JOIN_PREF_RSSI_DELTA;
        join_pref[0].len = 2;
        join_pref[0].rssi_gain = BWFM_JOIN_PREF_RSSI_BOOST;
        join_pref[0].band = BWFM_JOIN_PREF_BAND_5G;
        join_pref[1].type = BWFM_JOIN_PREF_RSSI;
        join_pref[1].len = 2;
        join_pref[1].rssi_gain = 0;
        join_pref[1].band = 0;
        if (bwfm_fwvar_var_set_data(sc, "join_pref", join_pref,
            sizeof(join_pref))) {
                printf("%s: could not set join pref\n", DEVNAME(sc));
                return EIO;
        }

        memset(evmask, 0, sizeof(evmask));

#define ENABLE_EVENT(e)         evmask[(e) / 8] |= 1 << ((e) % 8)
        /* Events used to drive the state machine */
        switch (ic->ic_opmode) {
        case IEEE80211_M_STA:
                ENABLE_EVENT(BWFM_E_IF);
                ENABLE_EVENT(BWFM_E_LINK);
                ENABLE_EVENT(BWFM_E_AUTH);
                ENABLE_EVENT(BWFM_E_ASSOC);
                ENABLE_EVENT(BWFM_E_DEAUTH);
                ENABLE_EVENT(BWFM_E_DISASSOC);
                ENABLE_EVENT(BWFM_E_SET_SSID);
                ENABLE_EVENT(BWFM_E_ESCAN_RESULT);
                break;
#ifndef IEEE80211_STA_ONLY
        case IEEE80211_M_HOSTAP:
                ENABLE_EVENT(BWFM_E_AUTH_IND);
                ENABLE_EVENT(BWFM_E_ASSOC_IND);
                ENABLE_EVENT(BWFM_E_REASSOC_IND);
                ENABLE_EVENT(BWFM_E_DEAUTH_IND);
                ENABLE_EVENT(BWFM_E_DISASSOC_IND);
                ENABLE_EVENT(BWFM_E_ESCAN_RESULT);
                ENABLE_EVENT(BWFM_E_ESCAN_RESULT);
                break;
#endif
        default:
                break;
        }
#undef  ENABLE_EVENT

#ifdef BWFM_DEBUG
        memset(evmask, 0xff, sizeof(evmask));
#endif

        if (bwfm_fwvar_var_set_data(sc, "event_msgs", evmask, sizeof(evmask))) {
                printf("%s: could not set event mask\n", DEVNAME(sc));
                return EIO;
        }

        if (bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_SCAN_CHANNEL_TIME,
            BWFM_DEFAULT_SCAN_CHANNEL_TIME)) {
                printf("%s: could not set scan channel time\n", DEVNAME(sc));
                return EIO;
        }
        if (bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_SCAN_UNASSOC_TIME,
            BWFM_DEFAULT_SCAN_UNASSOC_TIME)) {
                printf("%s: could not set scan unassoc time\n", DEVNAME(sc));
                return EIO;
        }
        if (bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_SCAN_PASSIVE_TIME,
            BWFM_DEFAULT_SCAN_PASSIVE_TIME)) {
                printf("%s: could not set scan passive time\n", DEVNAME(sc));
                return EIO;
        }

        /*
         * Use CAM (constantly awake) when we are running as AP
         * otherwise use fast power saving.
         */
        if (ic->ic_flags & IEEE80211_F_PMGTON) {
                sc->sc_pm = BWFM_PM_FAST_PS;
#ifndef IEEE80211_STA_ONLY
                if (ic->ic_opmode == IEEE80211_M_HOSTAP)
                        sc->sc_pm = BWFM_PM_CAM;
#endif
        } else {
                sc->sc_pm = BWFM_PM_CAM;
        }
        sc->sc_setpm = true;

        bwfm_fwvar_var_set_int(sc, "txbf", 1);
        bwfm_fwvar_cmd_set_int(sc, BWFM_C_UP, 0);
        bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_INFRA, 1);
        bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_AP, 0);

        /* Disable all offloading (ARP, NDP, TCP/UDP cksum). */
        bwfm_fwvar_var_set_int(sc, "arp_ol", 0);
        bwfm_fwvar_var_set_int(sc, "arpoe", 0);
        bwfm_fwvar_var_set_int(sc, "ndoe", 0);
        bwfm_fwvar_var_set_int(sc, "toe", 0);

        /* Accept all multicast frames. */
        bwfm_fwvar_var_set_int(sc, "allmulti", 1);

        /* Setup promiscuous mode */
        bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_PROMISC,
            (ifp->if_flags & IFF_PROMISC) ? 1 : 0);

        /*
         * Tell the firmware supplicant that we are going to handle the
         * WPA handshake ourselves.
         */
        bwfm_fwvar_var_set_int(sc, "sup_wpa", 0);

        ifp->if_flags |= IFF_RUNNING;
        ifp->if_flags &= ~IFF_OACTIVE;

        if (ic->ic_opmode != IEEE80211_M_MONITOR) {
                if (ic->ic_roaming != IEEE80211_ROAMING_MANUAL)
                        ieee80211_new_state(ic, IEEE80211_S_SCAN, -1);
        } else {
                ieee80211_new_state(ic, IEEE80211_S_RUN, -1);
        }

        return 0;
}

void
bwfm_stop(struct ifnet *ifp, int disable)
{
        struct bwfm_softc *sc = ifp->if_softc;
        struct ieee80211com *ic = &sc->sc_ic;
        struct bwfm_join_params join;

        sc->sc_tx_timer = 0;
        ifp->if_timer = 0;
        ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE);

        ieee80211_new_state(ic, IEEE80211_S_INIT, -1);

        memset(&join, 0, sizeof(join));
        bwfm_fwvar_cmd_set_data(sc, BWFM_C_SET_SSID, &join, sizeof(join));
        bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_PM, 0);
        bwfm_fwvar_cmd_set_int(sc, BWFM_C_DOWN, 1);
        bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_AP, 0);
        bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_INFRA, 0);
        bwfm_fwvar_cmd_set_int(sc, BWFM_C_UP, 1);
        bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_PM, BWFM_PM_FAST_PS);
        bwfm_fwvar_var_set_int(sc, "mpc", 1);

        if (sc->sc_bus_ops->bs_stop)
                sc->sc_bus_ops->bs_stop(sc);

        sc->sc_setpm = true;
}

void
bwfm_watchdog(struct ifnet *ifp)
{
        struct bwfm_softc *sc = ifp->if_softc;
        struct ieee80211com *ic = &sc->sc_ic;

        ifp->if_timer = 0;

        if (sc->sc_tx_timer > 0) {
                if (--sc->sc_tx_timer == 0) {
                        printf("%s: device timeout\n", DEVNAME(sc));
                        if_statinc(ifp, if_oerrors);
                        return;
                }
                ifp->if_timer = 1;
        }
        ieee80211_watchdog(ic);
}

int
bwfm_ioctl(struct ifnet *ifp, u_long cmd, void *data)
{
        struct bwfm_softc *sc = ifp->if_softc;
        struct ieee80211com *ic = &sc->sc_ic;
        int s, error = 0, oflags;

        s = splnet();

        switch (cmd) {
        case SIOCSIFFLAGS:
                oflags = ifp->if_flags;
                if ((error = ifioctl_common(ifp, cmd, data)) != 0)
                        break;
                switch (ifp->if_flags & (IFF_UP | IFF_RUNNING)) {
                case IFF_UP | IFF_RUNNING:
                        break;
                case IFF_UP:
                        if ((oflags & IFF_UP) == 0)
                                bwfm_init(ifp);
                        break;
                case IFF_RUNNING:
                        if ((oflags & IFF_UP) != 0)
                                bwfm_stop(ifp, 1);
                        break;
                case 0:
                        break;
                }
                break;

        case SIOCADDMULTI:
        case SIOCDELMULTI:
                if ((error = ether_ioctl(ifp, cmd, data)) == ENETRESET) {
                        /* setup multicast filter, etc */
                        error = 0;
                }
                break;

        case SIOCGIFMEDIA:
                error = ieee80211_ioctl(ic, cmd, data);
                if (error == 0 && ic->ic_state == IEEE80211_S_RUN)
                        bwfm_get_sta_info(sc, (struct ifmediareq *)data);
                break;

        default:
                error = ieee80211_ioctl(ic, cmd, data);
        }

        if (error == ENETRESET) {
                if ((ifp->if_flags & IFF_UP) != 0 &&
                    (ifp->if_flags & IFF_RUNNING) != 0) {
                        bwfm_init(ifp);
                }
                error = 0;
        }

        splx(s);

        return error;
}

int
bwfm_send_mgmt(struct ieee80211com *ic, struct ieee80211_node *ni,
    int type, int arg)
{
        return 0;
}

void
bwfm_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0,
    struct ieee80211_node *ni, int subtype, int rssi, uint32_t rstamp)
{
}

int
bwfm_key_set(struct ieee80211com *ic, const struct ieee80211_key *wk,
    const uint8_t mac[IEEE80211_ADDR_LEN])
{
        struct bwfm_softc *sc = ic->ic_ifp->if_softc;
        struct bwfm_task *t;

        t = pool_cache_get(sc->sc_freetask, PR_NOWAIT);
        if (t == NULL) {
                printf("%s: no free tasks\n", DEVNAME(sc));
                return 0;
        }

        t->t_sc = sc;
        t->t_cmd = BWFM_TASK_KEY_SET;
        t->t_key.key = wk;
        memcpy(t->t_key.mac, mac, sizeof(t->t_key.mac));
        workqueue_enqueue(sc->sc_taskq, (struct work *)t, NULL);
        return 1;
}

static void
bwfm_key_set_cb(struct bwfm_softc *sc, struct bwfm_cmd_key *ck)
{
        const struct ieee80211_key *wk = ck->key;
        const uint8_t *mac = ck->mac;
        struct bwfm_wsec_key wsec_key;
        uint32_t wsec_enable, wsec;
        bool ext_key;

#ifdef BWFM_DEBUG
        printf("key_set: key cipher %s len %d: ", wk->wk_cipher->ic_name, wk->wk_keylen);
        for (int j = 0; j < sizeof(wk->wk_key); j++)
                printf("%02x", wk->wk_key[j]);
#endif

        if ((wk->wk_flags & IEEE80211_KEY_GROUP) == 0 &&
            wk->wk_cipher->ic_cipher != IEEE80211_CIPHER_WEP) {
                ext_key = true;
        } else {
                ext_key = false;
        }

#ifdef BWFM_DEBUG
        printf(", ext_key = %d", ext_key);
        printf(", mac = %02x:%02x:%02x:%02x:%02x:%02x",
            mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
        printf("\n");
#endif

        memset(&wsec_key, 0, sizeof(wsec_key));
        if (ext_key && !IEEE80211_IS_MULTICAST(mac))
                memcpy(wsec_key.ea, mac, sizeof(wsec_key.ea));
        wsec_key.index = htole32(wk->wk_keyix);
        wsec_key.len = htole32(wk->wk_keylen);
        memcpy(wsec_key.data, wk->wk_key, sizeof(wsec_key.data));
        if (!ext_key)
                wsec_key.flags = htole32(BWFM_WSEC_PRIMARY_KEY);

        switch (wk->wk_cipher->ic_cipher) {
        case IEEE80211_CIPHER_WEP:
                if (wk->wk_keylen == 5)
                        wsec_key.algo = htole32(BWFM_CRYPTO_ALGO_WEP1);
                else if (wk->wk_keylen == 13)
                        wsec_key.algo = htole32(BWFM_CRYPTO_ALGO_WEP128);
                else
                        return;
                wsec_enable = BWFM_WSEC_WEP;
                break;
        case IEEE80211_CIPHER_TKIP:
                wsec_key.algo = htole32(BWFM_CRYPTO_ALGO_TKIP);
                wsec_enable = BWFM_WSEC_TKIP;
                break;
        case IEEE80211_CIPHER_AES_CCM:
                wsec_key.algo = htole32(BWFM_CRYPTO_ALGO_AES_CCM);
                wsec_enable = BWFM_WSEC_AES;
                break;
        default:
                printf("%s: %s: cipher %s not supported\n", DEVNAME(sc),
                    __func__, wk->wk_cipher->ic_name);
                return;
        }

        if (bwfm_fwvar_var_set_data(sc, "wsec_key", &wsec_key, sizeof(wsec_key)))
                return;

        bwfm_fwvar_var_set_int(sc, "wpa_auth", BWFM_WPA_AUTH_WPA2_PSK);

        bwfm_fwvar_var_get_int(sc, "wsec", &wsec);
        wsec |= wsec_enable;
        bwfm_fwvar_var_set_int(sc, "wsec", wsec);
}

int
bwfm_key_delete(struct ieee80211com *ic, const struct ieee80211_key *wk)
{
        struct bwfm_softc *sc = ic->ic_ifp->if_softc;
        struct bwfm_task *t;

        t = pool_cache_get(sc->sc_freetask, PR_NOWAIT);
        if (t == NULL) {
                printf("%s: no free tasks\n", DEVNAME(sc));
                return 0;
        }

        t->t_sc = sc;
        t->t_cmd = BWFM_TASK_KEY_DELETE;
        t->t_key.key = wk;
        memset(t->t_key.mac, 0, sizeof(t->t_key.mac));
        workqueue_enqueue(sc->sc_taskq, (struct work *)t, NULL);

        return 1;
}

static void
bwfm_key_delete_cb(struct bwfm_softc *sc, struct bwfm_cmd_key *ck)
{
        const struct ieee80211_key *wk = ck->key;
        struct bwfm_wsec_key wsec_key;

        memset(&wsec_key, 0, sizeof(wsec_key));
        wsec_key.index = htole32(wk->wk_keyix);
        wsec_key.flags = htole32(BWFM_WSEC_PRIMARY_KEY);

        if (bwfm_fwvar_var_set_data(sc, "wsec_key", &wsec_key, sizeof(wsec_key)))
                return;
}

int
bwfm_newstate(struct ieee80211com *ic, enum ieee80211_state nstate, int arg)
{
        struct bwfm_softc *sc = ic->ic_ifp->if_softc;
        struct bwfm_task *t;

        t = pool_cache_get(sc->sc_freetask, PR_NOWAIT);
        if (t == NULL) {
                printf("%s: no free tasks\n", DEVNAME(sc));
                return EIO;
        }

        t->t_sc = sc;
        t->t_cmd = BWFM_TASK_NEWSTATE;
        t->t_newstate.state = nstate;
        t->t_newstate.arg = arg;
        workqueue_enqueue(sc->sc_taskq, (struct work *)t, NULL);

        return 0;
}

void
bwfm_newstate_cb(struct bwfm_softc *sc, struct bwfm_cmd_newstate *cmd)
{
        struct ieee80211com *ic = &sc->sc_ic;
        enum ieee80211_state ostate = ic->ic_state;
        enum ieee80211_state nstate = cmd->state;
        int s;

        DPRINTF(("%s: newstate %d -> %d\n", DEVNAME(sc), ostate, nstate));

        s = splnet();

        switch (nstate) {
        case IEEE80211_S_INIT:
                break;

        case IEEE80211_S_SCAN:
                if (ostate != IEEE80211_S_SCAN) {
                        /* Start of scanning */
                        bwfm_scan(sc);
                }
                break;

        case IEEE80211_S_AUTH:
                bwfm_connect(sc);
                break;

        case IEEE80211_S_ASSOC:
                break;

        case IEEE80211_S_RUN:
                break;
        }

        sc->sc_newstate(ic, nstate, cmd->arg);

        splx(s);
}

void
bwfm_newassoc(struct ieee80211_node *ni, int isnew)
{
        /* Firmware handles rate adaptation for us */
        ni->ni_txrate = 0;
}

void
bwfm_task(struct work *wk, void *arg)
{
        struct bwfm_task *t = (struct bwfm_task *)wk;
        struct bwfm_softc *sc = t->t_sc;

        switch (t->t_cmd) {
        case BWFM_TASK_NEWSTATE:
                bwfm_newstate_cb(sc, &t->t_newstate);
                break;
        case BWFM_TASK_KEY_SET:
                bwfm_key_set_cb(sc, &t->t_key);
                break;
        case BWFM_TASK_KEY_DELETE:
                bwfm_key_delete_cb(sc, &t->t_key);
                break;
        case BWFM_TASK_RX_EVENT:
                bwfm_rx_event_cb(sc, t->t_mbuf);
                break;
        default:
                panic("bwfm: unknown task command %d", t->t_cmd);
        }

        pool_cache_put(sc->sc_freetask, t);
}

int
bwfm_media_change(struct ifnet *ifp)
{
        return 0;
}

/* Chip initialization (SDIO, PCIe) */
int
bwfm_chip_attach(struct bwfm_softc *sc)
{
        struct bwfm_core *core;
        int need_socram = 0;
        int has_socram = 0;
        int cpu_found = 0;
        uint32_t val;

        LIST_INIT(&sc->sc_chip.ch_list);

        if (sc->sc_buscore_ops->bc_prepare(sc) != 0) {
                printf("%s: failed buscore prepare\n", DEVNAME(sc));
                return 1;
        }

        val = sc->sc_buscore_ops->bc_read(sc,
            BWFM_CHIP_BASE + BWFM_CHIP_REG_CHIPID);
        sc->sc_chip.ch_chip = BWFM_CHIP_CHIPID_ID(val);
        sc->sc_chip.ch_chiprev = BWFM_CHIP_CHIPID_REV(val);

        if ((sc->sc_chip.ch_chip > 0xa000) || (sc->sc_chip.ch_chip < 0x4000))
                snprintf(sc->sc_chip.ch_name, sizeof(sc->sc_chip.ch_name),
                    "%d", sc->sc_chip.ch_chip);
        else
                snprintf(sc->sc_chip.ch_name, sizeof(sc->sc_chip.ch_name),
                    "%x", sc->sc_chip.ch_chip);

        switch (BWFM_CHIP_CHIPID_TYPE(val))
        {
        case BWFM_CHIP_CHIPID_TYPE_SOCI_SB:
                printf("%s: SoC interconnect SB not implemented\n",
                    DEVNAME(sc));
                return 1;
        case BWFM_CHIP_CHIPID_TYPE_SOCI_AI:
                sc->sc_chip.ch_core_isup = bwfm_chip_ai_isup;
                sc->sc_chip.ch_core_disable = bwfm_chip_ai_disable;
                sc->sc_chip.ch_core_reset = bwfm_chip_ai_reset;
                bwfm_chip_dmp_erom_scan(sc);
                break;
        default:
                printf("%s: SoC interconnect %d unknown\n",
                    DEVNAME(sc), BWFM_CHIP_CHIPID_TYPE(val));
                return 1;
        }

        LIST_FOREACH(core, &sc->sc_chip.ch_list, co_link) {
                DPRINTF(("%s: 0x%x:%-2d base 0x%08x wrap 0x%08x\n",
                    DEVNAME(sc), core->co_id, core->co_rev,
                    core->co_base, core->co_wrapbase));

                switch (core->co_id) {
                case BWFM_AGENT_CORE_ARM_CM3:
                        need_socram = true;
                        /* FALLTHROUGH */
                case BWFM_AGENT_CORE_ARM_CR4:
                case BWFM_AGENT_CORE_ARM_CA7:
                        cpu_found = true;
                        break;
                case BWFM_AGENT_INTERNAL_MEM:
                        has_socram = true;
                        break;
                default:
                        break;
                }
        }

        if (!cpu_found) {
                printf("%s: CPU core not detected\n", DEVNAME(sc));
                return 1;
        }
        if (need_socram && !has_socram) {
                printf("%s: RAM core not provided\n", DEVNAME(sc));
                return 1;
        }

        bwfm_chip_set_passive(sc);

        if (sc->sc_buscore_ops->bc_reset) {
                sc->sc_buscore_ops->bc_reset(sc);
                bwfm_chip_set_passive(sc);
        }

        if ((core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CR4)) != NULL) {
                bwfm_chip_tcm_ramsize(sc, core);
                bwfm_chip_tcm_rambase(sc);
        } else if ((core = bwfm_chip_get_core(sc, BWFM_AGENT_SYS_MEM)) != NULL) {
                bwfm_chip_sysmem_ramsize(sc, core);
                bwfm_chip_tcm_rambase(sc);
        } else if ((core = bwfm_chip_get_core(sc, BWFM_AGENT_INTERNAL_MEM)) != NULL) {
                bwfm_chip_socram_ramsize(sc, core);
        }

        core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_CHIPCOMMON);
        sc->sc_chip.ch_cc_caps = sc->sc_buscore_ops->bc_read(sc,
            core->co_base + BWFM_CHIP_REG_CAPABILITIES);
        sc->sc_chip.ch_cc_caps_ext = sc->sc_buscore_ops->bc_read(sc,
            core->co_base + BWFM_CHIP_REG_CAPABILITIES_EXT);

        core = bwfm_chip_get_pmu(sc);
        if (sc->sc_chip.ch_cc_caps & BWFM_CHIP_REG_CAPABILITIES_PMU) {
                sc->sc_chip.ch_pmucaps = sc->sc_buscore_ops->bc_read(sc,
                    core->co_base + BWFM_CHIP_REG_PMUCAPABILITIES);
                sc->sc_chip.ch_pmurev = sc->sc_chip.ch_pmucaps &
                    BWFM_CHIP_REG_PMUCAPABILITIES_REV_MASK;
        }

        if (sc->sc_buscore_ops->bc_setup)
                sc->sc_buscore_ops->bc_setup(sc);

        return 0;
}

struct bwfm_core *
bwfm_chip_get_core(struct bwfm_softc *sc, int id)
{
        struct bwfm_core *core;

        LIST_FOREACH(core, &sc->sc_chip.ch_list, co_link) {
                if (core->co_id == id)
                        return core;
        }

        return NULL;
}

struct bwfm_core *
bwfm_chip_get_pmu(struct bwfm_softc *sc)
{
        struct bwfm_core *cc, *pmu;

        cc = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_CHIPCOMMON);
        if (cc->co_rev >= 35 && sc->sc_chip.ch_cc_caps_ext &
            BWFM_CHIP_REG_CAPABILITIES_EXT_AOB_PRESENT) {
                pmu = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_PMU);
                if (pmu)
                        return pmu;
        }

        return cc;
}

/* Functions for the AI interconnect */
int
bwfm_chip_ai_isup(struct bwfm_softc *sc, struct bwfm_core *core)
{
        uint32_t ioctl, reset;

        ioctl = sc->sc_buscore_ops->bc_read(sc,
            core->co_wrapbase + BWFM_AGENT_IOCTL);
        reset = sc->sc_buscore_ops->bc_read(sc,
            core->co_wrapbase + BWFM_AGENT_RESET_CTL);

        if (((ioctl & (BWFM_AGENT_IOCTL_FGC | BWFM_AGENT_IOCTL_CLK)) ==
            BWFM_AGENT_IOCTL_CLK) &&
            ((reset & BWFM_AGENT_RESET_CTL_RESET) == 0))
                return 1;

        return 0;
}

void
bwfm_chip_ai_disable(struct bwfm_softc *sc, struct bwfm_core *core,
    uint32_t prereset, uint32_t reset)
{
        uint32_t val;
        int i;

        val = sc->sc_buscore_ops->bc_read(sc,
            core->co_wrapbase + BWFM_AGENT_RESET_CTL);
        if ((val & BWFM_AGENT_RESET_CTL_RESET) == 0) {

                sc->sc_buscore_ops->bc_write(sc,
                    core->co_wrapbase + BWFM_AGENT_IOCTL,
                    prereset | BWFM_AGENT_IOCTL_FGC | BWFM_AGENT_IOCTL_CLK);
                sc->sc_buscore_ops->bc_read(sc,
                    core->co_wrapbase + BWFM_AGENT_IOCTL);

                sc->sc_buscore_ops->bc_write(sc,
                    core->co_wrapbase + BWFM_AGENT_RESET_CTL,
                    BWFM_AGENT_RESET_CTL_RESET);
                delay(20);

                for (i = 300; i > 0; i--) {
                        if (sc->sc_buscore_ops->bc_read(sc,
                            core->co_wrapbase + BWFM_AGENT_RESET_CTL) ==
                            BWFM_AGENT_RESET_CTL_RESET)
                                break;
                }
                if (i == 0)
                        printf("%s: timeout on core reset\n", DEVNAME(sc));
        }

        sc->sc_buscore_ops->bc_write(sc,
            core->co_wrapbase + BWFM_AGENT_IOCTL,
            reset | BWFM_AGENT_IOCTL_FGC | BWFM_AGENT_IOCTL_CLK);
        sc->sc_buscore_ops->bc_read(sc,
            core->co_wrapbase + BWFM_AGENT_IOCTL);
}

void
bwfm_chip_ai_reset(struct bwfm_softc *sc, struct bwfm_core *core,
    uint32_t prereset, uint32_t reset, uint32_t postreset)
{
        int i;

        bwfm_chip_ai_disable(sc, core, prereset, reset);

        for (i = 50; i > 0; i--) {
                if ((sc->sc_buscore_ops->bc_read(sc,
                    core->co_wrapbase + BWFM_AGENT_RESET_CTL) &
                    BWFM_AGENT_RESET_CTL_RESET) == 0)
                        break;
                sc->sc_buscore_ops->bc_write(sc,
                    core->co_wrapbase + BWFM_AGENT_RESET_CTL, 0);
                delay(60);
        }
        if (i == 0)
                printf("%s: timeout on core reset\n", DEVNAME(sc));

        sc->sc_buscore_ops->bc_write(sc,
            core->co_wrapbase + BWFM_AGENT_IOCTL,
            postreset | BWFM_AGENT_IOCTL_CLK);
        sc->sc_buscore_ops->bc_read(sc,
            core->co_wrapbase + BWFM_AGENT_IOCTL);
}

void
bwfm_chip_dmp_erom_scan(struct bwfm_softc *sc)
{
        uint32_t erom, val, base, wrap;
        uint8_t type = 0;
        uint16_t id;
        uint8_t nmw, nsw, rev;
        struct bwfm_core *core;

        erom = sc->sc_buscore_ops->bc_read(sc,
            BWFM_CHIP_BASE + BWFM_CHIP_REG_EROMPTR);
        while (type != BWFM_DMP_DESC_EOT) {
                val = sc->sc_buscore_ops->bc_read(sc, erom);
                type = val & BWFM_DMP_DESC_MASK;
                erom += 4;

                if (type != BWFM_DMP_DESC_COMPONENT)
                        continue;

                id = (val & BWFM_DMP_COMP_PARTNUM)
                    >> BWFM_DMP_COMP_PARTNUM_S;

                val = sc->sc_buscore_ops->bc_read(sc, erom);
                type = val & BWFM_DMP_DESC_MASK;
                erom += 4;

                if (type != BWFM_DMP_DESC_COMPONENT) {
                        printf("%s: not component descriptor\n", DEVNAME(sc));
                        return;
                }

                nmw = (val & BWFM_DMP_COMP_NUM_MWRAP)
                    >> BWFM_DMP_COMP_NUM_MWRAP_S;
                nsw = (val & BWFM_DMP_COMP_NUM_SWRAP)
                    >> BWFM_DMP_COMP_NUM_SWRAP_S;
                rev = (val & BWFM_DMP_COMP_REVISION)
                    >> BWFM_DMP_COMP_REVISION_S;

                if (nmw + nsw == 0 && id != BWFM_AGENT_CORE_PMU)
                        continue;

                if (bwfm_chip_dmp_get_regaddr(sc, &erom, &base, &wrap))
                        continue;

                core = kmem_alloc(sizeof(*core), KM_SLEEP);
                core->co_id = id;
                core->co_base = base;
                core->co_wrapbase = wrap;
                core->co_rev = rev;
                LIST_INSERT_HEAD(&sc->sc_chip.ch_list, core, co_link);
        }
}

int
bwfm_chip_dmp_get_regaddr(struct bwfm_softc *sc, uint32_t *erom,
    uint32_t *base, uint32_t *wrap)
{
        uint8_t type = 0, mpnum __unused = 0;
        uint8_t stype, sztype, wraptype;
        uint32_t val;

        *base = 0;
        *wrap = 0;

        val = sc->sc_buscore_ops->bc_read(sc, *erom);
        type = val & BWFM_DMP_DESC_MASK;
        if (type == BWFM_DMP_DESC_MASTER_PORT) {
                mpnum = (val & BWFM_DMP_MASTER_PORT_NUM)
                    >> BWFM_DMP_MASTER_PORT_NUM_S;
                wraptype = BWFM_DMP_SLAVE_TYPE_MWRAP;
                *erom += 4;
        } else if ((type & ~BWFM_DMP_DESC_ADDRSIZE_GT32) ==
            BWFM_DMP_DESC_ADDRESS)
                wraptype = BWFM_DMP_SLAVE_TYPE_SWRAP;
        else
                return 1;

        do {
                do {
                        val = sc->sc_buscore_ops->bc_read(sc, *erom);
                        type = val & BWFM_DMP_DESC_MASK;
                        if (type == BWFM_DMP_DESC_COMPONENT)
                                return 0;
                        if (type == BWFM_DMP_DESC_EOT)
                                return 1;
                        *erom += 4;
                } while ((type & ~BWFM_DMP_DESC_ADDRSIZE_GT32) !=
                     BWFM_DMP_DESC_ADDRESS);

                if (type & BWFM_DMP_DESC_ADDRSIZE_GT32)
                        *erom += 4;

                sztype = (val & BWFM_DMP_SLAVE_SIZE_TYPE)
                    >> BWFM_DMP_SLAVE_SIZE_TYPE_S;
                if (sztype == BWFM_DMP_SLAVE_SIZE_DESC) {
                        val = sc->sc_buscore_ops->bc_read(sc, *erom);
                        type = val & BWFM_DMP_DESC_MASK;
                        if (type & BWFM_DMP_DESC_ADDRSIZE_GT32)
                                *erom += 8;
                        else
                                *erom += 4;
                }
                if (sztype != BWFM_DMP_SLAVE_SIZE_4K)
                        continue;

                stype = (val & BWFM_DMP_SLAVE_TYPE) >> BWFM_DMP_SLAVE_TYPE_S;
                if (*base == 0 && stype == BWFM_DMP_SLAVE_TYPE_SLAVE)
                        *base = val & BWFM_DMP_SLAVE_ADDR_BASE;
                if (*wrap == 0 && stype == wraptype)
                        *wrap = val & BWFM_DMP_SLAVE_ADDR_BASE;
        } while (*base == 0 || *wrap == 0);

        return 0;
}

/* Core configuration */
int
bwfm_chip_set_active(struct bwfm_softc *sc, const uint32_t rstvec)
{
        if (bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CR4) != NULL)
                return bwfm_chip_cr4_set_active(sc, rstvec);
        if (bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CA7) != NULL)
                return bwfm_chip_ca7_set_active(sc, rstvec);
        if (bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CM3) != NULL)
                return bwfm_chip_cm3_set_active(sc);
        return 1;
}

void
bwfm_chip_set_passive(struct bwfm_softc *sc)
{
        if (bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CR4) != NULL) {
                bwfm_chip_cr4_set_passive(sc);
                return;
        }
        if (bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CA7) != NULL) {
                bwfm_chip_ca7_set_passive(sc);
                return;
        }
        if (bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CM3) != NULL) {
                bwfm_chip_cm3_set_passive(sc);
                return;
        }
}

int
bwfm_chip_cr4_set_active(struct bwfm_softc *sc, const uint32_t rstvec)
{
        struct bwfm_core *core;

        sc->sc_buscore_ops->bc_activate(sc, rstvec);
        core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CR4);
        sc->sc_chip.ch_core_reset(sc, core,
            BWFM_AGENT_IOCTL_ARMCR4_CPUHALT, 0, 0);

        return 0;
}

void
bwfm_chip_cr4_set_passive(struct bwfm_softc *sc)
{
        struct bwfm_core *core;
        uint32_t val;

        core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CR4);
        val = sc->sc_buscore_ops->bc_read(sc,
            core->co_wrapbase + BWFM_AGENT_IOCTL);
        sc->sc_chip.ch_core_reset(sc, core,
            val & BWFM_AGENT_IOCTL_ARMCR4_CPUHALT,
            BWFM_AGENT_IOCTL_ARMCR4_CPUHALT,
            BWFM_AGENT_IOCTL_ARMCR4_CPUHALT);

        core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_80211);
        sc->sc_chip.ch_core_reset(sc, core, BWFM_AGENT_D11_IOCTL_PHYRESET |
            BWFM_AGENT_D11_IOCTL_PHYCLOCKEN, BWFM_AGENT_D11_IOCTL_PHYCLOCKEN,
            BWFM_AGENT_D11_IOCTL_PHYCLOCKEN);
}

int
bwfm_chip_ca7_set_active(struct bwfm_softc *sc, const uint32_t rstvec)
{
        struct bwfm_core *core;

        sc->sc_buscore_ops->bc_activate(sc, rstvec);
        core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CA7);
        sc->sc_chip.ch_core_reset(sc, core,
            BWFM_AGENT_IOCTL_ARMCR4_CPUHALT, 0, 0);

        return 0;
}

void
bwfm_chip_ca7_set_passive(struct bwfm_softc *sc)
{
        struct bwfm_core *core;
        uint32_t val;

        core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CA7);
        val = sc->sc_buscore_ops->bc_read(sc,
            core->co_wrapbase + BWFM_AGENT_IOCTL);
        sc->sc_chip.ch_core_reset(sc, core,
            val & BWFM_AGENT_IOCTL_ARMCR4_CPUHALT,
            BWFM_AGENT_IOCTL_ARMCR4_CPUHALT,
            BWFM_AGENT_IOCTL_ARMCR4_CPUHALT);

        core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_80211);
        sc->sc_chip.ch_core_reset(sc, core, BWFM_AGENT_D11_IOCTL_PHYRESET |
            BWFM_AGENT_D11_IOCTL_PHYCLOCKEN, BWFM_AGENT_D11_IOCTL_PHYCLOCKEN,
            BWFM_AGENT_D11_IOCTL_PHYCLOCKEN);
}

int
bwfm_chip_cm3_set_active(struct bwfm_softc *sc)
{
        struct bwfm_core *core;

        core = bwfm_chip_get_core(sc, BWFM_AGENT_INTERNAL_MEM);
        if (!sc->sc_chip.ch_core_isup(sc, core))
                return 1;

        sc->sc_buscore_ops->bc_activate(sc, 0);

        core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CM3);
        sc->sc_chip.ch_core_reset(sc, core, 0, 0, 0);

        return 0;
}

void
bwfm_chip_cm3_set_passive(struct bwfm_softc *sc)
{
        struct bwfm_core *core;

        core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CM3);
        sc->sc_chip.ch_core_disable(sc, core, 0, 0);
        core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_80211);
        sc->sc_chip.ch_core_reset(sc, core, BWFM_AGENT_D11_IOCTL_PHYRESET |
            BWFM_AGENT_D11_IOCTL_PHYCLOCKEN, BWFM_AGENT_D11_IOCTL_PHYCLOCKEN,
            BWFM_AGENT_D11_IOCTL_PHYCLOCKEN);
        core = bwfm_chip_get_core(sc, BWFM_AGENT_INTERNAL_MEM);
        sc->sc_chip.ch_core_reset(sc, core, 0, 0, 0);

        if (sc->sc_chip.ch_chip == BRCM_CC_43430_CHIP_ID) {
                sc->sc_buscore_ops->bc_write(sc,
                    core->co_base + BWFM_SOCRAM_BANKIDX, 3);
                sc->sc_buscore_ops->bc_write(sc,
                    core->co_base + BWFM_SOCRAM_BANKPDA, 0);
        }
}

int
bwfm_chip_sr_capable(struct bwfm_softc *sc)
{
        struct bwfm_core *core;
        uint32_t reg;

        if (sc->sc_chip.ch_pmurev < 17)
                return 0;

        switch (sc->sc_chip.ch_chip) {
        case BRCM_CC_4345_CHIP_ID:
        case BRCM_CC_4354_CHIP_ID:
        case BRCM_CC_4356_CHIP_ID:
                core = bwfm_chip_get_pmu(sc);
                sc->sc_buscore_ops->bc_write(sc, core->co_base +
                    BWFM_CHIP_REG_CHIPCONTROL_ADDR, 3);
                reg = sc->sc_buscore_ops->bc_read(sc, core->co_base +
                    BWFM_CHIP_REG_CHIPCONTROL_DATA);
                return (reg & (1 << 2)) != 0;
        case BRCM_CC_43241_CHIP_ID:
        case BRCM_CC_4335_CHIP_ID:
        case BRCM_CC_4339_CHIP_ID:
                core = bwfm_chip_get_pmu(sc);
                sc->sc_buscore_ops->bc_write(sc, core->co_base +
                    BWFM_CHIP_REG_CHIPCONTROL_ADDR, 3);
                reg = sc->sc_buscore_ops->bc_read(sc, core->co_base +
                    BWFM_CHIP_REG_CHIPCONTROL_DATA);
                return reg != 0;
        case BRCM_CC_43430_CHIP_ID:
                core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_CHIPCOMMON);
                reg = sc->sc_buscore_ops->bc_read(sc, core->co_base +
                    BWFM_CHIP_REG_SR_CONTROL1);
                return reg != 0;
        default:
                core = bwfm_chip_get_pmu(sc);
                reg = sc->sc_buscore_ops->bc_read(sc, core->co_base +
                    BWFM_CHIP_REG_PMUCAPABILITIES_EXT);
                if ((reg & BWFM_CHIP_REG_PMUCAPABILITIES_SR_SUPP) == 0)
                        return 0;
                reg = sc->sc_buscore_ops->bc_read(sc, core->co_base +
                    BWFM_CHIP_REG_RETENTION_CTL);
                return (reg & (BWFM_CHIP_REG_RETENTION_CTL_MACPHY_DIS |
                               BWFM_CHIP_REG_RETENTION_CTL_LOGIC_DIS)) == 0;
        }
}

/* RAM size helpers */
void
bwfm_chip_socram_ramsize(struct bwfm_softc *sc, struct bwfm_core *core)
{
        uint32_t coreinfo, nb, lss, banksize, bankinfo;
        uint32_t ramsize = 0, srsize = 0;
        int i;

        if (!sc->sc_chip.ch_core_isup(sc, core))
                sc->sc_chip.ch_core_reset(sc, core, 0, 0, 0);

        coreinfo = sc->sc_buscore_ops->bc_read(sc,
            core->co_base + BWFM_SOCRAM_COREINFO);
        nb = (coreinfo & BWFM_SOCRAM_COREINFO_SRNB_MASK)
            >> BWFM_SOCRAM_COREINFO_SRNB_SHIFT;

        if (core->co_rev <= 7 || core->co_rev == 12) {
                banksize = coreinfo & BWFM_SOCRAM_COREINFO_SRBSZ_MASK;
                lss = (coreinfo & BWFM_SOCRAM_COREINFO_LSS_MASK)
                    >> BWFM_SOCRAM_COREINFO_LSS_SHIFT;
                if (lss != 0)
                        nb--;
                ramsize = nb * (1 << (banksize + BWFM_SOCRAM_COREINFO_SRBSZ_BASE));
                if (lss != 0)
                        ramsize += (1 << ((lss - 1) + BWFM_SOCRAM_COREINFO_SRBSZ_BASE));
        } else {
                for (i = 0; i < nb; i++) {
                        sc->sc_buscore_ops->bc_write(sc,
                            core->co_base + BWFM_SOCRAM_BANKIDX,
                            (BWFM_SOCRAM_BANKIDX_MEMTYPE_RAM <<
                            BWFM_SOCRAM_BANKIDX_MEMTYPE_SHIFT) | i);
                        bankinfo = sc->sc_buscore_ops->bc_read(sc,
                            core->co_base + BWFM_SOCRAM_BANKINFO);
                        banksize = ((bankinfo & BWFM_SOCRAM_BANKINFO_SZMASK) + 1)
                            * BWFM_SOCRAM_BANKINFO_SZBASE;
                        ramsize += banksize;
                        if (bankinfo & BWFM_SOCRAM_BANKINFO_RETNTRAM_MASK)
                                srsize += banksize;
                }
        }

        switch (sc->sc_chip.ch_chip) {
        case BRCM_CC_4334_CHIP_ID:
                if (sc->sc_chip.ch_chiprev < 2)
                        srsize = 32 * 1024;
                break;
        case BRCM_CC_43430_CHIP_ID:
                srsize = 64 * 1024;
                break;
        default:
                break;
        }

        sc->sc_chip.ch_ramsize = ramsize;
        sc->sc_chip.ch_srsize = srsize;
}

void
bwfm_chip_sysmem_ramsize(struct bwfm_softc *sc, struct bwfm_core *core)
{
        uint32_t coreinfo, nb, banksize, bankinfo;
        uint32_t ramsize = 0;
        int i;

        if (!sc->sc_chip.ch_core_isup(sc, core))
                sc->sc_chip.ch_core_reset(sc, core, 0, 0, 0);

        coreinfo = sc->sc_buscore_ops->bc_read(sc,
            core->co_base + BWFM_SOCRAM_COREINFO);
        nb = (coreinfo & BWFM_SOCRAM_COREINFO_SRNB_MASK)
            >> BWFM_SOCRAM_COREINFO_SRNB_SHIFT;

        for (i = 0; i < nb; i++) {
                sc->sc_buscore_ops->bc_write(sc,
                    core->co_base + BWFM_SOCRAM_BANKIDX,
                    (BWFM_SOCRAM_BANKIDX_MEMTYPE_RAM <<
                    BWFM_SOCRAM_BANKIDX_MEMTYPE_SHIFT) | i);
                bankinfo = sc->sc_buscore_ops->bc_read(sc,
                    core->co_base + BWFM_SOCRAM_BANKINFO);
                banksize = ((bankinfo & BWFM_SOCRAM_BANKINFO_SZMASK) + 1)
                    * BWFM_SOCRAM_BANKINFO_SZBASE;
                ramsize += banksize;
        }

        sc->sc_chip.ch_ramsize = ramsize;
}

void
bwfm_chip_tcm_ramsize(struct bwfm_softc *sc, struct bwfm_core *core)
{
        uint32_t cap, nab, nbb, totb, bxinfo, ramsize = 0;
        int i;

        cap = sc->sc_buscore_ops->bc_read(sc, core->co_base + BWFM_ARMCR4_CAP);
        nab = (cap & BWFM_ARMCR4_CAP_TCBANB_MASK) >> BWFM_ARMCR4_CAP_TCBANB_SHIFT;
        nbb = (cap & BWFM_ARMCR4_CAP_TCBBNB_MASK) >> BWFM_ARMCR4_CAP_TCBBNB_SHIFT;
        totb = nab + nbb;

        for (i = 0; i < totb; i++) {
                sc->sc_buscore_ops->bc_write(sc,
                    core->co_base + BWFM_ARMCR4_BANKIDX, i);
                bxinfo = sc->sc_buscore_ops->bc_read(sc,
                    core->co_base + BWFM_ARMCR4_BANKINFO);
                ramsize += ((bxinfo & BWFM_ARMCR4_BANKINFO_BSZ_MASK) + 1) *
                    BWFM_ARMCR4_BANKINFO_BSZ_MULT;
        }

        sc->sc_chip.ch_ramsize = ramsize;
}

void
bwfm_chip_tcm_rambase(struct bwfm_softc *sc)
{
        switch (sc->sc_chip.ch_chip) {
        case BRCM_CC_4345_CHIP_ID:
                sc->sc_chip.ch_rambase = 0x198000;
                break;
        case BRCM_CC_4335_CHIP_ID:
        case BRCM_CC_4339_CHIP_ID:
        case BRCM_CC_4350_CHIP_ID:
        case BRCM_CC_4354_CHIP_ID:
        case BRCM_CC_4356_CHIP_ID:
        case BRCM_CC_43567_CHIP_ID:
        case BRCM_CC_43569_CHIP_ID:
        case BRCM_CC_43570_CHIP_ID:
        case BRCM_CC_4358_CHIP_ID:
        case BRCM_CC_4359_CHIP_ID:
        case BRCM_CC_43602_CHIP_ID:
        case BRCM_CC_4371_CHIP_ID:
                sc->sc_chip.ch_rambase = 0x180000;
                break;
        case BRCM_CC_43465_CHIP_ID:
        case BRCM_CC_43525_CHIP_ID:
        case BRCM_CC_4365_CHIP_ID:
        case BRCM_CC_4366_CHIP_ID:
                sc->sc_chip.ch_rambase = 0x200000;
                break;
        case CY_CC_4373_CHIP_ID:
                sc->sc_chip.ch_rambase = 0x160000;
                break;
        default:
                printf("%s: unknown chip: %d\n", DEVNAME(sc),
                    sc->sc_chip.ch_chip);
                break;
        }
}

/* BCDC protocol implementation */
int
bwfm_proto_bcdc_query_dcmd(struct bwfm_softc *sc, int ifidx,
    int cmd, char *buf, size_t *len)
{
        struct bwfm_proto_bcdc_dcmd *dcmd;
        size_t size = sizeof(dcmd->hdr) + *len;
        int reqid;
        int ret = 1;

        reqid = sc->sc_bcdc_reqid++;

        dcmd = kmem_zalloc(sizeof(*dcmd), KM_SLEEP);
        if (*len > sizeof(dcmd->buf))
                goto err;

        dcmd->hdr.cmd = htole32(cmd);
        dcmd->hdr.len = htole32(*len);
        dcmd->hdr.flags |= BWFM_BCDC_DCMD_GET;
        dcmd->hdr.flags |= BWFM_BCDC_DCMD_ID_SET(reqid);
        dcmd->hdr.flags |= BWFM_BCDC_DCMD_IF_SET(ifidx);
        dcmd->hdr.flags = htole32(dcmd->hdr.flags);
        memcpy(&dcmd->buf, buf, *len);

        if (sc->sc_bus_ops->bs_txctl(sc, (void *)dcmd,
             sizeof(dcmd->hdr) + *len)) {
                DPRINTF(("%s: tx failed\n", DEVNAME(sc)));
                goto err;
        }

        do {
                if (sc->sc_bus_ops->bs_rxctl(sc, (void *)dcmd, &size)) {
                        DPRINTF(("%s: rx failed\n", DEVNAME(sc)));
                        goto err;
                }
                dcmd->hdr.cmd = le32toh(dcmd->hdr.cmd);
                dcmd->hdr.len = le32toh(dcmd->hdr.len);
                dcmd->hdr.flags = le32toh(dcmd->hdr.flags);
                dcmd->hdr.status = le32toh(dcmd->hdr.status);
        } while (BWFM_BCDC_DCMD_ID_GET(dcmd->hdr.flags) != reqid);

        if (BWFM_BCDC_DCMD_ID_GET(dcmd->hdr.flags) != reqid) {
                printf("%s: unexpected request id\n", DEVNAME(sc));
                goto err;
        }

        if (buf) {
                if (size < *len)
                        *len = size;
                memcpy(buf, dcmd->buf, *len);
        }

        if (dcmd->hdr.flags & BWFM_BCDC_DCMD_ERROR)
                ret = dcmd->hdr.status;
        else
                ret = 0;
err:
        kmem_free(dcmd, sizeof(*dcmd));
        return ret;
}

int
bwfm_proto_bcdc_set_dcmd(struct bwfm_softc *sc, int ifidx,
    int cmd, char *buf, size_t len)
{
        struct bwfm_proto_bcdc_dcmd *dcmd;
        size_t size = sizeof(dcmd->hdr) + len;
        int ret = 1, reqid;

        reqid = sc->sc_bcdc_reqid++;

        dcmd = kmem_zalloc(sizeof(*dcmd), KM_SLEEP);
        if (len > sizeof(dcmd->buf))
                goto err;

        dcmd->hdr.cmd = htole32(cmd);
        dcmd->hdr.len = htole32(len);
        dcmd->hdr.flags |= BWFM_BCDC_DCMD_SET;
        dcmd->hdr.flags |= BWFM_BCDC_DCMD_ID_SET(reqid);
        dcmd->hdr.flags |= BWFM_BCDC_DCMD_IF_SET(ifidx);
        dcmd->hdr.flags = htole32(dcmd->hdr.flags);
        memcpy(&dcmd->buf, buf, len);

        if (sc->sc_bus_ops->bs_txctl(sc, (void *)dcmd, size)) {
                DPRINTF(("%s: tx failed\n", DEVNAME(sc)));
                goto err;
        }

        do {
                if (sc->sc_bus_ops->bs_rxctl(sc, (void *)dcmd, &size)) {
                        DPRINTF(("%s: rx failed\n", DEVNAME(sc)));
                        goto err;
                }
                dcmd->hdr.cmd = le32toh(dcmd->hdr.cmd);
                dcmd->hdr.len = le32toh(dcmd->hdr.len);
                dcmd->hdr.flags = le32toh(dcmd->hdr.flags);
                dcmd->hdr.status = le32toh(dcmd->hdr.status);
        } while (BWFM_BCDC_DCMD_ID_GET(dcmd->hdr.flags) != reqid);

        if (BWFM_BCDC_DCMD_ID_GET(dcmd->hdr.flags) != reqid) {
                printf("%s: unexpected request id\n", DEVNAME(sc));
                goto err;
        }

        if (dcmd->hdr.flags & BWFM_BCDC_DCMD_ERROR)
                return dcmd->hdr.status;

        ret = 0;
err:
        kmem_free(dcmd, sizeof(*dcmd));
        return ret;
}

void
bwfm_process_blob(struct bwfm_softc *sc, const char *var, uint8_t **blob,
    size_t *blobsize)
{
        struct bwfm_dload_data *data;
        size_t off, remain, len;

        if (*blob == NULL || *blobsize == 0)
                return;

        off = 0;
        remain = *blobsize;
        data = kmem_alloc(sizeof(*data) + BWFM_DLOAD_MAX_LEN, KM_SLEEP);

        while (remain) {
                len = uimin(remain, BWFM_DLOAD_MAX_LEN);

                data->flag = htole16(BWFM_DLOAD_FLAG_HANDLER_VER_1);
                if (off == 0)
                        data->flag |= htole16(BWFM_DLOAD_FLAG_BEGIN);
                if (remain <= BWFM_DLOAD_MAX_LEN)
                        data->flag |= htole16(BWFM_DLOAD_FLAG_END);
                data->type = htole16(BWFM_DLOAD_TYPE_CLM);
                data->len = htole32(len);
                data->crc = 0;
                memcpy(data->data, *blob + off, len);

                if (bwfm_fwvar_var_set_data(sc, var, data,
                    sizeof(*data) + len)) {
                        printf("%s: could not load blob (%s)\n", DEVNAME(sc),
                            var);
                        break;
                }

                off += len;
                remain -= len;
        }

        kmem_free(data, sizeof(*data) + BWFM_DLOAD_MAX_LEN);
        // kmem_free(*blob, *blobsize);
        *blob = NULL;
        *blobsize = 0;
}

/* FW Variable code */
int
bwfm_fwvar_cmd_get_data(struct bwfm_softc *sc, int cmd, void *data, size_t len)
{
        return sc->sc_proto_ops->proto_query_dcmd(sc, 0, cmd, data, &len);
}

int
bwfm_fwvar_cmd_set_data(struct bwfm_softc *sc, int cmd, void *data, size_t len)
{
        return sc->sc_proto_ops->proto_set_dcmd(sc, 0, cmd, data, len);
}

int
bwfm_fwvar_cmd_get_int(struct bwfm_softc *sc, int cmd, uint32_t *data)
{
        int ret;
        ret = bwfm_fwvar_cmd_get_data(sc, cmd, data, sizeof(*data));
        *data = le32toh(*data);
        return ret;
}

int
bwfm_fwvar_cmd_set_int(struct bwfm_softc *sc, int cmd, uint32_t data)
{
        data = htole32(data);
        return bwfm_fwvar_cmd_set_data(sc, cmd, &data, sizeof(data));
}

int
bwfm_fwvar_var_get_data(struct bwfm_softc *sc, const char *name, void *data, size_t len)
{
        char *buf;
        int ret;

        buf = kmem_alloc(strlen(name) + 1 + len, KM_SLEEP);
        memcpy(buf, name, strlen(name) + 1);
        memcpy(buf + strlen(name) + 1, data, len);
        ret = bwfm_fwvar_cmd_get_data(sc, BWFM_C_GET_VAR,
            buf, strlen(name) + 1 + len);
        memcpy(data, buf, len);
        kmem_free(buf, strlen(name) + 1 + len);
        return ret;
}

int
bwfm_fwvar_var_set_data(struct bwfm_softc *sc, const char *name, void *data, size_t len)
{
        char *buf;
        int ret;

        buf = kmem_alloc(strlen(name) + 1 + len, KM_SLEEP);
        memcpy(buf, name, strlen(name) + 1);
        memcpy(buf + strlen(name) + 1, data, len);
        ret = bwfm_fwvar_cmd_set_data(sc, BWFM_C_SET_VAR,
            buf, strlen(name) + 1 + len);
        kmem_free(buf, strlen(name) + 1 + len);
        return ret;
}

int
bwfm_fwvar_var_get_int(struct bwfm_softc *sc, const char *name, uint32_t *data)
{
        int ret;
        ret = bwfm_fwvar_var_get_data(sc, name, data, sizeof(*data));
        *data = le32toh(*data);
        return ret;
}

int
bwfm_fwvar_var_set_int(struct bwfm_softc *sc, const char *name, uint32_t data)
{
        data = htole32(data);
        return bwfm_fwvar_var_set_data(sc, name, &data, sizeof(data));
}

/* 802.11 code */
void
bwfm_scan(struct bwfm_softc *sc)
{
        struct bwfm_escan_params *params;
        uint32_t nssid = 0, nchannel = 0;
        size_t params_size;

#if 0
        /* Active scan is used for scanning for an SSID */
        bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_PASSIVE_SCAN, 0);
#endif
        bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_PASSIVE_SCAN, 1);

        params_size = sizeof(*params);
        params_size += sizeof(uint32_t) * ((nchannel + 1) / 2);
        params_size += sizeof(struct bwfm_ssid) * nssid;

        params = kmem_zalloc(params_size, KM_SLEEP);
        memset(params->scan_params.bssid, 0xff,
            sizeof(params->scan_params.bssid));
        params->scan_params.bss_type = 2;
        params->scan_params.nprobes = htole32(-1);
        params->scan_params.active_time = htole32(-1);
        params->scan_params.passive_time = htole32(-1);
        params->scan_params.home_time = htole32(-1);
        params->version = htole32(BWFM_ESCAN_REQ_VERSION);
        params->action = htole16(WL_ESCAN_ACTION_START);
        params->sync_id = htole16(0x1234);

#if 0
        /* Scan a specific channel */
        params->scan_params.channel_list[0] = htole16(
            (1 & 0xff) << 0 |
            (3 & 0x3) << 8 |
            (2 & 0x3) << 10 |
            (2 & 0x3) << 12
            );
        params->scan_params.channel_num = htole32(
            (1 & 0xffff) << 0
            );
#endif

        bwfm_fwvar_var_set_data(sc, "escan", params, params_size);
        kmem_free(params, params_size);
}

static __inline int
bwfm_iswpaoui(const uint8_t *frm)
{
        return frm[1] > 3 && le32dec(frm+2) == ((WPA_OUI_TYPE<<24)|WPA_OUI);
}

/*
 * Derive wireless security settings from WPA/RSN IE.
 */
static uint32_t
bwfm_get_wsec(struct bwfm_softc *sc)
{
        struct ieee80211com *ic = &sc->sc_ic;
        uint8_t *wpa = ic->ic_opt_ie;

        KASSERT(ic->ic_opt_ie_len > 0);

        if (wpa[0] != IEEE80211_ELEMID_RSN) {
                if (ic->ic_opt_ie_len < 12)
                        return BWFM_WSEC_NONE;

                /* non-RSN IE, expect that we are doing WPA1 */
                if ((ic->ic_flags & IEEE80211_F_WPA1) == 0)
                        return BWFM_WSEC_NONE;

                /* Must contain WPA OUI */
                if (!bwfm_iswpaoui(wpa))
                        return BWFM_WSEC_NONE;

                switch (le32dec(wpa + 8)) {
                case ((WPA_CSE_TKIP<<24)|WPA_OUI):
                        return BWFM_WSEC_TKIP;
                case ((WPA_CSE_CCMP<<24)|WPA_OUI):
                        return BWFM_WSEC_AES;
                default:
                        return BWFM_WSEC_NONE;
                }
        } else {
                if (ic->ic_opt_ie_len < 14)
                        return BWFM_WSEC_NONE;

                /* RSN IE, expect that we are doing WPA2 */
                if ((ic->ic_flags & IEEE80211_F_WPA2) == 0)
                        return BWFM_WSEC_NONE;

                switch (le32dec(wpa + 10)) {
                case ((RSN_CSE_TKIP<<24)|RSN_OUI):
                        return BWFM_WSEC_TKIP;
                case ((RSN_CSE_CCMP<<24)|RSN_OUI):
                        return BWFM_WSEC_AES;
                default:
                        return BWFM_WSEC_NONE;
                }
        }
}

void
bwfm_connect(struct bwfm_softc *sc)
{
        struct ieee80211com *ic = &sc->sc_ic;
        struct ieee80211_node *ni = ic->ic_bss;
        struct bwfm_ext_join_params *params;

        if (ic->ic_flags & IEEE80211_F_WPA) {
                uint32_t wsec = 0;
                uint32_t wpa = 0;

                if (ic->ic_opt_ie_len)
                        bwfm_fwvar_var_set_data(sc, "wpaie", ic->ic_opt_ie, ic->ic_opt_ie_len);

                if (ic->ic_flags & IEEE80211_F_WPA1)
                        wpa |= BWFM_WPA_AUTH_WPA_PSK;
                if (ic->ic_flags & IEEE80211_F_WPA2)
                        wpa |= BWFM_WPA_AUTH_WPA2_PSK;

                wsec |= bwfm_get_wsec(sc);

                DPRINTF(("%s: WPA enabled, ic_flags = 0x%x, wpa 0x%x, wsec 0x%x\n",
                    DEVNAME(sc), ic->ic_flags, wpa, wsec));

                bwfm_fwvar_var_set_int(sc, "wpa_auth", wpa);
                bwfm_fwvar_var_set_int(sc, "wsec", wsec);
        } else {
                bwfm_fwvar_var_set_int(sc, "wpa_auth", BWFM_WPA_AUTH_DISABLED);
                bwfm_fwvar_var_set_int(sc, "wsec", BWFM_WSEC_NONE);
        }

        bwfm_fwvar_var_set_int(sc, "auth", BWFM_AUTH_OPEN);
        bwfm_fwvar_var_set_int(sc, "mfp", BWFM_MFP_NONE);

        if (ni->ni_esslen && ni->ni_esslen < BWFM_MAX_SSID_LEN) {
                params = kmem_zalloc(sizeof(*params), KM_SLEEP);
                memcpy(params->ssid.ssid, ni->ni_essid, ni->ni_esslen);
                params->ssid.len = htole32(ni->ni_esslen);
                memcpy(params->assoc.bssid, ni->ni_bssid, sizeof(params->assoc.bssid));
                params->scan.scan_type = -1;
                params->scan.nprobes = htole32(-1);
                params->scan.active_time = htole32(-1);
                params->scan.passive_time = htole32(-1);
                params->scan.home_time = htole32(-1);
                if (bwfm_fwvar_var_set_data(sc, "join", params, sizeof(*params))) {
                        struct bwfm_join_params join;
                        memset(&join, 0, sizeof(join));
                        memcpy(join.ssid.ssid, ni->ni_essid, ni->ni_esslen);
                        join.ssid.len = htole32(ni->ni_esslen);
                        memcpy(join.assoc.bssid, ni->ni_bssid, sizeof(join.assoc.bssid));
                        bwfm_fwvar_cmd_set_data(sc, BWFM_C_SET_SSID, &join,
                            sizeof(join));
                }
                kmem_free(params, sizeof(*params));
        }
}

void
bwfm_get_sta_info(struct bwfm_softc *sc, struct ifmediareq *ifmr)
{
        struct ieee80211com *ic = &sc->sc_ic;
        struct ieee80211_node *ni = ic->ic_bss;
        struct bwfm_sta_info sta;
        uint32_t flags, txrate;

        memset(&sta, 0, sizeof(sta));
        memcpy(&sta, ni->ni_macaddr, sizeof(ni->ni_macaddr));

        if (bwfm_fwvar_var_get_data(sc, "sta_info", &sta, sizeof(sta)))
                return;

        if (!IEEE80211_ADDR_EQ(ni->ni_macaddr, sta.ea))
                return;

        if (le16toh(sta.ver) < 3)
                return;

        flags = le32toh(sta.flags);
        if ((flags & BWFM_STA_SCBSTATS) == 0)
                return;

        txrate = le32toh(sta.tx_rate);
        if (txrate == 0xffffffff)
                return;

        if ((flags & BWFM_STA_VHT_CAP) != 0) {
                ifmr->ifm_active &= ~IFM_TMASK;
                ifmr->ifm_active |= IFM_IEEE80211_VHT;
                ifmr->ifm_active &= ~IFM_MMASK;
                ifmr->ifm_active |= IFM_IEEE80211_11AC;
        } else if ((flags & BWFM_STA_N_CAP) != 0) {
                ifmr->ifm_active &= ~IFM_TMASK;
                ifmr->ifm_active |= IFM_IEEE80211_MCS;
                ifmr->ifm_active &= ~IFM_MMASK;
                if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan))
                        ifmr->ifm_active |= IFM_IEEE80211_11NG;
                else
                        ifmr->ifm_active |= IFM_IEEE80211_11NA;
        }
}

void
bwfm_rx(struct bwfm_softc *sc, struct mbuf *m)
{
        struct ieee80211com *ic = &sc->sc_ic;
        struct ifnet *ifp = ic->ic_ifp;
        struct bwfm_event *e = mtod(m, struct bwfm_event *);

        if (m->m_len >= sizeof(e->ehdr) &&
            ntohs(e->ehdr.ether_type) == BWFM_ETHERTYPE_LINK_CTL &&
            memcmp(BWFM_BRCM_OUI, e->hdr.oui, sizeof(e->hdr.oui)) == 0 &&
            ntohs(e->hdr.usr_subtype) == BWFM_BRCM_SUBTYPE_EVENT) {
                bwfm_rx_event(sc, m);
                // m_freem(m);
                return;
        }

        m_set_rcvif(m, ifp);
        if_percpuq_enqueue(ifp->if_percpuq, m);
}

void
bwfm_rx_event(struct bwfm_softc *sc, struct mbuf *m)
{
        struct bwfm_task *t;

        t = pool_cache_get(sc->sc_freetask, PR_NOWAIT);
        if (t == NULL) {
                m_freem(m);
                printf("%s: no free tasks\n", DEVNAME(sc));
                return;
        }

        t->t_sc = sc;
        t->t_cmd = BWFM_TASK_RX_EVENT;
        t->t_mbuf = m;
        workqueue_enqueue(sc->sc_taskq, (struct work*)t, NULL);
}

void
bwfm_rx_event_cb(struct bwfm_softc *sc, struct mbuf *m)
{
        struct ieee80211com *ic = &sc->sc_ic;
        struct bwfm_event *e = mtod(m, void *);
        size_t len = m->m_len;
        int s;

        DPRINTF(("%s: event %p len %lu datalen %u code %u status %u"
            " reason %u\n", __func__, e, len, ntohl(e->msg.datalen),
            ntohl(e->msg.event_type), ntohl(e->msg.status),
            ntohl(e->msg.reason)));

        if (ntohl(e->msg.event_type) >= BWFM_E_LAST) {
                m_freem(m);
                return;
        }

        switch (ntohl(e->msg.event_type)) {
        case BWFM_E_ESCAN_RESULT: {
                struct bwfm_escan_results *res = (void *)&e[1];
                struct bwfm_bss_info *bss;
                int i;
                if (ntohl(e->msg.status) != BWFM_E_STATUS_PARTIAL) {
                        /* Scan complete */
                        s = splnet();
                        if (ic->ic_opmode != IEEE80211_M_MONITOR)
                                ieee80211_end_scan(ic);
                        splx(s);
                        break;
                }
                len -= sizeof(*e);
                if (len < sizeof(*res) || len < le32toh(res->buflen)) {
                        m_freem(m);
                        printf("%s: results too small\n", DEVNAME(sc));
                        return;
                }
                len -= sizeof(*res);
                if (len < le16toh(res->bss_count) * sizeof(struct bwfm_bss_info)) {
                        m_freem(m);
                        printf("%s: results too small\n", DEVNAME(sc));
                        return;
                }
                bss = &res->bss_info[0];
                for (i = 0; i < le16toh(res->bss_count); i++) {
                        /* Fix alignment of bss_info */
                        if (len > sizeof(sc->sc_bss_buf)) {
                                printf("%s: bss_info buffer too big\n", DEVNAME(sc));
                        } else {
                                memcpy(&sc->sc_bss_buf, &res->bss_info[i], len);
                                bwfm_scan_node(sc, &sc->sc_bss_buf.bss_info,
                                    len);
                        }
                        len -= sizeof(*bss) + le32toh(bss->length);
                        bss = (void *)(((uintptr_t)bss) + le32toh(bss->length));
                        if (len <= 0)
                                break;
                }
                break;
                }

        case BWFM_E_SET_SSID:
                if (ntohl(e->msg.status) == BWFM_E_STATUS_SUCCESS) {
                        ieee80211_new_state(ic, IEEE80211_S_RUN, -1);
                } else {
                        ieee80211_new_state(ic, IEEE80211_S_SCAN, -1);
                }
                break;

        case BWFM_E_ASSOC:
                if (ntohl(e->msg.status) == BWFM_E_STATUS_SUCCESS) {
                        ieee80211_new_state(ic, IEEE80211_S_ASSOC, -1);
                } else {
                        ieee80211_new_state(ic, IEEE80211_S_SCAN, -1);
                }
                break;

        case BWFM_E_LINK:
                if (ntohl(e->msg.status) == BWFM_E_STATUS_SUCCESS &&
                    ntohl(e->msg.reason) == 0)
                        break;

                /* Link status has changed */
                ieee80211_new_state(ic, IEEE80211_S_SCAN, -1);
                break;

        default:
                break;
        }

        m_freem(m);
}

void
bwfm_scan_node(struct bwfm_softc *sc, struct bwfm_bss_info *bss, size_t len)
{
        struct ieee80211com *ic = &sc->sc_ic;
        struct ieee80211_frame wh;
        struct ieee80211_scanparams scan;
        uint8_t rates[sizeof(bss->rates) + 2];
        uint8_t ssid[sizeof(bss->ssid) + 2];
        uint8_t *frm, *sfrm, *efrm;
        uint64_t tsf;

        tsf = 0;
        sfrm = ((uint8_t *)bss) + le16toh(bss->ie_offset);
        efrm = sfrm + le32toh(bss->ie_length);

        /* Fake a wireless header with the scan result's BSSID */
        memset(&wh, 0, sizeof(wh));
        IEEE80211_ADDR_COPY(wh.i_addr2, bss->bssid);
        IEEE80211_ADDR_COPY(wh.i_addr3, bss->bssid);

        if (efrm - sfrm < 12) {
                ic->ic_stats.is_rx_elem_toosmall++;
                return;
        }

        rates[0] = 0;
        rates[1] = le32toh(bss->nrates);
        memcpy(&rates[2], bss->rates, sizeof(bss->rates));

        ssid[0] = 0;
        ssid[1] = bss->ssid_len;
        memcpy(&ssid[2], bss->ssid, sizeof(bss->ssid));

        /* Build scan result */
        memset(&scan, 0, sizeof(scan));
        scan.sp_tstamp  = (uint8_t *)&tsf;
        scan.sp_bintval = le16toh(bss->beacon_period);
        scan.sp_capinfo = le16toh(bss->capability);
        scan.sp_bchan   = ieee80211_chan2ieee(ic, ic->ic_curchan);
        scan.sp_chan    = scan.sp_bchan;
        scan.sp_rates   = rates;
        scan.sp_ssid    = ssid;

        for (frm = sfrm; frm < efrm; frm += frm[1] + 2) {
                switch (frm[0]) {
                case IEEE80211_ELEMID_COUNTRY:
                        scan.sp_country = frm;
                        break;
                case IEEE80211_ELEMID_FHPARMS:
                        if (ic->ic_phytype == IEEE80211_T_FH) {
                                if (frm + 6 >= efrm)
                                        break;
                                scan.sp_fhdwell = le16dec(&frm[2]);
                                scan.sp_chan = IEEE80211_FH_CHAN(frm[4], frm[5]);
                                scan.sp_fhindex = frm[6];
                        }
                        break;
                case IEEE80211_ELEMID_DSPARMS:
                        if (ic->ic_phytype != IEEE80211_T_FH) {
                                if (frm + 2 >= efrm)
                                        break;
                                scan.sp_chan = frm[2];
                        }
                        break;
                case IEEE80211_ELEMID_TIM:
                        scan.sp_tim = frm;
                        scan.sp_timoff = frm - sfrm;
                        break;
                case IEEE80211_ELEMID_XRATES:
                        scan.sp_xrates = frm;
                        break;
                case IEEE80211_ELEMID_ERP:
                        if (frm + 1 >= efrm)
                                break;
                        if (frm[1] != 1) {
                                ic->ic_stats.is_rx_elem_toobig++;
                                break;
                        }
                        scan.sp_erp = frm[2];
                        break;
                case IEEE80211_ELEMID_RSN:
                        scan.sp_wpa = frm;
                        break;
                case IEEE80211_ELEMID_VENDOR:
                        if (frm + 1 >= efrm)
                                break;
                        if (frm + frm[1] + 2 >= efrm)
                                break;
                        if (bwfm_iswpaoui(frm))
                                scan.sp_wpa = frm;
                        break;
                }
                if (frm + 1 >= efrm)
                        break;
        }

        if (ic->ic_flags & IEEE80211_F_SCAN)
                ieee80211_add_scan(ic, &scan, &wh, IEEE80211_FC0_SUBTYPE_BEACON,
                    le32toh(bss->rssi), 0);
}