3255 lines
86 KiB
C
3255 lines
86 KiB
C
/* $OpenBSD: bwfm.c,v 1.109 2023/03/28 14:01:42 jsg 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 "bpfilter.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/buf.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/device.h>
|
|
#include <sys/queue.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/sockio.h>
|
|
|
|
#if defined(__HAVE_FDT)
|
|
#include <machine/fdt.h>
|
|
#include <dev/ofw/openfirm.h>
|
|
#endif
|
|
|
|
#if NBPFILTER > 0
|
|
#include <net/bpf.h>
|
|
#endif
|
|
#include <net/if.h>
|
|
#include <net/if_dl.h>
|
|
#include <net/if_media.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <netinet/if_ether.h>
|
|
|
|
#include <net80211/ieee80211_var.h>
|
|
|
|
#include <dev/ic/bwfmvar.h>
|
|
#include <dev/ic/bwfmreg.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) ((sc)->sc_dev.dv_xname)
|
|
|
|
void bwfm_start(struct ifnet *);
|
|
void bwfm_init(struct ifnet *);
|
|
void bwfm_stop(struct ifnet *);
|
|
void bwfm_iff(struct bwfm_softc *);
|
|
void bwfm_watchdog(struct ifnet *);
|
|
void bwfm_update_node(void *, struct ieee80211_node *);
|
|
void bwfm_update_nodes(struct bwfm_softc *);
|
|
int bwfm_ioctl(struct ifnet *, u_long, caddr_t);
|
|
int bwfm_media_change(struct ifnet *);
|
|
|
|
void bwfm_init_board_type(struct bwfm_softc *);
|
|
void bwfm_process_blob(struct bwfm_softc *, char *, u_char **, size_t *);
|
|
|
|
int bwfm_chip_attach(struct bwfm_softc *);
|
|
void bwfm_chip_detach(struct bwfm_softc *);
|
|
struct bwfm_core *bwfm_chip_get_core_idx(struct bwfm_softc *, int, 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 *, uint32_t);
|
|
void bwfm_chip_cr4_set_passive(struct bwfm_softc *);
|
|
int bwfm_chip_ca7_set_active(struct bwfm_softc *, 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 *);
|
|
|
|
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);
|
|
void bwfm_proto_bcdc_rx(struct bwfm_softc *, struct mbuf *,
|
|
struct mbuf_list *);
|
|
int bwfm_proto_bcdc_txctl(struct bwfm_softc *, int, char *, size_t *);
|
|
void bwfm_proto_bcdc_rxctl(struct bwfm_softc *, 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 *, char *, void *, size_t);
|
|
int bwfm_fwvar_var_set_data(struct bwfm_softc *, char *, void *, size_t);
|
|
int bwfm_fwvar_var_get_int(struct bwfm_softc *, char *, uint32_t *);
|
|
int bwfm_fwvar_var_set_int(struct bwfm_softc *, char *, uint32_t);
|
|
|
|
uint32_t bwfm_chan2spec(struct bwfm_softc *, struct ieee80211_channel *);
|
|
uint32_t bwfm_chan2spec_d11n(struct bwfm_softc *, struct ieee80211_channel *);
|
|
uint32_t bwfm_chan2spec_d11ac(struct bwfm_softc *, struct ieee80211_channel *);
|
|
uint32_t bwfm_spec2chan(struct bwfm_softc *, uint32_t);
|
|
uint32_t bwfm_spec2chan_d11n(struct bwfm_softc *, uint32_t);
|
|
uint32_t bwfm_spec2chan_d11ac(struct bwfm_softc *, uint32_t);
|
|
|
|
void bwfm_connect(struct bwfm_softc *);
|
|
#ifndef IEEE80211_STA_ONLY
|
|
void bwfm_hostap(struct bwfm_softc *);
|
|
#endif
|
|
void bwfm_scan(struct bwfm_softc *);
|
|
void bwfm_scan_abort(struct bwfm_softc *);
|
|
|
|
void bwfm_task(void *);
|
|
void bwfm_do_async(struct bwfm_softc *,
|
|
void (*)(struct bwfm_softc *, void *), void *, int);
|
|
|
|
int bwfm_set_key(struct ieee80211com *, struct ieee80211_node *,
|
|
struct ieee80211_key *);
|
|
void bwfm_delete_key(struct ieee80211com *, struct ieee80211_node *,
|
|
struct ieee80211_key *);
|
|
int bwfm_send_mgmt(struct ieee80211com *, struct ieee80211_node *,
|
|
int, int, int);
|
|
int bwfm_newstate(struct ieee80211com *, enum ieee80211_state, int);
|
|
|
|
void bwfm_set_key_cb(struct bwfm_softc *, void *);
|
|
void bwfm_delete_key_cb(struct bwfm_softc *, void *);
|
|
void bwfm_rx_event_cb(struct bwfm_softc *, struct mbuf *);
|
|
|
|
struct mbuf *bwfm_newbuf(void);
|
|
#ifndef IEEE80211_STA_ONLY
|
|
void bwfm_rx_auth_ind(struct bwfm_softc *, struct bwfm_event *, size_t);
|
|
void bwfm_rx_assoc_ind(struct bwfm_softc *, struct bwfm_event *, size_t, int);
|
|
void bwfm_rx_deauth_ind(struct bwfm_softc *, struct bwfm_event *, size_t);
|
|
void bwfm_rx_disassoc_ind(struct bwfm_softc *, struct bwfm_event *, size_t);
|
|
void bwfm_rx_leave_ind(struct bwfm_softc *, struct bwfm_event *, size_t, int);
|
|
#endif
|
|
void bwfm_rx_event(struct bwfm_softc *, struct mbuf *);
|
|
void bwfm_scan_node(struct bwfm_softc *, struct bwfm_bss_info *, size_t);
|
|
|
|
extern void ieee80211_node2req(struct ieee80211com *,
|
|
const struct ieee80211_node *, struct ieee80211_nodereq *);
|
|
extern void ieee80211_req2node(struct ieee80211com *,
|
|
const struct ieee80211_nodereq *, struct ieee80211_node *);
|
|
|
|
uint8_t bwfm_2ghz_channels[] = {
|
|
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
|
|
};
|
|
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,
|
|
};
|
|
|
|
struct bwfm_proto_ops bwfm_proto_bcdc_ops = {
|
|
.proto_query_dcmd = bwfm_proto_bcdc_query_dcmd,
|
|
.proto_set_dcmd = bwfm_proto_bcdc_set_dcmd,
|
|
.proto_rx = bwfm_proto_bcdc_rx,
|
|
.proto_rxctl = bwfm_proto_bcdc_rxctl,
|
|
};
|
|
|
|
struct cfdriver bwfm_cd = {
|
|
NULL, "bwfm", DV_IFNET
|
|
};
|
|
|
|
void
|
|
bwfm_attach(struct bwfm_softc *sc)
|
|
{
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct ifnet *ifp = &ic->ic_if;
|
|
|
|
TAILQ_INIT(&sc->sc_bcdc_rxctlq);
|
|
|
|
/* Init host async commands ring. */
|
|
sc->sc_cmdq.cur = sc->sc_cmdq.next = sc->sc_cmdq.queued = 0;
|
|
sc->sc_taskq = taskq_create(DEVNAME(sc), 1, IPL_SOFTNET, 0);
|
|
task_set(&sc->sc_task, bwfm_task, sc);
|
|
ml_init(&sc->sc_evml);
|
|
|
|
ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */
|
|
ic->ic_opmode = IEEE80211_M_STA; /* default to BSS mode */
|
|
ic->ic_state = IEEE80211_S_INIT;
|
|
|
|
ic->ic_caps =
|
|
IEEE80211_C_WEP |
|
|
#ifndef IEEE80211_STA_ONLY
|
|
IEEE80211_C_HOSTAP | /* Access Point */
|
|
#endif
|
|
IEEE80211_C_RSN | /* WPA/RSN */
|
|
IEEE80211_C_SCANALL | /* device scans all channels at once */
|
|
IEEE80211_C_SCANALLBAND; /* device scans all bands at once */
|
|
|
|
/* IBSS channel undefined for now. */
|
|
ic->ic_ibss_chan = &ic->ic_channels[0];
|
|
|
|
ifp->if_softc = sc;
|
|
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
|
|
ifp->if_ioctl = bwfm_ioctl;
|
|
ifp->if_start = bwfm_start;
|
|
ifp->if_watchdog = bwfm_watchdog;
|
|
memcpy(ifp->if_xname, DEVNAME(sc), IFNAMSIZ);
|
|
|
|
if_attach(ifp);
|
|
ieee80211_ifattach(ifp);
|
|
|
|
sc->sc_newstate = ic->ic_newstate;
|
|
ic->ic_newstate = bwfm_newstate;
|
|
ic->ic_send_mgmt = bwfm_send_mgmt;
|
|
ic->ic_set_key = bwfm_set_key;
|
|
ic->ic_delete_key = bwfm_delete_key;
|
|
|
|
ieee80211_media_init(ifp, bwfm_media_change, ieee80211_media_status);
|
|
}
|
|
|
|
void
|
|
bwfm_attachhook(struct device *self)
|
|
{
|
|
struct bwfm_softc *sc = (struct bwfm_softc *)self;
|
|
|
|
if (sc->sc_bus_ops->bs_preinit != NULL &&
|
|
sc->sc_bus_ops->bs_preinit(sc))
|
|
return;
|
|
if (bwfm_preinit(sc))
|
|
return;
|
|
sc->sc_initialized = 1;
|
|
}
|
|
|
|
int
|
|
bwfm_preinit(struct bwfm_softc *sc)
|
|
{
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct ifnet *ifp = &ic->ic_if;
|
|
int i, j, nbands, nmode, vhtmode;
|
|
uint32_t bandlist[3], tmp;
|
|
|
|
if (sc->sc_initialized)
|
|
return 0;
|
|
|
|
if (bwfm_fwvar_cmd_get_int(sc, BWFM_C_GET_VERSION, &tmp)) {
|
|
printf("%s: could not read io type\n", DEVNAME(sc));
|
|
return 1;
|
|
} 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 1;
|
|
}
|
|
|
|
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);
|
|
|
|
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_var_get_int(sc, "scan_ver", &sc->sc_scan_ver))
|
|
sc->sc_scan_ver = 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 1;
|
|
}
|
|
nbands = letoh32(bandlist[0]);
|
|
for (i = 1; i <= nbands && i < nitems(bandlist); i++) {
|
|
switch (letoh32(bandlist[i])) {
|
|
case BWFM_BAND_2G:
|
|
DPRINTF(("%s: 2G HT %d VHT %d\n",
|
|
DEVNAME(sc), nmode, vhtmode));
|
|
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 < nitems(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;
|
|
/* VHT is 5GHz only */
|
|
}
|
|
break;
|
|
case BWFM_BAND_5G:
|
|
DPRINTF(("%s: 5G HT %d VHT %d\n",
|
|
DEVNAME(sc), nmode, vhtmode));
|
|
ic->ic_sup_rates[IEEE80211_MODE_11A] =
|
|
ieee80211_std_rateset_11a;
|
|
|
|
for (j = 0; j < nitems(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),
|
|
letoh32(bandlist[i]));
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Configure channel information obtained from firmware. */
|
|
ieee80211_channel_init(ifp);
|
|
|
|
/* Configure MAC address. */
|
|
if (if_setlladdr(ifp, ic->ic_myaddr))
|
|
printf("%s: could not set MAC address\n", DEVNAME(sc));
|
|
|
|
ieee80211_media_init(ifp, bwfm_media_change, ieee80211_media_status);
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
bwfm_cleanup(struct bwfm_softc *sc)
|
|
{
|
|
bwfm_chip_detach(sc);
|
|
sc->sc_initialized = 0;
|
|
}
|
|
|
|
int
|
|
bwfm_detach(struct bwfm_softc *sc, int flags)
|
|
{
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct ifnet *ifp = &ic->ic_if;
|
|
|
|
task_del(sc->sc_taskq, &sc->sc_task);
|
|
ieee80211_ifdetach(ifp);
|
|
taskq_barrier(sc->sc_taskq);
|
|
if_detach(ifp);
|
|
taskq_destroy(sc->sc_taskq);
|
|
|
|
bwfm_cleanup(sc);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
bwfm_activate(struct bwfm_softc *sc, int act)
|
|
{
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct ifnet *ifp = &ic->ic_if;
|
|
|
|
switch (act) {
|
|
case DVACT_QUIESCE:
|
|
if (ifp->if_flags & IFF_UP)
|
|
bwfm_stop(ifp);
|
|
break;
|
|
case DVACT_WAKEUP:
|
|
if (ifp->if_flags & IFF_UP)
|
|
bwfm_init(ifp);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
bwfm_start(struct ifnet *ifp)
|
|
{
|
|
struct bwfm_softc *sc = ifp->if_softc;
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct mbuf *m;
|
|
|
|
if (!(ifp->if_flags & IFF_RUNNING))
|
|
return;
|
|
if (ifq_is_oactive(&ifp->if_snd))
|
|
return;
|
|
if (ifq_empty(&ifp->if_snd))
|
|
return;
|
|
|
|
/* TODO: return if no link? */
|
|
|
|
for (;;) {
|
|
if (sc->sc_bus_ops->bs_txcheck(sc)) {
|
|
ifq_set_oactive(&ifp->if_snd);
|
|
break;
|
|
}
|
|
|
|
if (ic->ic_state != IEEE80211_S_RUN ||
|
|
(ic->ic_xflags & IEEE80211_F_TX_MGMT_ONLY))
|
|
break;
|
|
|
|
m = ifq_dequeue(&ifp->if_snd);
|
|
if (m == NULL)
|
|
break;
|
|
|
|
if (sc->sc_bus_ops->bs_txdata(sc, m) != 0) {
|
|
ifp->if_oerrors++;
|
|
m_freem(m);
|
|
continue;
|
|
}
|
|
|
|
#if NBPFILTER > 0
|
|
if (ifp->if_bpf)
|
|
bpf_mtap(ifp->if_bpf, m, BPF_DIRECTION_OUT);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void
|
|
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];
|
|
int pm;
|
|
|
|
if (!sc->sc_initialized) {
|
|
if (sc->sc_bus_ops->bs_preinit != NULL &&
|
|
sc->sc_bus_ops->bs_preinit(sc)) {
|
|
printf("%s: could not init bus\n", DEVNAME(sc));
|
|
return;
|
|
}
|
|
if (bwfm_preinit(sc)) {
|
|
printf("%s: could not init\n", DEVNAME(sc));
|
|
return;
|
|
}
|
|
sc->sc_initialized = 1;
|
|
}
|
|
|
|
/* Select default channel */
|
|
ic->ic_bss->ni_chan = ic->ic_ibss_chan;
|
|
|
|
if (bwfm_fwvar_var_set_int(sc, "mpc", 1)) {
|
|
printf("%s: could not set mpc\n", DEVNAME(sc));
|
|
return;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
#define BWFM_EVENT(event) evmask[(event) / 8] |= 1 << ((event) % 8)
|
|
memset(evmask, 0, sizeof(evmask));
|
|
switch (ic->ic_opmode) {
|
|
case IEEE80211_M_STA:
|
|
BWFM_EVENT(BWFM_E_IF);
|
|
BWFM_EVENT(BWFM_E_LINK);
|
|
BWFM_EVENT(BWFM_E_AUTH);
|
|
BWFM_EVENT(BWFM_E_ASSOC);
|
|
BWFM_EVENT(BWFM_E_DEAUTH);
|
|
BWFM_EVENT(BWFM_E_DISASSOC);
|
|
BWFM_EVENT(BWFM_E_ESCAN_RESULT);
|
|
break;
|
|
#ifndef IEEE80211_STA_ONLY
|
|
case IEEE80211_M_HOSTAP:
|
|
BWFM_EVENT(BWFM_E_AUTH_IND);
|
|
BWFM_EVENT(BWFM_E_ASSOC_IND);
|
|
BWFM_EVENT(BWFM_E_REASSOC_IND);
|
|
BWFM_EVENT(BWFM_E_DEAUTH_IND);
|
|
BWFM_EVENT(BWFM_E_DISASSOC_IND);
|
|
BWFM_EVENT(BWFM_E_ESCAN_RESULT);
|
|
break;
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
#undef BWFM_EVENT
|
|
|
|
if (bwfm_fwvar_var_set_data(sc, "event_msgs", evmask, sizeof(evmask))) {
|
|
printf("%s: could not set event mask\n", DEVNAME(sc));
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
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;
|
|
}
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Use CAM (constantly awake) when we are running as AP,
|
|
* otherwise use fast power saving.
|
|
*/
|
|
pm = BWFM_PM_FAST_PS;
|
|
#ifndef IEEE80211_STA_ONLY
|
|
if (ic->ic_opmode == IEEE80211_M_HOSTAP)
|
|
pm = BWFM_PM_CAM;
|
|
#endif
|
|
if (bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_PM, pm)) {
|
|
printf("%s: could not set power\n", DEVNAME(sc));
|
|
return;
|
|
}
|
|
|
|
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);
|
|
|
|
/*
|
|
* The firmware supplicant can handle the WPA handshake for
|
|
* us, but we honestly want to do this ourselves, so disable
|
|
* the firmware supplicant and let our stack handle it.
|
|
*/
|
|
bwfm_fwvar_var_set_int(sc, "sup_wpa", 0);
|
|
|
|
bwfm_iff(sc);
|
|
|
|
ifp->if_flags |= IFF_RUNNING;
|
|
ifq_clr_oactive(&ifp->if_snd);
|
|
|
|
ieee80211_begin_scan(ifp);
|
|
}
|
|
|
|
void
|
|
bwfm_stop(struct ifnet *ifp)
|
|
{
|
|
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;
|
|
ifq_clr_oactive(&ifp->if_snd);
|
|
|
|
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_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);
|
|
}
|
|
|
|
void
|
|
bwfm_iff(struct bwfm_softc *sc)
|
|
{
|
|
struct arpcom *ac = &sc->sc_ic.ic_ac;
|
|
struct ifnet *ifp = &ac->ac_if;
|
|
struct ether_multi *enm;
|
|
struct ether_multistep step;
|
|
size_t mcastlen;
|
|
char *mcast;
|
|
int i = 0;
|
|
|
|
mcastlen = sizeof(uint32_t) + ac->ac_multicnt * ETHER_ADDR_LEN;
|
|
mcast = malloc(mcastlen, M_TEMP, M_WAITOK);
|
|
htolem32((uint32_t *)mcast, ac->ac_multicnt);
|
|
|
|
ifp->if_flags &= ~IFF_ALLMULTI;
|
|
if (ifp->if_flags & IFF_PROMISC || ac->ac_multirangecnt > 0) {
|
|
ifp->if_flags |= IFF_ALLMULTI;
|
|
} else {
|
|
ETHER_FIRST_MULTI(step, ac, enm);
|
|
while (enm != NULL) {
|
|
memcpy(mcast + sizeof(uint32_t) + i * ETHER_ADDR_LEN,
|
|
enm->enm_addrlo, ETHER_ADDR_LEN);
|
|
ETHER_NEXT_MULTI(step, enm);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
bwfm_fwvar_var_set_data(sc, "mcast_list", mcast, mcastlen);
|
|
bwfm_fwvar_var_set_int(sc, "allmulti",
|
|
!!(ifp->if_flags & IFF_ALLMULTI));
|
|
bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_PROMISC,
|
|
!!(ifp->if_flags & IFF_PROMISC));
|
|
|
|
free(mcast, M_TEMP, mcastlen);
|
|
}
|
|
|
|
void
|
|
bwfm_watchdog(struct ifnet *ifp)
|
|
{
|
|
struct bwfm_softc *sc = ifp->if_softc;
|
|
|
|
ifp->if_timer = 0;
|
|
|
|
if (sc->sc_tx_timer > 0) {
|
|
if (--sc->sc_tx_timer == 0) {
|
|
printf("%s: device timeout\n", DEVNAME(sc));
|
|
ifp->if_oerrors++;
|
|
return;
|
|
}
|
|
ifp->if_timer = 1;
|
|
}
|
|
ieee80211_watchdog(ifp);
|
|
}
|
|
|
|
/*
|
|
* Tx-rate to MCS conversion might lie since some rates map to multiple MCS.
|
|
* But this is the best we can do given that firmware only reports kbit/s.
|
|
*/
|
|
|
|
void
|
|
bwfm_rate2vhtmcs(int *mcs, int *ss, uint32_t txrate)
|
|
{
|
|
const struct ieee80211_vht_rateset *rs;
|
|
int i, j;
|
|
|
|
*mcs = -1;
|
|
*ss = -1;
|
|
/* TODO: Select specific ratesets based on BSS channel width. */
|
|
for (i = 0; i < IEEE80211_VHT_NUM_RATESETS; i++) {
|
|
rs = &ieee80211_std_ratesets_11ac[i];
|
|
for (j = 0; j < rs->nrates; j++) {
|
|
if (rs->rates[j] == txrate / 500) {
|
|
*mcs = j;
|
|
*ss = rs->num_ss;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
bwfm_rate2htmcs(uint32_t txrate)
|
|
{
|
|
const struct ieee80211_ht_rateset *rs;
|
|
int i, j;
|
|
|
|
/* TODO: Select specific ratesets based on BSS channel width. */
|
|
for (i = 0; i < IEEE80211_HT_NUM_RATESETS; i++) {
|
|
rs = &ieee80211_std_ratesets_11n[i];
|
|
for (j = 0; j < rs->nrates; j++) {
|
|
if (rs->rates[j] == txrate / 500)
|
|
return rs->min_mcs + j;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void
|
|
bwfm_update_node(void *arg, struct ieee80211_node *ni)
|
|
{
|
|
struct bwfm_softc *sc = arg;
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct bwfm_sta_info sta;
|
|
uint32_t flags;
|
|
int8_t rssi;
|
|
uint32_t txrate;
|
|
int i;
|
|
|
|
memset(&sta, 0, sizeof(sta));
|
|
memcpy((uint8_t *)&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;
|
|
|
|
if (le16toh(sta.ver) >= 4) {
|
|
rssi = 0;
|
|
for (i = 0; i < BWFM_ANT_MAX; i++) {
|
|
if (sta.rssi[i] >= 0)
|
|
continue;
|
|
if (rssi == 0 || sta.rssi[i] > rssi)
|
|
rssi = sta.rssi[i];
|
|
}
|
|
if (rssi)
|
|
ni->ni_rssi = rssi;
|
|
}
|
|
|
|
txrate = le32toh(sta.tx_rate); /* in kbit/s */
|
|
if (txrate == 0xffffffff) /* Seen this happening during association. */
|
|
return;
|
|
|
|
if ((le32toh(sta.flags) & BWFM_STA_VHT_CAP)) {
|
|
int mcs, ss;
|
|
/* Tell net80211 that firmware has negotiated 11ac. */
|
|
ni->ni_flags |= IEEE80211_NODE_VHT;
|
|
ni->ni_flags |= IEEE80211_NODE_HT; /* VHT implies HT support */
|
|
if (ic->ic_curmode < IEEE80211_MODE_11AC)
|
|
ieee80211_setmode(ic, IEEE80211_MODE_11AC);
|
|
bwfm_rate2vhtmcs(&mcs, &ss, txrate);
|
|
if (mcs >= 0) {
|
|
ni->ni_txmcs = mcs;
|
|
ni->ni_vht_ss = ss;
|
|
} else {
|
|
ni->ni_txmcs = 0;
|
|
ni->ni_vht_ss = 1;
|
|
}
|
|
} else if ((le32toh(sta.flags) & BWFM_STA_N_CAP)) {
|
|
int mcs;
|
|
/* Tell net80211 that firmware has negotiated 11n. */
|
|
ni->ni_flags |= IEEE80211_NODE_HT;
|
|
if (ic->ic_curmode < IEEE80211_MODE_11N)
|
|
ieee80211_setmode(ic, IEEE80211_MODE_11N);
|
|
mcs = bwfm_rate2htmcs(txrate);
|
|
ni->ni_txmcs = (mcs >= 0 ? mcs : 0);
|
|
} else {
|
|
/* We're in 11a/g mode. Map to a legacy rate. */
|
|
for (i = 0; i < ni->ni_rates.rs_nrates; i++) {
|
|
uint8_t rate = ni->ni_rates.rs_rates[i];
|
|
rate &= IEEE80211_RATE_VAL;
|
|
if (rate == txrate / 500) {
|
|
ni->ni_txrate = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
bwfm_update_nodes(struct bwfm_softc *sc)
|
|
{
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct ieee80211_node *ni;
|
|
|
|
switch (ic->ic_opmode) {
|
|
case IEEE80211_M_STA:
|
|
bwfm_update_node(sc, ic->ic_bss);
|
|
/* Update cached copy in the nodes tree as well. */
|
|
ni = ieee80211_find_node(ic, ic->ic_bss->ni_macaddr);
|
|
if (ni) {
|
|
ni->ni_rssi = ic->ic_bss->ni_rssi;
|
|
}
|
|
break;
|
|
#ifndef IEEE80211_STA_ONLY
|
|
case IEEE80211_M_HOSTAP:
|
|
ieee80211_iterate_nodes(ic, bwfm_update_node, sc);
|
|
break;
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
int
|
|
bwfm_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
|
|
{
|
|
struct bwfm_softc *sc = ifp->if_softc;
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct ifreq *ifr;
|
|
int s, error = 0;
|
|
|
|
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))
|
|
bwfm_init(ifp);
|
|
} else {
|
|
if (ifp->if_flags & IFF_RUNNING)
|
|
bwfm_stop(ifp);
|
|
}
|
|
break;
|
|
case SIOCADDMULTI:
|
|
case SIOCDELMULTI:
|
|
ifr = (struct ifreq *)data;
|
|
error = (cmd == SIOCADDMULTI) ?
|
|
ether_addmulti(ifr, &ic->ic_ac) :
|
|
ether_delmulti(ifr, &ic->ic_ac);
|
|
if (error == ENETRESET) {
|
|
bwfm_iff(sc);
|
|
error = 0;
|
|
}
|
|
break;
|
|
case SIOCGIFMEDIA:
|
|
case SIOCG80211NODE:
|
|
case SIOCG80211ALLNODES:
|
|
if (ic->ic_state == IEEE80211_S_RUN)
|
|
bwfm_update_nodes(sc);
|
|
/* fall through */
|
|
default:
|
|
error = ieee80211_ioctl(ifp, cmd, data);
|
|
}
|
|
if (error == ENETRESET) {
|
|
if ((ifp->if_flags & (IFF_UP | IFF_RUNNING)) ==
|
|
(IFF_UP | IFF_RUNNING)) {
|
|
bwfm_stop(ifp);
|
|
bwfm_init(ifp);
|
|
}
|
|
error = 0;
|
|
}
|
|
splx(s);
|
|
return error;
|
|
}
|
|
|
|
int
|
|
bwfm_media_change(struct ifnet *ifp)
|
|
{
|
|
int error;
|
|
|
|
error = ieee80211_media_change(ifp);
|
|
if (error != ENETRESET)
|
|
return error;
|
|
|
|
if ((ifp->if_flags & (IFF_UP | IFF_RUNNING)) ==
|
|
(IFF_UP | IFF_RUNNING)) {
|
|
bwfm_stop(ifp);
|
|
bwfm_init(ifp);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
/* 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 = 1;
|
|
/* FALLTHROUGH */
|
|
case BWFM_AGENT_CORE_ARM_CR4:
|
|
case BWFM_AGENT_CORE_ARM_CA7:
|
|
cpu_found = 1;
|
|
break;
|
|
case BWFM_AGENT_INTERNAL_MEM:
|
|
has_socram = 1;
|
|
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);
|
|
|
|
bwfm_init_board_type(sc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
bwfm_chip_detach(struct bwfm_softc *sc)
|
|
{
|
|
struct bwfm_core *core, *tmp;
|
|
|
|
LIST_FOREACH_SAFE(core, &sc->sc_chip.ch_list, co_link, tmp) {
|
|
LIST_REMOVE(core, co_link);
|
|
free(core, M_DEVBUF, sizeof(*core));
|
|
}
|
|
}
|
|
|
|
struct bwfm_core *
|
|
bwfm_chip_get_core_idx(struct bwfm_softc *sc, int id, int idx)
|
|
{
|
|
struct bwfm_core *core;
|
|
|
|
LIST_FOREACH(core, &sc->sc_chip.ch_list, co_link) {
|
|
if (core->co_id == id && idx-- == 0)
|
|
return core;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct bwfm_core *
|
|
bwfm_chip_get_core(struct bwfm_softc *sc, int id)
|
|
{
|
|
return bwfm_chip_get_core_idx(sc, id, 0);
|
|
}
|
|
|
|
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)
|
|
{
|
|
struct bwfm_core *core2 = NULL;
|
|
int i;
|
|
|
|
if (core->co_id == BWFM_AGENT_CORE_80211)
|
|
core2 = bwfm_chip_get_core_idx(sc, BWFM_AGENT_CORE_80211, 1);
|
|
|
|
bwfm_chip_ai_disable(sc, core, prereset, reset);
|
|
if (core2)
|
|
bwfm_chip_ai_disable(sc, core2, 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));
|
|
if (core2) {
|
|
for (i = 50; i > 0; i--) {
|
|
if ((sc->sc_buscore_ops->bc_read(sc,
|
|
core2->co_wrapbase + BWFM_AGENT_RESET_CTL) &
|
|
BWFM_AGENT_RESET_CTL_RESET) == 0)
|
|
break;
|
|
sc->sc_buscore_ops->bc_write(sc,
|
|
core2->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);
|
|
if (core2) {
|
|
sc->sc_buscore_ops->bc_write(sc,
|
|
core2->co_wrapbase + BWFM_AGENT_IOCTL,
|
|
postreset | BWFM_AGENT_IOCTL_CLK);
|
|
sc->sc_buscore_ops->bc_read(sc,
|
|
core2->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 &&
|
|
id != BWFM_AGENT_CORE_GCI)
|
|
continue;
|
|
|
|
if (bwfm_chip_dmp_get_regaddr(sc, &erom, &base, &wrap))
|
|
continue;
|
|
|
|
core = malloc(sizeof(*core), M_DEVBUF, M_WAITOK);
|
|
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 = 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 &&
|
|
sztype != BWFM_DMP_SLAVE_SIZE_8K)
|
|
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, 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, 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;
|
|
int i = 0;
|
|
|
|
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);
|
|
|
|
while ((core = bwfm_chip_get_core_idx(sc, BWFM_AGENT_CORE_80211, i++)))
|
|
sc->sc_chip.ch_core_disable(sc, core,
|
|
BWFM_AGENT_D11_IOCTL_PHYRESET |
|
|
BWFM_AGENT_D11_IOCTL_PHYCLOCKEN,
|
|
BWFM_AGENT_D11_IOCTL_PHYCLOCKEN);
|
|
}
|
|
|
|
int
|
|
bwfm_chip_ca7_set_active(struct bwfm_softc *sc, 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;
|
|
case CY_CC_4373_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_CONTROL0);
|
|
return (reg & BWFM_CHIP_REG_SR_CONTROL0_ENABLE) != 0;
|
|
case BRCM_CC_4359_CHIP_ID:
|
|
case CY_CC_43752_CHIP_ID:
|
|
case CY_CC_43012_CHIP_ID:
|
|
core = bwfm_chip_get_pmu(sc);
|
|
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;
|
|
case BRCM_CC_4378_CHIP_ID:
|
|
return 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, blksize, 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);
|
|
if (bxinfo & BWFM_ARMCR4_BANKINFO_BLK_1K_MASK)
|
|
blksize = 1024;
|
|
else
|
|
blksize = 8192;
|
|
ramsize += ((bxinfo & BWFM_ARMCR4_BANKINFO_BSZ_MASK) + 1) *
|
|
blksize;
|
|
}
|
|
|
|
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_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:
|
|
case BRCM_CC_43664_CHIP_ID:
|
|
case BRCM_CC_43666_CHIP_ID:
|
|
sc->sc_chip.ch_rambase = 0x200000;
|
|
break;
|
|
case BRCM_CC_4359_CHIP_ID:
|
|
if (sc->sc_chip.ch_chiprev < 9)
|
|
sc->sc_chip.ch_rambase = 0x180000;
|
|
else
|
|
sc->sc_chip.ch_rambase = 0x160000;
|
|
break;
|
|
case BRCM_CC_4355_CHIP_ID:
|
|
case BRCM_CC_4364_CHIP_ID:
|
|
case CY_CC_4373_CHIP_ID:
|
|
sc->sc_chip.ch_rambase = 0x160000;
|
|
break;
|
|
case BRCM_CC_4377_CHIP_ID:
|
|
case CY_CC_43752_CHIP_ID:
|
|
sc->sc_chip.ch_rambase = 0x170000;
|
|
break;
|
|
case BRCM_CC_4378_CHIP_ID:
|
|
sc->sc_chip.ch_rambase = 0x352000;
|
|
break;
|
|
case BRCM_CC_4387_CHIP_ID:
|
|
sc->sc_chip.ch_rambase = 0x740000;
|
|
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 ret = 1, reqid;
|
|
|
|
reqid = sc->sc_bcdc_reqid++;
|
|
|
|
if (*len > sizeof(dcmd->buf))
|
|
return ret;
|
|
|
|
dcmd = malloc(size, M_TEMP, M_WAITOK | M_ZERO);
|
|
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 (bwfm_proto_bcdc_txctl(sc, reqid, (char *)dcmd, &size)) {
|
|
DPRINTF(("%s: tx failed\n", DEVNAME(sc)));
|
|
return ret;
|
|
}
|
|
|
|
if (buf) {
|
|
*len = min(*len, size);
|
|
memcpy(buf, dcmd->buf, *len);
|
|
}
|
|
|
|
if (dcmd->hdr.flags & BWFM_BCDC_DCMD_ERROR)
|
|
ret = dcmd->hdr.status;
|
|
else
|
|
ret = 0;
|
|
free(dcmd, M_TEMP, size);
|
|
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++;
|
|
|
|
if (len > sizeof(dcmd->buf))
|
|
return ret;
|
|
|
|
dcmd = malloc(size, M_TEMP, M_WAITOK | M_ZERO);
|
|
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 (bwfm_proto_bcdc_txctl(sc, reqid, (char *)dcmd, &size)) {
|
|
DPRINTF(("%s: txctl failed\n", DEVNAME(sc)));
|
|
return ret;
|
|
}
|
|
|
|
if (dcmd->hdr.flags & BWFM_BCDC_DCMD_ERROR)
|
|
ret = dcmd->hdr.status;
|
|
else
|
|
ret = 0;
|
|
free(dcmd, M_TEMP, size);
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
bwfm_proto_bcdc_txctl(struct bwfm_softc *sc, int reqid, char *buf, size_t *len)
|
|
{
|
|
struct bwfm_proto_bcdc_ctl *ctl, *tmp;
|
|
int timeout = 0;
|
|
|
|
ctl = malloc(sizeof(*ctl), M_TEMP, M_WAITOK|M_ZERO);
|
|
ctl->reqid = reqid;
|
|
ctl->buf = buf;
|
|
ctl->len = *len;
|
|
|
|
if (sc->sc_bus_ops->bs_txctl(sc, ctl)) {
|
|
DPRINTF(("%s: tx failed\n", DEVNAME(sc)));
|
|
return 1;
|
|
}
|
|
|
|
if (tsleep_nsec(ctl, PWAIT, "bwfm", SEC_TO_NSEC(1)))
|
|
timeout = 1;
|
|
|
|
TAILQ_FOREACH_SAFE(ctl, &sc->sc_bcdc_rxctlq, next, tmp) {
|
|
if (ctl->reqid != reqid)
|
|
continue;
|
|
if (ctl->done) {
|
|
TAILQ_REMOVE(&sc->sc_bcdc_rxctlq, ctl, next);
|
|
*len = ctl->len;
|
|
free(ctl, M_TEMP, sizeof(*ctl));
|
|
return 0;
|
|
}
|
|
if (timeout) {
|
|
TAILQ_REMOVE(&sc->sc_bcdc_rxctlq, ctl, next);
|
|
DPRINTF(("%s: timeout waiting for txctl response\n",
|
|
DEVNAME(sc)));
|
|
free(ctl->buf, M_TEMP, ctl->len);
|
|
free(ctl, M_TEMP, sizeof(*ctl));
|
|
return 1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
DPRINTF(("%s: did%s find txctl metadata (timeout %d)\n",
|
|
DEVNAME(sc), ctl == NULL ? " not": "", timeout));
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
bwfm_proto_bcdc_rxctl(struct bwfm_softc *sc, char *buf, size_t len)
|
|
{
|
|
struct bwfm_proto_bcdc_dcmd *dcmd;
|
|
struct bwfm_proto_bcdc_ctl *ctl, *tmp;
|
|
|
|
if (len < sizeof(dcmd->hdr))
|
|
return;
|
|
|
|
dcmd = (struct bwfm_proto_bcdc_dcmd *)buf;
|
|
dcmd->hdr.cmd = letoh32(dcmd->hdr.cmd);
|
|
dcmd->hdr.len = letoh32(dcmd->hdr.len);
|
|
dcmd->hdr.flags = letoh32(dcmd->hdr.flags);
|
|
dcmd->hdr.status = letoh32(dcmd->hdr.status);
|
|
|
|
TAILQ_FOREACH_SAFE(ctl, &sc->sc_bcdc_rxctlq, next, tmp) {
|
|
if (ctl->reqid != BWFM_BCDC_DCMD_ID_GET(dcmd->hdr.flags))
|
|
continue;
|
|
if (ctl->len != len) {
|
|
free(ctl->buf, M_TEMP, ctl->len);
|
|
free(ctl, M_TEMP, sizeof(*ctl));
|
|
return;
|
|
}
|
|
memcpy(ctl->buf, buf, len);
|
|
ctl->done = 1;
|
|
wakeup(ctl);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void
|
|
bwfm_proto_bcdc_rx(struct bwfm_softc *sc, struct mbuf *m, struct mbuf_list *ml)
|
|
{
|
|
struct bwfm_proto_bcdc_hdr *hdr;
|
|
|
|
hdr = mtod(m, struct bwfm_proto_bcdc_hdr *);
|
|
if (m->m_len < sizeof(*hdr)) {
|
|
m_freem(m);
|
|
return;
|
|
}
|
|
if (m->m_len < sizeof(*hdr) + (hdr->data_offset << 2)) {
|
|
m_freem(m);
|
|
return;
|
|
}
|
|
m_adj(m, sizeof(*hdr) + (hdr->data_offset << 2));
|
|
|
|
bwfm_rx(sc, m, ml);
|
|
}
|
|
|
|
/* 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 = letoh32(*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, char *name, void *data, size_t len)
|
|
{
|
|
char *buf;
|
|
int ret;
|
|
|
|
buf = malloc(strlen(name) + 1 + len, M_TEMP, M_WAITOK);
|
|
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);
|
|
free(buf, M_TEMP, strlen(name) + 1 + len);
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
bwfm_fwvar_var_set_data(struct bwfm_softc *sc, char *name, void *data, size_t len)
|
|
{
|
|
char *buf;
|
|
int ret;
|
|
|
|
buf = malloc(strlen(name) + 1 + len, M_TEMP, M_WAITOK);
|
|
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);
|
|
free(buf, M_TEMP, strlen(name) + 1 + len);
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
bwfm_fwvar_var_get_int(struct bwfm_softc *sc, char *name, uint32_t *data)
|
|
{
|
|
int ret;
|
|
ret = bwfm_fwvar_var_get_data(sc, name, data, sizeof(*data));
|
|
*data = letoh32(*data);
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
bwfm_fwvar_var_set_int(struct bwfm_softc *sc, char *name, uint32_t data)
|
|
{
|
|
data = htole32(data);
|
|
return bwfm_fwvar_var_set_data(sc, name, &data, sizeof(data));
|
|
}
|
|
|
|
/* Channel parameters */
|
|
uint32_t
|
|
bwfm_chan2spec(struct bwfm_softc *sc, struct ieee80211_channel *c)
|
|
{
|
|
if (sc->sc_io_type == BWFM_IO_TYPE_D11N)
|
|
return bwfm_chan2spec_d11n(sc, c);
|
|
else
|
|
return bwfm_chan2spec_d11ac(sc, c);
|
|
}
|
|
|
|
uint32_t
|
|
bwfm_chan2spec_d11n(struct bwfm_softc *sc, struct ieee80211_channel *c)
|
|
{
|
|
uint32_t chanspec;
|
|
|
|
chanspec = ieee80211_mhz2ieee(c->ic_freq, 0) & BWFM_CHANSPEC_CHAN_MASK;
|
|
chanspec |= BWFM_CHANSPEC_D11N_SB_N;
|
|
chanspec |= BWFM_CHANSPEC_D11N_BW_20;
|
|
if (IEEE80211_IS_CHAN_2GHZ(c))
|
|
chanspec |= BWFM_CHANSPEC_D11N_BND_2G;
|
|
if (IEEE80211_IS_CHAN_5GHZ(c))
|
|
chanspec |= BWFM_CHANSPEC_D11N_BND_5G;
|
|
|
|
return chanspec;
|
|
}
|
|
|
|
uint32_t
|
|
bwfm_chan2spec_d11ac(struct bwfm_softc *sc, struct ieee80211_channel *c)
|
|
{
|
|
uint32_t chanspec;
|
|
|
|
chanspec = ieee80211_mhz2ieee(c->ic_freq, 0) & BWFM_CHANSPEC_CHAN_MASK;
|
|
chanspec |= BWFM_CHANSPEC_D11AC_SB_LLL;
|
|
chanspec |= BWFM_CHANSPEC_D11AC_BW_20;
|
|
if (IEEE80211_IS_CHAN_2GHZ(c))
|
|
chanspec |= BWFM_CHANSPEC_D11AC_BND_2G;
|
|
if (IEEE80211_IS_CHAN_5GHZ(c))
|
|
chanspec |= BWFM_CHANSPEC_D11AC_BND_5G;
|
|
|
|
return chanspec;
|
|
}
|
|
|
|
uint32_t
|
|
bwfm_spec2chan(struct bwfm_softc *sc, uint32_t chanspec)
|
|
{
|
|
if (sc->sc_io_type == BWFM_IO_TYPE_D11N)
|
|
return bwfm_spec2chan_d11n(sc, chanspec);
|
|
else
|
|
return bwfm_spec2chan_d11ac(sc, chanspec);
|
|
}
|
|
|
|
uint32_t
|
|
bwfm_spec2chan_d11n(struct bwfm_softc *sc, uint32_t chanspec)
|
|
{
|
|
uint32_t chanidx;
|
|
|
|
chanidx = chanspec & BWFM_CHANSPEC_CHAN_MASK;
|
|
|
|
switch (chanspec & BWFM_CHANSPEC_D11N_BW_MASK) {
|
|
case BWFM_CHANSPEC_D11N_BW_40:
|
|
switch (chanspec & BWFM_CHANSPEC_D11N_SB_MASK) {
|
|
case BWFM_CHANSPEC_D11N_SB_L:
|
|
chanidx -= 2;
|
|
break;
|
|
case BWFM_CHANSPEC_D11N_SB_U:
|
|
chanidx += 2;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return chanidx;
|
|
}
|
|
|
|
uint32_t
|
|
bwfm_spec2chan_d11ac(struct bwfm_softc *sc, uint32_t chanspec)
|
|
{
|
|
uint32_t chanidx;
|
|
|
|
chanidx = chanspec & BWFM_CHANSPEC_CHAN_MASK;
|
|
|
|
switch (chanspec & BWFM_CHANSPEC_D11AC_BW_MASK) {
|
|
case BWFM_CHANSPEC_D11AC_BW_40:
|
|
switch (chanspec & BWFM_CHANSPEC_D11AC_SB_MASK) {
|
|
case BWFM_CHANSPEC_D11AC_SB_LLL:
|
|
chanidx -= 2;
|
|
break;
|
|
case BWFM_CHANSPEC_D11AC_SB_LLU:
|
|
chanidx += 2;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case BWFM_CHANSPEC_D11AC_BW_80:
|
|
switch (chanspec & BWFM_CHANSPEC_D11AC_SB_MASK) {
|
|
case BWFM_CHANSPEC_D11AC_SB_LLL:
|
|
chanidx -= 6;
|
|
break;
|
|
case BWFM_CHANSPEC_D11AC_SB_LLU:
|
|
chanidx -= 2;
|
|
break;
|
|
case BWFM_CHANSPEC_D11AC_SB_LUL:
|
|
chanidx += 2;
|
|
break;
|
|
case BWFM_CHANSPEC_D11AC_SB_LUU:
|
|
chanidx += 6;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return chanidx;
|
|
}
|
|
|
|
/* 802.11 code */
|
|
void
|
|
bwfm_connect(struct bwfm_softc *sc)
|
|
{
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct bwfm_ext_join_params *params;
|
|
uint8_t buf[64]; /* XXX max WPA/RSN/WMM IE length */
|
|
uint8_t *frm;
|
|
|
|
/*
|
|
* OPEN: Open or WEP or WPA/WPA2 on newer Chips/Firmware.
|
|
* AUTO: Automatic, probably for older Chips/Firmware.
|
|
*/
|
|
if (ic->ic_flags & IEEE80211_F_RSNON) {
|
|
uint32_t wsec = 0;
|
|
uint32_t wpa = 0;
|
|
|
|
/* tell firmware to add WPA/RSN IE to (re)assoc request */
|
|
if (ic->ic_bss->ni_rsnprotos == IEEE80211_PROTO_RSN)
|
|
frm = ieee80211_add_rsn(buf, ic, ic->ic_bss);
|
|
else
|
|
frm = ieee80211_add_wpa(buf, ic, ic->ic_bss);
|
|
bwfm_fwvar_var_set_data(sc, "wpaie", buf, frm - buf);
|
|
|
|
if (ic->ic_rsnprotos & IEEE80211_PROTO_WPA) {
|
|
if (ic->ic_rsnakms & IEEE80211_AKM_PSK)
|
|
wpa |= BWFM_WPA_AUTH_WPA_PSK;
|
|
if (ic->ic_rsnakms & IEEE80211_AKM_8021X)
|
|
wpa |= BWFM_WPA_AUTH_WPA_UNSPECIFIED;
|
|
}
|
|
if (ic->ic_rsnprotos & IEEE80211_PROTO_RSN) {
|
|
if (ic->ic_rsnakms & IEEE80211_AKM_PSK)
|
|
wpa |= BWFM_WPA_AUTH_WPA2_PSK;
|
|
if (ic->ic_rsnakms & IEEE80211_AKM_SHA256_PSK)
|
|
wpa |= BWFM_WPA_AUTH_WPA2_PSK_SHA256;
|
|
if (ic->ic_rsnakms & IEEE80211_AKM_8021X)
|
|
wpa |= BWFM_WPA_AUTH_WPA2_UNSPECIFIED;
|
|
if (ic->ic_rsnakms & IEEE80211_AKM_SHA256_8021X)
|
|
wpa |= BWFM_WPA_AUTH_WPA2_1X_SHA256;
|
|
}
|
|
if (ic->ic_rsnciphers & IEEE80211_WPA_CIPHER_TKIP ||
|
|
ic->ic_rsngroupcipher & IEEE80211_WPA_CIPHER_TKIP)
|
|
wsec |= BWFM_WSEC_TKIP;
|
|
if (ic->ic_rsnciphers & IEEE80211_WPA_CIPHER_CCMP ||
|
|
ic->ic_rsngroupcipher & IEEE80211_WPA_CIPHER_CCMP)
|
|
wsec |= BWFM_WSEC_AES;
|
|
|
|
bwfm_fwvar_var_set_int(sc, "wpa_auth", wpa);
|
|
bwfm_fwvar_var_set_int(sc, "wsec", wsec);
|
|
} else if (ic->ic_flags & IEEE80211_F_WEPON) {
|
|
bwfm_fwvar_var_set_int(sc, "wpa_auth", BWFM_WPA_AUTH_DISABLED);
|
|
bwfm_fwvar_var_set_int(sc, "wsec", BWFM_WSEC_WEP);
|
|
} 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 (ic->ic_des_esslen && ic->ic_des_esslen <= BWFM_MAX_SSID_LEN) {
|
|
params = malloc(sizeof(*params), M_TEMP, M_WAITOK | M_ZERO);
|
|
memcpy(params->ssid.ssid, ic->ic_des_essid, ic->ic_des_esslen);
|
|
params->ssid.len = htole32(ic->ic_des_esslen);
|
|
memcpy(params->assoc.bssid, ic->ic_bss->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, ic->ic_des_essid,
|
|
ic->ic_des_esslen);
|
|
join.ssid.len = htole32(ic->ic_des_esslen);
|
|
memcpy(join.assoc.bssid, ic->ic_bss->ni_bssid,
|
|
sizeof(join.assoc.bssid));
|
|
bwfm_fwvar_cmd_set_data(sc, BWFM_C_SET_SSID, &join,
|
|
sizeof(join));
|
|
}
|
|
free(params, M_TEMP, sizeof(*params));
|
|
}
|
|
}
|
|
|
|
#ifndef IEEE80211_STA_ONLY
|
|
void
|
|
bwfm_hostap(struct bwfm_softc *sc)
|
|
{
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct ieee80211_node *ni = ic->ic_bss;
|
|
struct bwfm_join_params join;
|
|
|
|
/*
|
|
* OPEN: Open or WEP or WPA/WPA2 on newer Chips/Firmware.
|
|
* AUTO: Automatic, probably for older Chips/Firmware.
|
|
*/
|
|
if (ic->ic_flags & IEEE80211_F_RSNON) {
|
|
uint32_t wsec = 0;
|
|
uint32_t wpa = 0;
|
|
|
|
/* TODO: Turn off if replay counter set */
|
|
if (ni->ni_rsnprotos & IEEE80211_PROTO_RSN)
|
|
bwfm_fwvar_var_set_int(sc, "wme_bss_disable", 1);
|
|
|
|
if (ni->ni_rsnprotos & IEEE80211_PROTO_WPA) {
|
|
if (ni->ni_rsnakms & IEEE80211_AKM_PSK)
|
|
wpa |= BWFM_WPA_AUTH_WPA_PSK;
|
|
if (ni->ni_rsnakms & IEEE80211_AKM_8021X)
|
|
wpa |= BWFM_WPA_AUTH_WPA_UNSPECIFIED;
|
|
}
|
|
if (ni->ni_rsnprotos & IEEE80211_PROTO_RSN) {
|
|
if (ni->ni_rsnakms & IEEE80211_AKM_PSK)
|
|
wpa |= BWFM_WPA_AUTH_WPA2_PSK;
|
|
if (ni->ni_rsnakms & IEEE80211_AKM_SHA256_PSK)
|
|
wpa |= BWFM_WPA_AUTH_WPA2_PSK_SHA256;
|
|
if (ni->ni_rsnakms & IEEE80211_AKM_8021X)
|
|
wpa |= BWFM_WPA_AUTH_WPA2_UNSPECIFIED;
|
|
if (ni->ni_rsnakms & IEEE80211_AKM_SHA256_8021X)
|
|
wpa |= BWFM_WPA_AUTH_WPA2_1X_SHA256;
|
|
}
|
|
if (ni->ni_rsnciphers & IEEE80211_WPA_CIPHER_TKIP ||
|
|
ni->ni_rsngroupcipher & IEEE80211_WPA_CIPHER_TKIP)
|
|
wsec |= BWFM_WSEC_TKIP;
|
|
if (ni->ni_rsnciphers & IEEE80211_WPA_CIPHER_CCMP ||
|
|
ni->ni_rsngroupcipher & IEEE80211_WPA_CIPHER_CCMP)
|
|
wsec |= BWFM_WSEC_AES;
|
|
|
|
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);
|
|
|
|
bwfm_fwvar_var_set_int(sc, "mpc", 0);
|
|
bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_INFRA, 1);
|
|
bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_AP, 1);
|
|
bwfm_fwvar_var_set_int(sc, "chanspec",
|
|
bwfm_chan2spec(sc, ic->ic_bss->ni_chan));
|
|
bwfm_fwvar_cmd_set_int(sc, BWFM_C_UP, 1);
|
|
|
|
memset(&join, 0, sizeof(join));
|
|
memcpy(join.ssid.ssid, ic->ic_des_essid, ic->ic_des_esslen);
|
|
join.ssid.len = htole32(ic->ic_des_esslen);
|
|
memset(join.assoc.bssid, 0xff, sizeof(join.assoc.bssid));
|
|
bwfm_fwvar_cmd_set_data(sc, BWFM_C_SET_SSID, &join, sizeof(join));
|
|
bwfm_fwvar_var_set_int(sc, "closednet",
|
|
(ic->ic_userflags & IEEE80211_F_HIDENWID) != 0);
|
|
}
|
|
#endif
|
|
|
|
void
|
|
bwfm_scan_v0(struct bwfm_softc *sc)
|
|
{
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct bwfm_escan_params_v0 *params;
|
|
uint32_t nssid = 0, nchan = 0;
|
|
size_t params_size, chan_size, ssid_size;
|
|
struct bwfm_ssid *ssid;
|
|
|
|
if (ic->ic_flags & IEEE80211_F_ASCAN &&
|
|
ic->ic_des_esslen && ic->ic_des_esslen <= BWFM_MAX_SSID_LEN)
|
|
nssid = 1;
|
|
|
|
chan_size = roundup(nchan * sizeof(uint16_t), sizeof(uint32_t));
|
|
ssid_size = sizeof(struct bwfm_ssid) * nssid;
|
|
params_size = sizeof(*params) + chan_size + ssid_size;
|
|
|
|
params = malloc(params_size, M_TEMP, M_WAITOK | M_ZERO);
|
|
ssid = (struct bwfm_ssid *)
|
|
(((uint8_t *)params) + sizeof(*params) + chan_size);
|
|
|
|
memset(params->scan_params.bssid, 0xff,
|
|
sizeof(params->scan_params.bssid));
|
|
params->scan_params.bss_type = 2;
|
|
params->scan_params.scan_type = BWFM_SCANTYPE_PASSIVE;
|
|
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 (ic->ic_flags & IEEE80211_F_ASCAN &&
|
|
ic->ic_des_esslen && ic->ic_des_esslen <= BWFM_MAX_SSID_LEN) {
|
|
params->scan_params.scan_type = BWFM_SCANTYPE_ACTIVE;
|
|
ssid->len = htole32(ic->ic_des_esslen);
|
|
memcpy(ssid->ssid, ic->ic_des_essid, ic->ic_des_esslen);
|
|
}
|
|
|
|
params->scan_params.channel_num = htole32(
|
|
nssid << BWFM_CHANNUM_NSSID_SHIFT |
|
|
nchan << BWFM_CHANNUM_NCHAN_SHIFT);
|
|
|
|
#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
|
|
);
|
|
#endif
|
|
|
|
bwfm_fwvar_var_set_data(sc, "escan", params, params_size);
|
|
free(params, M_TEMP, params_size);
|
|
}
|
|
|
|
void
|
|
bwfm_scan_v2(struct bwfm_softc *sc)
|
|
{
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct bwfm_escan_params_v2 *params;
|
|
uint32_t nssid = 0, nchan = 0;
|
|
size_t params_size, chan_size, ssid_size;
|
|
struct bwfm_ssid *ssid;
|
|
|
|
if (ic->ic_flags & IEEE80211_F_ASCAN &&
|
|
ic->ic_des_esslen && ic->ic_des_esslen <= BWFM_MAX_SSID_LEN)
|
|
nssid = 1;
|
|
|
|
chan_size = roundup(nchan * sizeof(uint16_t), sizeof(uint32_t));
|
|
ssid_size = sizeof(struct bwfm_ssid) * nssid;
|
|
params_size = sizeof(*params) + chan_size + ssid_size;
|
|
|
|
params = malloc(params_size, M_TEMP, M_WAITOK | M_ZERO);
|
|
ssid = (struct bwfm_ssid *)
|
|
(((uint8_t *)params) + sizeof(*params) + chan_size);
|
|
|
|
params->scan_params.version = 2;
|
|
params->scan_params.length = params_size;
|
|
memset(params->scan_params.bssid, 0xff,
|
|
sizeof(params->scan_params.bssid));
|
|
params->scan_params.bss_type = 2;
|
|
params->scan_params.scan_type = BWFM_SCANTYPE_PASSIVE;
|
|
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_V2);
|
|
params->action = htole16(WL_ESCAN_ACTION_START);
|
|
params->sync_id = htole16(0x1234);
|
|
|
|
if (ic->ic_flags & IEEE80211_F_ASCAN &&
|
|
ic->ic_des_esslen && ic->ic_des_esslen <= BWFM_MAX_SSID_LEN) {
|
|
params->scan_params.scan_type = BWFM_SCANTYPE_ACTIVE;
|
|
ssid->len = htole32(ic->ic_des_esslen);
|
|
memcpy(ssid->ssid, ic->ic_des_essid, ic->ic_des_esslen);
|
|
}
|
|
|
|
params->scan_params.channel_num = htole32(
|
|
nssid << BWFM_CHANNUM_NSSID_SHIFT |
|
|
nchan << BWFM_CHANNUM_NCHAN_SHIFT);
|
|
|
|
#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
|
|
);
|
|
#endif
|
|
|
|
bwfm_fwvar_var_set_data(sc, "escan", params, params_size);
|
|
free(params, M_TEMP, params_size);
|
|
}
|
|
|
|
void
|
|
bwfm_scan(struct bwfm_softc *sc)
|
|
{
|
|
if (sc->sc_scan_ver == 0)
|
|
bwfm_scan_v0(sc);
|
|
else
|
|
bwfm_scan_v2(sc);
|
|
}
|
|
|
|
void
|
|
bwfm_scan_abort_v0(struct bwfm_softc *sc)
|
|
{
|
|
struct bwfm_escan_params_v0 *params;
|
|
size_t params_size;
|
|
|
|
params_size = sizeof(*params) + sizeof(uint16_t);
|
|
params = malloc(params_size, M_TEMP, M_WAITOK | M_ZERO);
|
|
memset(params->scan_params.bssid, 0xff,
|
|
sizeof(params->scan_params.bssid));
|
|
params->scan_params.bss_type = 2;
|
|
params->scan_params.scan_type = BWFM_SCANTYPE_PASSIVE;
|
|
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);
|
|
params->scan_params.channel_num = htole32(1);
|
|
params->scan_params.channel_list[0] = htole16(-1);
|
|
bwfm_fwvar_var_set_data(sc, "escan", params, params_size);
|
|
free(params, M_TEMP, params_size);
|
|
}
|
|
|
|
void
|
|
bwfm_scan_abort_v2(struct bwfm_softc *sc)
|
|
{
|
|
struct bwfm_escan_params_v2 *params;
|
|
size_t params_size;
|
|
|
|
params_size = sizeof(*params) + sizeof(uint16_t);
|
|
params = malloc(params_size, M_TEMP, M_WAITOK | M_ZERO);
|
|
params->scan_params.version = 2;
|
|
params->scan_params.length = params_size;
|
|
memset(params->scan_params.bssid, 0xff,
|
|
sizeof(params->scan_params.bssid));
|
|
params->scan_params.bss_type = 2;
|
|
params->scan_params.scan_type = BWFM_SCANTYPE_PASSIVE;
|
|
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_V2);
|
|
params->action = htole16(WL_ESCAN_ACTION_START);
|
|
params->sync_id = htole16(0x1234);
|
|
params->scan_params.channel_num = htole32(1);
|
|
params->scan_params.channel_list[0] = htole16(-1);
|
|
bwfm_fwvar_var_set_data(sc, "escan", params, params_size);
|
|
free(params, M_TEMP, params_size);
|
|
}
|
|
|
|
void
|
|
bwfm_scan_abort(struct bwfm_softc *sc)
|
|
{
|
|
if (sc->sc_scan_ver == 0)
|
|
bwfm_scan_abort_v0(sc);
|
|
else
|
|
bwfm_scan_abort_v2(sc);
|
|
}
|
|
|
|
struct mbuf *
|
|
bwfm_newbuf(void)
|
|
{
|
|
struct mbuf *m;
|
|
|
|
MGETHDR(m, M_DONTWAIT, MT_DATA);
|
|
if (m == NULL)
|
|
return (NULL);
|
|
|
|
MCLGET(m, M_DONTWAIT);
|
|
if (!(m->m_flags & M_EXT)) {
|
|
m_freem(m);
|
|
return (NULL);
|
|
}
|
|
|
|
m->m_len = m->m_pkthdr.len = MCLBYTES;
|
|
|
|
return (m);
|
|
}
|
|
|
|
void
|
|
bwfm_rx(struct bwfm_softc *sc, struct mbuf *m, struct mbuf_list *ml)
|
|
{
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct ifnet *ifp = &ic->ic_if;
|
|
struct ieee80211_node *ni;
|
|
struct bwfm_event *e;
|
|
|
|
#ifdef __STRICT_ALIGNMENT
|
|
/* Remaining data is an ethernet packet, so align. */
|
|
if ((mtod(m, paddr_t) & 0x3) != ETHER_ALIGN) {
|
|
struct mbuf *m0;
|
|
m0 = m_dup_pkt(m, ETHER_ALIGN, M_WAITOK);
|
|
m_freem(m);
|
|
if (m0 == NULL) {
|
|
ifp->if_ierrors++;
|
|
return;
|
|
}
|
|
m = m0;
|
|
}
|
|
#endif
|
|
|
|
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);
|
|
return;
|
|
}
|
|
|
|
/* Drop network packets if we are not in RUN state. */
|
|
if (ic->ic_state != IEEE80211_S_RUN) {
|
|
m_freem(m);
|
|
return;
|
|
}
|
|
|
|
if ((ic->ic_flags & IEEE80211_F_RSNON) &&
|
|
m->m_len >= sizeof(e->ehdr) &&
|
|
ntohs(e->ehdr.ether_type) == ETHERTYPE_EAPOL) {
|
|
ifp->if_ipackets++;
|
|
#if NBPFILTER > 0
|
|
if (ifp->if_bpf)
|
|
bpf_mtap(ifp->if_bpf, m, BPF_DIRECTION_IN);
|
|
#endif
|
|
#ifndef IEEE80211_STA_ONLY
|
|
if (ic->ic_opmode == IEEE80211_M_HOSTAP) {
|
|
ni = ieee80211_find_node(ic,
|
|
(void *)&e->ehdr.ether_shost);
|
|
if (ni == NULL) {
|
|
m_freem(m);
|
|
return;
|
|
}
|
|
} else
|
|
#endif
|
|
ni = ic->ic_bss;
|
|
ieee80211_eapol_key_input(ic, m, ni);
|
|
} else
|
|
ml_enqueue(ml, m);
|
|
}
|
|
|
|
#ifndef IEEE80211_STA_ONLY
|
|
void
|
|
bwfm_rx_auth_ind(struct bwfm_softc *sc, struct bwfm_event *e, size_t len)
|
|
{
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct ifnet *ifp = &ic->ic_if;
|
|
struct ieee80211_rxinfo rxi;
|
|
struct ieee80211_frame *wh;
|
|
struct mbuf *m;
|
|
uint32_t pktlen, ieslen;
|
|
|
|
/* Build a fake beacon frame to let net80211 do all the parsing. */
|
|
ieslen = betoh32(e->msg.datalen);
|
|
pktlen = sizeof(*wh) + ieslen + 6;
|
|
if (pktlen > MCLBYTES)
|
|
return;
|
|
m = bwfm_newbuf();
|
|
if (m == NULL)
|
|
return;
|
|
wh = mtod(m, struct ieee80211_frame *);
|
|
wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_MGT |
|
|
IEEE80211_FC0_SUBTYPE_AUTH;
|
|
wh->i_fc[1] = IEEE80211_FC1_DIR_NODS;
|
|
*(uint16_t *)wh->i_dur = 0;
|
|
IEEE80211_ADDR_COPY(wh->i_addr1, etherbroadcastaddr);
|
|
IEEE80211_ADDR_COPY(wh->i_addr2, &e->msg.addr);
|
|
IEEE80211_ADDR_COPY(wh->i_addr3, ic->ic_bss->ni_bssid);
|
|
*(uint16_t *)wh->i_seq = 0;
|
|
((uint16_t *)(&wh[1]))[0] = IEEE80211_AUTH_ALG_OPEN;
|
|
((uint16_t *)(&wh[1]))[1] = IEEE80211_AUTH_OPEN_REQUEST;
|
|
((uint16_t *)(&wh[1]))[2] = 0;
|
|
|
|
/* Finalize mbuf. */
|
|
m->m_pkthdr.len = m->m_len = pktlen;
|
|
memset(&rxi, 0, sizeof(rxi));
|
|
ieee80211_input(ifp, m, ic->ic_bss, &rxi);
|
|
}
|
|
|
|
void
|
|
bwfm_rx_assoc_ind(struct bwfm_softc *sc, struct bwfm_event *e, size_t len,
|
|
int reassoc)
|
|
{
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct ifnet *ifp = &ic->ic_if;
|
|
struct ieee80211_rxinfo rxi;
|
|
struct ieee80211_frame *wh;
|
|
struct ieee80211_node *ni;
|
|
struct mbuf *m;
|
|
uint32_t pktlen, ieslen;
|
|
|
|
/* Build a fake beacon frame to let net80211 do all the parsing. */
|
|
ieslen = betoh32(e->msg.datalen);
|
|
pktlen = sizeof(*wh) + ieslen + 4;
|
|
if (reassoc)
|
|
pktlen += IEEE80211_ADDR_LEN;
|
|
if (pktlen > MCLBYTES)
|
|
return;
|
|
m = bwfm_newbuf();
|
|
if (m == NULL)
|
|
return;
|
|
wh = mtod(m, struct ieee80211_frame *);
|
|
wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_MGT;
|
|
if (reassoc)
|
|
wh->i_fc[0] |= IEEE80211_FC0_SUBTYPE_REASSOC_REQ;
|
|
else
|
|
wh->i_fc[0] |= IEEE80211_FC0_SUBTYPE_ASSOC_REQ;
|
|
wh->i_fc[1] = IEEE80211_FC1_DIR_NODS;
|
|
*(uint16_t *)wh->i_dur = 0;
|
|
IEEE80211_ADDR_COPY(wh->i_addr1, etherbroadcastaddr);
|
|
IEEE80211_ADDR_COPY(wh->i_addr2, &e->msg.addr);
|
|
IEEE80211_ADDR_COPY(wh->i_addr3, ic->ic_bss->ni_bssid);
|
|
*(uint16_t *)wh->i_seq = 0;
|
|
((uint16_t *)(&wh[1]))[0] = IEEE80211_CAPINFO_ESS; /* XXX */
|
|
((uint16_t *)(&wh[1]))[1] = 100; /* XXX */
|
|
if (reassoc) {
|
|
memset(((uint8_t *)&wh[1]) + 4, 0, IEEE80211_ADDR_LEN);
|
|
memcpy(((uint8_t *)&wh[1]) + 4 + IEEE80211_ADDR_LEN,
|
|
&e[1], ieslen);
|
|
} else
|
|
memcpy(((uint8_t *)&wh[1]) + 4, &e[1], ieslen);
|
|
|
|
/* Finalize mbuf. */
|
|
m->m_pkthdr.len = m->m_len = pktlen;
|
|
ni = ieee80211_find_node(ic, wh->i_addr2);
|
|
if (ni == NULL) {
|
|
m_freem(m);
|
|
return;
|
|
}
|
|
memset(&rxi, 0, sizeof(rxi));
|
|
ieee80211_input(ifp, m, ni, &rxi);
|
|
}
|
|
|
|
void
|
|
bwfm_rx_deauth_ind(struct bwfm_softc *sc, struct bwfm_event *e, size_t len)
|
|
{
|
|
bwfm_rx_leave_ind(sc, e, len, IEEE80211_FC0_SUBTYPE_DEAUTH);
|
|
}
|
|
|
|
void
|
|
bwfm_rx_disassoc_ind(struct bwfm_softc *sc, struct bwfm_event *e, size_t len)
|
|
{
|
|
bwfm_rx_leave_ind(sc, e, len, IEEE80211_FC0_SUBTYPE_DISASSOC);
|
|
}
|
|
|
|
void
|
|
bwfm_rx_leave_ind(struct bwfm_softc *sc, struct bwfm_event *e, size_t len,
|
|
int subtype)
|
|
{
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct ifnet *ifp = &ic->ic_if;
|
|
struct ieee80211_rxinfo rxi;
|
|
struct ieee80211_frame *wh;
|
|
struct ieee80211_node *ni;
|
|
struct mbuf *m;
|
|
uint32_t pktlen;
|
|
|
|
/* Build a fake beacon frame to let net80211 do all the parsing. */
|
|
pktlen = sizeof(*wh) + 2;
|
|
if (pktlen > MCLBYTES)
|
|
return;
|
|
m = bwfm_newbuf();
|
|
if (m == NULL)
|
|
return;
|
|
wh = mtod(m, struct ieee80211_frame *);
|
|
wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_MGT |
|
|
subtype;
|
|
wh->i_fc[1] = IEEE80211_FC1_DIR_NODS;
|
|
*(uint16_t *)wh->i_dur = 0;
|
|
IEEE80211_ADDR_COPY(wh->i_addr1, etherbroadcastaddr);
|
|
IEEE80211_ADDR_COPY(wh->i_addr2, &e->msg.addr);
|
|
IEEE80211_ADDR_COPY(wh->i_addr3, ic->ic_bss->ni_bssid);
|
|
*(uint16_t *)wh->i_seq = 0;
|
|
memset((uint8_t *)&wh[1], 0, 2);
|
|
|
|
/* Finalize mbuf. */
|
|
m->m_pkthdr.len = m->m_len = pktlen;
|
|
ni = ieee80211_find_node(ic, wh->i_addr2);
|
|
if (ni == NULL) {
|
|
m_freem(m);
|
|
return;
|
|
}
|
|
memset(&rxi, 0, sizeof(rxi));
|
|
ieee80211_input(ifp, m, ni, &rxi);
|
|
}
|
|
#endif
|
|
|
|
void
|
|
bwfm_rx_event(struct bwfm_softc *sc, struct mbuf *m)
|
|
{
|
|
int s;
|
|
|
|
s = splnet();
|
|
ml_enqueue(&sc->sc_evml, m);
|
|
splx(s);
|
|
|
|
task_add(sc->sc_taskq, &sc->sc_task);
|
|
}
|
|
|
|
void
|
|
bwfm_rx_event_cb(struct bwfm_softc *sc, struct mbuf *m)
|
|
{
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct ifnet *ifp = &ic->ic_if;
|
|
struct bwfm_event *e = mtod(m, void *);
|
|
size_t len = m->m_len;
|
|
|
|
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;
|
|
struct bwfm_bss_info *bss;
|
|
size_t reslen;
|
|
int i;
|
|
/* Abort event triggered by SCAN -> INIT */
|
|
if (ic->ic_state == IEEE80211_S_INIT &&
|
|
ntohl(e->msg.status) == BWFM_E_STATUS_ABORT)
|
|
break;
|
|
if (ic->ic_state != IEEE80211_S_SCAN) {
|
|
DPRINTF(("%s: scan result (%u) while not in SCAN\n",
|
|
DEVNAME(sc), ntohl(e->msg.status)));
|
|
break;
|
|
}
|
|
if (ntohl(e->msg.status) != BWFM_E_STATUS_SUCCESS &&
|
|
ntohl(e->msg.status) != BWFM_E_STATUS_PARTIAL) {
|
|
DPRINTF(("%s: unexpected scan result (%u)\n",
|
|
DEVNAME(sc), ntohl(e->msg.status)));
|
|
break;
|
|
}
|
|
if (ntohl(e->msg.status) == BWFM_E_STATUS_SUCCESS) {
|
|
ieee80211_end_scan(ifp);
|
|
break;
|
|
}
|
|
len -= sizeof(*e);
|
|
if (len < sizeof(*res)) {
|
|
DPRINTF(("%s: results too small\n", DEVNAME(sc)));
|
|
m_freem(m);
|
|
return;
|
|
}
|
|
reslen = len;
|
|
res = malloc(len, M_TEMP, M_WAITOK);
|
|
memcpy(res, (void *)&e[1], len);
|
|
if (len < letoh32(res->buflen)) {
|
|
DPRINTF(("%s: results too small\n", DEVNAME(sc)));
|
|
free(res, M_TEMP, reslen);
|
|
m_freem(m);
|
|
return;
|
|
}
|
|
len -= sizeof(*res);
|
|
if (len < letoh16(res->bss_count) * sizeof(struct bwfm_bss_info)) {
|
|
DPRINTF(("%s: results too small\n", DEVNAME(sc)));
|
|
free(res, M_TEMP, reslen);
|
|
m_freem(m);
|
|
return;
|
|
}
|
|
bss = &res->bss_info[0];
|
|
for (i = 0; i < letoh16(res->bss_count); i++) {
|
|
bwfm_scan_node(sc, &res->bss_info[i], len);
|
|
len -= sizeof(*bss) + letoh32(bss->length);
|
|
bss = (void *)((char *)bss) + letoh32(bss->length);
|
|
if (len <= 0)
|
|
break;
|
|
}
|
|
free(res, M_TEMP, reslen);
|
|
break;
|
|
}
|
|
case BWFM_E_AUTH:
|
|
if (ntohl(e->msg.status) == BWFM_E_STATUS_SUCCESS &&
|
|
ic->ic_state == IEEE80211_S_AUTH)
|
|
ieee80211_new_state(ic, IEEE80211_S_ASSOC, -1);
|
|
else
|
|
ieee80211_begin_scan(ifp);
|
|
break;
|
|
case BWFM_E_ASSOC:
|
|
if (ntohl(e->msg.status) == BWFM_E_STATUS_SUCCESS &&
|
|
ic->ic_state == IEEE80211_S_ASSOC)
|
|
ieee80211_new_state(ic, IEEE80211_S_RUN, -1);
|
|
else if (ntohl(e->msg.status) != BWFM_E_STATUS_UNSOLICITED)
|
|
ieee80211_begin_scan(ifp);
|
|
break;
|
|
case BWFM_E_DEAUTH:
|
|
case BWFM_E_DISASSOC:
|
|
if (ic->ic_state > IEEE80211_S_SCAN)
|
|
ieee80211_begin_scan(ifp);
|
|
break;
|
|
case BWFM_E_LINK:
|
|
if (ntohl(e->msg.status) == BWFM_E_STATUS_SUCCESS &&
|
|
ntohl(e->msg.reason) == 0)
|
|
break;
|
|
/* Link status has changed */
|
|
if (ic->ic_state > IEEE80211_S_SCAN)
|
|
ieee80211_begin_scan(ifp);
|
|
break;
|
|
#ifndef IEEE80211_STA_ONLY
|
|
case BWFM_E_AUTH_IND:
|
|
bwfm_rx_auth_ind(sc, e, len);
|
|
break;
|
|
case BWFM_E_ASSOC_IND:
|
|
bwfm_rx_assoc_ind(sc, e, len, 0);
|
|
break;
|
|
case BWFM_E_REASSOC_IND:
|
|
bwfm_rx_assoc_ind(sc, e, len, 1);
|
|
break;
|
|
case BWFM_E_DEAUTH_IND:
|
|
bwfm_rx_deauth_ind(sc, e, len);
|
|
break;
|
|
case BWFM_E_DISASSOC_IND:
|
|
bwfm_rx_disassoc_ind(sc, e, len);
|
|
break;
|
|
#endif
|
|
default:
|
|
DPRINTF(("%s: len %lu datalen %u code %u status %u"
|
|
" reason %u\n", __func__, len, ntohl(e->msg.datalen),
|
|
ntohl(e->msg.event_type), ntohl(e->msg.status),
|
|
ntohl(e->msg.reason)));
|
|
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 ifnet *ifp = &ic->ic_if;
|
|
struct ieee80211_frame *wh;
|
|
struct ieee80211_node *ni;
|
|
struct ieee80211_rxinfo rxi;
|
|
struct mbuf *m;
|
|
uint32_t pktlen, ieslen;
|
|
uint16_t iesoff;
|
|
int chanidx;
|
|
|
|
iesoff = letoh16(bss->ie_offset);
|
|
ieslen = letoh32(bss->ie_length);
|
|
if (ieslen > len - iesoff)
|
|
return;
|
|
|
|
/* Build a fake beacon frame to let net80211 do all the parsing. */
|
|
pktlen = sizeof(*wh) + ieslen + 12;
|
|
if (pktlen > MCLBYTES)
|
|
return;
|
|
m = bwfm_newbuf();
|
|
if (m == NULL)
|
|
return;
|
|
wh = mtod(m, struct ieee80211_frame *);
|
|
wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_MGT |
|
|
IEEE80211_FC0_SUBTYPE_BEACON;
|
|
wh->i_fc[1] = IEEE80211_FC1_DIR_NODS;
|
|
*(uint16_t *)wh->i_dur = 0;
|
|
IEEE80211_ADDR_COPY(wh->i_addr1, etherbroadcastaddr);
|
|
IEEE80211_ADDR_COPY(wh->i_addr2, bss->bssid);
|
|
IEEE80211_ADDR_COPY(wh->i_addr3, bss->bssid);
|
|
*(uint16_t *)wh->i_seq = 0;
|
|
memset(&wh[1], 0, 12);
|
|
((uint16_t *)(&wh[1]))[4] = bss->beacon_period;
|
|
((uint16_t *)(&wh[1]))[5] = bss->capability;
|
|
memcpy(((uint8_t *)&wh[1]) + 12, ((uint8_t *)bss) + iesoff, ieslen);
|
|
|
|
/* Finalize mbuf. */
|
|
m->m_pkthdr.len = m->m_len = pktlen;
|
|
ni = ieee80211_find_rxnode(ic, wh);
|
|
/* Channel mask equals IEEE80211_CHAN_MAX */
|
|
chanidx = bwfm_spec2chan(sc, letoh32(bss->chanspec));
|
|
/* Supply RSSI */
|
|
memset(&rxi, 0, sizeof(rxi));
|
|
rxi.rxi_rssi = (int16_t)letoh16(bss->rssi);
|
|
rxi.rxi_chan = chanidx;
|
|
ieee80211_input(ifp, m, ni, &rxi);
|
|
/* Node is no longer needed. */
|
|
ieee80211_release_node(ic, ni);
|
|
}
|
|
|
|
void
|
|
bwfm_task(void *arg)
|
|
{
|
|
struct bwfm_softc *sc = arg;
|
|
struct bwfm_host_cmd_ring *ring = &sc->sc_cmdq;
|
|
struct bwfm_host_cmd *cmd;
|
|
struct mbuf *m;
|
|
int s;
|
|
|
|
s = splnet();
|
|
while (ring->next != ring->cur) {
|
|
cmd = &ring->cmd[ring->next];
|
|
splx(s);
|
|
cmd->cb(sc, cmd->data);
|
|
s = splnet();
|
|
ring->queued--;
|
|
ring->next = (ring->next + 1) % BWFM_HOST_CMD_RING_COUNT;
|
|
}
|
|
splx(s);
|
|
|
|
s = splnet();
|
|
while ((m = ml_dequeue(&sc->sc_evml)) != NULL) {
|
|
splx(s);
|
|
bwfm_rx_event_cb(sc, m);
|
|
s = splnet();
|
|
}
|
|
splx(s);
|
|
}
|
|
|
|
void
|
|
bwfm_do_async(struct bwfm_softc *sc,
|
|
void (*cb)(struct bwfm_softc *, void *), void *arg, int len)
|
|
{
|
|
struct bwfm_host_cmd_ring *ring = &sc->sc_cmdq;
|
|
struct bwfm_host_cmd *cmd;
|
|
int s;
|
|
|
|
s = splnet();
|
|
KASSERT(ring->queued < BWFM_HOST_CMD_RING_COUNT);
|
|
if (ring->queued >= BWFM_HOST_CMD_RING_COUNT) {
|
|
splx(s);
|
|
return;
|
|
}
|
|
cmd = &ring->cmd[ring->cur];
|
|
cmd->cb = cb;
|
|
KASSERT(len <= sizeof(cmd->data));
|
|
memcpy(cmd->data, arg, len);
|
|
ring->cur = (ring->cur + 1) % BWFM_HOST_CMD_RING_COUNT;
|
|
ring->queued++;
|
|
task_add(sc->sc_taskq, &sc->sc_task);
|
|
splx(s);
|
|
}
|
|
|
|
int
|
|
bwfm_send_mgmt(struct ieee80211com *ic, struct ieee80211_node *ni,
|
|
int type, int arg1, int arg2)
|
|
{
|
|
#ifdef BWFM_DEBUG
|
|
struct bwfm_softc *sc = ic->ic_softc;
|
|
DPRINTF(("%s: %s\n", DEVNAME(sc), __func__));
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
bwfm_set_key(struct ieee80211com *ic, struct ieee80211_node *ni,
|
|
struct ieee80211_key *k)
|
|
{
|
|
struct bwfm_softc *sc = ic->ic_softc;
|
|
struct bwfm_cmd_key cmd;
|
|
|
|
cmd.ni = ni;
|
|
cmd.k = k;
|
|
bwfm_do_async(sc, bwfm_set_key_cb, &cmd, sizeof(cmd));
|
|
sc->sc_key_tasks++;
|
|
return EBUSY;
|
|
}
|
|
|
|
void
|
|
bwfm_set_key_cb(struct bwfm_softc *sc, void *arg)
|
|
{
|
|
struct bwfm_cmd_key *cmd = arg;
|
|
struct ieee80211_key *k = cmd->k;
|
|
struct ieee80211_node *ni = cmd->ni;
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct bwfm_wsec_key key;
|
|
uint32_t wsec, wsec_enable;
|
|
int ext_key = 0;
|
|
|
|
sc->sc_key_tasks--;
|
|
|
|
if ((k->k_flags & IEEE80211_KEY_GROUP) == 0 &&
|
|
k->k_cipher != IEEE80211_CIPHER_WEP40 &&
|
|
k->k_cipher != IEEE80211_CIPHER_WEP104)
|
|
ext_key = 1;
|
|
|
|
memset(&key, 0, sizeof(key));
|
|
if (ext_key && !IEEE80211_IS_MULTICAST(ni->ni_macaddr))
|
|
memcpy(key.ea, ni->ni_macaddr, sizeof(key.ea));
|
|
key.index = htole32(k->k_id);
|
|
key.len = htole32(k->k_len);
|
|
memcpy(key.data, k->k_key, sizeof(key.data));
|
|
if (!ext_key)
|
|
key.flags = htole32(BWFM_WSEC_PRIMARY_KEY);
|
|
|
|
switch (k->k_cipher) {
|
|
case IEEE80211_CIPHER_WEP40:
|
|
key.algo = htole32(BWFM_CRYPTO_ALGO_WEP1);
|
|
wsec_enable = BWFM_WSEC_WEP;
|
|
break;
|
|
case IEEE80211_CIPHER_WEP104:
|
|
key.algo = htole32(BWFM_CRYPTO_ALGO_WEP128);
|
|
wsec_enable = BWFM_WSEC_WEP;
|
|
break;
|
|
case IEEE80211_CIPHER_TKIP:
|
|
key.algo = htole32(BWFM_CRYPTO_ALGO_TKIP);
|
|
wsec_enable = BWFM_WSEC_TKIP;
|
|
break;
|
|
case IEEE80211_CIPHER_CCMP:
|
|
key.algo = htole32(BWFM_CRYPTO_ALGO_AES_CCM);
|
|
wsec_enable = BWFM_WSEC_AES;
|
|
break;
|
|
default:
|
|
printf("%s: cipher %x not supported\n", DEVNAME(sc),
|
|
k->k_cipher);
|
|
ieee80211_new_state(ic, IEEE80211_S_SCAN, -1);
|
|
return;
|
|
}
|
|
|
|
delay(100);
|
|
|
|
bwfm_fwvar_var_set_data(sc, "wsec_key", &key, sizeof(key));
|
|
bwfm_fwvar_var_get_int(sc, "wsec", &wsec);
|
|
wsec &= ~(BWFM_WSEC_WEP | BWFM_WSEC_TKIP | BWFM_WSEC_AES);
|
|
wsec |= wsec_enable;
|
|
bwfm_fwvar_var_set_int(sc, "wsec", wsec);
|
|
|
|
if (wsec_enable != BWFM_WSEC_WEP && cmd->ni != NULL &&
|
|
sc->sc_key_tasks == 0) {
|
|
DPRINTF(("%s: marking port %s valid\n", DEVNAME(sc),
|
|
ether_sprintf(cmd->ni->ni_macaddr)));
|
|
cmd->ni->ni_port_valid = 1;
|
|
ieee80211_set_link_state(ic, LINK_STATE_UP);
|
|
}
|
|
}
|
|
|
|
void
|
|
bwfm_delete_key(struct ieee80211com *ic, struct ieee80211_node *ni,
|
|
struct ieee80211_key *k)
|
|
{
|
|
struct bwfm_softc *sc = ic->ic_softc;
|
|
struct bwfm_cmd_key cmd;
|
|
|
|
cmd.ni = ni;
|
|
cmd.k = k;
|
|
bwfm_do_async(sc, bwfm_delete_key_cb, &cmd, sizeof(cmd));
|
|
}
|
|
|
|
void
|
|
bwfm_delete_key_cb(struct bwfm_softc *sc, void *arg)
|
|
{
|
|
struct bwfm_cmd_key *cmd = arg;
|
|
struct ieee80211_key *k = cmd->k;
|
|
struct bwfm_wsec_key key;
|
|
|
|
memset(&key, 0, sizeof(key));
|
|
key.index = htole32(k->k_id);
|
|
key.flags = htole32(BWFM_WSEC_PRIMARY_KEY);
|
|
bwfm_fwvar_var_set_data(sc, "wsec_key", &key, sizeof(key));
|
|
}
|
|
|
|
int
|
|
bwfm_newstate(struct ieee80211com *ic, enum ieee80211_state nstate, int arg)
|
|
{
|
|
struct bwfm_softc *sc = ic->ic_softc;
|
|
struct ifnet *ifp = &ic->ic_if;
|
|
int s;
|
|
|
|
s = splnet();
|
|
|
|
switch (nstate) {
|
|
case IEEE80211_S_INIT:
|
|
if (ic->ic_state == IEEE80211_S_SCAN)
|
|
bwfm_scan_abort(sc);
|
|
break;
|
|
case IEEE80211_S_SCAN:
|
|
#ifndef IEEE80211_STA_ONLY
|
|
/* Don't start a scan if we already have a channel. */
|
|
if (ic->ic_state == IEEE80211_S_INIT &&
|
|
ic->ic_opmode == IEEE80211_M_HOSTAP &&
|
|
ic->ic_des_chan != IEEE80211_CHAN_ANYC) {
|
|
break;
|
|
}
|
|
#endif
|
|
/* If we tried to connect, abort. */
|
|
if (ic->ic_state > IEEE80211_S_SCAN)
|
|
bwfm_fwvar_cmd_set_data(sc, BWFM_C_DISASSOC, NULL, 0);
|
|
/* Initiate scan. */
|
|
bwfm_scan(sc);
|
|
if (ifp->if_flags & IFF_DEBUG)
|
|
printf("%s: %s -> %s\n", DEVNAME(sc),
|
|
ieee80211_state_name[ic->ic_state],
|
|
ieee80211_state_name[nstate]);
|
|
/* No need to do this again. */
|
|
if (ic->ic_state == IEEE80211_S_SCAN) {
|
|
splx(s);
|
|
return 0;
|
|
}
|
|
ieee80211_set_link_state(ic, LINK_STATE_DOWN);
|
|
ieee80211_free_allnodes(ic, 1);
|
|
ic->ic_state = nstate;
|
|
splx(s);
|
|
return 0;
|
|
case IEEE80211_S_AUTH:
|
|
ic->ic_bss->ni_rsn_supp_state = RSNA_SUPP_INITIALIZE;
|
|
bwfm_connect(sc);
|
|
if (ifp->if_flags & IFF_DEBUG)
|
|
printf("%s: %s -> %s\n", DEVNAME(sc),
|
|
ieee80211_state_name[ic->ic_state],
|
|
ieee80211_state_name[nstate]);
|
|
ic->ic_state = nstate;
|
|
if (ic->ic_flags & IEEE80211_F_RSNON)
|
|
ic->ic_bss->ni_rsn_supp_state = RSNA_SUPP_PTKSTART;
|
|
splx(s);
|
|
return 0;
|
|
#ifndef IEEE80211_STA_ONLY
|
|
case IEEE80211_S_RUN:
|
|
if (ic->ic_opmode == IEEE80211_M_HOSTAP)
|
|
bwfm_hostap(sc);
|
|
break;
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
sc->sc_newstate(ic, nstate, arg);
|
|
splx(s);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
bwfm_nvram_convert(int node, u_char **bufp, size_t *sizep, size_t *newlenp)
|
|
{
|
|
u_char *src, *dst, *end = *bufp + *sizep, *newbuf;
|
|
size_t count = 0, newsize, pad;
|
|
uint32_t token;
|
|
int skip = 0;
|
|
|
|
/*
|
|
* Allocate a new buffer with enough space for the MAC
|
|
* address, padding and final token.
|
|
*/
|
|
newsize = *sizep + 64;
|
|
newbuf = malloc(newsize, M_DEVBUF, M_NOWAIT);
|
|
if (newbuf == NULL)
|
|
return 1;
|
|
|
|
for (src = *bufp, dst = newbuf; src != end; ++src) {
|
|
if (*src == '\n') {
|
|
if (count > 0)
|
|
*dst++ = '\0';
|
|
count = 0;
|
|
skip = 0;
|
|
continue;
|
|
}
|
|
if (skip)
|
|
continue;
|
|
if (*src == '#' && count == 0) {
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
if (*src == '\r')
|
|
continue;
|
|
*dst++ = *src;
|
|
++count;
|
|
}
|
|
|
|
#if defined(__HAVE_FDT)
|
|
/*
|
|
* Append MAC address if one is provided in the device tree.
|
|
* This is needed on Apple Silicon Macs.
|
|
*/
|
|
if (node) {
|
|
u_char enaddr[ETHER_ADDR_LEN];
|
|
char macaddr[32];
|
|
|
|
if (OF_getprop(node, "local-mac-address",
|
|
enaddr, sizeof(enaddr))) {
|
|
snprintf(macaddr, sizeof(macaddr),
|
|
"macaddr=%02x:%02x:%02x:%02x:%02x:%02x",
|
|
enaddr[0], enaddr[1], enaddr[2], enaddr[3],
|
|
enaddr[4], enaddr[5]);
|
|
if (*dst)
|
|
*dst++ = '\0';
|
|
memcpy(dst, macaddr, strlen(macaddr));
|
|
dst += strlen(macaddr);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
count = dst - newbuf;
|
|
pad = roundup(count + 1, 4) - count;
|
|
|
|
memset(dst, 0, pad);
|
|
count += pad;
|
|
dst += pad;
|
|
|
|
token = (count / 4) & 0xffff;
|
|
token |= ~token << 16;
|
|
token = htole32(token);
|
|
|
|
memcpy(dst, &token, sizeof(token));
|
|
count += sizeof(token);
|
|
|
|
free(*bufp, M_DEVBUF, *sizep);
|
|
*bufp = newbuf;
|
|
*sizep = newsize;
|
|
*newlenp = count;
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
bwfm_process_blob(struct bwfm_softc *sc, char *var, u_char **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 = malloc(sizeof(*data) + BWFM_DLOAD_MAX_LEN, M_TEMP, M_WAITOK);
|
|
|
|
while (remain) {
|
|
len = min(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);
|
|
goto out;
|
|
}
|
|
|
|
off += len;
|
|
remain -= len;
|
|
}
|
|
|
|
out:
|
|
free(data, M_TEMP, sizeof(*data) + BWFM_DLOAD_MAX_LEN);
|
|
free(*blob, M_DEVBUF, *blobsize);
|
|
*blob = NULL;
|
|
*blobsize = 0;
|
|
}
|
|
|
|
void
|
|
bwfm_init_board_type(struct bwfm_softc *sc)
|
|
{
|
|
#if defined(__HAVE_FDT)
|
|
char compat[128];
|
|
int len;
|
|
char *p;
|
|
|
|
len = OF_getprop(OF_peer(0), "compatible", compat, sizeof(compat));
|
|
if (len > 0 && len < sizeof(compat)) {
|
|
compat[len] = '\0';
|
|
if ((p = strchr(compat, '/')) != NULL)
|
|
*p = '\0';
|
|
strlcpy(sc->sc_board_type, compat, sizeof(sc->sc_board_type));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
int
|
|
bwfm_loadfirmware(struct bwfm_softc *sc, const char *chip, const char *bus,
|
|
u_char **ucode, size_t *size, u_char **nvram, size_t *nvsize, size_t *nvlen)
|
|
{
|
|
const char *board_type = NULL;
|
|
char name[128];
|
|
int r;
|
|
|
|
*ucode = *nvram = NULL;
|
|
*size = *nvsize = *nvlen = 0;
|
|
|
|
if (strlen(sc->sc_board_type) > 0)
|
|
board_type = sc->sc_board_type;
|
|
|
|
if (board_type != NULL) {
|
|
r = snprintf(name, sizeof(name), "%sbrcmfmac%s%s.%s.bin",
|
|
sc->sc_fwdir, chip, bus, board_type);
|
|
if ((r > 0 && r < sizeof(name)) &&
|
|
loadfirmware(name, ucode, size) != 0)
|
|
*size = 0;
|
|
}
|
|
if (*size == 0) {
|
|
snprintf(name, sizeof(name), "%sbrcmfmac%s%s.bin",
|
|
sc->sc_fwdir, chip, bus);
|
|
if (loadfirmware(name, ucode, size) != 0) {
|
|
snprintf(name, sizeof(name), "%sbrcmfmac%s%s%s%s.bin",
|
|
sc->sc_fwdir, chip, bus, board_type ? "." : "",
|
|
board_type ? board_type : "");
|
|
printf("%s: failed loadfirmware of file %s\n",
|
|
DEVNAME(sc), name);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* .txt needs to be processed first */
|
|
if (strlen(sc->sc_modrev) > 0) {
|
|
r = snprintf(name, sizeof(name),
|
|
"%sbrcmfmac%s%s.%s-%s-%s-%s.txt", sc->sc_fwdir, chip, bus,
|
|
board_type, sc->sc_module, sc->sc_vendor, sc->sc_modrev);
|
|
if (r > 0 && r < sizeof(name))
|
|
loadfirmware(name, nvram, nvsize);
|
|
}
|
|
if (*nvsize == 0 && strlen(sc->sc_vendor) > 0) {
|
|
r = snprintf(name, sizeof(name),
|
|
"%sbrcmfmac%s%s.%s-%s-%s.txt", sc->sc_fwdir, chip, bus,
|
|
board_type, sc->sc_module, sc->sc_vendor);
|
|
if (r > 0 && r < sizeof(name))
|
|
loadfirmware(name, nvram, nvsize);
|
|
}
|
|
|
|
if (*nvsize == 0 && board_type != NULL) {
|
|
r = snprintf(name, sizeof(name), "%sbrcmfmac%s%s.%s.txt",
|
|
sc->sc_fwdir, chip, bus, board_type);
|
|
if (r > 0 && r < sizeof(name))
|
|
loadfirmware(name, nvram, nvsize);
|
|
}
|
|
|
|
if (*nvsize == 0) {
|
|
snprintf(name, sizeof(name), "%sbrcmfmac%s%s.txt",
|
|
sc->sc_fwdir, chip, bus);
|
|
loadfirmware(name, nvram, nvsize);
|
|
}
|
|
|
|
if (*nvsize != 0) {
|
|
if (bwfm_nvram_convert(sc->sc_node, nvram, nvsize, nvlen)) {
|
|
printf("%s: failed to process file %s\n",
|
|
DEVNAME(sc), name);
|
|
free(*ucode, M_DEVBUF, *size);
|
|
free(*nvram, M_DEVBUF, *nvsize);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* .nvram is the pre-processed version */
|
|
if (*nvlen == 0) {
|
|
snprintf(name, sizeof(name), "%sbrcmfmac%s%s.nvram",
|
|
sc->sc_fwdir, chip, bus);
|
|
if (loadfirmware(name, nvram, nvsize) == 0)
|
|
*nvlen = *nvsize;
|
|
}
|
|
|
|
if (*nvlen == 0 && strcmp(bus, "-sdio") == 0) {
|
|
snprintf(name, sizeof(name), "%sbrcmfmac%s%s%s%s.txt",
|
|
sc->sc_fwdir, chip, bus, board_type ? "." : "",
|
|
board_type ? board_type : "");
|
|
printf("%s: failed loadfirmware of file %s\n",
|
|
DEVNAME(sc), name);
|
|
free(*ucode, M_DEVBUF, *size);
|
|
return 1;
|
|
}
|
|
|
|
if (board_type != NULL) {
|
|
r = snprintf(name, sizeof(name), "%sbrcmfmac%s%s.%s.clm_blob",
|
|
sc->sc_fwdir, chip, bus, board_type);
|
|
if (r > 0 && r < sizeof(name))
|
|
loadfirmware(name, &sc->sc_clm, &sc->sc_clmsize);
|
|
}
|
|
if (sc->sc_clmsize == 0) {
|
|
snprintf(name, sizeof(name), "%sbrcmfmac%s%s.clm_blob",
|
|
sc->sc_fwdir, chip, bus);
|
|
loadfirmware(name, &sc->sc_clm, &sc->sc_clmsize);
|
|
}
|
|
|
|
if (board_type != NULL) {
|
|
r = snprintf(name, sizeof(name),
|
|
"%sbrcmfmac%s%s.%s.txcap_blob", sc->sc_fwdir,
|
|
chip, bus, board_type);
|
|
if (r > 0 && r < sizeof(name))
|
|
loadfirmware(name, &sc->sc_txcap, &sc->sc_txcapsize);
|
|
}
|
|
if (sc->sc_txcapsize == 0) {
|
|
snprintf(name, sizeof(name), "%sbrcmfmac%s%s.txcap_blob",
|
|
sc->sc_fwdir, chip, bus);
|
|
loadfirmware(name, &sc->sc_txcap, &sc->sc_txcapsize);
|
|
}
|
|
|
|
return 0;
|
|
}
|