/* $NetBSD: bwfm.c,v 1.14.6.3 2020/08/17 11:22:45 martin Exp $ */ /* $OpenBSD: bwfm.c,v 1.5 2017/10/16 22:27:16 patrick Exp $ */ /* * Copyright (c) 2010-2016 Broadcom Corporation * Copyright (c) 2016,2017 Patrick Wildt * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* #define BWFM_DEBUG */ #ifdef BWFM_DEBUG #define DPRINTF(x) do { if (bwfm_debug > 0) printf x; } while (0) #define DPRINTFN(n, x) do { if (bwfm_debug >= (n)) printf x; } while (0) static int bwfm_debug = 1; #else #define DPRINTF(x) do { ; } while (0) #define DPRINTFN(n, x) do { ; } while (0) #endif #define DEVNAME(sc) device_xname((sc)->sc_dev) void bwfm_start(struct ifnet *); int bwfm_init(struct ifnet *); void bwfm_stop(struct ifnet *, int); void bwfm_watchdog(struct ifnet *); int bwfm_ioctl(struct ifnet *, u_long, void *); int bwfm_media_change(struct ifnet *); int bwfm_send_mgmt(struct ieee80211com *, struct ieee80211_node *, int, int); void bwfm_recv_mgmt(struct ieee80211com *, struct mbuf *, struct ieee80211_node *, int, int, uint32_t); int bwfm_key_set(struct ieee80211com *, const struct ieee80211_key *, const uint8_t *); int bwfm_key_delete(struct ieee80211com *, const struct ieee80211_key *); int bwfm_newstate(struct ieee80211com *, enum ieee80211_state, int); void bwfm_newstate_cb(struct bwfm_softc *, struct bwfm_cmd_newstate *); void bwfm_newassoc(struct ieee80211_node *, int); void bwfm_task(struct work *, void *); int bwfm_chip_attach(struct bwfm_softc *); int bwfm_chip_detach(struct bwfm_softc *, int); struct bwfm_core *bwfm_chip_get_core(struct bwfm_softc *, int); struct bwfm_core *bwfm_chip_get_pmu(struct bwfm_softc *); int bwfm_chip_ai_isup(struct bwfm_softc *, struct bwfm_core *); void bwfm_chip_ai_disable(struct bwfm_softc *, struct bwfm_core *, uint32_t, uint32_t); void bwfm_chip_ai_reset(struct bwfm_softc *, struct bwfm_core *, uint32_t, uint32_t, uint32_t); void bwfm_chip_dmp_erom_scan(struct bwfm_softc *); int bwfm_chip_dmp_get_regaddr(struct bwfm_softc *, uint32_t *, uint32_t *, uint32_t *); int bwfm_chip_cr4_set_active(struct bwfm_softc *, const uint32_t); void bwfm_chip_cr4_set_passive(struct bwfm_softc *); int bwfm_chip_ca7_set_active(struct bwfm_softc *, const uint32_t); void bwfm_chip_ca7_set_passive(struct bwfm_softc *); int bwfm_chip_cm3_set_active(struct bwfm_softc *); void bwfm_chip_cm3_set_passive(struct bwfm_softc *); void bwfm_chip_socram_ramsize(struct bwfm_softc *, struct bwfm_core *); void bwfm_chip_sysmem_ramsize(struct bwfm_softc *, struct bwfm_core *); void bwfm_chip_tcm_ramsize(struct bwfm_softc *, struct bwfm_core *); void bwfm_chip_tcm_rambase(struct bwfm_softc *); int bwfm_proto_bcdc_query_dcmd(struct bwfm_softc *, int, int, char *, size_t *); int bwfm_proto_bcdc_set_dcmd(struct bwfm_softc *, int, int, char *, size_t); int bwfm_fwvar_cmd_get_data(struct bwfm_softc *, int, void *, size_t); int bwfm_fwvar_cmd_set_data(struct bwfm_softc *, int, void *, size_t); int bwfm_fwvar_cmd_get_int(struct bwfm_softc *, int, uint32_t *); int bwfm_fwvar_cmd_set_int(struct bwfm_softc *, int, uint32_t); int bwfm_fwvar_var_get_data(struct bwfm_softc *, const char *, void *, size_t); int bwfm_fwvar_var_set_data(struct bwfm_softc *, const char *, void *, size_t); int bwfm_fwvar_var_get_int(struct bwfm_softc *, const char *, uint32_t *); int bwfm_fwvar_var_set_int(struct bwfm_softc *, const char *, uint32_t); struct ieee80211_channel *bwfm_bss2chan(struct bwfm_softc *, struct bwfm_bss_info *); void bwfm_scan(struct bwfm_softc *); void bwfm_connect(struct bwfm_softc *); void bwfm_get_sta_info(struct bwfm_softc *, struct ifmediareq *); void bwfm_rx(struct bwfm_softc *, struct mbuf *); void bwfm_rx_event(struct bwfm_softc *, struct mbuf *); void bwfm_rx_event_cb(struct bwfm_softc *, struct mbuf *); void bwfm_scan_node(struct bwfm_softc *, struct bwfm_bss_info *, size_t); 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, }; void bwfm_attach(struct bwfm_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct ifnet *ifp = &sc->sc_if; char fw_version[BWFM_DCMD_SMLEN]; uint32_t bandlist[3]; uint32_t tmp; int i, j, error; error = workqueue_create(&sc->sc_taskq, DEVNAME(sc), bwfm_task, sc, PRI_NONE, IPL_NET, 0); if (error != 0) { printf("%s: could not create workqueue\n", DEVNAME(sc)); return; } sc->sc_freetask = pool_cache_init(sizeof(struct bwfm_task), 0, 0, 0, "bwfmtask", NULL, IPL_NET /* XXX IPL_SOFTNET? */, NULL, NULL, NULL); pool_prime(&sc->sc_freetask->pc_pool, BWFM_TASK_COUNT); /* Stop the device in case it was previously initialized */ bwfm_fwvar_cmd_set_int(sc, BWFM_C_DOWN, 1); if (bwfm_fwvar_cmd_get_int(sc, BWFM_C_GET_VERSION, &tmp)) { printf("%s: could not read io type\n", DEVNAME(sc)); return; } else sc->sc_io_type = tmp; if (bwfm_fwvar_var_get_data(sc, "cur_etheraddr", ic->ic_myaddr, sizeof(ic->ic_myaddr))) { printf("%s: could not read mac address\n", DEVNAME(sc)); return; } memset(fw_version, 0, sizeof(fw_version)); if (bwfm_fwvar_var_get_data(sc, "ver", fw_version, sizeof(fw_version)) == 0) printf("%s: %s", DEVNAME(sc), fw_version); printf("%s: address %s\n", DEVNAME(sc), ether_sprintf(ic->ic_myaddr)); ic->ic_ifp = ifp; ic->ic_phytype = IEEE80211_T_OFDM; ic->ic_opmode = IEEE80211_M_STA; ic->ic_state = IEEE80211_S_INIT; ic->ic_caps = IEEE80211_C_WEP | IEEE80211_C_TKIP | IEEE80211_C_AES | IEEE80211_C_AES_CCM | #if notyet IEEE80211_C_MONITOR | /* monitor mode suported */ IEEE80211_C_IBSS | IEEE80211_C_TXPMGT | IEEE80211_C_WME | #endif IEEE80211_C_SHSLOT | /* short slot time supported */ IEEE80211_C_SHPREAMBLE | /* short preamble supported */ IEEE80211_C_WPA | /* 802.11i */ /* IEEE80211_C_WPA_4WAY */0; /* WPA 4-way handshake in hw */ /* IBSS channel undefined for now. */ ic->ic_ibss_chan = &ic->ic_channels[0]; if (bwfm_fwvar_cmd_get_data(sc, BWFM_C_GET_BANDLIST, bandlist, sizeof(bandlist))) { printf("%s: couldn't get supported band list\n", DEVNAME(sc)); return; } const u_int nbands = le32toh(bandlist[0]); for (i = 1; i <= MIN(nbands, __arraycount(bandlist) - 1); i++) { switch (le32toh(bandlist[i])) { case BWFM_BAND_2G: ic->ic_sup_rates[IEEE80211_MODE_11B] = ieee80211_std_rateset_11b; ic->ic_sup_rates[IEEE80211_MODE_11G] = ieee80211_std_rateset_11g; for (j = 0; j < __arraycount(bwfm_2ghz_channels); j++) { uint8_t chan = bwfm_2ghz_channels[j]; ic->ic_channels[chan].ic_freq = ieee80211_ieee2mhz(chan, IEEE80211_CHAN_2GHZ); ic->ic_channels[chan].ic_flags = IEEE80211_CHAN_CCK | IEEE80211_CHAN_OFDM | IEEE80211_CHAN_DYN | IEEE80211_CHAN_2GHZ; } break; case BWFM_BAND_5G: ic->ic_sup_rates[IEEE80211_MODE_11A] = ieee80211_std_rateset_11a; for (j = 0; j < __arraycount(bwfm_5ghz_channels); j++) { uint8_t chan = bwfm_5ghz_channels[j]; ic->ic_channels[chan].ic_freq = ieee80211_ieee2mhz(chan, IEEE80211_CHAN_5GHZ); ic->ic_channels[chan].ic_flags = IEEE80211_CHAN_A; } break; } } ifp->if_softc = sc; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_init = bwfm_init; ifp->if_ioctl = bwfm_ioctl; ifp->if_start = bwfm_start; ifp->if_stop = bwfm_stop; ifp->if_watchdog = bwfm_watchdog; IFQ_SET_READY(&ifp->if_snd); memcpy(ifp->if_xname, DEVNAME(sc), IFNAMSIZ); error = if_initialize(ifp); if (error != 0) { printf("%s: if_initialize failed(%d)\n", DEVNAME(sc), error); pool_cache_destroy(sc->sc_freetask); workqueue_destroy(sc->sc_taskq); return; /* Error */ } ieee80211_ifattach(ic); sc->sc_newstate = ic->ic_newstate; ic->ic_newstate = bwfm_newstate; ic->ic_newassoc = bwfm_newassoc; ic->ic_send_mgmt = bwfm_send_mgmt; ic->ic_recv_mgmt = bwfm_recv_mgmt; ic->ic_crypto.cs_key_set = bwfm_key_set; ic->ic_crypto.cs_key_delete = bwfm_key_delete; ifp->if_percpuq = if_percpuq_create(ifp); if_deferred_start_init(ifp, NULL); if_register(ifp); ieee80211_media_init(ic, bwfm_media_change, ieee80211_media_status); ieee80211_announce(ic); sc->sc_if_attached = true; } int bwfm_detach(struct bwfm_softc *sc, int flags) { struct ieee80211com *ic = &sc->sc_ic; struct ifnet *ifp = ic->ic_ifp; if (sc->sc_if_attached) { bpf_detach(ifp); ieee80211_ifdetach(ic); if_detach(ifp); } if (sc->sc_taskq) workqueue_destroy(sc->sc_taskq); if (sc->sc_freetask) pool_cache_destroy(sc->sc_freetask); return 0; } void bwfm_start(struct ifnet *ifp) { struct bwfm_softc *sc = ifp->if_softc; struct ieee80211com *ic = &sc->sc_ic; struct mbuf *m; int error; if ((ifp->if_flags & (IFF_RUNNING | IFF_OACTIVE)) != IFF_RUNNING) return; /* TODO: return if no link? */ for (;;) { /* Discard management packets (fw handles this for us) */ IF_DEQUEUE(&ic->ic_mgtq, m); if (m != NULL) { m_freem(m); continue; } if (sc->sc_bus_ops->bs_txcheck(sc)) { ifp->if_flags |= IFF_OACTIVE; break; } IFQ_DEQUEUE(&ifp->if_snd, m); if (m == NULL) break; error = sc->sc_bus_ops->bs_txdata(sc, &m); if (error == ENOBUFS) { IF_PREPEND(&ifp->if_snd, m); ifp->if_flags |= IFF_OACTIVE; break; } if (error != 0) { ifp->if_oerrors++; m_freem(m); continue; } bpf_mtap(ifp, m, BPF_D_OUT); } } int bwfm_init(struct ifnet *ifp) { struct bwfm_softc *sc = ifp->if_softc; struct ieee80211com *ic = &sc->sc_ic; uint8_t evmask[BWFM_EVENT_MASK_LEN]; struct bwfm_join_pref_params join_pref[2]; int pm; if (bwfm_fwvar_var_set_int(sc, "mpc", 1)) { printf("%s: could not set mpc\n", DEVNAME(sc)); return EIO; } /* Select target by RSSI (boost on 5GHz) */ join_pref[0].type = BWFM_JOIN_PREF_RSSI_DELTA; join_pref[0].len = 2; join_pref[0].rssi_gain = BWFM_JOIN_PREF_RSSI_BOOST; join_pref[0].band = BWFM_JOIN_PREF_BAND_5G; join_pref[1].type = BWFM_JOIN_PREF_RSSI; join_pref[1].len = 2; join_pref[1].rssi_gain = 0; join_pref[1].band = 0; if (bwfm_fwvar_var_set_data(sc, "join_pref", join_pref, sizeof(join_pref))) { printf("%s: could not set join pref\n", DEVNAME(sc)); return EIO; } memset(evmask, 0, sizeof(evmask)); #define ENABLE_EVENT(e) evmask[(e) / 8] |= 1 << ((e) % 8) /* Events used to drive the state machine */ switch (ic->ic_opmode) { case IEEE80211_M_STA: ENABLE_EVENT(BWFM_E_IF); ENABLE_EVENT(BWFM_E_LINK); ENABLE_EVENT(BWFM_E_AUTH); ENABLE_EVENT(BWFM_E_ASSOC); ENABLE_EVENT(BWFM_E_DEAUTH); ENABLE_EVENT(BWFM_E_DISASSOC); ENABLE_EVENT(BWFM_E_SET_SSID); ENABLE_EVENT(BWFM_E_ESCAN_RESULT); break; #ifndef IEEE80211_STA_ONLY case IEEE80211_M_HOSTAP: ENABLE_EVENT(BWFM_E_AUTH_IND); ENABLE_EVENT(BWFM_E_ASSOC_IND); ENABLE_EVENT(BWFM_E_REASSOC_IND); ENABLE_EVENT(BWFM_E_DEAUTH_IND); ENABLE_EVENT(BWFM_E_DISASSOC_IND); ENABLE_EVENT(BWFM_E_ESCAN_RESULT); ENABLE_EVENT(BWFM_E_ESCAN_RESULT); break; #endif default: break; } #undef ENABLE_EVENT #ifdef BWFM_DEBUG memset(evmask, 0xff, sizeof(evmask)); #endif if (bwfm_fwvar_var_set_data(sc, "event_msgs", evmask, sizeof(evmask))) { printf("%s: could not set event mask\n", DEVNAME(sc)); return EIO; } if (bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_SCAN_CHANNEL_TIME, BWFM_DEFAULT_SCAN_CHANNEL_TIME)) { printf("%s: could not set scan channel time\n", DEVNAME(sc)); return EIO; } if (bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_SCAN_UNASSOC_TIME, BWFM_DEFAULT_SCAN_UNASSOC_TIME)) { printf("%s: could not set scan unassoc time\n", DEVNAME(sc)); return EIO; } if (bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_SCAN_PASSIVE_TIME, BWFM_DEFAULT_SCAN_PASSIVE_TIME)) { printf("%s: could not set scan passive time\n", DEVNAME(sc)); return EIO; } /* * Use CAM (constantly awake) when we are running as AP * otherwise use fast power saving. */ 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 EIO; } bwfm_fwvar_var_set_int(sc, "txbf", 1); bwfm_fwvar_cmd_set_int(sc, BWFM_C_UP, 0); bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_INFRA, 1); bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_AP, 0); /* Disable all offloading (ARP, NDP, TCP/UDP cksum). */ bwfm_fwvar_var_set_int(sc, "arp_ol", 0); bwfm_fwvar_var_set_int(sc, "arpoe", 0); bwfm_fwvar_var_set_int(sc, "ndoe", 0); bwfm_fwvar_var_set_int(sc, "toe", 0); /* Accept all multicast frames. */ bwfm_fwvar_var_set_int(sc, "allmulti", 1); /* Setup promiscuous mode */ bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_PROMISC, (ifp->if_flags & IFF_PROMISC) ? 1 : 0); /* * Tell the firmware supplicant that we are going to handle the * WPA handshake ourselves. */ bwfm_fwvar_var_set_int(sc, "sup_wpa", 0); ifp->if_flags |= IFF_RUNNING; ifp->if_flags &= ~IFF_OACTIVE; if (ic->ic_opmode != IEEE80211_M_MONITOR) { if (ic->ic_roaming != IEEE80211_ROAMING_MANUAL) ieee80211_new_state(ic, IEEE80211_S_SCAN, -1); } else { ieee80211_new_state(ic, IEEE80211_S_RUN, -1); } return 0; } void bwfm_stop(struct ifnet *ifp, int disable) { struct bwfm_softc *sc = ifp->if_softc; struct ieee80211com *ic = &sc->sc_ic; struct bwfm_join_params join; sc->sc_tx_timer = 0; ifp->if_timer = 0; ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE); 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_PM, 0); 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); ieee80211_new_state(ic, IEEE80211_S_INIT, -1); if (sc->sc_bus_ops->bs_stop) sc->sc_bus_ops->bs_stop(sc); } void bwfm_watchdog(struct ifnet *ifp) { struct bwfm_softc *sc = ifp->if_softc; struct ieee80211com *ic = &sc->sc_ic; ifp->if_timer = 0; if (sc->sc_tx_timer > 0) { if (--sc->sc_tx_timer == 0) { printf("%s: device timeout\n", DEVNAME(sc)); ifp->if_oerrors++; return; } ifp->if_timer = 1; } ieee80211_watchdog(ic); } int bwfm_ioctl(struct ifnet *ifp, u_long cmd, void *data) { struct bwfm_softc *sc = ifp->if_softc; struct ieee80211com *ic = &sc->sc_ic; int s, error = 0; s = splnet(); switch (cmd) { case SIOCSIFFLAGS: if ((error = ifioctl_common(ifp, cmd, data)) != 0) break; switch (ifp->if_flags & (IFF_UP | IFF_RUNNING)) { case IFF_UP | IFF_RUNNING: break; case IFF_UP: bwfm_init(ifp); break; case IFF_RUNNING: bwfm_stop(ifp, 1); break; case 0: break; } break; case SIOCADDMULTI: case SIOCDELMULTI: if ((error = ether_ioctl(ifp, cmd, data)) == ENETRESET) { /* setup multicast filter, etc */ error = 0; } break; case SIOCGIFMEDIA: error = ieee80211_ioctl(ic, cmd, data); if (error == 0 && ic->ic_state == IEEE80211_S_RUN) bwfm_get_sta_info(sc, (struct ifmediareq *)data); break; default: error = ieee80211_ioctl(ic, cmd, data); } if (error == ENETRESET) { if ((ifp->if_flags & IFF_UP) != 0 && (ifp->if_flags & IFF_RUNNING) != 0 && ic->ic_roaming != IEEE80211_ROAMING_MANUAL) { bwfm_init(ifp); } error = 0; } splx(s); return error; } int bwfm_send_mgmt(struct ieee80211com *ic, struct ieee80211_node *ni, int type, int arg) { return 0; } void bwfm_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0, struct ieee80211_node *ni, int subtype, int rssi, uint32_t rstamp) { } int bwfm_key_set(struct ieee80211com *ic, const struct ieee80211_key *wk, const uint8_t mac[IEEE80211_ADDR_LEN]) { struct bwfm_softc *sc = ic->ic_ifp->if_softc; struct bwfm_task *t; t = pool_cache_get(sc->sc_freetask, PR_NOWAIT); if (t == NULL) { printf("%s: no free tasks\n", DEVNAME(sc)); return 0; } t->t_sc = sc; t->t_cmd = BWFM_TASK_KEY_SET; t->t_key.key = wk; memcpy(t->t_key.mac, mac, sizeof(t->t_key.mac)); workqueue_enqueue(sc->sc_taskq, (struct work *)t, NULL); return 1; } static void bwfm_key_set_cb(struct bwfm_softc *sc, struct bwfm_cmd_key *ck) { const struct ieee80211_key *wk = ck->key; const uint8_t *mac = ck->mac; struct bwfm_wsec_key wsec_key; uint32_t wsec_enable, wsec; bool ext_key; #ifdef BWFM_DEBUG printf("key_set: key cipher %s len %d: ", wk->wk_cipher->ic_name, wk->wk_keylen); for (int j = 0; j < sizeof(wk->wk_key); j++) printf("%02x", wk->wk_key[j]); #endif if ((wk->wk_flags & IEEE80211_KEY_GROUP) == 0 && wk->wk_cipher->ic_cipher != IEEE80211_CIPHER_WEP) { ext_key = true; } else { ext_key = false; } #ifdef BWFM_DEBUG printf(", ext_key = %d", ext_key); printf(", mac = %02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); printf("\n"); #endif memset(&wsec_key, 0, sizeof(wsec_key)); if (ext_key && !IEEE80211_IS_MULTICAST(mac)) memcpy(wsec_key.ea, mac, sizeof(wsec_key.ea)); wsec_key.index = htole32(wk->wk_keyix); wsec_key.len = htole32(wk->wk_keylen); memcpy(wsec_key.data, wk->wk_key, sizeof(wsec_key.data)); if (!ext_key) wsec_key.flags = htole32(BWFM_WSEC_PRIMARY_KEY); switch (wk->wk_cipher->ic_cipher) { case IEEE80211_CIPHER_WEP: if (wk->wk_keylen == 5) wsec_key.algo = htole32(BWFM_CRYPTO_ALGO_WEP1); else if (wk->wk_keylen == 13) wsec_key.algo = htole32(BWFM_CRYPTO_ALGO_WEP128); else return; wsec_enable = BWFM_WSEC_WEP; break; case IEEE80211_CIPHER_TKIP: wsec_key.algo = htole32(BWFM_CRYPTO_ALGO_TKIP); wsec_enable = BWFM_WSEC_TKIP; break; case IEEE80211_CIPHER_AES_CCM: wsec_key.algo = htole32(BWFM_CRYPTO_ALGO_AES_CCM); wsec_enable = BWFM_WSEC_AES; break; default: printf("%s: %s: cipher %s not supported\n", DEVNAME(sc), __func__, wk->wk_cipher->ic_name); return; } if (bwfm_fwvar_var_set_data(sc, "wsec_key", &wsec_key, sizeof(wsec_key))) return; bwfm_fwvar_var_set_int(sc, "wpa_auth", BWFM_WPA_AUTH_WPA2_PSK); bwfm_fwvar_var_get_int(sc, "wsec", &wsec); wsec |= wsec_enable; bwfm_fwvar_var_set_int(sc, "wsec", wsec); } int bwfm_key_delete(struct ieee80211com *ic, const struct ieee80211_key *wk) { struct bwfm_softc *sc = ic->ic_ifp->if_softc; struct bwfm_task *t; t = pool_cache_get(sc->sc_freetask, PR_NOWAIT); if (t == NULL) { printf("%s: no free tasks\n", DEVNAME(sc)); return 0; } t->t_sc = sc; t->t_cmd = BWFM_TASK_KEY_DELETE; t->t_key.key = wk; memset(t->t_key.mac, 0, sizeof(t->t_key.mac)); workqueue_enqueue(sc->sc_taskq, (struct work *)t, NULL); return 1; } static void bwfm_key_delete_cb(struct bwfm_softc *sc, struct bwfm_cmd_key *ck) { const struct ieee80211_key *wk = ck->key; struct bwfm_wsec_key wsec_key; memset(&wsec_key, 0, sizeof(wsec_key)); wsec_key.index = htole32(wk->wk_keyix); wsec_key.flags = htole32(BWFM_WSEC_PRIMARY_KEY); if (bwfm_fwvar_var_set_data(sc, "wsec_key", &wsec_key, sizeof(wsec_key))) return; } int bwfm_newstate(struct ieee80211com *ic, enum ieee80211_state nstate, int arg) { struct bwfm_softc *sc = ic->ic_ifp->if_softc; struct bwfm_task *t; t = pool_cache_get(sc->sc_freetask, PR_NOWAIT); if (t == NULL) { printf("%s: no free tasks\n", DEVNAME(sc)); return EIO; } t->t_sc = sc; t->t_cmd = BWFM_TASK_NEWSTATE; t->t_newstate.state = nstate; t->t_newstate.arg = arg; workqueue_enqueue(sc->sc_taskq, (struct work *)t, NULL); return 0; } void bwfm_newstate_cb(struct bwfm_softc *sc, struct bwfm_cmd_newstate *cmd) { struct ieee80211com *ic = &sc->sc_ic; enum ieee80211_state ostate = ic->ic_state; enum ieee80211_state nstate = cmd->state; int s; DPRINTF(("%s: newstate %d -> %d\n", DEVNAME(sc), ostate, nstate)); s = splnet(); switch (nstate) { case IEEE80211_S_INIT: break; case IEEE80211_S_SCAN: if (ostate != IEEE80211_S_SCAN) { /* Start of scanning */ bwfm_scan(sc); } break; case IEEE80211_S_AUTH: bwfm_connect(sc); break; case IEEE80211_S_ASSOC: break; case IEEE80211_S_RUN: break; } sc->sc_newstate(ic, nstate, cmd->arg); splx(s); } void bwfm_newassoc(struct ieee80211_node *ni, int isnew) { /* Firmware handles rate adaptation for us */ ni->ni_txrate = 0; } void bwfm_task(struct work *wk, void *arg) { struct bwfm_task *t = (struct bwfm_task *)wk; struct bwfm_softc *sc = t->t_sc; switch (t->t_cmd) { case BWFM_TASK_NEWSTATE: bwfm_newstate_cb(sc, &t->t_newstate); break; case BWFM_TASK_KEY_SET: bwfm_key_set_cb(sc, &t->t_key); break; case BWFM_TASK_KEY_DELETE: bwfm_key_delete_cb(sc, &t->t_key); break; case BWFM_TASK_RX_EVENT: bwfm_rx_event_cb(sc, t->t_mbuf); break; default: panic("bwfm: unknown task command %d", t->t_cmd); } pool_cache_put(sc->sc_freetask, t); } int bwfm_media_change(struct ifnet *ifp) { return 0; } /* Chip initialization (SDIO, PCIe) */ int bwfm_chip_attach(struct bwfm_softc *sc) { struct bwfm_core *core; int need_socram = 0; int has_socram = 0; int cpu_found = 0; uint32_t val; LIST_INIT(&sc->sc_chip.ch_list); if (sc->sc_buscore_ops->bc_prepare(sc) != 0) { printf("%s: failed buscore prepare\n", DEVNAME(sc)); return 1; } val = sc->sc_buscore_ops->bc_read(sc, BWFM_CHIP_BASE + BWFM_CHIP_REG_CHIPID); sc->sc_chip.ch_chip = BWFM_CHIP_CHIPID_ID(val); sc->sc_chip.ch_chiprev = BWFM_CHIP_CHIPID_REV(val); if ((sc->sc_chip.ch_chip > 0xa000) || (sc->sc_chip.ch_chip < 0x4000)) snprintf(sc->sc_chip.ch_name, sizeof(sc->sc_chip.ch_name), "%d", sc->sc_chip.ch_chip); else snprintf(sc->sc_chip.ch_name, sizeof(sc->sc_chip.ch_name), "%x", sc->sc_chip.ch_chip); switch (BWFM_CHIP_CHIPID_TYPE(val)) { case BWFM_CHIP_CHIPID_TYPE_SOCI_SB: printf("%s: SoC interconnect SB not implemented\n", DEVNAME(sc)); return 1; case BWFM_CHIP_CHIPID_TYPE_SOCI_AI: sc->sc_chip.ch_core_isup = bwfm_chip_ai_isup; sc->sc_chip.ch_core_disable = bwfm_chip_ai_disable; sc->sc_chip.ch_core_reset = bwfm_chip_ai_reset; bwfm_chip_dmp_erom_scan(sc); break; default: printf("%s: SoC interconnect %d unknown\n", DEVNAME(sc), BWFM_CHIP_CHIPID_TYPE(val)); return 1; } LIST_FOREACH(core, &sc->sc_chip.ch_list, co_link) { DPRINTF(("%s: 0x%x:%-2d base 0x%08x wrap 0x%08x\n", DEVNAME(sc), core->co_id, core->co_rev, core->co_base, core->co_wrapbase)); switch (core->co_id) { case BWFM_AGENT_CORE_ARM_CM3: need_socram = true; /* FALLTHROUGH */ case BWFM_AGENT_CORE_ARM_CR4: case BWFM_AGENT_CORE_ARM_CA7: cpu_found = true; break; case BWFM_AGENT_INTERNAL_MEM: has_socram = true; break; default: break; } } if (!cpu_found) { printf("%s: CPU core not detected\n", DEVNAME(sc)); return 1; } if (need_socram && !has_socram) { printf("%s: RAM core not provided\n", DEVNAME(sc)); return 1; } bwfm_chip_set_passive(sc); if (sc->sc_buscore_ops->bc_reset) { sc->sc_buscore_ops->bc_reset(sc); bwfm_chip_set_passive(sc); } if ((core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CR4)) != NULL) { bwfm_chip_tcm_ramsize(sc, core); bwfm_chip_tcm_rambase(sc); } else if ((core = bwfm_chip_get_core(sc, BWFM_AGENT_SYS_MEM)) != NULL) { bwfm_chip_sysmem_ramsize(sc, core); bwfm_chip_tcm_rambase(sc); } else if ((core = bwfm_chip_get_core(sc, BWFM_AGENT_INTERNAL_MEM)) != NULL) { bwfm_chip_socram_ramsize(sc, core); } core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_CHIPCOMMON); sc->sc_chip.ch_cc_caps = sc->sc_buscore_ops->bc_read(sc, core->co_base + BWFM_CHIP_REG_CAPABILITIES); sc->sc_chip.ch_cc_caps_ext = sc->sc_buscore_ops->bc_read(sc, core->co_base + BWFM_CHIP_REG_CAPABILITIES_EXT); core = bwfm_chip_get_pmu(sc); if (sc->sc_chip.ch_cc_caps & BWFM_CHIP_REG_CAPABILITIES_PMU) { sc->sc_chip.ch_pmucaps = sc->sc_buscore_ops->bc_read(sc, core->co_base + BWFM_CHIP_REG_PMUCAPABILITIES); sc->sc_chip.ch_pmurev = sc->sc_chip.ch_pmucaps & BWFM_CHIP_REG_PMUCAPABILITIES_REV_MASK; } if (sc->sc_buscore_ops->bc_setup) sc->sc_buscore_ops->bc_setup(sc); return 0; } struct bwfm_core * bwfm_chip_get_core(struct bwfm_softc *sc, int id) { struct bwfm_core *core; LIST_FOREACH(core, &sc->sc_chip.ch_list, co_link) { if (core->co_id == id) return core; } return NULL; } struct bwfm_core * bwfm_chip_get_pmu(struct bwfm_softc *sc) { struct bwfm_core *cc, *pmu; cc = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_CHIPCOMMON); if (cc->co_rev >= 35 && sc->sc_chip.ch_cc_caps_ext & BWFM_CHIP_REG_CAPABILITIES_EXT_AOB_PRESENT) { pmu = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_PMU); if (pmu) return pmu; } return cc; } /* Functions for the AI interconnect */ int bwfm_chip_ai_isup(struct bwfm_softc *sc, struct bwfm_core *core) { uint32_t ioctl, reset; ioctl = sc->sc_buscore_ops->bc_read(sc, core->co_wrapbase + BWFM_AGENT_IOCTL); reset = sc->sc_buscore_ops->bc_read(sc, core->co_wrapbase + BWFM_AGENT_RESET_CTL); if (((ioctl & (BWFM_AGENT_IOCTL_FGC | BWFM_AGENT_IOCTL_CLK)) == BWFM_AGENT_IOCTL_CLK) && ((reset & BWFM_AGENT_RESET_CTL_RESET) == 0)) return 1; return 0; } void bwfm_chip_ai_disable(struct bwfm_softc *sc, struct bwfm_core *core, uint32_t prereset, uint32_t reset) { uint32_t val; int i; val = sc->sc_buscore_ops->bc_read(sc, core->co_wrapbase + BWFM_AGENT_RESET_CTL); if ((val & BWFM_AGENT_RESET_CTL_RESET) == 0) { sc->sc_buscore_ops->bc_write(sc, core->co_wrapbase + BWFM_AGENT_IOCTL, prereset | BWFM_AGENT_IOCTL_FGC | BWFM_AGENT_IOCTL_CLK); sc->sc_buscore_ops->bc_read(sc, core->co_wrapbase + BWFM_AGENT_IOCTL); sc->sc_buscore_ops->bc_write(sc, core->co_wrapbase + BWFM_AGENT_RESET_CTL, BWFM_AGENT_RESET_CTL_RESET); delay(20); for (i = 300; i > 0; i--) { if (sc->sc_buscore_ops->bc_read(sc, core->co_wrapbase + BWFM_AGENT_RESET_CTL) == BWFM_AGENT_RESET_CTL_RESET) break; } if (i == 0) printf("%s: timeout on core reset\n", DEVNAME(sc)); } sc->sc_buscore_ops->bc_write(sc, core->co_wrapbase + BWFM_AGENT_IOCTL, reset | BWFM_AGENT_IOCTL_FGC | BWFM_AGENT_IOCTL_CLK); sc->sc_buscore_ops->bc_read(sc, core->co_wrapbase + BWFM_AGENT_IOCTL); } void bwfm_chip_ai_reset(struct bwfm_softc *sc, struct bwfm_core *core, uint32_t prereset, uint32_t reset, uint32_t postreset) { int i; bwfm_chip_ai_disable(sc, core, prereset, reset); for (i = 50; i > 0; i--) { if ((sc->sc_buscore_ops->bc_read(sc, core->co_wrapbase + BWFM_AGENT_RESET_CTL) & BWFM_AGENT_RESET_CTL_RESET) == 0) break; sc->sc_buscore_ops->bc_write(sc, core->co_wrapbase + BWFM_AGENT_RESET_CTL, 0); delay(60); } if (i == 0) printf("%s: timeout on core reset\n", DEVNAME(sc)); sc->sc_buscore_ops->bc_write(sc, core->co_wrapbase + BWFM_AGENT_IOCTL, postreset | BWFM_AGENT_IOCTL_CLK); sc->sc_buscore_ops->bc_read(sc, core->co_wrapbase + BWFM_AGENT_IOCTL); } void bwfm_chip_dmp_erom_scan(struct bwfm_softc *sc) { uint32_t erom, val, base, wrap; uint8_t type = 0; uint16_t id; uint8_t nmw, nsw, rev; struct bwfm_core *core; erom = sc->sc_buscore_ops->bc_read(sc, BWFM_CHIP_BASE + BWFM_CHIP_REG_EROMPTR); while (type != BWFM_DMP_DESC_EOT) { val = sc->sc_buscore_ops->bc_read(sc, erom); type = val & BWFM_DMP_DESC_MASK; erom += 4; if (type != BWFM_DMP_DESC_COMPONENT) continue; id = (val & BWFM_DMP_COMP_PARTNUM) >> BWFM_DMP_COMP_PARTNUM_S; val = sc->sc_buscore_ops->bc_read(sc, erom); type = val & BWFM_DMP_DESC_MASK; erom += 4; if (type != BWFM_DMP_DESC_COMPONENT) { printf("%s: not component descriptor\n", DEVNAME(sc)); return; } nmw = (val & BWFM_DMP_COMP_NUM_MWRAP) >> BWFM_DMP_COMP_NUM_MWRAP_S; nsw = (val & BWFM_DMP_COMP_NUM_SWRAP) >> BWFM_DMP_COMP_NUM_SWRAP_S; rev = (val & BWFM_DMP_COMP_REVISION) >> BWFM_DMP_COMP_REVISION_S; if (nmw + nsw == 0 && id != BWFM_AGENT_CORE_PMU) continue; if (bwfm_chip_dmp_get_regaddr(sc, &erom, &base, &wrap)) continue; core = kmem_alloc(sizeof(*core), KM_SLEEP); core->co_id = id; core->co_base = base; core->co_wrapbase = wrap; core->co_rev = rev; LIST_INSERT_HEAD(&sc->sc_chip.ch_list, core, co_link); } } int bwfm_chip_dmp_get_regaddr(struct bwfm_softc *sc, uint32_t *erom, uint32_t *base, uint32_t *wrap) { uint8_t type = 0, mpnum __unused = 0; uint8_t stype, sztype, wraptype; uint32_t val; *base = 0; *wrap = 0; val = sc->sc_buscore_ops->bc_read(sc, *erom); type = val & BWFM_DMP_DESC_MASK; if (type == BWFM_DMP_DESC_MASTER_PORT) { mpnum = (val & BWFM_DMP_MASTER_PORT_NUM) >> BWFM_DMP_MASTER_PORT_NUM_S; wraptype = BWFM_DMP_SLAVE_TYPE_MWRAP; *erom += 4; } else if ((type & ~BWFM_DMP_DESC_ADDRSIZE_GT32) == BWFM_DMP_DESC_ADDRESS) wraptype = BWFM_DMP_SLAVE_TYPE_SWRAP; else return 1; do { do { val = sc->sc_buscore_ops->bc_read(sc, *erom); type = val & BWFM_DMP_DESC_MASK; if (type == BWFM_DMP_DESC_COMPONENT) return 0; if (type == BWFM_DMP_DESC_EOT) return 1; *erom += 4; } while ((type & ~BWFM_DMP_DESC_ADDRSIZE_GT32) != BWFM_DMP_DESC_ADDRESS); if (type & BWFM_DMP_DESC_ADDRSIZE_GT32) *erom += 4; sztype = (val & BWFM_DMP_SLAVE_SIZE_TYPE) >> BWFM_DMP_SLAVE_SIZE_TYPE_S; if (sztype == BWFM_DMP_SLAVE_SIZE_DESC) { val = sc->sc_buscore_ops->bc_read(sc, *erom); type = val & BWFM_DMP_DESC_MASK; if (type & BWFM_DMP_DESC_ADDRSIZE_GT32) *erom += 8; else *erom += 4; } if (sztype != BWFM_DMP_SLAVE_SIZE_4K) continue; stype = (val & BWFM_DMP_SLAVE_TYPE) >> BWFM_DMP_SLAVE_TYPE_S; if (*base == 0 && stype == BWFM_DMP_SLAVE_TYPE_SLAVE) *base = val & BWFM_DMP_SLAVE_ADDR_BASE; if (*wrap == 0 && stype == wraptype) *wrap = val & BWFM_DMP_SLAVE_ADDR_BASE; } while (*base == 0 || *wrap == 0); return 0; } /* Core configuration */ int bwfm_chip_set_active(struct bwfm_softc *sc, const uint32_t rstvec) { if (bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CR4) != NULL) return bwfm_chip_cr4_set_active(sc, rstvec); if (bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CA7) != NULL) return bwfm_chip_ca7_set_active(sc, rstvec); if (bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CM3) != NULL) return bwfm_chip_cm3_set_active(sc); return 1; } void bwfm_chip_set_passive(struct bwfm_softc *sc) { if (bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CR4) != NULL) { bwfm_chip_cr4_set_passive(sc); return; } if (bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CA7) != NULL) { bwfm_chip_ca7_set_passive(sc); return; } if (bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CM3) != NULL) { bwfm_chip_cm3_set_passive(sc); return; } } int bwfm_chip_cr4_set_active(struct bwfm_softc *sc, const uint32_t rstvec) { struct bwfm_core *core; sc->sc_buscore_ops->bc_activate(sc, rstvec); core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CR4); sc->sc_chip.ch_core_reset(sc, core, BWFM_AGENT_IOCTL_ARMCR4_CPUHALT, 0, 0); return 0; } void bwfm_chip_cr4_set_passive(struct bwfm_softc *sc) { struct bwfm_core *core; uint32_t val; core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CR4); val = sc->sc_buscore_ops->bc_read(sc, core->co_wrapbase + BWFM_AGENT_IOCTL); sc->sc_chip.ch_core_reset(sc, core, val & BWFM_AGENT_IOCTL_ARMCR4_CPUHALT, BWFM_AGENT_IOCTL_ARMCR4_CPUHALT, BWFM_AGENT_IOCTL_ARMCR4_CPUHALT); core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_80211); sc->sc_chip.ch_core_reset(sc, core, BWFM_AGENT_D11_IOCTL_PHYRESET | BWFM_AGENT_D11_IOCTL_PHYCLOCKEN, BWFM_AGENT_D11_IOCTL_PHYCLOCKEN, BWFM_AGENT_D11_IOCTL_PHYCLOCKEN); } int bwfm_chip_ca7_set_active(struct bwfm_softc *sc, const uint32_t rstvec) { struct bwfm_core *core; sc->sc_buscore_ops->bc_activate(sc, rstvec); core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CA7); sc->sc_chip.ch_core_reset(sc, core, BWFM_AGENT_IOCTL_ARMCR4_CPUHALT, 0, 0); return 0; } void bwfm_chip_ca7_set_passive(struct bwfm_softc *sc) { struct bwfm_core *core; uint32_t val; core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CA7); val = sc->sc_buscore_ops->bc_read(sc, core->co_wrapbase + BWFM_AGENT_IOCTL); sc->sc_chip.ch_core_reset(sc, core, val & BWFM_AGENT_IOCTL_ARMCR4_CPUHALT, BWFM_AGENT_IOCTL_ARMCR4_CPUHALT, BWFM_AGENT_IOCTL_ARMCR4_CPUHALT); core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_80211); sc->sc_chip.ch_core_reset(sc, core, BWFM_AGENT_D11_IOCTL_PHYRESET | BWFM_AGENT_D11_IOCTL_PHYCLOCKEN, BWFM_AGENT_D11_IOCTL_PHYCLOCKEN, BWFM_AGENT_D11_IOCTL_PHYCLOCKEN); } int bwfm_chip_cm3_set_active(struct bwfm_softc *sc) { struct bwfm_core *core; core = bwfm_chip_get_core(sc, BWFM_AGENT_INTERNAL_MEM); if (!sc->sc_chip.ch_core_isup(sc, core)) return 1; sc->sc_buscore_ops->bc_activate(sc, 0); core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CM3); sc->sc_chip.ch_core_reset(sc, core, 0, 0, 0); return 0; } void bwfm_chip_cm3_set_passive(struct bwfm_softc *sc) { struct bwfm_core *core; core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CM3); sc->sc_chip.ch_core_disable(sc, core, 0, 0); core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_80211); sc->sc_chip.ch_core_reset(sc, core, BWFM_AGENT_D11_IOCTL_PHYRESET | BWFM_AGENT_D11_IOCTL_PHYCLOCKEN, BWFM_AGENT_D11_IOCTL_PHYCLOCKEN, BWFM_AGENT_D11_IOCTL_PHYCLOCKEN); core = bwfm_chip_get_core(sc, BWFM_AGENT_INTERNAL_MEM); sc->sc_chip.ch_core_reset(sc, core, 0, 0, 0); if (sc->sc_chip.ch_chip == BRCM_CC_43430_CHIP_ID) { sc->sc_buscore_ops->bc_write(sc, core->co_base + BWFM_SOCRAM_BANKIDX, 3); sc->sc_buscore_ops->bc_write(sc, core->co_base + BWFM_SOCRAM_BANKPDA, 0); } } int bwfm_chip_sr_capable(struct bwfm_softc *sc) { struct bwfm_core *core; uint32_t reg; if (sc->sc_chip.ch_pmurev < 17) return 0; switch (sc->sc_chip.ch_chip) { case BRCM_CC_4345_CHIP_ID: case BRCM_CC_4354_CHIP_ID: case BRCM_CC_4356_CHIP_ID: core = bwfm_chip_get_pmu(sc); sc->sc_buscore_ops->bc_write(sc, core->co_base + BWFM_CHIP_REG_CHIPCONTROL_ADDR, 3); reg = sc->sc_buscore_ops->bc_read(sc, core->co_base + BWFM_CHIP_REG_CHIPCONTROL_DATA); return (reg & (1 << 2)) != 0; case BRCM_CC_43241_CHIP_ID: case BRCM_CC_4335_CHIP_ID: case BRCM_CC_4339_CHIP_ID: core = bwfm_chip_get_pmu(sc); sc->sc_buscore_ops->bc_write(sc, core->co_base + BWFM_CHIP_REG_CHIPCONTROL_ADDR, 3); reg = sc->sc_buscore_ops->bc_read(sc, core->co_base + BWFM_CHIP_REG_CHIPCONTROL_DATA); return reg != 0; case BRCM_CC_43430_CHIP_ID: core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_CHIPCOMMON); reg = sc->sc_buscore_ops->bc_read(sc, core->co_base + BWFM_CHIP_REG_SR_CONTROL1); return reg != 0; default: core = bwfm_chip_get_pmu(sc); reg = sc->sc_buscore_ops->bc_read(sc, core->co_base + BWFM_CHIP_REG_PMUCAPABILITIES_EXT); if ((reg & BWFM_CHIP_REG_PMUCAPABILITIES_SR_SUPP) == 0) return 0; reg = sc->sc_buscore_ops->bc_read(sc, core->co_base + BWFM_CHIP_REG_RETENTION_CTL); return (reg & (BWFM_CHIP_REG_RETENTION_CTL_MACPHY_DIS | BWFM_CHIP_REG_RETENTION_CTL_LOGIC_DIS)) == 0; } } /* RAM size helpers */ void bwfm_chip_socram_ramsize(struct bwfm_softc *sc, struct bwfm_core *core) { uint32_t coreinfo, nb, lss, banksize, bankinfo; uint32_t ramsize = 0, srsize = 0; int i; if (!sc->sc_chip.ch_core_isup(sc, core)) sc->sc_chip.ch_core_reset(sc, core, 0, 0, 0); coreinfo = sc->sc_buscore_ops->bc_read(sc, core->co_base + BWFM_SOCRAM_COREINFO); nb = (coreinfo & BWFM_SOCRAM_COREINFO_SRNB_MASK) >> BWFM_SOCRAM_COREINFO_SRNB_SHIFT; if (core->co_rev <= 7 || core->co_rev == 12) { banksize = coreinfo & BWFM_SOCRAM_COREINFO_SRBSZ_MASK; lss = (coreinfo & BWFM_SOCRAM_COREINFO_LSS_MASK) >> BWFM_SOCRAM_COREINFO_LSS_SHIFT; if (lss != 0) nb--; ramsize = nb * (1 << (banksize + BWFM_SOCRAM_COREINFO_SRBSZ_BASE)); if (lss != 0) ramsize += (1 << ((lss - 1) + BWFM_SOCRAM_COREINFO_SRBSZ_BASE)); } else { for (i = 0; i < nb; i++) { sc->sc_buscore_ops->bc_write(sc, core->co_base + BWFM_SOCRAM_BANKIDX, (BWFM_SOCRAM_BANKIDX_MEMTYPE_RAM << BWFM_SOCRAM_BANKIDX_MEMTYPE_SHIFT) | i); bankinfo = sc->sc_buscore_ops->bc_read(sc, core->co_base + BWFM_SOCRAM_BANKINFO); banksize = ((bankinfo & BWFM_SOCRAM_BANKINFO_SZMASK) + 1) * BWFM_SOCRAM_BANKINFO_SZBASE; ramsize += banksize; if (bankinfo & BWFM_SOCRAM_BANKINFO_RETNTRAM_MASK) srsize += banksize; } } switch (sc->sc_chip.ch_chip) { case BRCM_CC_4334_CHIP_ID: if (sc->sc_chip.ch_chiprev < 2) srsize = 32 * 1024; break; case BRCM_CC_43430_CHIP_ID: srsize = 64 * 1024; break; default: break; } sc->sc_chip.ch_ramsize = ramsize; sc->sc_chip.ch_srsize = srsize; } void bwfm_chip_sysmem_ramsize(struct bwfm_softc *sc, struct bwfm_core *core) { uint32_t coreinfo, nb, banksize, bankinfo; uint32_t ramsize = 0; int i; if (!sc->sc_chip.ch_core_isup(sc, core)) sc->sc_chip.ch_core_reset(sc, core, 0, 0, 0); coreinfo = sc->sc_buscore_ops->bc_read(sc, core->co_base + BWFM_SOCRAM_COREINFO); nb = (coreinfo & BWFM_SOCRAM_COREINFO_SRNB_MASK) >> BWFM_SOCRAM_COREINFO_SRNB_SHIFT; for (i = 0; i < nb; i++) { sc->sc_buscore_ops->bc_write(sc, core->co_base + BWFM_SOCRAM_BANKIDX, (BWFM_SOCRAM_BANKIDX_MEMTYPE_RAM << BWFM_SOCRAM_BANKIDX_MEMTYPE_SHIFT) | i); bankinfo = sc->sc_buscore_ops->bc_read(sc, core->co_base + BWFM_SOCRAM_BANKINFO); banksize = ((bankinfo & BWFM_SOCRAM_BANKINFO_SZMASK) + 1) * BWFM_SOCRAM_BANKINFO_SZBASE; ramsize += banksize; } sc->sc_chip.ch_ramsize = ramsize; } void bwfm_chip_tcm_ramsize(struct bwfm_softc *sc, struct bwfm_core *core) { uint32_t cap, nab, nbb, totb, bxinfo, ramsize = 0; int i; cap = sc->sc_buscore_ops->bc_read(sc, core->co_base + BWFM_ARMCR4_CAP); nab = (cap & BWFM_ARMCR4_CAP_TCBANB_MASK) >> BWFM_ARMCR4_CAP_TCBANB_SHIFT; nbb = (cap & BWFM_ARMCR4_CAP_TCBBNB_MASK) >> BWFM_ARMCR4_CAP_TCBBNB_SHIFT; totb = nab + nbb; for (i = 0; i < totb; i++) { sc->sc_buscore_ops->bc_write(sc, core->co_base + BWFM_ARMCR4_BANKIDX, i); bxinfo = sc->sc_buscore_ops->bc_read(sc, core->co_base + BWFM_ARMCR4_BANKINFO); ramsize += ((bxinfo & BWFM_ARMCR4_BANKINFO_BSZ_MASK) + 1) * BWFM_ARMCR4_BANKINFO_BSZ_MULT; } sc->sc_chip.ch_ramsize = ramsize; } void bwfm_chip_tcm_rambase(struct bwfm_softc *sc) { switch (sc->sc_chip.ch_chip) { case BRCM_CC_4345_CHIP_ID: sc->sc_chip.ch_rambase = 0x198000; break; case BRCM_CC_4335_CHIP_ID: case BRCM_CC_4339_CHIP_ID: case BRCM_CC_4350_CHIP_ID: case BRCM_CC_4354_CHIP_ID: case BRCM_CC_4356_CHIP_ID: case BRCM_CC_43567_CHIP_ID: case BRCM_CC_43569_CHIP_ID: case BRCM_CC_43570_CHIP_ID: case BRCM_CC_4358_CHIP_ID: case BRCM_CC_4359_CHIP_ID: case BRCM_CC_43602_CHIP_ID: case BRCM_CC_4371_CHIP_ID: sc->sc_chip.ch_rambase = 0x180000; break; case BRCM_CC_43465_CHIP_ID: case BRCM_CC_43525_CHIP_ID: case BRCM_CC_4365_CHIP_ID: case BRCM_CC_4366_CHIP_ID: sc->sc_chip.ch_rambase = 0x200000; break; case CY_CC_4373_CHIP_ID: sc->sc_chip.ch_rambase = 0x160000; break; default: printf("%s: unknown chip: %d\n", DEVNAME(sc), sc->sc_chip.ch_chip); break; } } /* BCDC protocol implementation */ int bwfm_proto_bcdc_query_dcmd(struct bwfm_softc *sc, int ifidx, int cmd, char *buf, size_t *len) { struct bwfm_proto_bcdc_dcmd *dcmd; size_t size = sizeof(dcmd->hdr) + *len; int reqid; int ret = 1; reqid = sc->sc_bcdc_reqid++; dcmd = kmem_zalloc(sizeof(*dcmd), KM_SLEEP); if (*len > sizeof(dcmd->buf)) goto err; dcmd->hdr.cmd = htole32(cmd); dcmd->hdr.len = htole32(*len); dcmd->hdr.flags |= BWFM_BCDC_DCMD_GET; dcmd->hdr.flags |= BWFM_BCDC_DCMD_ID_SET(reqid); dcmd->hdr.flags |= BWFM_BCDC_DCMD_IF_SET(ifidx); dcmd->hdr.flags = htole32(dcmd->hdr.flags); memcpy(&dcmd->buf, buf, *len); if (sc->sc_bus_ops->bs_txctl(sc, (void *)dcmd, sizeof(dcmd->hdr) + *len)) { DPRINTF(("%s: tx failed\n", DEVNAME(sc))); goto err; } do { if (sc->sc_bus_ops->bs_rxctl(sc, (void *)dcmd, &size)) { DPRINTF(("%s: rx failed\n", DEVNAME(sc))); goto err; } dcmd->hdr.cmd = le32toh(dcmd->hdr.cmd); dcmd->hdr.len = le32toh(dcmd->hdr.len); dcmd->hdr.flags = le32toh(dcmd->hdr.flags); dcmd->hdr.status = le32toh(dcmd->hdr.status); } while (BWFM_BCDC_DCMD_ID_GET(dcmd->hdr.flags) != reqid); if (BWFM_BCDC_DCMD_ID_GET(dcmd->hdr.flags) != reqid) { printf("%s: unexpected request id\n", DEVNAME(sc)); goto err; } if (buf) { if (size < *len) *len = size; memcpy(buf, dcmd->buf, *len); } if (dcmd->hdr.flags & BWFM_BCDC_DCMD_ERROR) ret = dcmd->hdr.status; else ret = 0; err: kmem_free(dcmd, sizeof(*dcmd)); return ret; } int bwfm_proto_bcdc_set_dcmd(struct bwfm_softc *sc, int ifidx, int cmd, char *buf, size_t len) { struct bwfm_proto_bcdc_dcmd *dcmd; size_t size = sizeof(dcmd->hdr) + len; int ret = 1, reqid; reqid = sc->sc_bcdc_reqid++; dcmd = kmem_zalloc(sizeof(*dcmd), KM_SLEEP); if (len > sizeof(dcmd->buf)) goto err; dcmd->hdr.cmd = htole32(cmd); dcmd->hdr.len = htole32(len); dcmd->hdr.flags |= BWFM_BCDC_DCMD_SET; dcmd->hdr.flags |= BWFM_BCDC_DCMD_ID_SET(reqid); dcmd->hdr.flags |= BWFM_BCDC_DCMD_IF_SET(ifidx); dcmd->hdr.flags = htole32(dcmd->hdr.flags); memcpy(&dcmd->buf, buf, len); if (sc->sc_bus_ops->bs_txctl(sc, (void *)dcmd, size)) { DPRINTF(("%s: tx failed\n", DEVNAME(sc))); goto err; } do { if (sc->sc_bus_ops->bs_rxctl(sc, (void *)dcmd, &size)) { DPRINTF(("%s: rx failed\n", DEVNAME(sc))); goto err; } dcmd->hdr.cmd = le32toh(dcmd->hdr.cmd); dcmd->hdr.len = le32toh(dcmd->hdr.len); dcmd->hdr.flags = le32toh(dcmd->hdr.flags); dcmd->hdr.status = le32toh(dcmd->hdr.status); } while (BWFM_BCDC_DCMD_ID_GET(dcmd->hdr.flags) != reqid); if (BWFM_BCDC_DCMD_ID_GET(dcmd->hdr.flags) != reqid) { printf("%s: unexpected request id\n", DEVNAME(sc)); goto err; } if (dcmd->hdr.flags & BWFM_BCDC_DCMD_ERROR) return dcmd->hdr.status; ret = 0; err: kmem_free(dcmd, sizeof(*dcmd)); return ret; } /* FW Variable code */ int bwfm_fwvar_cmd_get_data(struct bwfm_softc *sc, int cmd, void *data, size_t len) { return sc->sc_proto_ops->proto_query_dcmd(sc, 0, cmd, data, &len); } int bwfm_fwvar_cmd_set_data(struct bwfm_softc *sc, int cmd, void *data, size_t len) { return sc->sc_proto_ops->proto_set_dcmd(sc, 0, cmd, data, len); } int bwfm_fwvar_cmd_get_int(struct bwfm_softc *sc, int cmd, uint32_t *data) { int ret; ret = bwfm_fwvar_cmd_get_data(sc, cmd, data, sizeof(*data)); *data = le32toh(*data); return ret; } int bwfm_fwvar_cmd_set_int(struct bwfm_softc *sc, int cmd, uint32_t data) { data = htole32(data); return bwfm_fwvar_cmd_set_data(sc, cmd, &data, sizeof(data)); } int bwfm_fwvar_var_get_data(struct bwfm_softc *sc, const char *name, void *data, size_t len) { char *buf; int ret; buf = kmem_alloc(strlen(name) + 1 + len, KM_SLEEP); memcpy(buf, name, strlen(name) + 1); memcpy(buf + strlen(name) + 1, data, len); ret = bwfm_fwvar_cmd_get_data(sc, BWFM_C_GET_VAR, buf, strlen(name) + 1 + len); memcpy(data, buf, len); kmem_free(buf, strlen(name) + 1 + len); return ret; } int bwfm_fwvar_var_set_data(struct bwfm_softc *sc, const char *name, void *data, size_t len) { char *buf; int ret; buf = kmem_alloc(strlen(name) + 1 + len, KM_SLEEP); memcpy(buf, name, strlen(name) + 1); memcpy(buf + strlen(name) + 1, data, len); ret = bwfm_fwvar_cmd_set_data(sc, BWFM_C_SET_VAR, buf, strlen(name) + 1 + len); kmem_free(buf, strlen(name) + 1 + len); return ret; } int bwfm_fwvar_var_get_int(struct bwfm_softc *sc, const char *name, uint32_t *data) { int ret; ret = bwfm_fwvar_var_get_data(sc, name, data, sizeof(*data)); *data = le32toh(*data); return ret; } int bwfm_fwvar_var_set_int(struct bwfm_softc *sc, const char *name, uint32_t data) { data = htole32(data); return bwfm_fwvar_var_set_data(sc, name, &data, sizeof(data)); } /* 802.11 code */ void bwfm_scan(struct bwfm_softc *sc) { struct bwfm_escan_params *params; uint32_t nssid = 0, nchannel = 0; size_t params_size; #if 0 /* Active scan is used for scanning for an SSID */ bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_PASSIVE_SCAN, 0); #endif bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_PASSIVE_SCAN, 1); params_size = sizeof(*params); params_size += sizeof(uint32_t) * ((nchannel + 1) / 2); params_size += sizeof(struct bwfm_ssid) * nssid; params = kmem_zalloc(params_size, KM_SLEEP); memset(params->scan_params.bssid, 0xff, sizeof(params->scan_params.bssid)); params->scan_params.bss_type = 2; params->scan_params.nprobes = htole32(-1); params->scan_params.active_time = htole32(-1); params->scan_params.passive_time = htole32(-1); params->scan_params.home_time = htole32(-1); params->version = htole32(BWFM_ESCAN_REQ_VERSION); params->action = htole16(WL_ESCAN_ACTION_START); params->sync_id = htole16(0x1234); #if 0 /* Scan a specific channel */ params->scan_params.channel_list[0] = htole16( (1 & 0xff) << 0 | (3 & 0x3) << 8 | (2 & 0x3) << 10 | (2 & 0x3) << 12 ); params->scan_params.channel_num = htole32( (1 & 0xffff) << 0 ); #endif bwfm_fwvar_var_set_data(sc, "escan", params, params_size); kmem_free(params, params_size); } static __inline int bwfm_iswpaoui(const uint8_t *frm) { return frm[1] > 3 && le32dec(frm+2) == ((WPA_OUI_TYPE<<24)|WPA_OUI); } /* * Derive wireless security settings from WPA/RSN IE. */ static uint32_t bwfm_get_wsec(struct bwfm_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; uint8_t *wpa = ic->ic_opt_ie; KASSERT(ic->ic_opt_ie_len > 0); if (wpa[0] != IEEE80211_ELEMID_RSN) { if (ic->ic_opt_ie_len < 12) return BWFM_WSEC_NONE; /* non-RSN IE, expect that we are doing WPA1 */ if ((ic->ic_flags & IEEE80211_F_WPA1) == 0) return BWFM_WSEC_NONE; /* Must contain WPA OUI */ if (!bwfm_iswpaoui(wpa)) return BWFM_WSEC_NONE; switch (le32dec(wpa + 8)) { case ((WPA_CSE_TKIP<<24)|WPA_OUI): return BWFM_WSEC_TKIP; case ((WPA_CSE_CCMP<<24)|WPA_OUI): return BWFM_WSEC_AES; default: return BWFM_WSEC_NONE; } } else { if (ic->ic_opt_ie_len < 14) return BWFM_WSEC_NONE; /* RSN IE, expect that we are doing WPA2 */ if ((ic->ic_flags & IEEE80211_F_WPA2) == 0) return BWFM_WSEC_NONE; switch (le32dec(wpa + 10)) { case ((RSN_CSE_TKIP<<24)|RSN_OUI): return BWFM_WSEC_TKIP; case ((RSN_CSE_CCMP<<24)|RSN_OUI): return BWFM_WSEC_AES; default: return BWFM_WSEC_NONE; } } } void bwfm_connect(struct bwfm_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_node *ni = ic->ic_bss; struct bwfm_ext_join_params *params; if (ic->ic_flags & IEEE80211_F_WPA) { uint32_t wsec = 0; uint32_t wpa = 0; if (ic->ic_opt_ie_len) bwfm_fwvar_var_set_data(sc, "wpaie", ic->ic_opt_ie, ic->ic_opt_ie_len); if (ic->ic_flags & IEEE80211_F_WPA1) wpa |= BWFM_WPA_AUTH_WPA_PSK; if (ic->ic_flags & IEEE80211_F_WPA2) wpa |= BWFM_WPA_AUTH_WPA2_PSK; wsec |= bwfm_get_wsec(sc); DPRINTF(("%s: WPA enabled, ic_flags = 0x%x, wpa 0x%x, wsec 0x%x\n", DEVNAME(sc), ic->ic_flags, wpa, wsec)); bwfm_fwvar_var_set_int(sc, "wpa_auth", wpa); bwfm_fwvar_var_set_int(sc, "wsec", wsec); } else { bwfm_fwvar_var_set_int(sc, "wpa_auth", BWFM_WPA_AUTH_DISABLED); bwfm_fwvar_var_set_int(sc, "wsec", BWFM_WSEC_NONE); } bwfm_fwvar_var_set_int(sc, "auth", BWFM_AUTH_OPEN); bwfm_fwvar_var_set_int(sc, "mfp", BWFM_MFP_NONE); if (ni->ni_esslen && ni->ni_esslen < BWFM_MAX_SSID_LEN) { params = kmem_zalloc(sizeof(*params), KM_SLEEP); memcpy(params->ssid.ssid, ni->ni_essid, ni->ni_esslen); params->ssid.len = htole32(ni->ni_esslen); memcpy(params->assoc.bssid, ni->ni_bssid, sizeof(params->assoc.bssid)); params->scan.scan_type = -1; params->scan.nprobes = htole32(-1); params->scan.active_time = htole32(-1); params->scan.passive_time = htole32(-1); params->scan.home_time = htole32(-1); if (bwfm_fwvar_var_set_data(sc, "join", params, sizeof(*params))) { struct bwfm_join_params join; memset(&join, 0, sizeof(join)); memcpy(join.ssid.ssid, ni->ni_essid, ni->ni_esslen); join.ssid.len = htole32(ni->ni_esslen); memcpy(join.assoc.bssid, ni->ni_bssid, sizeof(join.assoc.bssid)); bwfm_fwvar_cmd_set_data(sc, BWFM_C_SET_SSID, &join, sizeof(join)); } kmem_free(params, sizeof(*params)); } } void bwfm_get_sta_info(struct bwfm_softc *sc, struct ifmediareq *ifmr) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_node *ni = ic->ic_bss; struct bwfm_sta_info sta; uint32_t flags, txrate; memset(&sta, 0, sizeof(sta)); memcpy(&sta, ni->ni_macaddr, sizeof(ni->ni_macaddr)); if (bwfm_fwvar_var_get_data(sc, "sta_info", &sta, sizeof(sta))) return; if (!IEEE80211_ADDR_EQ(ni->ni_macaddr, sta.ea)) return; if (le16toh(sta.ver) < 4) return; flags = le32toh(sta.flags); if ((flags & BWFM_STA_SCBSTATS) == 0) return; txrate = le32toh(sta.tx_rate); if (txrate == 0xffffffff) return; if ((flags & BWFM_STA_VHT_CAP) != 0) { ifmr->ifm_active &= ~IFM_TMASK; ifmr->ifm_active |= IFM_IEEE80211_VHT; ifmr->ifm_active &= ~IFM_MMASK; ifmr->ifm_active |= IFM_IEEE80211_11AC; } else if ((flags & BWFM_STA_N_CAP) != 0) { ifmr->ifm_active &= ~IFM_TMASK; ifmr->ifm_active |= IFM_IEEE80211_MCS; ifmr->ifm_active &= ~IFM_MMASK; if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) ifmr->ifm_active |= IFM_IEEE80211_11NG; else ifmr->ifm_active |= IFM_IEEE80211_11NA; } } void bwfm_rx(struct bwfm_softc *sc, struct mbuf *m) { struct ieee80211com *ic = &sc->sc_ic; struct ifnet *ifp = ic->ic_ifp; struct bwfm_event *e = mtod(m, struct bwfm_event *); if (m->m_len >= sizeof(e->ehdr) && ntohs(e->ehdr.ether_type) == BWFM_ETHERTYPE_LINK_CTL && memcmp(BWFM_BRCM_OUI, e->hdr.oui, sizeof(e->hdr.oui)) == 0 && ntohs(e->hdr.usr_subtype) == BWFM_BRCM_SUBTYPE_EVENT) { bwfm_rx_event(sc, m); // m_freem(m); return; } m_set_rcvif(m, ifp); if_percpuq_enqueue(ifp->if_percpuq, m); } void bwfm_rx_event(struct bwfm_softc *sc, struct mbuf *m) { struct bwfm_task *t; t = pool_cache_get(sc->sc_freetask, PR_NOWAIT); if (t == NULL) { m_freem(m); printf("%s: no free tasks\n", DEVNAME(sc)); return; } t->t_sc = sc; t->t_cmd = BWFM_TASK_RX_EVENT; t->t_mbuf = m; workqueue_enqueue(sc->sc_taskq, (struct work*)t, NULL); } void bwfm_rx_event_cb(struct bwfm_softc *sc, struct mbuf *m) { struct ieee80211com *ic = &sc->sc_ic; struct bwfm_event *e = mtod(m, void *); size_t len = m->m_len; int s; DPRINTF(("%s: event %p len %lu datalen %u code %u status %u" " reason %u\n", __func__, e, len, ntohl(e->msg.datalen), ntohl(e->msg.event_type), ntohl(e->msg.status), ntohl(e->msg.reason))); if (ntohl(e->msg.event_type) >= BWFM_E_LAST) { m_freem(m); return; } switch (ntohl(e->msg.event_type)) { case BWFM_E_ESCAN_RESULT: { struct bwfm_escan_results *res = (void *)&e[1]; struct bwfm_bss_info *bss; int i; if (ntohl(e->msg.status) != BWFM_E_STATUS_PARTIAL) { /* Scan complete */ s = splnet(); if (ic->ic_opmode != IEEE80211_M_MONITOR) ieee80211_end_scan(ic); splx(s); break; } len -= sizeof(*e); if (len < sizeof(*res) || len < le32toh(res->buflen)) { m_freem(m); printf("%s: results too small\n", DEVNAME(sc)); return; } len -= sizeof(*res); if (len < le16toh(res->bss_count) * sizeof(struct bwfm_bss_info)) { m_freem(m); printf("%s: results too small\n", DEVNAME(sc)); return; } bss = &res->bss_info[0]; for (i = 0; i < le16toh(res->bss_count); i++) { /* Fix alignment of bss_info */ union { struct bwfm_bss_info bss_info; uint8_t padding[BWFM_BSS_INFO_BUFLEN]; } bss_buf; if (len > sizeof(bss_buf)) { printf("%s: bss_info buffer too big\n", DEVNAME(sc)); } else { memcpy(&bss_buf, &res->bss_info[i], len); bwfm_scan_node(sc, &bss_buf.bss_info, len); } len -= sizeof(*bss) + le32toh(bss->length); bss = (void *)(((uintptr_t)bss) + le32toh(bss->length)); if (len <= 0) break; } break; } case BWFM_E_SET_SSID: if (ntohl(e->msg.status) == BWFM_E_STATUS_SUCCESS) { ieee80211_new_state(ic, IEEE80211_S_RUN, -1); } else { ieee80211_new_state(ic, IEEE80211_S_SCAN, -1); } break; case BWFM_E_ASSOC: if (ntohl(e->msg.status) == BWFM_E_STATUS_SUCCESS) { ieee80211_new_state(ic, IEEE80211_S_ASSOC, -1); } else { ieee80211_new_state(ic, IEEE80211_S_SCAN, -1); } break; case BWFM_E_LINK: if (ntohl(e->msg.status) == BWFM_E_STATUS_SUCCESS && ntohl(e->msg.reason) == 0) break; /* Link status has changed */ ieee80211_new_state(ic, IEEE80211_S_SCAN, -1); break; default: break; } m_freem(m); } void bwfm_scan_node(struct bwfm_softc *sc, struct bwfm_bss_info *bss, size_t len) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_frame wh; struct ieee80211_scanparams scan; uint8_t rates[sizeof(bss->rates) + 2]; uint8_t ssid[sizeof(bss->ssid) + 2]; uint8_t *frm, *sfrm, *efrm; uint64_t tsf; tsf = 0; sfrm = ((uint8_t *)bss) + le16toh(bss->ie_offset); efrm = sfrm + le32toh(bss->ie_length); /* Fake a wireless header with the scan result's BSSID */ memset(&wh, 0, sizeof(wh)); IEEE80211_ADDR_COPY(wh.i_addr2, bss->bssid); IEEE80211_ADDR_COPY(wh.i_addr3, bss->bssid); if (efrm - sfrm < 12) { ic->ic_stats.is_rx_elem_toosmall++; return; } rates[0] = 0; rates[1] = le32toh(bss->nrates); memcpy(&rates[2], bss->rates, sizeof(bss->rates)); ssid[0] = 0; ssid[1] = bss->ssid_len; memcpy(&ssid[2], bss->ssid, sizeof(bss->ssid)); /* Build scan result */ memset(&scan, 0, sizeof(scan)); scan.sp_tstamp = (uint8_t *)&tsf; scan.sp_bintval = le16toh(bss->beacon_period); scan.sp_capinfo = le16toh(bss->capability); scan.sp_bchan = ieee80211_chan2ieee(ic, ic->ic_curchan); scan.sp_chan = scan.sp_bchan; scan.sp_rates = rates; scan.sp_ssid = ssid; for (frm = sfrm; frm < efrm; frm += frm[1] + 2) { switch (frm[0]) { case IEEE80211_ELEMID_COUNTRY: scan.sp_country = frm; break; case IEEE80211_ELEMID_FHPARMS: if (ic->ic_phytype == IEEE80211_T_FH) { if (frm + 6 >= efrm) break; scan.sp_fhdwell = le16dec(&frm[2]); scan.sp_chan = IEEE80211_FH_CHAN(frm[4], frm[5]); scan.sp_fhindex = frm[6]; } break; case IEEE80211_ELEMID_DSPARMS: if (ic->ic_phytype != IEEE80211_T_FH) { if (frm + 2 >= efrm) break; scan.sp_chan = frm[2]; } break; case IEEE80211_ELEMID_TIM: scan.sp_tim = frm; scan.sp_timoff = frm - sfrm; break; case IEEE80211_ELEMID_XRATES: scan.sp_xrates = frm; break; case IEEE80211_ELEMID_ERP: if (frm + 1 >= efrm) break; if (frm[1] != 1) { ic->ic_stats.is_rx_elem_toobig++; break; } scan.sp_erp = frm[2]; break; case IEEE80211_ELEMID_RSN: scan.sp_wpa = frm; break; case IEEE80211_ELEMID_VENDOR: if (frm + 1 >= efrm) break; if (frm + frm[1] + 2 >= efrm) break; if (bwfm_iswpaoui(frm)) scan.sp_wpa = frm; break; } if (frm + 1 >= efrm) break; } if (ic->ic_flags & IEEE80211_F_SCAN) ieee80211_add_scan(ic, &scan, &wh, IEEE80211_FC0_SUBTYPE_BEACON, le32toh(bss->rssi), 0); }