Index: head/sys/dev/ath/if_ath_lna_div.c =================================================================== --- head/sys/dev/ath/if_ath_lna_div.c (revision 297404) +++ head/sys/dev/ath/if_ath_lna_div.c (revision 297405) @@ -1,1018 +1,1018 @@ /*- * Copyright (c) 2013 Adrian Chadd * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any * redistribution must be conditioned upon including a substantially * similar Disclaimer requirement for further binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGES. * * $FreeBSD$ */ #include __FBSDID("$FreeBSD$"); /* * This module handles LNA diversity for those chips which implement LNA * mixing (AR9285/AR9485.) */ #include "opt_ath.h" #include "opt_inet.h" #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* XXX for ether_sprintf */ #include #include #ifdef INET #include #include #endif #include #include #include /* Linux compability macros */ /* * XXX these don't handle rounding, underflow, overflow, wrapping! */ #define msecs_to_jiffies(a) ( (a) * hz / 1000 ) /* * Methods which are required */ /* * Attach the LNA diversity to the given interface */ int ath_lna_div_attach(struct ath_softc *sc) { struct if_ath_ant_comb_state *ss; HAL_ANT_COMB_CONFIG div_ant_conf; /* Only do this if diversity is enabled */ if (! ath_hal_hasdivantcomb(sc->sc_ah)) return (0); ss = malloc(sizeof(struct if_ath_ant_comb_state), M_TEMP, M_WAITOK | M_ZERO); if (ss == NULL) { device_printf(sc->sc_dev, "%s: failed to allocate\n", __func__); /* Don't fail at this point */ return (0); } /* Fetch the hardware configuration */ OS_MEMZERO(&div_ant_conf, sizeof(div_ant_conf)); ath_hal_div_comb_conf_get(sc->sc_ah, &div_ant_conf); /* Figure out what the hardware specific bits should be */ if ((div_ant_conf.antdiv_configgroup == HAL_ANTDIV_CONFIG_GROUP_1) || (div_ant_conf.antdiv_configgroup == HAL_ANTDIV_CONFIG_GROUP_2)) { ss->lna1_lna2_delta = -9; } else { ss->lna1_lna2_delta = -3; } /* Let's flip this on */ sc->sc_lna_div = ss; sc->sc_dolnadiv = 1; return (0); } /* * Detach the LNA diversity state from the given interface */ int ath_lna_div_detach(struct ath_softc *sc) { if (sc->sc_lna_div != NULL) { free(sc->sc_lna_div, M_TEMP); sc->sc_lna_div = NULL; } sc->sc_dolnadiv = 0; return (0); } /* * Enable LNA diversity on the current channel if it's required. */ int ath_lna_div_enable(struct ath_softc *sc, const struct ieee80211_channel *chan) { return (0); } /* * Handle ioctl requests from the diagnostic interface. * * The initial part of this code resembles ath_ioctl_diag(); * it's likely a good idea to reduce duplication between * these two routines. */ int ath_lna_div_ioctl(struct ath_softc *sc, struct ath_diag *ad) { unsigned int id = ad->ad_id & ATH_DIAG_ID; void *indata = NULL; void *outdata = NULL; u_int32_t insize = ad->ad_in_size; u_int32_t outsize = ad->ad_out_size; int error = 0; // int val; if (ad->ad_id & ATH_DIAG_IN) { /* * Copy in data. */ indata = malloc(insize, M_TEMP, M_NOWAIT); if (indata == NULL) { error = ENOMEM; goto bad; } error = copyin(ad->ad_in_data, indata, insize); if (error) goto bad; } if (ad->ad_id & ATH_DIAG_DYN) { /* * Allocate a buffer for the results (otherwise the HAL * returns a pointer to a buffer where we can read the * results). Note that we depend on the HAL leaving this * pointer for us to use below in reclaiming the buffer; * may want to be more defensive. */ outdata = malloc(outsize, M_TEMP, M_NOWAIT); if (outdata == NULL) { error = ENOMEM; goto bad; } } switch (id) { default: error = EINVAL; } if (outsize < ad->ad_out_size) ad->ad_out_size = outsize; if (outdata && copyout(outdata, ad->ad_out_data, ad->ad_out_size)) error = EFAULT; bad: if ((ad->ad_id & ATH_DIAG_IN) && indata != NULL) free(indata, M_TEMP); if ((ad->ad_id & ATH_DIAG_DYN) && outdata != NULL) free(outdata, M_TEMP); return (error); } /* * XXX need to low_rssi_thresh config from ath9k, to support CUS198 * antenna diversity correctly. */ static HAL_BOOL ath_is_alt_ant_ratio_better(int alt_ratio, int maxdelta, int mindelta, int main_rssi_avg, int alt_rssi_avg, int pkt_count) { return (((alt_ratio >= ATH_ANT_DIV_COMB_ALT_ANT_RATIO2) && (alt_rssi_avg > main_rssi_avg + maxdelta)) || (alt_rssi_avg > main_rssi_avg + mindelta)) && (pkt_count > 50); } static void ath_lnaconf_alt_good_scan(struct if_ath_ant_comb_state *antcomb, HAL_ANT_COMB_CONFIG *ant_conf, int main_rssi_avg) { antcomb->quick_scan_cnt = 0; if (ant_conf->main_lna_conf == HAL_ANT_DIV_COMB_LNA2) antcomb->rssi_lna2 = main_rssi_avg; else if (ant_conf->main_lna_conf == HAL_ANT_DIV_COMB_LNA1) antcomb->rssi_lna1 = main_rssi_avg; switch ((ant_conf->main_lna_conf << 4) | ant_conf->alt_lna_conf) { case (0x10): /* LNA2 A-B */ antcomb->main_conf = HAL_ANT_DIV_COMB_LNA1_MINUS_LNA2; antcomb->first_quick_scan_conf = HAL_ANT_DIV_COMB_LNA1_PLUS_LNA2; antcomb->second_quick_scan_conf = HAL_ANT_DIV_COMB_LNA1; break; case (0x20): /* LNA1 A-B */ antcomb->main_conf = HAL_ANT_DIV_COMB_LNA1_MINUS_LNA2; antcomb->first_quick_scan_conf = HAL_ANT_DIV_COMB_LNA1_PLUS_LNA2; antcomb->second_quick_scan_conf = HAL_ANT_DIV_COMB_LNA2; break; case (0x21): /* LNA1 LNA2 */ antcomb->main_conf = HAL_ANT_DIV_COMB_LNA2; antcomb->first_quick_scan_conf = HAL_ANT_DIV_COMB_LNA1_MINUS_LNA2; antcomb->second_quick_scan_conf = HAL_ANT_DIV_COMB_LNA1_PLUS_LNA2; break; case (0x12): /* LNA2 LNA1 */ antcomb->main_conf = HAL_ANT_DIV_COMB_LNA1; antcomb->first_quick_scan_conf = HAL_ANT_DIV_COMB_LNA1_MINUS_LNA2; antcomb->second_quick_scan_conf = HAL_ANT_DIV_COMB_LNA1_PLUS_LNA2; break; case (0x13): /* LNA2 A+B */ antcomb->main_conf = HAL_ANT_DIV_COMB_LNA1_PLUS_LNA2; antcomb->first_quick_scan_conf = HAL_ANT_DIV_COMB_LNA1_MINUS_LNA2; antcomb->second_quick_scan_conf = HAL_ANT_DIV_COMB_LNA1; break; case (0x23): /* LNA1 A+B */ antcomb->main_conf = HAL_ANT_DIV_COMB_LNA1_PLUS_LNA2; antcomb->first_quick_scan_conf = HAL_ANT_DIV_COMB_LNA1_MINUS_LNA2; antcomb->second_quick_scan_conf = HAL_ANT_DIV_COMB_LNA2; break; default: break; } } static void ath_select_ant_div_from_quick_scan(struct if_ath_ant_comb_state *antcomb, HAL_ANT_COMB_CONFIG *div_ant_conf, int main_rssi_avg, int alt_rssi_avg, int alt_ratio) { /* alt_good */ switch (antcomb->quick_scan_cnt) { case 0: /* set alt to main, and alt to first conf */ div_ant_conf->main_lna_conf = antcomb->main_conf; div_ant_conf->alt_lna_conf = antcomb->first_quick_scan_conf; break; case 1: /* set alt to main, and alt to first conf */ div_ant_conf->main_lna_conf = antcomb->main_conf; div_ant_conf->alt_lna_conf = antcomb->second_quick_scan_conf; antcomb->rssi_first = main_rssi_avg; antcomb->rssi_second = alt_rssi_avg; if (antcomb->main_conf == HAL_ANT_DIV_COMB_LNA1) { /* main is LNA1 */ if (ath_is_alt_ant_ratio_better(alt_ratio, ATH_ANT_DIV_COMB_LNA1_DELTA_HI, ATH_ANT_DIV_COMB_LNA1_DELTA_LOW, main_rssi_avg, alt_rssi_avg, antcomb->total_pkt_count)) antcomb->first_ratio = AH_TRUE; else antcomb->first_ratio = AH_FALSE; } else if (antcomb->main_conf == HAL_ANT_DIV_COMB_LNA2) { if (ath_is_alt_ant_ratio_better(alt_ratio, ATH_ANT_DIV_COMB_LNA1_DELTA_MID, ATH_ANT_DIV_COMB_LNA1_DELTA_LOW, main_rssi_avg, alt_rssi_avg, antcomb->total_pkt_count)) antcomb->first_ratio = AH_TRUE; else antcomb->first_ratio = AH_FALSE; } else { if ((((alt_ratio >= ATH_ANT_DIV_COMB_ALT_ANT_RATIO2) && (alt_rssi_avg > main_rssi_avg + ATH_ANT_DIV_COMB_LNA1_DELTA_HI)) || (alt_rssi_avg > main_rssi_avg)) && (antcomb->total_pkt_count > 50)) antcomb->first_ratio = AH_TRUE; else antcomb->first_ratio = AH_FALSE; } break; case 2: antcomb->alt_good = AH_FALSE; antcomb->scan_not_start = AH_FALSE; antcomb->scan = AH_FALSE; antcomb->rssi_first = main_rssi_avg; antcomb->rssi_third = alt_rssi_avg; if (antcomb->second_quick_scan_conf == HAL_ANT_DIV_COMB_LNA1) antcomb->rssi_lna1 = alt_rssi_avg; else if (antcomb->second_quick_scan_conf == HAL_ANT_DIV_COMB_LNA2) antcomb->rssi_lna2 = alt_rssi_avg; else if (antcomb->second_quick_scan_conf == HAL_ANT_DIV_COMB_LNA1_PLUS_LNA2) { if (antcomb->main_conf == HAL_ANT_DIV_COMB_LNA2) antcomb->rssi_lna2 = main_rssi_avg; else if (antcomb->main_conf == HAL_ANT_DIV_COMB_LNA1) antcomb->rssi_lna1 = main_rssi_avg; } if (antcomb->rssi_lna2 > antcomb->rssi_lna1 + ATH_ANT_DIV_COMB_LNA1_LNA2_SWITCH_DELTA) div_ant_conf->main_lna_conf = HAL_ANT_DIV_COMB_LNA2; else div_ant_conf->main_lna_conf = HAL_ANT_DIV_COMB_LNA1; if (antcomb->main_conf == HAL_ANT_DIV_COMB_LNA1) { if (ath_is_alt_ant_ratio_better(alt_ratio, ATH_ANT_DIV_COMB_LNA1_DELTA_HI, ATH_ANT_DIV_COMB_LNA1_DELTA_LOW, main_rssi_avg, alt_rssi_avg, antcomb->total_pkt_count)) antcomb->second_ratio = AH_TRUE; else antcomb->second_ratio = AH_FALSE; } else if (antcomb->main_conf == HAL_ANT_DIV_COMB_LNA2) { if (ath_is_alt_ant_ratio_better(alt_ratio, ATH_ANT_DIV_COMB_LNA1_DELTA_MID, ATH_ANT_DIV_COMB_LNA1_DELTA_LOW, main_rssi_avg, alt_rssi_avg, antcomb->total_pkt_count)) antcomb->second_ratio = AH_TRUE; else antcomb->second_ratio = AH_FALSE; } else { if ((((alt_ratio >= ATH_ANT_DIV_COMB_ALT_ANT_RATIO2) && (alt_rssi_avg > main_rssi_avg + ATH_ANT_DIV_COMB_LNA1_DELTA_HI)) || (alt_rssi_avg > main_rssi_avg)) && (antcomb->total_pkt_count > 50)) antcomb->second_ratio = AH_TRUE; else antcomb->second_ratio = AH_FALSE; } /* set alt to the conf with maximun ratio */ if (antcomb->first_ratio && antcomb->second_ratio) { if (antcomb->rssi_second > antcomb->rssi_third) { /* first alt*/ if ((antcomb->first_quick_scan_conf == HAL_ANT_DIV_COMB_LNA1) || (antcomb->first_quick_scan_conf == HAL_ANT_DIV_COMB_LNA2)) /* Set alt LNA1 or LNA2*/ if (div_ant_conf->main_lna_conf == HAL_ANT_DIV_COMB_LNA2) div_ant_conf->alt_lna_conf = HAL_ANT_DIV_COMB_LNA1; else div_ant_conf->alt_lna_conf = HAL_ANT_DIV_COMB_LNA2; else /* Set alt to A+B or A-B */ div_ant_conf->alt_lna_conf = antcomb->first_quick_scan_conf; } else if ((antcomb->second_quick_scan_conf == HAL_ANT_DIV_COMB_LNA1) || (antcomb->second_quick_scan_conf == HAL_ANT_DIV_COMB_LNA2)) { /* Set alt LNA1 or LNA2 */ if (div_ant_conf->main_lna_conf == HAL_ANT_DIV_COMB_LNA2) div_ant_conf->alt_lna_conf = HAL_ANT_DIV_COMB_LNA1; else div_ant_conf->alt_lna_conf = HAL_ANT_DIV_COMB_LNA2; } else { /* Set alt to A+B or A-B */ div_ant_conf->alt_lna_conf = antcomb->second_quick_scan_conf; } } else if (antcomb->first_ratio) { /* first alt */ if ((antcomb->first_quick_scan_conf == HAL_ANT_DIV_COMB_LNA1) || (antcomb->first_quick_scan_conf == HAL_ANT_DIV_COMB_LNA2)) /* Set alt LNA1 or LNA2 */ if (div_ant_conf->main_lna_conf == HAL_ANT_DIV_COMB_LNA2) div_ant_conf->alt_lna_conf = HAL_ANT_DIV_COMB_LNA1; else div_ant_conf->alt_lna_conf = HAL_ANT_DIV_COMB_LNA2; else /* Set alt to A+B or A-B */ div_ant_conf->alt_lna_conf = antcomb->first_quick_scan_conf; } else if (antcomb->second_ratio) { /* second alt */ if ((antcomb->second_quick_scan_conf == HAL_ANT_DIV_COMB_LNA1) || (antcomb->second_quick_scan_conf == HAL_ANT_DIV_COMB_LNA2)) /* Set alt LNA1 or LNA2 */ if (div_ant_conf->main_lna_conf == HAL_ANT_DIV_COMB_LNA2) div_ant_conf->alt_lna_conf = HAL_ANT_DIV_COMB_LNA1; else div_ant_conf->alt_lna_conf = HAL_ANT_DIV_COMB_LNA2; else /* Set alt to A+B or A-B */ div_ant_conf->alt_lna_conf = antcomb->second_quick_scan_conf; } else { /* main is largest */ if ((antcomb->main_conf == HAL_ANT_DIV_COMB_LNA1) || (antcomb->main_conf == HAL_ANT_DIV_COMB_LNA2)) /* Set alt LNA1 or LNA2 */ if (div_ant_conf->main_lna_conf == HAL_ANT_DIV_COMB_LNA2) div_ant_conf->alt_lna_conf = HAL_ANT_DIV_COMB_LNA1; else div_ant_conf->alt_lna_conf = HAL_ANT_DIV_COMB_LNA2; else /* Set alt to A+B or A-B */ div_ant_conf->alt_lna_conf = antcomb->main_conf; } break; default: break; } } static void ath_ant_adjust_fast_divbias(struct if_ath_ant_comb_state *antcomb, int alt_ratio, int alt_ant_ratio_th, u_int config_group, HAL_ANT_COMB_CONFIG *pdiv_ant_conf) { if (config_group == HAL_ANTDIV_CONFIG_GROUP_1) { switch ((pdiv_ant_conf->main_lna_conf << 4) | pdiv_ant_conf->alt_lna_conf) { case (0x01): //A-B LNA2 pdiv_ant_conf->fast_div_bias = 0x1; pdiv_ant_conf->main_gaintb = 0; pdiv_ant_conf->alt_gaintb = 0; break; case (0x02): //A-B LNA1 pdiv_ant_conf->fast_div_bias = 0x1; pdiv_ant_conf->main_gaintb = 0; pdiv_ant_conf->alt_gaintb = 0; break; case (0x03): //A-B A+B pdiv_ant_conf->fast_div_bias = 0x1; pdiv_ant_conf->main_gaintb = 0; pdiv_ant_conf->alt_gaintb = 0; break; case (0x10): //LNA2 A-B if ((antcomb->scan == 0) && (alt_ratio > ATH_ANT_DIV_COMB_ALT_ANT_RATIO)) { pdiv_ant_conf->fast_div_bias = 0x3f; } else { pdiv_ant_conf->fast_div_bias = 0x1; } pdiv_ant_conf->main_gaintb = 0; pdiv_ant_conf->alt_gaintb = 0; break; case (0x12): //LNA2 LNA1 pdiv_ant_conf->fast_div_bias = 0x1; pdiv_ant_conf->main_gaintb = 0; pdiv_ant_conf->alt_gaintb = 0; break; case (0x13): //LNA2 A+B if ((antcomb->scan == 0) && (alt_ratio > ATH_ANT_DIV_COMB_ALT_ANT_RATIO)) { pdiv_ant_conf->fast_div_bias = 0x3f; } else { pdiv_ant_conf->fast_div_bias = 0x1; } pdiv_ant_conf->main_gaintb = 0; pdiv_ant_conf->alt_gaintb = 0; break; case (0x20): //LNA1 A-B if ((antcomb->scan == 0) && (alt_ratio > ATH_ANT_DIV_COMB_ALT_ANT_RATIO)) { pdiv_ant_conf->fast_div_bias = 0x3f; } else { pdiv_ant_conf->fast_div_bias = 0x1; } pdiv_ant_conf->main_gaintb = 0; pdiv_ant_conf->alt_gaintb = 0; break; case (0x21): //LNA1 LNA2 pdiv_ant_conf->fast_div_bias = 0x1; pdiv_ant_conf->main_gaintb = 0; pdiv_ant_conf->alt_gaintb = 0; break; case (0x23): //LNA1 A+B if ((antcomb->scan == 0) && (alt_ratio > ATH_ANT_DIV_COMB_ALT_ANT_RATIO)) { pdiv_ant_conf->fast_div_bias = 0x3f; } else { pdiv_ant_conf->fast_div_bias = 0x1; } pdiv_ant_conf->main_gaintb = 0; pdiv_ant_conf->alt_gaintb = 0; break; case (0x30): //A+B A-B pdiv_ant_conf->fast_div_bias = 0x1; pdiv_ant_conf->main_gaintb = 0; pdiv_ant_conf->alt_gaintb = 0; break; case (0x31): //A+B LNA2 pdiv_ant_conf->fast_div_bias = 0x1; pdiv_ant_conf->main_gaintb = 0; pdiv_ant_conf->alt_gaintb = 0; break; case (0x32): //A+B LNA1 pdiv_ant_conf->fast_div_bias = 0x1; pdiv_ant_conf->main_gaintb = 0; pdiv_ant_conf->alt_gaintb = 0; break; default: break; } } else if (config_group == HAL_ANTDIV_CONFIG_GROUP_2) { switch ((pdiv_ant_conf->main_lna_conf << 4) | pdiv_ant_conf->alt_lna_conf) { case (0x01): //A-B LNA2 pdiv_ant_conf->fast_div_bias = 0x1; pdiv_ant_conf->main_gaintb = 0; pdiv_ant_conf->alt_gaintb = 0; break; case (0x02): //A-B LNA1 pdiv_ant_conf->fast_div_bias = 0x1; pdiv_ant_conf->main_gaintb = 0; pdiv_ant_conf->alt_gaintb = 0; break; case (0x03): //A-B A+B pdiv_ant_conf->fast_div_bias = 0x1; pdiv_ant_conf->main_gaintb = 0; pdiv_ant_conf->alt_gaintb = 0; break; case (0x10): //LNA2 A-B if ((antcomb->scan == 0) && (alt_ratio > alt_ant_ratio_th)) { pdiv_ant_conf->fast_div_bias = 0x1; } else { pdiv_ant_conf->fast_div_bias = 0x2; } pdiv_ant_conf->main_gaintb = 0; pdiv_ant_conf->alt_gaintb = 0; break; case (0x12): //LNA2 LNA1 pdiv_ant_conf->fast_div_bias = 0x1; pdiv_ant_conf->main_gaintb = 0; pdiv_ant_conf->alt_gaintb = 0; break; case (0x13): //LNA2 A+B if ((antcomb->scan == 0) && (alt_ratio > alt_ant_ratio_th)) { pdiv_ant_conf->fast_div_bias = 0x1; } else { pdiv_ant_conf->fast_div_bias = 0x2; } pdiv_ant_conf->main_gaintb = 0; pdiv_ant_conf->alt_gaintb = 0; break; case (0x20): //LNA1 A-B if ((antcomb->scan == 0) && (alt_ratio > alt_ant_ratio_th)) { pdiv_ant_conf->fast_div_bias = 0x1; } else { pdiv_ant_conf->fast_div_bias = 0x2; } pdiv_ant_conf->main_gaintb = 0; pdiv_ant_conf->alt_gaintb = 0; break; case (0x21): //LNA1 LNA2 pdiv_ant_conf->fast_div_bias = 0x1; pdiv_ant_conf->main_gaintb = 0; pdiv_ant_conf->alt_gaintb = 0; break; case (0x23): //LNA1 A+B if ((antcomb->scan == 0) && (alt_ratio > alt_ant_ratio_th)) { pdiv_ant_conf->fast_div_bias = 0x1; } else { pdiv_ant_conf->fast_div_bias = 0x2; } pdiv_ant_conf->main_gaintb = 0; pdiv_ant_conf->alt_gaintb = 0; break; case (0x30): //A+B A-B pdiv_ant_conf->fast_div_bias = 0x1; pdiv_ant_conf->main_gaintb = 0; pdiv_ant_conf->alt_gaintb = 0; break; case (0x31): //A+B LNA2 pdiv_ant_conf->fast_div_bias = 0x1; pdiv_ant_conf->main_gaintb = 0; pdiv_ant_conf->alt_gaintb = 0; break; case (0x32): //A+B LNA1 pdiv_ant_conf->fast_div_bias = 0x1; pdiv_ant_conf->main_gaintb = 0; pdiv_ant_conf->alt_gaintb = 0; break; default: break; } } else { /* DEFAULT_ANTDIV_CONFIG_GROUP */ switch ((pdiv_ant_conf->main_lna_conf << 4) | pdiv_ant_conf->alt_lna_conf) { case (0x01): //A-B LNA2 pdiv_ant_conf->fast_div_bias = 0x3b; break; case (0x02): //A-B LNA1 pdiv_ant_conf->fast_div_bias = 0x3d; break; case (0x03): //A-B A+B pdiv_ant_conf->fast_div_bias = 0x1; break; case (0x10): //LNA2 A-B pdiv_ant_conf->fast_div_bias = 0x7; break; case (0x12): //LNA2 LNA1 pdiv_ant_conf->fast_div_bias = 0x2; break; case (0x13): //LNA2 A+B pdiv_ant_conf->fast_div_bias = 0x7; break; case (0x20): //LNA1 A-B pdiv_ant_conf->fast_div_bias = 0x6; break; case (0x21): //LNA1 LNA2 pdiv_ant_conf->fast_div_bias = 0x0; break; case (0x23): //LNA1 A+B pdiv_ant_conf->fast_div_bias = 0x6; break; case (0x30): //A+B A-B pdiv_ant_conf->fast_div_bias = 0x1; break; case (0x31): //A+B LNA2 pdiv_ant_conf->fast_div_bias = 0x3b; break; case (0x32): //A+B LNA1 pdiv_ant_conf->fast_div_bias = 0x3d; break; default: break; } } } /* * AR9485/AR933x TODO: * + Select a ratio based on whether RSSI is low or not; but I need * to figure out what "low_rssi_th" is sourced from. * + What's ath_ant_div_comb_alt_check() in the reference driver do? * + .. and there's likely a bunch of other things to include in this. */ /* Antenna diversity and combining */ void ath_lna_rx_comb_scan(struct ath_softc *sc, struct ath_rx_status *rs, unsigned long ticks, int hz) { HAL_ANT_COMB_CONFIG div_ant_conf; struct if_ath_ant_comb_state *antcomb = sc->sc_lna_div; int alt_ratio = 0, alt_rssi_avg = 0, main_rssi_avg = 0, curr_alt_set; int curr_main_set, curr_bias; int main_rssi = rs->rs_rssi_ctl[0]; int alt_rssi = rs->rs_rssi_ctl[1]; int rx_ant_conf, main_ant_conf, alt_ant_conf; HAL_BOOL short_scan = AH_FALSE; rx_ant_conf = (rs->rs_rssi_ctl[2] >> 4) & ATH_ANT_RX_MASK; main_ant_conf = (rs->rs_rssi_ctl[2] >> 2) & ATH_ANT_RX_MASK; alt_ant_conf = (rs->rs_rssi_ctl[2] >> 0) & ATH_ANT_RX_MASK; #if 0 DPRINTF(sc, ATH_DEBUG_DIVERSITY, "%s: RSSI %d/%d, conf %x/%x, rxconf %x, LNA: %d; ANT: %d; " "FastDiv: %d\n", __func__, main_rssi, alt_rssi, main_ant_conf, alt_ant_conf, rx_ant_conf, !!(rs->rs_rssi_ctl[2] & 0x80), !!(rs->rs_rssi_ctl[2] & 0x40), !!(rs->rs_rssi_ext[2] & 0x40)); #endif /* * If LNA diversity combining isn't enabled, don't run this. */ if (! sc->sc_dolnadiv) return; /* * XXX this is ugly, but the HAL code attaches the * LNA diversity to the TX antenna settings. * I don't know why. */ if (sc->sc_txantenna != HAL_ANT_VARIABLE) return; /* Record packet only when alt_rssi is positive */ if (main_rssi > 0 && alt_rssi > 0) { antcomb->total_pkt_count++; antcomb->main_total_rssi += main_rssi; antcomb->alt_total_rssi += alt_rssi; if (main_ant_conf == rx_ant_conf) antcomb->main_recv_cnt++; else antcomb->alt_recv_cnt++; } /* Short scan check */ if (antcomb->scan && antcomb->alt_good) { - if (time_after(ticks, antcomb->scan_start_time + + if (ieee80211_time_after(ticks, antcomb->scan_start_time + msecs_to_jiffies(ATH_ANT_DIV_COMB_SHORT_SCAN_INTR))) short_scan = AH_TRUE; else if (antcomb->total_pkt_count == ATH_ANT_DIV_COMB_SHORT_SCAN_PKTCOUNT) { alt_ratio = ((antcomb->alt_recv_cnt * 100) / antcomb->total_pkt_count); if (alt_ratio < ATH_ANT_DIV_COMB_ALT_ANT_RATIO) short_scan = AH_TRUE; } } #if 0 DPRINTF(sc, ATH_DEBUG_DIVERSITY, "%s: total pkt=%d, aggr=%d, short_scan=%d\n", __func__, antcomb->total_pkt_count, !! (rs->rs_moreaggr), !! (short_scan)); #endif if (((antcomb->total_pkt_count < ATH_ANT_DIV_COMB_MAX_PKTCOUNT) || rs->rs_moreaggr) && !short_scan) return; if (antcomb->total_pkt_count) { alt_ratio = ((antcomb->alt_recv_cnt * 100) / antcomb->total_pkt_count); main_rssi_avg = (antcomb->main_total_rssi / antcomb->total_pkt_count); alt_rssi_avg = (antcomb->alt_total_rssi / antcomb->total_pkt_count); } OS_MEMZERO(&div_ant_conf, sizeof(div_ant_conf)); ath_hal_div_comb_conf_get(sc->sc_ah, &div_ant_conf); curr_alt_set = div_ant_conf.alt_lna_conf; curr_main_set = div_ant_conf.main_lna_conf; curr_bias = div_ant_conf.fast_div_bias; antcomb->count++; if (antcomb->count == ATH_ANT_DIV_COMB_MAX_COUNT) { if (alt_ratio > ATH_ANT_DIV_COMB_ALT_ANT_RATIO) { ath_lnaconf_alt_good_scan(antcomb, &div_ant_conf, main_rssi_avg); antcomb->alt_good = AH_TRUE; } else { antcomb->alt_good = AH_FALSE; } antcomb->count = 0; antcomb->scan = AH_TRUE; antcomb->scan_not_start = AH_TRUE; } if (!antcomb->scan) { if (alt_ratio > ATH_ANT_DIV_COMB_ALT_ANT_RATIO) { if (curr_alt_set == HAL_ANT_DIV_COMB_LNA2) { /* Switch main and alt LNA */ div_ant_conf.main_lna_conf = HAL_ANT_DIV_COMB_LNA2; div_ant_conf.alt_lna_conf = HAL_ANT_DIV_COMB_LNA1; } else if (curr_alt_set == HAL_ANT_DIV_COMB_LNA1) { div_ant_conf.main_lna_conf = HAL_ANT_DIV_COMB_LNA1; div_ant_conf.alt_lna_conf = HAL_ANT_DIV_COMB_LNA2; } goto div_comb_done; } else if ((curr_alt_set != HAL_ANT_DIV_COMB_LNA1) && (curr_alt_set != HAL_ANT_DIV_COMB_LNA2)) { /* Set alt to another LNA */ if (curr_main_set == HAL_ANT_DIV_COMB_LNA2) div_ant_conf.alt_lna_conf = HAL_ANT_DIV_COMB_LNA1; else if (curr_main_set == HAL_ANT_DIV_COMB_LNA1) div_ant_conf.alt_lna_conf = HAL_ANT_DIV_COMB_LNA2; goto div_comb_done; } if ((alt_rssi_avg < (main_rssi_avg + antcomb->lna1_lna2_delta))) goto div_comb_done; } if (!antcomb->scan_not_start) { switch (curr_alt_set) { case HAL_ANT_DIV_COMB_LNA2: antcomb->rssi_lna2 = alt_rssi_avg; antcomb->rssi_lna1 = main_rssi_avg; antcomb->scan = AH_TRUE; /* set to A+B */ div_ant_conf.main_lna_conf = HAL_ANT_DIV_COMB_LNA1; div_ant_conf.alt_lna_conf = HAL_ANT_DIV_COMB_LNA1_PLUS_LNA2; break; case HAL_ANT_DIV_COMB_LNA1: antcomb->rssi_lna1 = alt_rssi_avg; antcomb->rssi_lna2 = main_rssi_avg; antcomb->scan = AH_TRUE; /* set to A+B */ div_ant_conf.main_lna_conf = HAL_ANT_DIV_COMB_LNA2; div_ant_conf.alt_lna_conf = HAL_ANT_DIV_COMB_LNA1_PLUS_LNA2; break; case HAL_ANT_DIV_COMB_LNA1_PLUS_LNA2: antcomb->rssi_add = alt_rssi_avg; antcomb->scan = AH_TRUE; /* set to A-B */ div_ant_conf.alt_lna_conf = HAL_ANT_DIV_COMB_LNA1_MINUS_LNA2; break; case HAL_ANT_DIV_COMB_LNA1_MINUS_LNA2: antcomb->rssi_sub = alt_rssi_avg; antcomb->scan = AH_FALSE; if (antcomb->rssi_lna2 > (antcomb->rssi_lna1 + ATH_ANT_DIV_COMB_LNA1_LNA2_SWITCH_DELTA)) { /* use LNA2 as main LNA */ if ((antcomb->rssi_add > antcomb->rssi_lna1) && (antcomb->rssi_add > antcomb->rssi_sub)) { /* set to A+B */ div_ant_conf.main_lna_conf = HAL_ANT_DIV_COMB_LNA2; div_ant_conf.alt_lna_conf = HAL_ANT_DIV_COMB_LNA1_PLUS_LNA2; } else if (antcomb->rssi_sub > antcomb->rssi_lna1) { /* set to A-B */ div_ant_conf.main_lna_conf = HAL_ANT_DIV_COMB_LNA2; div_ant_conf.alt_lna_conf = HAL_ANT_DIV_COMB_LNA1_MINUS_LNA2; } else { /* set to LNA1 */ div_ant_conf.main_lna_conf = HAL_ANT_DIV_COMB_LNA2; div_ant_conf.alt_lna_conf = HAL_ANT_DIV_COMB_LNA1; } } else { /* use LNA1 as main LNA */ if ((antcomb->rssi_add > antcomb->rssi_lna2) && (antcomb->rssi_add > antcomb->rssi_sub)) { /* set to A+B */ div_ant_conf.main_lna_conf = HAL_ANT_DIV_COMB_LNA1; div_ant_conf.alt_lna_conf = HAL_ANT_DIV_COMB_LNA1_PLUS_LNA2; } else if (antcomb->rssi_sub > antcomb->rssi_lna1) { /* set to A-B */ div_ant_conf.main_lna_conf = HAL_ANT_DIV_COMB_LNA1; div_ant_conf.alt_lna_conf = HAL_ANT_DIV_COMB_LNA1_MINUS_LNA2; } else { /* set to LNA2 */ div_ant_conf.main_lna_conf = HAL_ANT_DIV_COMB_LNA1; div_ant_conf.alt_lna_conf = HAL_ANT_DIV_COMB_LNA2; } } break; default: break; } } else { if (!antcomb->alt_good) { antcomb->scan_not_start = AH_FALSE; /* Set alt to another LNA */ if (curr_main_set == HAL_ANT_DIV_COMB_LNA2) { div_ant_conf.main_lna_conf = HAL_ANT_DIV_COMB_LNA2; div_ant_conf.alt_lna_conf = HAL_ANT_DIV_COMB_LNA1; } else if (curr_main_set == HAL_ANT_DIV_COMB_LNA1) { div_ant_conf.main_lna_conf = HAL_ANT_DIV_COMB_LNA1; div_ant_conf.alt_lna_conf = HAL_ANT_DIV_COMB_LNA2; } goto div_comb_done; } } ath_select_ant_div_from_quick_scan(antcomb, &div_ant_conf, main_rssi_avg, alt_rssi_avg, alt_ratio); antcomb->quick_scan_cnt++; div_comb_done: #if 0 ath_ant_div_conf_fast_divbias(&div_ant_conf); #endif ath_ant_adjust_fast_divbias(antcomb, alt_ratio, ATH_ANT_DIV_COMB_ALT_ANT_RATIO, div_ant_conf.antdiv_configgroup, &div_ant_conf); ath_hal_div_comb_conf_set(sc->sc_ah, &div_ant_conf); DPRINTF(sc, ATH_DEBUG_DIVERSITY, "%s: total_pkt_count=%d\n", __func__, antcomb->total_pkt_count); DPRINTF(sc, ATH_DEBUG_DIVERSITY, "%s: main_total_rssi=%d\n", __func__, antcomb->main_total_rssi); DPRINTF(sc, ATH_DEBUG_DIVERSITY, "%s: alt_total_rssi=%d\n", __func__, antcomb->alt_total_rssi); DPRINTF(sc, ATH_DEBUG_DIVERSITY, "%s: main_rssi_avg=%d\n", __func__, main_rssi_avg); DPRINTF(sc, ATH_DEBUG_DIVERSITY, "%s: alt_alt_rssi_avg=%d\n", __func__, alt_rssi_avg); DPRINTF(sc, ATH_DEBUG_DIVERSITY, "%s: main_recv_cnt=%d\n", __func__, antcomb->main_recv_cnt); DPRINTF(sc, ATH_DEBUG_DIVERSITY, "%s: alt_recv_cnt=%d\n", __func__, antcomb->alt_recv_cnt); // if (curr_alt_set != div_ant_conf.alt_lna_conf) DPRINTF(sc, ATH_DEBUG_DIVERSITY, "%s: lna_conf: %x -> %x\n", __func__, curr_alt_set, div_ant_conf.alt_lna_conf); // if (curr_main_set != div_ant_conf.main_lna_conf) DPRINTF(sc, ATH_DEBUG_DIVERSITY, "%s: main_lna_conf: %x -> %x\n", __func__, curr_main_set, div_ant_conf.main_lna_conf); // if (curr_bias != div_ant_conf.fast_div_bias) DPRINTF(sc, ATH_DEBUG_DIVERSITY, "%s: fast_div_bias: %x -> %x\n", __func__, curr_bias, div_ant_conf.fast_div_bias); antcomb->scan_start_time = ticks; antcomb->total_pkt_count = 0; antcomb->main_total_rssi = 0; antcomb->alt_total_rssi = 0; antcomb->main_recv_cnt = 0; antcomb->alt_recv_cnt = 0; } Index: head/sys/net80211/ieee80211_dfs.c =================================================================== --- head/sys/net80211/ieee80211_dfs.c (revision 297404) +++ head/sys/net80211/ieee80211_dfs.c (revision 297405) @@ -1,439 +1,439 @@ /*- * Copyright (c) 2007-2008 Sam Leffler, Errno Consulting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #ifdef __FreeBSD__ __FBSDID("$FreeBSD$"); #endif /* * IEEE 802.11 DFS/Radar support. */ #include "opt_inet.h" #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static MALLOC_DEFINE(M_80211_DFS, "80211dfs", "802.11 DFS state"); static int ieee80211_nol_timeout = 30*60; /* 30 minutes */ SYSCTL_INT(_net_wlan, OID_AUTO, nol_timeout, CTLFLAG_RW, &ieee80211_nol_timeout, 0, "NOL timeout (secs)"); #define NOL_TIMEOUT msecs_to_ticks(ieee80211_nol_timeout*1000) static int ieee80211_cac_timeout = 60; /* 60 seconds */ SYSCTL_INT(_net_wlan, OID_AUTO, cac_timeout, CTLFLAG_RW, &ieee80211_cac_timeout, 0, "CAC timeout (secs)"); #define CAC_TIMEOUT msecs_to_ticks(ieee80211_cac_timeout*1000) /* DFS* In order to facilitate debugging, a couple of operating * modes aside from the default are needed. * * 0 - default CAC/NOL behaviour - ie, start CAC, place * channel on NOL list. * 1 - send CAC, but don't change channel or add the channel * to the NOL list. * 2 - just match on radar, don't send CAC or place channel in * the NOL list. */ static int ieee80211_dfs_debug = DFS_DBG_NONE; /* * This option must not be included in the default kernel * as it allows users to plainly disable CAC/NOL handling. */ #ifdef IEEE80211_DFS_DEBUG SYSCTL_INT(_net_wlan, OID_AUTO, dfs_debug, CTLFLAG_RW, &ieee80211_dfs_debug, 0, "DFS debug behaviour"); #endif static int null_set_quiet(struct ieee80211_node *ni, u_int8_t *quiet_elm) { return ENOSYS; } void ieee80211_dfs_attach(struct ieee80211com *ic) { struct ieee80211_dfs_state *dfs = &ic->ic_dfs; callout_init_mtx(&dfs->nol_timer, IEEE80211_LOCK_OBJ(ic), 0); callout_init_mtx(&dfs->cac_timer, IEEE80211_LOCK_OBJ(ic), 0); ic->ic_set_quiet = null_set_quiet; } void ieee80211_dfs_detach(struct ieee80211com *ic) { /* NB: we assume no locking is needed */ ieee80211_dfs_reset(ic); } void ieee80211_dfs_reset(struct ieee80211com *ic) { struct ieee80211_dfs_state *dfs = &ic->ic_dfs; int i; /* NB: we assume no locking is needed */ /* NB: cac_timer should be cleared by the state machine */ callout_drain(&dfs->nol_timer); for (i = 0; i < ic->ic_nchans; i++) ic->ic_channels[i].ic_state = 0; dfs->lastchan = NULL; } static void cac_timeout(void *arg) { struct ieee80211vap *vap = arg; struct ieee80211com *ic = vap->iv_ic; struct ieee80211_dfs_state *dfs = &ic->ic_dfs; int i; IEEE80211_LOCK_ASSERT(ic); if (vap->iv_state != IEEE80211_S_CAC) /* NB: just in case */ return; /* * When radar is detected during a CAC we are woken * up prematurely to switch to a new channel. * Check the channel to decide how to act. */ if (IEEE80211_IS_CHAN_RADAR(ic->ic_curchan)) { ieee80211_notify_cac(ic, ic->ic_curchan, IEEE80211_NOTIFY_CAC_RADAR); if_printf(vap->iv_ifp, "CAC timer on channel %u (%u MHz) stopped due to radar\n", ic->ic_curchan->ic_ieee, ic->ic_curchan->ic_freq); /* XXX clobbers any existing desired channel */ /* NB: dfs->newchan may be NULL, that's ok */ vap->iv_des_chan = dfs->newchan; /* XXX recursive lock need ieee80211_new_state_locked */ ieee80211_new_state(vap, IEEE80211_S_SCAN, 0); } else { if_printf(vap->iv_ifp, "CAC timer on channel %u (%u MHz) expired; " "no radar detected\n", ic->ic_curchan->ic_ieee, ic->ic_curchan->ic_freq); /* * Mark all channels with the current frequency * as having completed CAC; this keeps us from * doing it again until we change channels. */ for (i = 0; i < ic->ic_nchans; i++) { struct ieee80211_channel *c = &ic->ic_channels[i]; if (c->ic_freq == ic->ic_curchan->ic_freq) c->ic_state |= IEEE80211_CHANSTATE_CACDONE; } ieee80211_notify_cac(ic, ic->ic_curchan, IEEE80211_NOTIFY_CAC_EXPIRE); ieee80211_cac_completeswitch(vap); } } /* * Initiate the CAC timer. The driver is responsible * for setting up the hardware to scan for radar on the * channnel, we just handle timing things out. */ void ieee80211_dfs_cac_start(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_dfs_state *dfs = &ic->ic_dfs; IEEE80211_LOCK_ASSERT(ic); callout_reset(&dfs->cac_timer, CAC_TIMEOUT, cac_timeout, vap); if_printf(vap->iv_ifp, "start %d second CAC timer on channel %u (%u MHz)\n", ticks_to_secs(CAC_TIMEOUT), ic->ic_curchan->ic_ieee, ic->ic_curchan->ic_freq); ieee80211_notify_cac(ic, ic->ic_curchan, IEEE80211_NOTIFY_CAC_START); } /* * Clear the CAC timer. */ void ieee80211_dfs_cac_stop(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_dfs_state *dfs = &ic->ic_dfs; IEEE80211_LOCK_ASSERT(ic); /* NB: racey but not important */ if (callout_pending(&dfs->cac_timer)) { if_printf(vap->iv_ifp, "stop CAC timer on channel %u (%u MHz)\n", ic->ic_curchan->ic_ieee, ic->ic_curchan->ic_freq); ieee80211_notify_cac(ic, ic->ic_curchan, IEEE80211_NOTIFY_CAC_STOP); } callout_stop(&dfs->cac_timer); } void ieee80211_dfs_cac_clear(struct ieee80211com *ic, const struct ieee80211_channel *chan) { int i; for (i = 0; i < ic->ic_nchans; i++) { struct ieee80211_channel *c = &ic->ic_channels[i]; if (c->ic_freq == chan->ic_freq) c->ic_state &= ~IEEE80211_CHANSTATE_CACDONE; } } static void dfs_timeout(void *arg) { struct ieee80211com *ic = arg; struct ieee80211_dfs_state *dfs = &ic->ic_dfs; struct ieee80211_channel *c; int i, oldest, now; IEEE80211_LOCK_ASSERT(ic); now = oldest = ticks; for (i = 0; i < ic->ic_nchans; i++) { c = &ic->ic_channels[i]; if (IEEE80211_IS_CHAN_RADAR(c)) { - if (time_after_eq(now, dfs->nol_event[i]+NOL_TIMEOUT)) { + if (ieee80211_time_after_eq(now, dfs->nol_event[i]+NOL_TIMEOUT)) { c->ic_state &= ~IEEE80211_CHANSTATE_RADAR; if (c->ic_state & IEEE80211_CHANSTATE_NORADAR) { /* * NB: do this here so we get only one * msg instead of one for every channel * table entry. */ ic_printf(ic, "radar on channel %u " "(%u MHz) cleared after timeout\n", c->ic_ieee, c->ic_freq); /* notify user space */ c->ic_state &= ~IEEE80211_CHANSTATE_NORADAR; ieee80211_notify_radar(ic, c); } } else if (dfs->nol_event[i] < oldest) oldest = dfs->nol_event[i]; } } if (oldest != now) { /* arrange to process next channel up for a status change */ callout_schedule(&dfs->nol_timer, oldest + NOL_TIMEOUT - now); } } static void announce_radar(struct ieee80211com *ic, const struct ieee80211_channel *curchan, const struct ieee80211_channel *newchan) { if (newchan == NULL) ic_printf(ic, "radar detected on channel %u (%u MHz)\n", curchan->ic_ieee, curchan->ic_freq); else ic_printf(ic, "radar detected on channel %u (%u MHz), " "moving to channel %u (%u MHz)\n", curchan->ic_ieee, curchan->ic_freq, newchan->ic_ieee, newchan->ic_freq); } /* * Handle a radar detection event on a channel. The channel is * added to the NOL list and we record the time of the event. * Entries are aged out after NOL_TIMEOUT. If radar was * detected while doing CAC we force a state/channel change. * Otherwise radar triggers a channel switch using the CSA * mechanism (when the channel is the bss channel). */ void ieee80211_dfs_notify_radar(struct ieee80211com *ic, struct ieee80211_channel *chan) { struct ieee80211_dfs_state *dfs = &ic->ic_dfs; int i, now; IEEE80211_LOCK_ASSERT(ic); /* * If doing DFS debugging (mode 2), don't bother * running the rest of this function. * * Simply announce the presence of the radar and continue * along merrily. */ if (ieee80211_dfs_debug == DFS_DBG_NOCSANOL) { announce_radar(ic, chan, chan); ieee80211_notify_radar(ic, chan); return; } /* * Don't mark the channel and don't put it into NOL * if we're doing DFS debugging. */ if (ieee80211_dfs_debug == DFS_DBG_NONE) { /* * Mark all entries with this frequency. Notify user * space and arrange for notification when the radar * indication is cleared. Then kick the NOL processing * thread if not already running. */ now = ticks; for (i = 0; i < ic->ic_nchans; i++) { struct ieee80211_channel *c = &ic->ic_channels[i]; if (c->ic_freq == chan->ic_freq) { c->ic_state &= ~IEEE80211_CHANSTATE_CACDONE; c->ic_state |= IEEE80211_CHANSTATE_RADAR; dfs->nol_event[i] = now; } } ieee80211_notify_radar(ic, chan); chan->ic_state |= IEEE80211_CHANSTATE_NORADAR; if (!callout_pending(&dfs->nol_timer)) callout_reset(&dfs->nol_timer, NOL_TIMEOUT, dfs_timeout, ic); } /* * If radar is detected on the bss channel while * doing CAC; force a state change by scheduling the * callout to be dispatched asap. Otherwise, if this * event is for the bss channel then we must quiet * traffic and schedule a channel switch. * * Note this allows us to receive notification about * channels other than the bss channel; not sure * that can/will happen but it's simple to support. */ if (chan == ic->ic_bsschan) { /* XXX need a way to defer to user app */ /* * Don't flip over to a new channel if * we are currently doing DFS debugging. */ if (ieee80211_dfs_debug == DFS_DBG_NONE) dfs->newchan = ieee80211_dfs_pickchannel(ic); else dfs->newchan = chan; announce_radar(ic, chan, dfs->newchan); if (callout_pending(&dfs->cac_timer)) callout_schedule(&dfs->cac_timer, 0); else if (dfs->newchan != NULL) { /* XXX mode 1, switch count 2 */ /* XXX calculate switch count based on max switch time and beacon interval? */ ieee80211_csa_startswitch(ic, dfs->newchan, 1, 2); } else { /* * Spec says to stop all transmissions and * wait on the current channel for an entry * on the NOL to expire. */ /*XXX*/ ic_printf(ic, "%s: No free channels; waiting for entry " "on NOL to expire\n", __func__); } } else { /* * Issue rate-limited console msgs. */ if (dfs->lastchan != chan) { dfs->lastchan = chan; dfs->cureps = 0; announce_radar(ic, chan, NULL); } else if (ppsratecheck(&dfs->lastevent, &dfs->cureps, 1)) { announce_radar(ic, chan, NULL); } } } struct ieee80211_channel * ieee80211_dfs_pickchannel(struct ieee80211com *ic) { struct ieee80211_channel *c; int i, flags; uint16_t v; /* * Consult the scan cache first. */ flags = ic->ic_curchan->ic_flags & IEEE80211_CHAN_ALL; /* * XXX if curchan is HT this will never find a channel * XXX 'cuz we scan only legacy channels */ c = ieee80211_scan_pickchannel(ic, flags); if (c != NULL) return c; /* * No channel found in scan cache; select a compatible * one at random (skipping channels where radar has * been detected). */ get_random_bytes(&v, sizeof(v)); v %= ic->ic_nchans; for (i = v; i < ic->ic_nchans; i++) { c = &ic->ic_channels[i]; if (!IEEE80211_IS_CHAN_RADAR(c) && (c->ic_flags & flags) == flags) return c; } for (i = 0; i < v; i++) { c = &ic->ic_channels[i]; if (!IEEE80211_IS_CHAN_RADAR(c) && (c->ic_flags & flags) == flags) return c; } ic_printf(ic, "HELP, no channel located to switch to!\n"); return NULL; } Index: head/sys/net80211/ieee80211_freebsd.h =================================================================== --- head/sys/net80211/ieee80211_freebsd.h (revision 297404) +++ head/sys/net80211/ieee80211_freebsd.h (revision 297405) @@ -1,678 +1,678 @@ /*- * Copyright (c) 2003-2008 Sam Leffler, Errno Consulting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * $FreeBSD$ */ #ifndef _NET80211_IEEE80211_FREEBSD_H_ #define _NET80211_IEEE80211_FREEBSD_H_ #ifdef _KERNEL #include #include #include #include #include #include #include #include /* * Common state locking definitions. */ typedef struct { char name[16]; /* e.g. "ath0_com_lock" */ struct mtx mtx; } ieee80211_com_lock_t; #define IEEE80211_LOCK_INIT(_ic, _name) do { \ ieee80211_com_lock_t *cl = &(_ic)->ic_comlock; \ snprintf(cl->name, sizeof(cl->name), "%s_com_lock", _name); \ mtx_init(&cl->mtx, cl->name, NULL, MTX_DEF | MTX_RECURSE); \ } while (0) #define IEEE80211_LOCK_OBJ(_ic) (&(_ic)->ic_comlock.mtx) #define IEEE80211_LOCK_DESTROY(_ic) mtx_destroy(IEEE80211_LOCK_OBJ(_ic)) #define IEEE80211_LOCK(_ic) mtx_lock(IEEE80211_LOCK_OBJ(_ic)) #define IEEE80211_UNLOCK(_ic) mtx_unlock(IEEE80211_LOCK_OBJ(_ic)) #define IEEE80211_LOCK_ASSERT(_ic) \ mtx_assert(IEEE80211_LOCK_OBJ(_ic), MA_OWNED) #define IEEE80211_UNLOCK_ASSERT(_ic) \ mtx_assert(IEEE80211_LOCK_OBJ(_ic), MA_NOTOWNED) /* * Transmit lock. * * This is a (mostly) temporary lock designed to serialise all of the * transmission operations throughout the stack. */ typedef struct { char name[16]; /* e.g. "ath0_tx_lock" */ struct mtx mtx; } ieee80211_tx_lock_t; #define IEEE80211_TX_LOCK_INIT(_ic, _name) do { \ ieee80211_tx_lock_t *cl = &(_ic)->ic_txlock; \ snprintf(cl->name, sizeof(cl->name), "%s_tx_lock", _name); \ mtx_init(&cl->mtx, cl->name, NULL, MTX_DEF); \ } while (0) #define IEEE80211_TX_LOCK_OBJ(_ic) (&(_ic)->ic_txlock.mtx) #define IEEE80211_TX_LOCK_DESTROY(_ic) mtx_destroy(IEEE80211_TX_LOCK_OBJ(_ic)) #define IEEE80211_TX_LOCK(_ic) mtx_lock(IEEE80211_TX_LOCK_OBJ(_ic)) #define IEEE80211_TX_UNLOCK(_ic) mtx_unlock(IEEE80211_TX_LOCK_OBJ(_ic)) #define IEEE80211_TX_LOCK_ASSERT(_ic) \ mtx_assert(IEEE80211_TX_LOCK_OBJ(_ic), MA_OWNED) #define IEEE80211_TX_UNLOCK_ASSERT(_ic) \ mtx_assert(IEEE80211_TX_LOCK_OBJ(_ic), MA_NOTOWNED) /* * Node locking definitions. */ typedef struct { char name[16]; /* e.g. "ath0_node_lock" */ struct mtx mtx; } ieee80211_node_lock_t; #define IEEE80211_NODE_LOCK_INIT(_nt, _name) do { \ ieee80211_node_lock_t *nl = &(_nt)->nt_nodelock; \ snprintf(nl->name, sizeof(nl->name), "%s_node_lock", _name); \ mtx_init(&nl->mtx, nl->name, NULL, MTX_DEF | MTX_RECURSE); \ } while (0) #define IEEE80211_NODE_LOCK_OBJ(_nt) (&(_nt)->nt_nodelock.mtx) #define IEEE80211_NODE_LOCK_DESTROY(_nt) \ mtx_destroy(IEEE80211_NODE_LOCK_OBJ(_nt)) #define IEEE80211_NODE_LOCK(_nt) \ mtx_lock(IEEE80211_NODE_LOCK_OBJ(_nt)) #define IEEE80211_NODE_IS_LOCKED(_nt) \ mtx_owned(IEEE80211_NODE_LOCK_OBJ(_nt)) #define IEEE80211_NODE_UNLOCK(_nt) \ mtx_unlock(IEEE80211_NODE_LOCK_OBJ(_nt)) #define IEEE80211_NODE_LOCK_ASSERT(_nt) \ mtx_assert(IEEE80211_NODE_LOCK_OBJ(_nt), MA_OWNED) /* * Node table iteration locking definitions; this protects the * scan generation # used to iterate over the station table * while grabbing+releasing the node lock. */ typedef struct { char name[16]; /* e.g. "ath0_scan_lock" */ struct mtx mtx; } ieee80211_scan_lock_t; #define IEEE80211_NODE_ITERATE_LOCK_INIT(_nt, _name) do { \ ieee80211_scan_lock_t *sl = &(_nt)->nt_scanlock; \ snprintf(sl->name, sizeof(sl->name), "%s_scan_lock", _name); \ mtx_init(&sl->mtx, sl->name, NULL, MTX_DEF); \ } while (0) #define IEEE80211_NODE_ITERATE_LOCK_OBJ(_nt) (&(_nt)->nt_scanlock.mtx) #define IEEE80211_NODE_ITERATE_LOCK_DESTROY(_nt) \ mtx_destroy(IEEE80211_NODE_ITERATE_LOCK_OBJ(_nt)) #define IEEE80211_NODE_ITERATE_LOCK(_nt) \ mtx_lock(IEEE80211_NODE_ITERATE_LOCK_OBJ(_nt)) #define IEEE80211_NODE_ITERATE_UNLOCK(_nt) \ mtx_unlock(IEEE80211_NODE_ITERATE_LOCK_OBJ(_nt)) /* * Power-save queue definitions. */ typedef struct mtx ieee80211_psq_lock_t; #define IEEE80211_PSQ_INIT(_psq, _name) \ mtx_init(&(_psq)->psq_lock, _name, "802.11 ps q", MTX_DEF) #define IEEE80211_PSQ_DESTROY(_psq) mtx_destroy(&(_psq)->psq_lock) #define IEEE80211_PSQ_LOCK(_psq) mtx_lock(&(_psq)->psq_lock) #define IEEE80211_PSQ_UNLOCK(_psq) mtx_unlock(&(_psq)->psq_lock) #ifndef IF_PREPEND_LIST #define _IF_PREPEND_LIST(ifq, mhead, mtail, mcount) do { \ (mtail)->m_nextpkt = (ifq)->ifq_head; \ if ((ifq)->ifq_tail == NULL) \ (ifq)->ifq_tail = (mtail); \ (ifq)->ifq_head = (mhead); \ (ifq)->ifq_len += (mcount); \ } while (0) #define IF_PREPEND_LIST(ifq, mhead, mtail, mcount) do { \ IF_LOCK(ifq); \ _IF_PREPEND_LIST(ifq, mhead, mtail, mcount); \ IF_UNLOCK(ifq); \ } while (0) #endif /* IF_PREPEND_LIST */ /* * Age queue definitions. */ typedef struct mtx ieee80211_ageq_lock_t; #define IEEE80211_AGEQ_INIT(_aq, _name) \ mtx_init(&(_aq)->aq_lock, _name, "802.11 age q", MTX_DEF) #define IEEE80211_AGEQ_DESTROY(_aq) mtx_destroy(&(_aq)->aq_lock) #define IEEE80211_AGEQ_LOCK(_aq) mtx_lock(&(_aq)->aq_lock) #define IEEE80211_AGEQ_UNLOCK(_aq) mtx_unlock(&(_aq)->aq_lock) /* * 802.1x MAC ACL database locking definitions. */ typedef struct mtx acl_lock_t; #define ACL_LOCK_INIT(_as, _name) \ mtx_init(&(_as)->as_lock, _name, "802.11 ACL", MTX_DEF) #define ACL_LOCK_DESTROY(_as) mtx_destroy(&(_as)->as_lock) #define ACL_LOCK(_as) mtx_lock(&(_as)->as_lock) #define ACL_UNLOCK(_as) mtx_unlock(&(_as)->as_lock) #define ACL_LOCK_ASSERT(_as) \ mtx_assert((&(_as)->as_lock), MA_OWNED) /* * Scan table definitions. */ typedef struct mtx ieee80211_scan_table_lock_t; #define IEEE80211_SCAN_TABLE_LOCK_INIT(_st, _name) \ mtx_init(&(_st)->st_lock, _name, "802.11 scan table", MTX_DEF) #define IEEE80211_SCAN_TABLE_LOCK_DESTROY(_st) mtx_destroy(&(_st)->st_lock) #define IEEE80211_SCAN_TABLE_LOCK(_st) mtx_lock(&(_st)->st_lock) #define IEEE80211_SCAN_TABLE_UNLOCK(_st) mtx_unlock(&(_st)->st_lock) typedef struct mtx ieee80211_scan_iter_lock_t; #define IEEE80211_SCAN_ITER_LOCK_INIT(_st, _name) \ mtx_init(&(_st)->st_scanlock, _name, "802.11 scangen", MTX_DEF) #define IEEE80211_SCAN_ITER_LOCK_DESTROY(_st) mtx_destroy(&(_st)->st_scanlock) #define IEEE80211_SCAN_ITER_LOCK(_st) mtx_lock(&(_st)->st_scanlock) #define IEEE80211_SCAN_ITER_UNLOCK(_st) mtx_unlock(&(_st)->st_scanlock) /* * Mesh node/routing definitions. */ typedef struct mtx ieee80211_rte_lock_t; #define MESH_RT_ENTRY_LOCK_INIT(_rt, _name) \ mtx_init(&(rt)->rt_lock, _name, "802.11s route entry", MTX_DEF) #define MESH_RT_ENTRY_LOCK_DESTROY(_rt) \ mtx_destroy(&(_rt)->rt_lock) #define MESH_RT_ENTRY_LOCK(rt) mtx_lock(&(rt)->rt_lock) #define MESH_RT_ENTRY_LOCK_ASSERT(rt) mtx_assert(&(rt)->rt_lock, MA_OWNED) #define MESH_RT_ENTRY_UNLOCK(rt) mtx_unlock(&(rt)->rt_lock) typedef struct mtx ieee80211_rt_lock_t; #define MESH_RT_LOCK(ms) mtx_lock(&(ms)->ms_rt_lock) #define MESH_RT_LOCK_ASSERT(ms) mtx_assert(&(ms)->ms_rt_lock, MA_OWNED) #define MESH_RT_UNLOCK(ms) mtx_unlock(&(ms)->ms_rt_lock) #define MESH_RT_LOCK_INIT(ms, name) \ mtx_init(&(ms)->ms_rt_lock, name, "802.11s routing table", MTX_DEF) #define MESH_RT_LOCK_DESTROY(ms) \ mtx_destroy(&(ms)->ms_rt_lock) /* * Node reference counting definitions. * * ieee80211_node_initref initialize the reference count to 1 * ieee80211_node_incref add a reference * ieee80211_node_decref remove a reference * ieee80211_node_dectestref remove a reference and return 1 if this * is the last reference, otherwise 0 * ieee80211_node_refcnt reference count for printing (only) */ #include #define ieee80211_node_initref(_ni) \ do { ((_ni)->ni_refcnt = 1); } while (0) #define ieee80211_node_incref(_ni) \ atomic_add_int(&(_ni)->ni_refcnt, 1) #define ieee80211_node_decref(_ni) \ atomic_subtract_int(&(_ni)->ni_refcnt, 1) struct ieee80211_node; int ieee80211_node_dectestref(struct ieee80211_node *ni); #define ieee80211_node_refcnt(_ni) (_ni)->ni_refcnt struct ifqueue; struct ieee80211vap; void ieee80211_drain_ifq(struct ifqueue *); void ieee80211_flush_ifq(struct ifqueue *, struct ieee80211vap *); void ieee80211_vap_destroy(struct ieee80211vap *); #define IFNET_IS_UP_RUNNING(_ifp) \ (((_ifp)->if_flags & IFF_UP) && \ ((_ifp)->if_drv_flags & IFF_DRV_RUNNING)) /* XXX TODO: cap these at 1, as hz may not be 1000 */ #define msecs_to_ticks(ms) (((ms)*hz)/1000) #define ticks_to_msecs(t) (1000*(t) / hz) #define ticks_to_secs(t) ((t) / hz) -#define time_after(a,b) ((long)(b) - (long)(a) < 0) -#define time_before(a,b) time_after(b,a) -#define time_after_eq(a,b) ((long)(a) - (long)(b) >= 0) -#define time_before_eq(a,b) time_after_eq(b,a) +#define ieee80211_time_after(a,b) ((long)(b) - (long)(a) < 0) +#define ieee80211_time_before(a,b) ieee80211_time_after(b,a) +#define ieee80211_time_after_eq(a,b) ((long)(a) - (long)(b) >= 0) +#define ieee80211_time_before_eq(a,b) ieee80211_time_after_eq(b,a) struct mbuf *ieee80211_getmgtframe(uint8_t **frm, int headroom, int pktlen); /* tx path usage */ #define M_ENCAP M_PROTO1 /* 802.11 encap done */ #define M_EAPOL M_PROTO3 /* PAE/EAPOL frame */ #define M_PWR_SAV M_PROTO4 /* bypass PS handling */ #define M_MORE_DATA M_PROTO5 /* more data frames to follow */ #define M_FF M_PROTO6 /* fast frame */ #define M_TXCB M_PROTO7 /* do tx complete callback */ #define M_AMPDU_MPDU M_PROTO8 /* ok for A-MPDU aggregation */ #define M_FRAG M_PROTO9 /* frame fragmentation */ #define M_FIRSTFRAG M_PROTO10 /* first frame fragment */ #define M_LASTFRAG M_PROTO11 /* last frame fragment */ #define M_80211_TX \ (M_ENCAP|M_EAPOL|M_PWR_SAV|M_MORE_DATA|M_FF|M_TXCB| \ M_AMPDU_MPDU|M_FRAG|M_FIRSTFRAG|M_LASTFRAG) /* rx path usage */ #define M_AMPDU M_PROTO1 /* A-MPDU subframe */ #define M_WEP M_PROTO2 /* WEP done by hardware */ #if 0 #define M_AMPDU_MPDU M_PROTO8 /* A-MPDU re-order done */ #endif #define M_80211_RX (M_AMPDU|M_WEP|M_AMPDU_MPDU) #define IEEE80211_MBUF_TX_FLAG_BITS \ M_FLAG_BITS \ "\15M_ENCAP\17M_EAPOL\20M_PWR_SAV\21M_MORE_DATA\22M_FF\23M_TXCB" \ "\24M_AMPDU_MPDU\25M_FRAG\26M_FIRSTFRAG\27M_LASTFRAG" #define IEEE80211_MBUF_RX_FLAG_BITS \ M_FLAG_BITS \ "\15M_AMPDU\16M_WEP\24M_AMPDU_MPDU" /* * Store WME access control bits in the vlan tag. * This is safe since it's done after the packet is classified * (where we use any previous tag) and because it's passed * directly in to the driver and there's no chance someone * else will clobber them on us. */ #define M_WME_SETAC(m, ac) \ ((m)->m_pkthdr.ether_vtag = (ac)) #define M_WME_GETAC(m) ((m)->m_pkthdr.ether_vtag) /* * Mbufs on the power save queue are tagged with an age and * timed out. We reuse the hardware checksum field in the * mbuf packet header to store this data. */ #define M_AGE_SET(m,v) (m->m_pkthdr.csum_data = v) #define M_AGE_GET(m) (m->m_pkthdr.csum_data) #define M_AGE_SUB(m,adj) (m->m_pkthdr.csum_data -= adj) /* * Store the sequence number. */ #define M_SEQNO_SET(m, seqno) \ ((m)->m_pkthdr.tso_segsz = (seqno)) #define M_SEQNO_GET(m) ((m)->m_pkthdr.tso_segsz) #define MTAG_ABI_NET80211 1132948340 /* net80211 ABI */ struct ieee80211_cb { void (*func)(struct ieee80211_node *, void *, int status); void *arg; }; #define NET80211_TAG_CALLBACK 0 /* xmit complete callback */ int ieee80211_add_callback(struct mbuf *m, void (*func)(struct ieee80211_node *, void *, int), void *arg); void ieee80211_process_callback(struct ieee80211_node *, struct mbuf *, int); #define NET80211_TAG_XMIT_PARAMS 1 /* See below; this is after the bpf_params definition */ #define NET80211_TAG_RECV_PARAMS 2 struct ieee80211com; int ieee80211_parent_xmitpkt(struct ieee80211com *, struct mbuf *); int ieee80211_vap_xmitpkt(struct ieee80211vap *, struct mbuf *); void get_random_bytes(void *, size_t); void ieee80211_sysctl_attach(struct ieee80211com *); void ieee80211_sysctl_detach(struct ieee80211com *); void ieee80211_sysctl_vattach(struct ieee80211vap *); void ieee80211_sysctl_vdetach(struct ieee80211vap *); SYSCTL_DECL(_net_wlan); int ieee80211_sysctl_msecs_ticks(SYSCTL_HANDLER_ARGS); void ieee80211_load_module(const char *); /* * A "policy module" is an adjunct module to net80211 that provides * functionality that typically includes policy decisions. This * modularity enables extensibility and vendor-supplied functionality. */ #define _IEEE80211_POLICY_MODULE(policy, name, version) \ typedef void (*policy##_setup)(int); \ SET_DECLARE(policy##_set, policy##_setup); \ static int \ wlan_##name##_modevent(module_t mod, int type, void *unused) \ { \ policy##_setup * const *iter, f; \ switch (type) { \ case MOD_LOAD: \ SET_FOREACH(iter, policy##_set) { \ f = (void*) *iter; \ f(type); \ } \ return 0; \ case MOD_UNLOAD: \ case MOD_QUIESCE: \ if (nrefs) { \ printf("wlan_" #name ": still in use " \ "(%u dynamic refs)\n", nrefs); \ return EBUSY; \ } \ if (type == MOD_UNLOAD) { \ SET_FOREACH(iter, policy##_set) { \ f = (void*) *iter; \ f(type); \ } \ } \ return 0; \ } \ return EINVAL; \ } \ static moduledata_t name##_mod = { \ "wlan_" #name, \ wlan_##name##_modevent, \ 0 \ }; \ DECLARE_MODULE(wlan_##name, name##_mod, SI_SUB_DRIVERS, SI_ORDER_FIRST);\ MODULE_VERSION(wlan_##name, version); \ MODULE_DEPEND(wlan_##name, wlan, 1, 1, 1) /* * Crypto modules implement cipher support. */ #define IEEE80211_CRYPTO_MODULE(name, version) \ _IEEE80211_POLICY_MODULE(crypto, name, version); \ static void \ name##_modevent(int type) \ { \ if (type == MOD_LOAD) \ ieee80211_crypto_register(&name); \ else \ ieee80211_crypto_unregister(&name); \ } \ TEXT_SET(crypto##_set, name##_modevent) /* * Scanner modules provide scanning policy. */ #define IEEE80211_SCANNER_MODULE(name, version) \ _IEEE80211_POLICY_MODULE(scanner, name, version) #define IEEE80211_SCANNER_ALG(name, alg, v) \ static void \ name##_modevent(int type) \ { \ if (type == MOD_LOAD) \ ieee80211_scanner_register(alg, &v); \ else \ ieee80211_scanner_unregister(alg, &v); \ } \ TEXT_SET(scanner_set, name##_modevent); \ /* * ACL modules implement acl policy. */ #define IEEE80211_ACL_MODULE(name, alg, version) \ _IEEE80211_POLICY_MODULE(acl, name, version); \ static void \ alg##_modevent(int type) \ { \ if (type == MOD_LOAD) \ ieee80211_aclator_register(&alg); \ else \ ieee80211_aclator_unregister(&alg); \ } \ TEXT_SET(acl_set, alg##_modevent); \ /* * Authenticator modules handle 802.1x/WPA authentication. */ #define IEEE80211_AUTH_MODULE(name, version) \ _IEEE80211_POLICY_MODULE(auth, name, version) #define IEEE80211_AUTH_ALG(name, alg, v) \ static void \ name##_modevent(int type) \ { \ if (type == MOD_LOAD) \ ieee80211_authenticator_register(alg, &v); \ else \ ieee80211_authenticator_unregister(alg); \ } \ TEXT_SET(auth_set, name##_modevent) /* * Rate control modules provide tx rate control support. */ #define IEEE80211_RATECTL_MODULE(alg, version) \ _IEEE80211_POLICY_MODULE(ratectl, alg, version); \ #define IEEE80211_RATECTL_ALG(name, alg, v) \ static void \ alg##_modevent(int type) \ { \ if (type == MOD_LOAD) \ ieee80211_ratectl_register(alg, &v); \ else \ ieee80211_ratectl_unregister(alg); \ } \ TEXT_SET(ratectl##_set, alg##_modevent) struct ieee80211req; typedef int ieee80211_ioctl_getfunc(struct ieee80211vap *, struct ieee80211req *); SET_DECLARE(ieee80211_ioctl_getset, ieee80211_ioctl_getfunc); #define IEEE80211_IOCTL_GET(_name, _get) TEXT_SET(ieee80211_ioctl_getset, _get) typedef int ieee80211_ioctl_setfunc(struct ieee80211vap *, struct ieee80211req *); SET_DECLARE(ieee80211_ioctl_setset, ieee80211_ioctl_setfunc); #define IEEE80211_IOCTL_SET(_name, _set) TEXT_SET(ieee80211_ioctl_setset, _set) #endif /* _KERNEL */ /* XXX this stuff belongs elsewhere */ /* * Message formats for messages from the net80211 layer to user * applications via the routing socket. These messages are appended * to an if_announcemsghdr structure. */ struct ieee80211_join_event { uint8_t iev_addr[6]; }; struct ieee80211_leave_event { uint8_t iev_addr[6]; }; struct ieee80211_replay_event { uint8_t iev_src[6]; /* src MAC */ uint8_t iev_dst[6]; /* dst MAC */ uint8_t iev_cipher; /* cipher type */ uint8_t iev_keyix; /* key id/index */ uint64_t iev_keyrsc; /* RSC from key */ uint64_t iev_rsc; /* RSC from frame */ }; struct ieee80211_michael_event { uint8_t iev_src[6]; /* src MAC */ uint8_t iev_dst[6]; /* dst MAC */ uint8_t iev_cipher; /* cipher type */ uint8_t iev_keyix; /* key id/index */ }; struct ieee80211_wds_event { uint8_t iev_addr[6]; }; struct ieee80211_csa_event { uint32_t iev_flags; /* channel flags */ uint16_t iev_freq; /* setting in Mhz */ uint8_t iev_ieee; /* IEEE channel number */ uint8_t iev_mode; /* CSA mode */ uint8_t iev_count; /* CSA count */ }; struct ieee80211_cac_event { uint32_t iev_flags; /* channel flags */ uint16_t iev_freq; /* setting in Mhz */ uint8_t iev_ieee; /* IEEE channel number */ /* XXX timestamp? */ uint8_t iev_type; /* IEEE80211_NOTIFY_CAC_* */ }; struct ieee80211_radar_event { uint32_t iev_flags; /* channel flags */ uint16_t iev_freq; /* setting in Mhz */ uint8_t iev_ieee; /* IEEE channel number */ /* XXX timestamp? */ }; struct ieee80211_auth_event { uint8_t iev_addr[6]; }; struct ieee80211_deauth_event { uint8_t iev_addr[6]; }; struct ieee80211_country_event { uint8_t iev_addr[6]; uint8_t iev_cc[2]; /* ISO country code */ }; struct ieee80211_radio_event { uint8_t iev_state; /* 1 on, 0 off */ }; #define RTM_IEEE80211_ASSOC 100 /* station associate (bss mode) */ #define RTM_IEEE80211_REASSOC 101 /* station re-associate (bss mode) */ #define RTM_IEEE80211_DISASSOC 102 /* station disassociate (bss mode) */ #define RTM_IEEE80211_JOIN 103 /* station join (ap mode) */ #define RTM_IEEE80211_LEAVE 104 /* station leave (ap mode) */ #define RTM_IEEE80211_SCAN 105 /* scan complete, results available */ #define RTM_IEEE80211_REPLAY 106 /* sequence counter replay detected */ #define RTM_IEEE80211_MICHAEL 107 /* Michael MIC failure detected */ #define RTM_IEEE80211_REJOIN 108 /* station re-associate (ap mode) */ #define RTM_IEEE80211_WDS 109 /* WDS discovery (ap mode) */ #define RTM_IEEE80211_CSA 110 /* Channel Switch Announcement event */ #define RTM_IEEE80211_RADAR 111 /* radar event */ #define RTM_IEEE80211_CAC 112 /* Channel Availability Check event */ #define RTM_IEEE80211_DEAUTH 113 /* station deauthenticate */ #define RTM_IEEE80211_AUTH 114 /* station authenticate (ap mode) */ #define RTM_IEEE80211_COUNTRY 115 /* discovered country code (sta mode) */ #define RTM_IEEE80211_RADIO 116 /* RF kill switch state change */ /* * Structure prepended to raw packets sent through the bpf * interface when set to DLT_IEEE802_11_RADIO. This allows * user applications to specify pretty much everything in * an Atheros tx descriptor. XXX need to generalize. * * XXX cannot be more than 14 bytes as it is copied to a sockaddr's * XXX sa_data area. */ struct ieee80211_bpf_params { uint8_t ibp_vers; /* version */ #define IEEE80211_BPF_VERSION 0 uint8_t ibp_len; /* header length in bytes */ uint8_t ibp_flags; #define IEEE80211_BPF_SHORTPRE 0x01 /* tx with short preamble */ #define IEEE80211_BPF_NOACK 0x02 /* tx with no ack */ #define IEEE80211_BPF_CRYPTO 0x04 /* tx with h/w encryption */ #define IEEE80211_BPF_FCS 0x10 /* frame incldues FCS */ #define IEEE80211_BPF_DATAPAD 0x20 /* frame includes data padding */ #define IEEE80211_BPF_RTS 0x40 /* tx with RTS/CTS */ #define IEEE80211_BPF_CTS 0x80 /* tx with CTS only */ uint8_t ibp_pri; /* WME/WMM AC+tx antenna */ uint8_t ibp_try0; /* series 1 try count */ uint8_t ibp_rate0; /* series 1 IEEE tx rate */ uint8_t ibp_power; /* tx power (device units) */ uint8_t ibp_ctsrate; /* IEEE tx rate for CTS */ uint8_t ibp_try1; /* series 2 try count */ uint8_t ibp_rate1; /* series 2 IEEE tx rate */ uint8_t ibp_try2; /* series 3 try count */ uint8_t ibp_rate2; /* series 3 IEEE tx rate */ uint8_t ibp_try3; /* series 4 try count */ uint8_t ibp_rate3; /* series 4 IEEE tx rate */ }; #ifdef _KERNEL struct ieee80211_tx_params { struct ieee80211_bpf_params params; }; int ieee80211_add_xmit_params(struct mbuf *m, const struct ieee80211_bpf_params *); int ieee80211_get_xmit_params(struct mbuf *m, struct ieee80211_bpf_params *); #define IEEE80211_MAX_CHAINS 3 #define IEEE80211_MAX_EVM_PILOTS 6 #define IEEE80211_R_NF 0x0000001 /* global NF value valid */ #define IEEE80211_R_RSSI 0x0000002 /* global RSSI value valid */ #define IEEE80211_R_C_CHAIN 0x0000004 /* RX chain count valid */ #define IEEE80211_R_C_NF 0x0000008 /* per-chain NF value valid */ #define IEEE80211_R_C_RSSI 0x0000010 /* per-chain RSSI value valid */ #define IEEE80211_R_C_EVM 0x0000020 /* per-chain EVM valid */ #define IEEE80211_R_C_HT40 0x0000040 /* RX'ed packet is 40mhz, pilots 4,5 valid */ #define IEEE80211_R_FREQ 0x0000080 /* Freq value populated, MHz */ #define IEEE80211_R_IEEE 0x0000100 /* IEEE value populated */ #define IEEE80211_R_BAND 0x0000200 /* Frequency band populated */ struct ieee80211_rx_stats { uint32_t r_flags; /* IEEE80211_R_* flags */ uint8_t c_chain; /* number of RX chains involved */ int16_t c_nf_ctl[IEEE80211_MAX_CHAINS]; /* per-chain NF */ int16_t c_nf_ext[IEEE80211_MAX_CHAINS]; /* per-chain NF */ int16_t c_rssi_ctl[IEEE80211_MAX_CHAINS]; /* per-chain RSSI */ int16_t c_rssi_ext[IEEE80211_MAX_CHAINS]; /* per-chain RSSI */ uint8_t nf; /* global NF */ uint8_t rssi; /* global RSSI */ uint8_t evm[IEEE80211_MAX_CHAINS][IEEE80211_MAX_EVM_PILOTS]; /* per-chain, per-pilot EVM values */ uint16_t c_freq; uint8_t c_ieee; }; struct ieee80211_rx_params { struct ieee80211_rx_stats params; }; int ieee80211_add_rx_params(struct mbuf *m, const struct ieee80211_rx_stats *rxs); int ieee80211_get_rx_params(struct mbuf *m, struct ieee80211_rx_stats *rxs); #endif /* _KERNEL */ /* * Malloc API. Other BSD operating systems have slightly * different malloc/free namings (eg DragonflyBSD.) */ #define IEEE80211_MALLOC malloc #define IEEE80211_FREE free /* XXX TODO: get rid of WAITOK, fix all the users of it? */ #define IEEE80211_M_NOWAIT M_NOWAIT #define IEEE80211_M_WAITOK M_WAITOK #define IEEE80211_M_ZERO M_ZERO /* XXX TODO: the type fields */ #endif /* _NET80211_IEEE80211_FREEBSD_H_ */ Index: head/sys/net80211/ieee80211_ht.c =================================================================== --- head/sys/net80211/ieee80211_ht.c (revision 297404) +++ head/sys/net80211/ieee80211_ht.c (revision 297405) @@ -1,2979 +1,2979 @@ /*- * Copyright (c) 2007-2008 Sam Leffler, Errno Consulting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #ifdef __FreeBSD__ __FBSDID("$FreeBSD$"); #endif /* * IEEE 802.11n protocol support. */ #include "opt_inet.h" #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include /* define here, used throughout file */ #define MS(_v, _f) (((_v) & _f) >> _f##_S) #define SM(_v, _f) (((_v) << _f##_S) & _f) const struct ieee80211_mcs_rates ieee80211_htrates[IEEE80211_HTRATE_MAXSIZE] = { { 13, 14, 27, 30 }, /* MCS 0 */ { 26, 29, 54, 60 }, /* MCS 1 */ { 39, 43, 81, 90 }, /* MCS 2 */ { 52, 58, 108, 120 }, /* MCS 3 */ { 78, 87, 162, 180 }, /* MCS 4 */ { 104, 116, 216, 240 }, /* MCS 5 */ { 117, 130, 243, 270 }, /* MCS 6 */ { 130, 144, 270, 300 }, /* MCS 7 */ { 26, 29, 54, 60 }, /* MCS 8 */ { 52, 58, 108, 120 }, /* MCS 9 */ { 78, 87, 162, 180 }, /* MCS 10 */ { 104, 116, 216, 240 }, /* MCS 11 */ { 156, 173, 324, 360 }, /* MCS 12 */ { 208, 231, 432, 480 }, /* MCS 13 */ { 234, 260, 486, 540 }, /* MCS 14 */ { 260, 289, 540, 600 }, /* MCS 15 */ { 39, 43, 81, 90 }, /* MCS 16 */ { 78, 87, 162, 180 }, /* MCS 17 */ { 117, 130, 243, 270 }, /* MCS 18 */ { 156, 173, 324, 360 }, /* MCS 19 */ { 234, 260, 486, 540 }, /* MCS 20 */ { 312, 347, 648, 720 }, /* MCS 21 */ { 351, 390, 729, 810 }, /* MCS 22 */ { 390, 433, 810, 900 }, /* MCS 23 */ { 52, 58, 108, 120 }, /* MCS 24 */ { 104, 116, 216, 240 }, /* MCS 25 */ { 156, 173, 324, 360 }, /* MCS 26 */ { 208, 231, 432, 480 }, /* MCS 27 */ { 312, 347, 648, 720 }, /* MCS 28 */ { 416, 462, 864, 960 }, /* MCS 29 */ { 468, 520, 972, 1080 }, /* MCS 30 */ { 520, 578, 1080, 1200 }, /* MCS 31 */ { 0, 0, 12, 13 }, /* MCS 32 */ { 78, 87, 162, 180 }, /* MCS 33 */ { 104, 116, 216, 240 }, /* MCS 34 */ { 130, 144, 270, 300 }, /* MCS 35 */ { 117, 130, 243, 270 }, /* MCS 36 */ { 156, 173, 324, 360 }, /* MCS 37 */ { 195, 217, 405, 450 }, /* MCS 38 */ { 104, 116, 216, 240 }, /* MCS 39 */ { 130, 144, 270, 300 }, /* MCS 40 */ { 130, 144, 270, 300 }, /* MCS 41 */ { 156, 173, 324, 360 }, /* MCS 42 */ { 182, 202, 378, 420 }, /* MCS 43 */ { 182, 202, 378, 420 }, /* MCS 44 */ { 208, 231, 432, 480 }, /* MCS 45 */ { 156, 173, 324, 360 }, /* MCS 46 */ { 195, 217, 405, 450 }, /* MCS 47 */ { 195, 217, 405, 450 }, /* MCS 48 */ { 234, 260, 486, 540 }, /* MCS 49 */ { 273, 303, 567, 630 }, /* MCS 50 */ { 273, 303, 567, 630 }, /* MCS 51 */ { 312, 347, 648, 720 }, /* MCS 52 */ { 130, 144, 270, 300 }, /* MCS 53 */ { 156, 173, 324, 360 }, /* MCS 54 */ { 182, 202, 378, 420 }, /* MCS 55 */ { 156, 173, 324, 360 }, /* MCS 56 */ { 182, 202, 378, 420 }, /* MCS 57 */ { 208, 231, 432, 480 }, /* MCS 58 */ { 234, 260, 486, 540 }, /* MCS 59 */ { 208, 231, 432, 480 }, /* MCS 60 */ { 234, 260, 486, 540 }, /* MCS 61 */ { 260, 289, 540, 600 }, /* MCS 62 */ { 260, 289, 540, 600 }, /* MCS 63 */ { 286, 318, 594, 660 }, /* MCS 64 */ { 195, 217, 405, 450 }, /* MCS 65 */ { 234, 260, 486, 540 }, /* MCS 66 */ { 273, 303, 567, 630 }, /* MCS 67 */ { 234, 260, 486, 540 }, /* MCS 68 */ { 273, 303, 567, 630 }, /* MCS 69 */ { 312, 347, 648, 720 }, /* MCS 70 */ { 351, 390, 729, 810 }, /* MCS 71 */ { 312, 347, 648, 720 }, /* MCS 72 */ { 351, 390, 729, 810 }, /* MCS 73 */ { 390, 433, 810, 900 }, /* MCS 74 */ { 390, 433, 810, 900 }, /* MCS 75 */ { 429, 477, 891, 990 }, /* MCS 76 */ }; #ifdef IEEE80211_AMPDU_AGE static int ieee80211_ampdu_age = -1; /* threshold for ampdu reorder q (ms) */ SYSCTL_PROC(_net_wlan, OID_AUTO, ampdu_age, CTLTYPE_INT | CTLFLAG_RW, &ieee80211_ampdu_age, 0, ieee80211_sysctl_msecs_ticks, "I", "AMPDU max reorder age (ms)"); #endif static int ieee80211_recv_bar_ena = 1; SYSCTL_INT(_net_wlan, OID_AUTO, recv_bar, CTLFLAG_RW, &ieee80211_recv_bar_ena, 0, "BAR frame processing (ena/dis)"); static int ieee80211_addba_timeout = -1;/* timeout for ADDBA response */ SYSCTL_PROC(_net_wlan, OID_AUTO, addba_timeout, CTLTYPE_INT | CTLFLAG_RW, &ieee80211_addba_timeout, 0, ieee80211_sysctl_msecs_ticks, "I", "ADDBA request timeout (ms)"); static int ieee80211_addba_backoff = -1;/* backoff after max ADDBA requests */ SYSCTL_PROC(_net_wlan, OID_AUTO, addba_backoff, CTLTYPE_INT | CTLFLAG_RW, &ieee80211_addba_backoff, 0, ieee80211_sysctl_msecs_ticks, "I", "ADDBA request backoff (ms)"); static int ieee80211_addba_maxtries = 3;/* max ADDBA requests before backoff */ SYSCTL_INT(_net_wlan, OID_AUTO, addba_maxtries, CTLFLAG_RW, &ieee80211_addba_maxtries, 0, "max ADDBA requests sent before backoff"); static int ieee80211_bar_timeout = -1; /* timeout waiting for BAR response */ static int ieee80211_bar_maxtries = 50;/* max BAR requests before DELBA */ static ieee80211_recv_action_func ht_recv_action_ba_addba_request; static ieee80211_recv_action_func ht_recv_action_ba_addba_response; static ieee80211_recv_action_func ht_recv_action_ba_delba; static ieee80211_recv_action_func ht_recv_action_ht_mimopwrsave; static ieee80211_recv_action_func ht_recv_action_ht_txchwidth; static ieee80211_send_action_func ht_send_action_ba_addba; static ieee80211_send_action_func ht_send_action_ba_delba; static ieee80211_send_action_func ht_send_action_ht_txchwidth; static void ieee80211_ht_init(void) { /* * Setup HT parameters that depends on the clock frequency. */ #ifdef IEEE80211_AMPDU_AGE ieee80211_ampdu_age = msecs_to_ticks(500); #endif ieee80211_addba_timeout = msecs_to_ticks(250); ieee80211_addba_backoff = msecs_to_ticks(10*1000); ieee80211_bar_timeout = msecs_to_ticks(250); /* * Register action frame handlers. */ ieee80211_recv_action_register(IEEE80211_ACTION_CAT_BA, IEEE80211_ACTION_BA_ADDBA_REQUEST, ht_recv_action_ba_addba_request); ieee80211_recv_action_register(IEEE80211_ACTION_CAT_BA, IEEE80211_ACTION_BA_ADDBA_RESPONSE, ht_recv_action_ba_addba_response); ieee80211_recv_action_register(IEEE80211_ACTION_CAT_BA, IEEE80211_ACTION_BA_DELBA, ht_recv_action_ba_delba); ieee80211_recv_action_register(IEEE80211_ACTION_CAT_HT, IEEE80211_ACTION_HT_MIMOPWRSAVE, ht_recv_action_ht_mimopwrsave); ieee80211_recv_action_register(IEEE80211_ACTION_CAT_HT, IEEE80211_ACTION_HT_TXCHWIDTH, ht_recv_action_ht_txchwidth); ieee80211_send_action_register(IEEE80211_ACTION_CAT_BA, IEEE80211_ACTION_BA_ADDBA_REQUEST, ht_send_action_ba_addba); ieee80211_send_action_register(IEEE80211_ACTION_CAT_BA, IEEE80211_ACTION_BA_ADDBA_RESPONSE, ht_send_action_ba_addba); ieee80211_send_action_register(IEEE80211_ACTION_CAT_BA, IEEE80211_ACTION_BA_DELBA, ht_send_action_ba_delba); ieee80211_send_action_register(IEEE80211_ACTION_CAT_HT, IEEE80211_ACTION_HT_TXCHWIDTH, ht_send_action_ht_txchwidth); } SYSINIT(wlan_ht, SI_SUB_DRIVERS, SI_ORDER_FIRST, ieee80211_ht_init, NULL); static int ieee80211_ampdu_enable(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap); static int ieee80211_addba_request(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap, int dialogtoken, int baparamset, int batimeout); static int ieee80211_addba_response(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap, int code, int baparamset, int batimeout); static void ieee80211_addba_stop(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap); static void null_addba_response_timeout(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap); static void ieee80211_bar_response(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap, int status); static void ampdu_tx_stop(struct ieee80211_tx_ampdu *tap); static void bar_stop_timer(struct ieee80211_tx_ampdu *tap); static int ampdu_rx_start(struct ieee80211_node *, struct ieee80211_rx_ampdu *, int baparamset, int batimeout, int baseqctl); static void ampdu_rx_stop(struct ieee80211_node *, struct ieee80211_rx_ampdu *); void ieee80211_ht_attach(struct ieee80211com *ic) { /* setup default aggregation policy */ ic->ic_recv_action = ieee80211_recv_action; ic->ic_send_action = ieee80211_send_action; ic->ic_ampdu_enable = ieee80211_ampdu_enable; ic->ic_addba_request = ieee80211_addba_request; ic->ic_addba_response = ieee80211_addba_response; ic->ic_addba_response_timeout = null_addba_response_timeout; ic->ic_addba_stop = ieee80211_addba_stop; ic->ic_bar_response = ieee80211_bar_response; ic->ic_ampdu_rx_start = ampdu_rx_start; ic->ic_ampdu_rx_stop = ampdu_rx_stop; ic->ic_htprotmode = IEEE80211_PROT_RTSCTS; ic->ic_curhtprotmode = IEEE80211_HTINFO_OPMODE_PURE; } void ieee80211_ht_detach(struct ieee80211com *ic) { } void ieee80211_ht_vattach(struct ieee80211vap *vap) { /* driver can override defaults */ vap->iv_ampdu_rxmax = IEEE80211_HTCAP_MAXRXAMPDU_8K; vap->iv_ampdu_density = IEEE80211_HTCAP_MPDUDENSITY_NA; vap->iv_ampdu_limit = vap->iv_ampdu_rxmax; vap->iv_amsdu_limit = vap->iv_htcaps & IEEE80211_HTCAP_MAXAMSDU; /* tx aggregation traffic thresholds */ vap->iv_ampdu_mintraffic[WME_AC_BK] = 128; vap->iv_ampdu_mintraffic[WME_AC_BE] = 64; vap->iv_ampdu_mintraffic[WME_AC_VO] = 32; vap->iv_ampdu_mintraffic[WME_AC_VI] = 32; if (vap->iv_htcaps & IEEE80211_HTC_HT) { /* * Device is HT capable; enable all HT-related * facilities by default. * XXX these choices may be too aggressive. */ vap->iv_flags_ht |= IEEE80211_FHT_HT | IEEE80211_FHT_HTCOMPAT ; if (vap->iv_htcaps & IEEE80211_HTCAP_SHORTGI20) vap->iv_flags_ht |= IEEE80211_FHT_SHORTGI20; /* XXX infer from channel list? */ if (vap->iv_htcaps & IEEE80211_HTCAP_CHWIDTH40) { vap->iv_flags_ht |= IEEE80211_FHT_USEHT40; if (vap->iv_htcaps & IEEE80211_HTCAP_SHORTGI40) vap->iv_flags_ht |= IEEE80211_FHT_SHORTGI40; } /* enable RIFS if capable */ if (vap->iv_htcaps & IEEE80211_HTC_RIFS) vap->iv_flags_ht |= IEEE80211_FHT_RIFS; /* NB: A-MPDU and A-MSDU rx are mandated, these are tx only */ vap->iv_flags_ht |= IEEE80211_FHT_AMPDU_RX; if (vap->iv_htcaps & IEEE80211_HTC_AMPDU) vap->iv_flags_ht |= IEEE80211_FHT_AMPDU_TX; vap->iv_flags_ht |= IEEE80211_FHT_AMSDU_RX; if (vap->iv_htcaps & IEEE80211_HTC_AMSDU) vap->iv_flags_ht |= IEEE80211_FHT_AMSDU_TX; } /* NB: disable default legacy WDS, too many issues right now */ if (vap->iv_flags_ext & IEEE80211_FEXT_WDSLEGACY) vap->iv_flags_ht &= ~IEEE80211_FHT_HT; } void ieee80211_ht_vdetach(struct ieee80211vap *vap) { } static int ht_getrate(struct ieee80211com *ic, int index, enum ieee80211_phymode mode, int ratetype) { int mword, rate; mword = ieee80211_rate2media(ic, index | IEEE80211_RATE_MCS, mode); if (IFM_SUBTYPE(mword) != IFM_IEEE80211_MCS) return (0); switch (ratetype) { case 0: rate = ieee80211_htrates[index].ht20_rate_800ns; break; case 1: rate = ieee80211_htrates[index].ht20_rate_400ns; break; case 2: rate = ieee80211_htrates[index].ht40_rate_800ns; break; default: rate = ieee80211_htrates[index].ht40_rate_400ns; break; } return (rate); } static struct printranges { int minmcs; int maxmcs; int txstream; int ratetype; int htcapflags; } ranges[] = { { 0, 7, 1, 0, 0 }, { 8, 15, 2, 0, 0 }, { 16, 23, 3, 0, 0 }, { 24, 31, 4, 0, 0 }, { 32, 0, 1, 2, IEEE80211_HTC_TXMCS32 }, { 33, 38, 2, 0, IEEE80211_HTC_TXUNEQUAL }, { 39, 52, 3, 0, IEEE80211_HTC_TXUNEQUAL }, { 53, 76, 4, 0, IEEE80211_HTC_TXUNEQUAL }, { 0, 0, 0, 0, 0 }, }; static void ht_rateprint(struct ieee80211com *ic, enum ieee80211_phymode mode, int ratetype) { int minrate, maxrate; struct printranges *range; for (range = ranges; range->txstream != 0; range++) { if (ic->ic_txstream < range->txstream) continue; if (range->htcapflags && (ic->ic_htcaps & range->htcapflags) == 0) continue; if (ratetype < range->ratetype) continue; minrate = ht_getrate(ic, range->minmcs, mode, ratetype); maxrate = ht_getrate(ic, range->maxmcs, mode, ratetype); if (range->maxmcs) { ic_printf(ic, "MCS %d-%d: %d%sMbps - %d%sMbps\n", range->minmcs, range->maxmcs, minrate/2, ((minrate & 0x1) != 0 ? ".5" : ""), maxrate/2, ((maxrate & 0x1) != 0 ? ".5" : "")); } else { ic_printf(ic, "MCS %d: %d%sMbps\n", range->minmcs, minrate/2, ((minrate & 0x1) != 0 ? ".5" : "")); } } } static void ht_announce(struct ieee80211com *ic, enum ieee80211_phymode mode) { const char *modestr = ieee80211_phymode_name[mode]; ic_printf(ic, "%s MCS 20MHz\n", modestr); ht_rateprint(ic, mode, 0); if (ic->ic_htcaps & IEEE80211_HTCAP_SHORTGI20) { ic_printf(ic, "%s MCS 20MHz SGI\n", modestr); ht_rateprint(ic, mode, 1); } if (ic->ic_htcaps & IEEE80211_HTCAP_CHWIDTH40) { ic_printf(ic, "%s MCS 40MHz:\n", modestr); ht_rateprint(ic, mode, 2); } if ((ic->ic_htcaps & IEEE80211_HTCAP_CHWIDTH40) && (ic->ic_htcaps & IEEE80211_HTCAP_SHORTGI40)) { ic_printf(ic, "%s MCS 40MHz SGI:\n", modestr); ht_rateprint(ic, mode, 3); } } void ieee80211_ht_announce(struct ieee80211com *ic) { if (isset(ic->ic_modecaps, IEEE80211_MODE_11NA) || isset(ic->ic_modecaps, IEEE80211_MODE_11NG)) ic_printf(ic, "%dT%dR\n", ic->ic_txstream, ic->ic_rxstream); if (isset(ic->ic_modecaps, IEEE80211_MODE_11NA)) ht_announce(ic, IEEE80211_MODE_11NA); if (isset(ic->ic_modecaps, IEEE80211_MODE_11NG)) ht_announce(ic, IEEE80211_MODE_11NG); } static struct ieee80211_htrateset htrateset; const struct ieee80211_htrateset * ieee80211_get_suphtrates(struct ieee80211com *ic, const struct ieee80211_channel *c) { #define ADDRATE(x) do { \ htrateset.rs_rates[htrateset.rs_nrates] = x; \ htrateset.rs_nrates++; \ } while (0) int i; memset(&htrateset, 0, sizeof(struct ieee80211_htrateset)); for (i = 0; i < ic->ic_txstream * 8; i++) ADDRATE(i); if ((ic->ic_htcaps & IEEE80211_HTCAP_CHWIDTH40) && (ic->ic_htcaps & IEEE80211_HTC_TXMCS32)) ADDRATE(32); if (ic->ic_htcaps & IEEE80211_HTC_TXUNEQUAL) { if (ic->ic_txstream >= 2) { for (i = 33; i <= 38; i++) ADDRATE(i); } if (ic->ic_txstream >= 3) { for (i = 39; i <= 52; i++) ADDRATE(i); } if (ic->ic_txstream == 4) { for (i = 53; i <= 76; i++) ADDRATE(i); } } return &htrateset; #undef ADDRATE } /* * Receive processing. */ /* * Decap the encapsulated A-MSDU frames and dispatch all but * the last for delivery. The last frame is returned for * delivery via the normal path. */ struct mbuf * ieee80211_decap_amsdu(struct ieee80211_node *ni, struct mbuf *m) { struct ieee80211vap *vap = ni->ni_vap; int framelen; struct mbuf *n; /* discard 802.3 header inserted by ieee80211_decap */ m_adj(m, sizeof(struct ether_header)); vap->iv_stats.is_amsdu_decap++; for (;;) { /* * Decap the first frame, bust it apart from the * remainder and deliver. We leave the last frame * delivery to the caller (for consistency with other * code paths, could also do it here). */ m = ieee80211_decap1(m, &framelen); if (m == NULL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, "a-msdu", "%s", "decap failed"); vap->iv_stats.is_amsdu_tooshort++; return NULL; } if (m->m_pkthdr.len == framelen) break; n = m_split(m, framelen, M_NOWAIT); if (n == NULL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, "a-msdu", "%s", "unable to split encapsulated frames"); vap->iv_stats.is_amsdu_split++; m_freem(m); /* NB: must reclaim */ return NULL; } vap->iv_deliver_data(vap, ni, m); /* * Remove frame contents; each intermediate frame * is required to be aligned to a 4-byte boundary. */ m = n; m_adj(m, roundup2(framelen, 4) - framelen); /* padding */ } return m; /* last delivered by caller */ } /* * Purge all frames in the A-MPDU re-order queue. */ static void ampdu_rx_purge(struct ieee80211_rx_ampdu *rap) { struct mbuf *m; int i; for (i = 0; i < rap->rxa_wnd; i++) { m = rap->rxa_m[i]; if (m != NULL) { rap->rxa_m[i] = NULL; rap->rxa_qbytes -= m->m_pkthdr.len; m_freem(m); if (--rap->rxa_qframes == 0) break; } } KASSERT(rap->rxa_qbytes == 0 && rap->rxa_qframes == 0, ("lost %u data, %u frames on ampdu rx q", rap->rxa_qbytes, rap->rxa_qframes)); } /* * Start A-MPDU rx/re-order processing for the specified TID. */ static int ampdu_rx_start(struct ieee80211_node *ni, struct ieee80211_rx_ampdu *rap, int baparamset, int batimeout, int baseqctl) { int bufsiz = MS(baparamset, IEEE80211_BAPS_BUFSIZ); if (rap->rxa_flags & IEEE80211_AGGR_RUNNING) { /* * AMPDU previously setup and not terminated with a DELBA, * flush the reorder q's in case anything remains. */ ampdu_rx_purge(rap); } memset(rap, 0, sizeof(*rap)); rap->rxa_wnd = (bufsiz == 0) ? IEEE80211_AGGR_BAWMAX : min(bufsiz, IEEE80211_AGGR_BAWMAX); rap->rxa_start = MS(baseqctl, IEEE80211_BASEQ_START); rap->rxa_flags |= IEEE80211_AGGR_RUNNING | IEEE80211_AGGR_XCHGPEND; return 0; } /* * Public function; manually setup the RX ampdu state. */ int ieee80211_ampdu_rx_start_ext(struct ieee80211_node *ni, int tid, int seq, int baw) { struct ieee80211_rx_ampdu *rap; /* XXX TODO: sanity check tid, seq, baw */ rap = &ni->ni_rx_ampdu[tid]; if (rap->rxa_flags & IEEE80211_AGGR_RUNNING) { /* * AMPDU previously setup and not terminated with a DELBA, * flush the reorder q's in case anything remains. */ ampdu_rx_purge(rap); } memset(rap, 0, sizeof(*rap)); rap->rxa_wnd = (baw== 0) ? IEEE80211_AGGR_BAWMAX : min(baw, IEEE80211_AGGR_BAWMAX); rap->rxa_start = seq; rap->rxa_flags |= IEEE80211_AGGR_RUNNING | IEEE80211_AGGR_XCHGPEND; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: tid=%d, start=%d, wnd=%d, flags=0x%08x\n", __func__, tid, seq, rap->rxa_wnd, rap->rxa_flags); return 0; } /* * Stop A-MPDU rx processing for the specified TID. */ static void ampdu_rx_stop(struct ieee80211_node *ni, struct ieee80211_rx_ampdu *rap) { ampdu_rx_purge(rap); rap->rxa_flags &= ~(IEEE80211_AGGR_RUNNING | IEEE80211_AGGR_XCHGPEND); } /* * Dispatch a frame from the A-MPDU reorder queue. The * frame is fed back into ieee80211_input marked with an * M_AMPDU_MPDU flag so it doesn't come back to us (it also * permits ieee80211_input to optimize re-processing). */ static __inline void ampdu_dispatch(struct ieee80211_node *ni, struct mbuf *m) { m->m_flags |= M_AMPDU_MPDU; /* bypass normal processing */ /* NB: rssi and noise are ignored w/ M_AMPDU_MPDU set */ (void) ieee80211_input(ni, m, 0, 0); } /* * Dispatch as many frames as possible from the re-order queue. * Frames will always be "at the front"; we process all frames * up to the first empty slot in the window. On completion we * cleanup state if there are still pending frames in the current * BA window. We assume the frame at slot 0 is already handled * by the caller; we always start at slot 1. */ static void ampdu_rx_dispatch(struct ieee80211_rx_ampdu *rap, struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct mbuf *m; int i; /* flush run of frames */ for (i = 1; i < rap->rxa_wnd; i++) { m = rap->rxa_m[i]; if (m == NULL) break; rap->rxa_m[i] = NULL; rap->rxa_qbytes -= m->m_pkthdr.len; rap->rxa_qframes--; ampdu_dispatch(ni, m); } /* * If frames remain, copy the mbuf pointers down so * they correspond to the offsets in the new window. */ if (rap->rxa_qframes != 0) { int n = rap->rxa_qframes, j; for (j = i+1; j < rap->rxa_wnd; j++) { if (rap->rxa_m[j] != NULL) { rap->rxa_m[j-i] = rap->rxa_m[j]; rap->rxa_m[j] = NULL; if (--n == 0) break; } } KASSERT(n == 0, ("lost %d frames", n)); vap->iv_stats.is_ampdu_rx_copy += rap->rxa_qframes; } /* * Adjust the start of the BA window to * reflect the frames just dispatched. */ rap->rxa_start = IEEE80211_SEQ_ADD(rap->rxa_start, i); vap->iv_stats.is_ampdu_rx_oor += i; } #ifdef IEEE80211_AMPDU_AGE /* * Dispatch all frames in the A-MPDU re-order queue. */ static void ampdu_rx_flush(struct ieee80211_node *ni, struct ieee80211_rx_ampdu *rap) { struct ieee80211vap *vap = ni->ni_vap; struct mbuf *m; int i; for (i = 0; i < rap->rxa_wnd; i++) { m = rap->rxa_m[i]; if (m == NULL) continue; rap->rxa_m[i] = NULL; rap->rxa_qbytes -= m->m_pkthdr.len; rap->rxa_qframes--; vap->iv_stats.is_ampdu_rx_oor++; ampdu_dispatch(ni, m); if (rap->rxa_qframes == 0) break; } } #endif /* IEEE80211_AMPDU_AGE */ /* * Dispatch all frames in the A-MPDU re-order queue * preceding the specified sequence number. This logic * handles window moves due to a received MSDU or BAR. */ static void ampdu_rx_flush_upto(struct ieee80211_node *ni, struct ieee80211_rx_ampdu *rap, ieee80211_seq winstart) { struct ieee80211vap *vap = ni->ni_vap; struct mbuf *m; ieee80211_seq seqno; int i; /* * Flush any complete MSDU's with a sequence number lower * than winstart. Gaps may exist. Note that we may actually * dispatch frames past winstart if a run continues; this is * an optimization that avoids having to do a separate pass * to dispatch frames after moving the BA window start. */ seqno = rap->rxa_start; for (i = 0; i < rap->rxa_wnd; i++) { m = rap->rxa_m[i]; if (m != NULL) { rap->rxa_m[i] = NULL; rap->rxa_qbytes -= m->m_pkthdr.len; rap->rxa_qframes--; vap->iv_stats.is_ampdu_rx_oor++; ampdu_dispatch(ni, m); } else { if (!IEEE80211_SEQ_BA_BEFORE(seqno, winstart)) break; } seqno = IEEE80211_SEQ_INC(seqno); } /* * If frames remain, copy the mbuf pointers down so * they correspond to the offsets in the new window. */ if (rap->rxa_qframes != 0) { int n = rap->rxa_qframes, j; /* NB: this loop assumes i > 0 and/or rxa_m[0] is NULL */ KASSERT(rap->rxa_m[0] == NULL, ("%s: BA window slot 0 occupied", __func__)); for (j = i+1; j < rap->rxa_wnd; j++) { if (rap->rxa_m[j] != NULL) { rap->rxa_m[j-i] = rap->rxa_m[j]; rap->rxa_m[j] = NULL; if (--n == 0) break; } } KASSERT(n == 0, ("%s: lost %d frames, qframes %d off %d " "BA win <%d:%d> winstart %d", __func__, n, rap->rxa_qframes, i, rap->rxa_start, IEEE80211_SEQ_ADD(rap->rxa_start, rap->rxa_wnd-1), winstart)); vap->iv_stats.is_ampdu_rx_copy += rap->rxa_qframes; } /* * Move the start of the BA window; we use the * sequence number of the last MSDU that was * passed up the stack+1 or winstart if stopped on * a gap in the reorder buffer. */ rap->rxa_start = seqno; } /* * Process a received QoS data frame for an HT station. Handle * A-MPDU reordering: if this frame is received out of order * and falls within the BA window hold onto it. Otherwise if * this frame completes a run, flush any pending frames. We * return 1 if the frame is consumed. A 0 is returned if * the frame should be processed normally by the caller. */ int ieee80211_ampdu_reorder(struct ieee80211_node *ni, struct mbuf *m) { #define IEEE80211_FC0_QOSDATA \ (IEEE80211_FC0_TYPE_DATA|IEEE80211_FC0_SUBTYPE_QOS|IEEE80211_FC0_VERSION_0) #define PROCESS 0 /* caller should process frame */ #define CONSUMED 1 /* frame consumed, caller does nothing */ struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_qosframe *wh; struct ieee80211_rx_ampdu *rap; ieee80211_seq rxseq; uint8_t tid; int off; KASSERT((m->m_flags & (M_AMPDU | M_AMPDU_MPDU)) == M_AMPDU, ("!a-mpdu or already re-ordered, flags 0x%x", m->m_flags)); KASSERT(ni->ni_flags & IEEE80211_NODE_HT, ("not an HT sta")); /* NB: m_len known to be sufficient */ wh = mtod(m, struct ieee80211_qosframe *); if (wh->i_fc[0] != IEEE80211_FC0_QOSDATA) { /* * Not QoS data, shouldn't get here but just * return it to the caller for processing. */ return PROCESS; } if (IEEE80211_IS_DSTODS(wh)) tid = ((struct ieee80211_qosframe_addr4 *)wh)->i_qos[0]; else tid = wh->i_qos[0]; tid &= IEEE80211_QOS_TID; rap = &ni->ni_rx_ampdu[tid]; if ((rap->rxa_flags & IEEE80211_AGGR_XCHGPEND) == 0) { /* * No ADDBA request yet, don't touch. */ return PROCESS; } rxseq = le16toh(*(uint16_t *)wh->i_seq); if ((rxseq & IEEE80211_SEQ_FRAG_MASK) != 0) { /* * Fragments are not allowed; toss. */ IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_11N, ni->ni_macaddr, "A-MPDU", "fragment, rxseq 0x%x tid %u%s", rxseq, tid, wh->i_fc[1] & IEEE80211_FC1_RETRY ? " (retransmit)" : ""); vap->iv_stats.is_ampdu_rx_drop++; IEEE80211_NODE_STAT(ni, rx_drop); m_freem(m); return CONSUMED; } rxseq >>= IEEE80211_SEQ_SEQ_SHIFT; rap->rxa_nframes++; again: if (rxseq == rap->rxa_start) { /* * First frame in window. */ if (rap->rxa_qframes != 0) { /* * Dispatch as many packets as we can. */ KASSERT(rap->rxa_m[0] == NULL, ("unexpected dup")); ampdu_dispatch(ni, m); ampdu_rx_dispatch(rap, ni); return CONSUMED; } else { /* * In order; advance window and notify * caller to dispatch directly. */ rap->rxa_start = IEEE80211_SEQ_INC(rxseq); return PROCESS; } } /* * Frame is out of order; store if in the BA window. */ /* calculate offset in BA window */ off = IEEE80211_SEQ_SUB(rxseq, rap->rxa_start); if (off < rap->rxa_wnd) { /* * Common case (hopefully): in the BA window. * Sec 9.10.7.6.2 a) (p.137) */ #ifdef IEEE80211_AMPDU_AGE /* * Check for frames sitting too long in the reorder queue. * This should only ever happen if frames are not delivered * without the sender otherwise notifying us (e.g. with a * BAR to move the window). Typically this happens because * of vendor bugs that cause the sequence number to jump. * When this happens we get a gap in the reorder queue that * leaves frame sitting on the queue until they get pushed * out due to window moves. When the vendor does not send * BAR this move only happens due to explicit packet sends * * NB: we only track the time of the oldest frame in the * reorder q; this means that if we flush we might push * frames that still "new"; if this happens then subsequent * frames will result in BA window moves which cost something * but is still better than a big throughput dip. */ if (rap->rxa_qframes != 0) { /* XXX honor batimeout? */ if (ticks - rap->rxa_age > ieee80211_ampdu_age) { /* * Too long since we received the first * frame; flush the reorder buffer. */ if (rap->rxa_qframes != 0) { vap->iv_stats.is_ampdu_rx_age += rap->rxa_qframes; ampdu_rx_flush(ni, rap); } rap->rxa_start = IEEE80211_SEQ_INC(rxseq); return PROCESS; } } else { /* * First frame, start aging timer. */ rap->rxa_age = ticks; } #endif /* IEEE80211_AMPDU_AGE */ /* save packet */ if (rap->rxa_m[off] == NULL) { rap->rxa_m[off] = m; rap->rxa_qframes++; rap->rxa_qbytes += m->m_pkthdr.len; vap->iv_stats.is_ampdu_rx_reorder++; } else { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_11N, ni->ni_macaddr, "a-mpdu duplicate", "seqno %u tid %u BA win <%u:%u>", rxseq, tid, rap->rxa_start, IEEE80211_SEQ_ADD(rap->rxa_start, rap->rxa_wnd-1)); vap->iv_stats.is_rx_dup++; IEEE80211_NODE_STAT(ni, rx_dup); m_freem(m); } return CONSUMED; } if (off < IEEE80211_SEQ_BA_RANGE) { /* * Outside the BA window, but within range; * flush the reorder q and move the window. * Sec 9.10.7.6.2 b) (p.138) */ IEEE80211_NOTE(vap, IEEE80211_MSG_11N, ni, "move BA win <%u:%u> (%u frames) rxseq %u tid %u", rap->rxa_start, IEEE80211_SEQ_ADD(rap->rxa_start, rap->rxa_wnd-1), rap->rxa_qframes, rxseq, tid); vap->iv_stats.is_ampdu_rx_move++; /* * The spec says to flush frames up to but not including: * WinStart_B = rxseq - rap->rxa_wnd + 1 * Then insert the frame or notify the caller to process * it immediately. We can safely do this by just starting * over again because we know the frame will now be within * the BA window. */ /* NB: rxa_wnd known to be >0 */ ampdu_rx_flush_upto(ni, rap, IEEE80211_SEQ_SUB(rxseq, rap->rxa_wnd-1)); goto again; } else { /* * Outside the BA window and out of range; toss. * Sec 9.10.7.6.2 c) (p.138) */ IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_11N, ni->ni_macaddr, "MPDU", "BA win <%u:%u> (%u frames) rxseq %u tid %u%s", rap->rxa_start, IEEE80211_SEQ_ADD(rap->rxa_start, rap->rxa_wnd-1), rap->rxa_qframes, rxseq, tid, wh->i_fc[1] & IEEE80211_FC1_RETRY ? " (retransmit)" : ""); vap->iv_stats.is_ampdu_rx_drop++; IEEE80211_NODE_STAT(ni, rx_drop); m_freem(m); return CONSUMED; } #undef CONSUMED #undef PROCESS #undef IEEE80211_FC0_QOSDATA } /* * Process a BAR ctl frame. Dispatch all frames up to * the sequence number of the frame. If this frame is * out of range it's discarded. */ void ieee80211_recv_bar(struct ieee80211_node *ni, struct mbuf *m0) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_frame_bar *wh; struct ieee80211_rx_ampdu *rap; ieee80211_seq rxseq; int tid, off; if (!ieee80211_recv_bar_ena) { #if 0 IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_11N, ni->ni_macaddr, "BAR", "%s", "processing disabled"); #endif vap->iv_stats.is_ampdu_bar_bad++; return; } wh = mtod(m0, struct ieee80211_frame_bar *); /* XXX check basic BAR */ tid = MS(le16toh(wh->i_ctl), IEEE80211_BAR_TID); rap = &ni->ni_rx_ampdu[tid]; if ((rap->rxa_flags & IEEE80211_AGGR_XCHGPEND) == 0) { /* * No ADDBA request yet, don't touch. */ IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_11N, ni->ni_macaddr, "BAR", "no BA stream, tid %u", tid); vap->iv_stats.is_ampdu_bar_bad++; return; } vap->iv_stats.is_ampdu_bar_rx++; rxseq = le16toh(wh->i_seq) >> IEEE80211_SEQ_SEQ_SHIFT; if (rxseq == rap->rxa_start) return; /* calculate offset in BA window */ off = IEEE80211_SEQ_SUB(rxseq, rap->rxa_start); if (off < IEEE80211_SEQ_BA_RANGE) { /* * Flush the reorder q up to rxseq and move the window. * Sec 9.10.7.6.3 a) (p.138) */ IEEE80211_NOTE(vap, IEEE80211_MSG_11N, ni, "BAR moves BA win <%u:%u> (%u frames) rxseq %u tid %u", rap->rxa_start, IEEE80211_SEQ_ADD(rap->rxa_start, rap->rxa_wnd-1), rap->rxa_qframes, rxseq, tid); vap->iv_stats.is_ampdu_bar_move++; ampdu_rx_flush_upto(ni, rap, rxseq); if (off >= rap->rxa_wnd) { /* * BAR specifies a window start to the right of BA * window; we must move it explicitly since * ampdu_rx_flush_upto will not. */ rap->rxa_start = rxseq; } } else { /* * Out of range; toss. * Sec 9.10.7.6.3 b) (p.138) */ IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_11N, ni->ni_macaddr, "BAR", "BA win <%u:%u> (%u frames) rxseq %u tid %u%s", rap->rxa_start, IEEE80211_SEQ_ADD(rap->rxa_start, rap->rxa_wnd-1), rap->rxa_qframes, rxseq, tid, wh->i_fc[1] & IEEE80211_FC1_RETRY ? " (retransmit)" : ""); vap->iv_stats.is_ampdu_bar_oow++; IEEE80211_NODE_STAT(ni, rx_drop); } } /* * Setup HT-specific state in a node. Called only * when HT use is negotiated so we don't do extra * work for temporary and/or legacy sta's. */ void ieee80211_ht_node_init(struct ieee80211_node *ni) { struct ieee80211_tx_ampdu *tap; int tid; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: called", __func__); if (ni->ni_flags & IEEE80211_NODE_HT) { /* * Clean AMPDU state on re-associate. This handles the case * where a station leaves w/o notifying us and then returns * before node is reaped for inactivity. */ IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: calling cleanup", __func__); ieee80211_ht_node_cleanup(ni); } for (tid = 0; tid < WME_NUM_TID; tid++) { tap = &ni->ni_tx_ampdu[tid]; tap->txa_tid = tid; tap->txa_ni = ni; ieee80211_txampdu_init_pps(tap); /* NB: further initialization deferred */ } ni->ni_flags |= IEEE80211_NODE_HT | IEEE80211_NODE_AMPDU; } /* * Cleanup HT-specific state in a node. Called only * when HT use has been marked. */ void ieee80211_ht_node_cleanup(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; int i; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: called", __func__); KASSERT(ni->ni_flags & IEEE80211_NODE_HT, ("not an HT node")); /* XXX optimize this */ for (i = 0; i < WME_NUM_TID; i++) { struct ieee80211_tx_ampdu *tap = &ni->ni_tx_ampdu[i]; if (tap->txa_flags & IEEE80211_AGGR_SETUP) ampdu_tx_stop(tap); } for (i = 0; i < WME_NUM_TID; i++) ic->ic_ampdu_rx_stop(ni, &ni->ni_rx_ampdu[i]); ni->ni_htcap = 0; ni->ni_flags &= ~IEEE80211_NODE_HT_ALL; } /* * Age out HT resources for a station. */ void ieee80211_ht_node_age(struct ieee80211_node *ni) { #ifdef IEEE80211_AMPDU_AGE struct ieee80211vap *vap = ni->ni_vap; uint8_t tid; #endif KASSERT(ni->ni_flags & IEEE80211_NODE_HT, ("not an HT sta")); #ifdef IEEE80211_AMPDU_AGE for (tid = 0; tid < WME_NUM_TID; tid++) { struct ieee80211_rx_ampdu *rap; rap = &ni->ni_rx_ampdu[tid]; if ((rap->rxa_flags & IEEE80211_AGGR_XCHGPEND) == 0) continue; if (rap->rxa_qframes == 0) continue; /* * Check for frames sitting too long in the reorder queue. * See above for more details on what's happening here. */ /* XXX honor batimeout? */ if (ticks - rap->rxa_age > ieee80211_ampdu_age) { /* * Too long since we received the first * frame; flush the reorder buffer. */ vap->iv_stats.is_ampdu_rx_age += rap->rxa_qframes; ampdu_rx_flush(ni, rap); } } #endif /* IEEE80211_AMPDU_AGE */ } static struct ieee80211_channel * findhtchan(struct ieee80211com *ic, struct ieee80211_channel *c, int htflags) { return ieee80211_find_channel(ic, c->ic_freq, (c->ic_flags &~ IEEE80211_CHAN_HT) | htflags); } /* * Adjust a channel to be HT/non-HT according to the vap's configuration. */ struct ieee80211_channel * ieee80211_ht_adjust_channel(struct ieee80211com *ic, struct ieee80211_channel *chan, int flags) { struct ieee80211_channel *c; if (flags & IEEE80211_FHT_HT) { /* promote to HT if possible */ if (flags & IEEE80211_FHT_USEHT40) { if (!IEEE80211_IS_CHAN_HT40(chan)) { /* NB: arbitrarily pick ht40+ over ht40- */ c = findhtchan(ic, chan, IEEE80211_CHAN_HT40U); if (c == NULL) c = findhtchan(ic, chan, IEEE80211_CHAN_HT40D); if (c == NULL) c = findhtchan(ic, chan, IEEE80211_CHAN_HT20); if (c != NULL) chan = c; } } else if (!IEEE80211_IS_CHAN_HT20(chan)) { c = findhtchan(ic, chan, IEEE80211_CHAN_HT20); if (c != NULL) chan = c; } } else if (IEEE80211_IS_CHAN_HT(chan)) { /* demote to legacy, HT use is disabled */ c = ieee80211_find_channel(ic, chan->ic_freq, chan->ic_flags &~ IEEE80211_CHAN_HT); if (c != NULL) chan = c; } return chan; } /* * Setup HT-specific state for a legacy WDS peer. */ void ieee80211_ht_wds_init(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_tx_ampdu *tap; int tid; KASSERT(vap->iv_flags_ht & IEEE80211_FHT_HT, ("no HT requested")); /* XXX check scan cache in case peer has an ap and we have info */ /* * If setup with a legacy channel; locate an HT channel. * Otherwise if the inherited channel (from a companion * AP) is suitable use it so we use the same location * for the extension channel). */ ni->ni_chan = ieee80211_ht_adjust_channel(ni->ni_ic, ni->ni_chan, ieee80211_htchanflags(ni->ni_chan)); ni->ni_htcap = 0; if (vap->iv_flags_ht & IEEE80211_FHT_SHORTGI20) ni->ni_htcap |= IEEE80211_HTCAP_SHORTGI20; if (IEEE80211_IS_CHAN_HT40(ni->ni_chan)) { ni->ni_htcap |= IEEE80211_HTCAP_CHWIDTH40; ni->ni_chw = 40; if (IEEE80211_IS_CHAN_HT40U(ni->ni_chan)) ni->ni_ht2ndchan = IEEE80211_HTINFO_2NDCHAN_ABOVE; else if (IEEE80211_IS_CHAN_HT40D(ni->ni_chan)) ni->ni_ht2ndchan = IEEE80211_HTINFO_2NDCHAN_BELOW; if (vap->iv_flags_ht & IEEE80211_FHT_SHORTGI40) ni->ni_htcap |= IEEE80211_HTCAP_SHORTGI40; } else { ni->ni_chw = 20; ni->ni_ht2ndchan = IEEE80211_HTINFO_2NDCHAN_NONE; } ni->ni_htctlchan = ni->ni_chan->ic_ieee; if (vap->iv_flags_ht & IEEE80211_FHT_RIFS) ni->ni_flags |= IEEE80211_NODE_RIFS; /* XXX does it make sense to enable SMPS? */ ni->ni_htopmode = 0; /* XXX need protection state */ ni->ni_htstbc = 0; /* XXX need info */ for (tid = 0; tid < WME_NUM_TID; tid++) { tap = &ni->ni_tx_ampdu[tid]; tap->txa_tid = tid; ieee80211_txampdu_init_pps(tap); } /* NB: AMPDU tx/rx governed by IEEE80211_FHT_AMPDU_{TX,RX} */ ni->ni_flags |= IEEE80211_NODE_HT | IEEE80211_NODE_AMPDU; } /* * Notify hostap vaps of a change in the HTINFO ie. */ static void htinfo_notify(struct ieee80211com *ic) { struct ieee80211vap *vap; int first = 1; IEEE80211_LOCK_ASSERT(ic); TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { if (vap->iv_opmode != IEEE80211_M_HOSTAP) continue; if (vap->iv_state != IEEE80211_S_RUN || !IEEE80211_IS_CHAN_HT(vap->iv_bss->ni_chan)) continue; if (first) { IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_11N, vap->iv_bss, "HT bss occupancy change: %d sta, %d ht, " "%d ht40%s, HT protmode now 0x%x" , ic->ic_sta_assoc , ic->ic_ht_sta_assoc , ic->ic_ht40_sta_assoc , (ic->ic_flags_ht & IEEE80211_FHT_NONHT_PR) ? ", non-HT sta present" : "" , ic->ic_curhtprotmode); first = 0; } ieee80211_beacon_notify(vap, IEEE80211_BEACON_HTINFO); } } /* * Calculate HT protection mode from current * state and handle updates. */ static void htinfo_update(struct ieee80211com *ic) { uint8_t protmode; if (ic->ic_sta_assoc != ic->ic_ht_sta_assoc) { protmode = IEEE80211_HTINFO_OPMODE_MIXED | IEEE80211_HTINFO_NONHT_PRESENT; } else if (ic->ic_flags_ht & IEEE80211_FHT_NONHT_PR) { protmode = IEEE80211_HTINFO_OPMODE_PROTOPT | IEEE80211_HTINFO_NONHT_PRESENT; } else if (ic->ic_bsschan != IEEE80211_CHAN_ANYC && IEEE80211_IS_CHAN_HT40(ic->ic_bsschan) && ic->ic_sta_assoc != ic->ic_ht40_sta_assoc) { protmode = IEEE80211_HTINFO_OPMODE_HT20PR; } else { protmode = IEEE80211_HTINFO_OPMODE_PURE; } if (protmode != ic->ic_curhtprotmode) { ic->ic_curhtprotmode = protmode; htinfo_notify(ic); } } /* * Handle an HT station joining a BSS. */ void ieee80211_ht_node_join(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; IEEE80211_LOCK_ASSERT(ic); if (ni->ni_flags & IEEE80211_NODE_HT) { ic->ic_ht_sta_assoc++; if (ni->ni_chw == 40) ic->ic_ht40_sta_assoc++; } htinfo_update(ic); } /* * Handle an HT station leaving a BSS. */ void ieee80211_ht_node_leave(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; IEEE80211_LOCK_ASSERT(ic); if (ni->ni_flags & IEEE80211_NODE_HT) { ic->ic_ht_sta_assoc--; if (ni->ni_chw == 40) ic->ic_ht40_sta_assoc--; } htinfo_update(ic); } /* * Public version of htinfo_update; used for processing * beacon frames from overlapping bss. * * Caller can specify either IEEE80211_HTINFO_OPMODE_MIXED * (on receipt of a beacon that advertises MIXED) or * IEEE80211_HTINFO_OPMODE_PROTOPT (on receipt of a beacon * from an overlapping legacy bss). We treat MIXED with * a higher precedence than PROTOPT (i.e. we will not change * change PROTOPT -> MIXED; only MIXED -> PROTOPT). This * corresponds to how we handle things in htinfo_update. */ void ieee80211_htprot_update(struct ieee80211com *ic, int protmode) { #define OPMODE(x) SM(x, IEEE80211_HTINFO_OPMODE) IEEE80211_LOCK(ic); /* track non-HT station presence */ KASSERT(protmode & IEEE80211_HTINFO_NONHT_PRESENT, ("protmode 0x%x", protmode)); ic->ic_flags_ht |= IEEE80211_FHT_NONHT_PR; ic->ic_lastnonht = ticks; if (protmode != ic->ic_curhtprotmode && (OPMODE(ic->ic_curhtprotmode) != IEEE80211_HTINFO_OPMODE_MIXED || OPMODE(protmode) == IEEE80211_HTINFO_OPMODE_PROTOPT)) { /* push beacon update */ ic->ic_curhtprotmode = protmode; htinfo_notify(ic); } IEEE80211_UNLOCK(ic); #undef OPMODE } /* * Time out presence of an overlapping bss with non-HT * stations. When operating in hostap mode we listen for * beacons from other stations and if we identify a non-HT * station is present we update the opmode field of the * HTINFO ie. To identify when all non-HT stations are * gone we time out this condition. */ void ieee80211_ht_timeout(struct ieee80211com *ic) { IEEE80211_LOCK_ASSERT(ic); if ((ic->ic_flags_ht & IEEE80211_FHT_NONHT_PR) && - time_after(ticks, ic->ic_lastnonht + IEEE80211_NONHT_PRESENT_AGE)) { + ieee80211_time_after(ticks, ic->ic_lastnonht + IEEE80211_NONHT_PRESENT_AGE)) { #if 0 IEEE80211_NOTE(vap, IEEE80211_MSG_11N, ni, "%s", "time out non-HT STA present on channel"); #endif ic->ic_flags_ht &= ~IEEE80211_FHT_NONHT_PR; htinfo_update(ic); } } /* * Process an 802.11n HT capabilities ie. */ void ieee80211_parse_htcap(struct ieee80211_node *ni, const uint8_t *ie) { if (ie[0] == IEEE80211_ELEMID_VENDOR) { /* * Station used Vendor OUI ie to associate; * mark the node so when we respond we'll use * the Vendor OUI's and not the standard ie's. */ ni->ni_flags |= IEEE80211_NODE_HTCOMPAT; ie += 4; } else ni->ni_flags &= ~IEEE80211_NODE_HTCOMPAT; ni->ni_htcap = LE_READ_2(ie + __offsetof(struct ieee80211_ie_htcap, hc_cap)); ni->ni_htparam = ie[__offsetof(struct ieee80211_ie_htcap, hc_param)]; } static void htinfo_parse(struct ieee80211_node *ni, const struct ieee80211_ie_htinfo *htinfo) { uint16_t w; ni->ni_htctlchan = htinfo->hi_ctrlchannel; ni->ni_ht2ndchan = SM(htinfo->hi_byte1, IEEE80211_HTINFO_2NDCHAN); w = LE_READ_2(&htinfo->hi_byte2); ni->ni_htopmode = SM(w, IEEE80211_HTINFO_OPMODE); w = LE_READ_2(&htinfo->hi_byte45); ni->ni_htstbc = SM(w, IEEE80211_HTINFO_BASIC_STBCMCS); } /* * Parse an 802.11n HT info ie and save useful information * to the node state. Note this does not effect any state * changes such as for channel width change. */ void ieee80211_parse_htinfo(struct ieee80211_node *ni, const uint8_t *ie) { if (ie[0] == IEEE80211_ELEMID_VENDOR) ie += 4; htinfo_parse(ni, (const struct ieee80211_ie_htinfo *) ie); } /* * Handle 11n channel switch. Use the received HT ie's to * identify the right channel to use. If we cannot locate it * in the channel table then fallback to legacy operation. * Note that we use this information to identify the node's * channel only; the caller is responsible for insuring any * required channel change is done (e.g. in sta mode when * parsing the contents of a beacon frame). */ static int htinfo_update_chw(struct ieee80211_node *ni, int htflags) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211_channel *c; int chanflags; int ret = 0; chanflags = (ni->ni_chan->ic_flags &~ IEEE80211_CHAN_HT) | htflags; if (chanflags != ni->ni_chan->ic_flags) { /* XXX not right for ht40- */ c = ieee80211_find_channel(ic, ni->ni_chan->ic_freq, chanflags); if (c == NULL && (htflags & IEEE80211_CHAN_HT40)) { /* * No HT40 channel entry in our table; fall back * to HT20 operation. This should not happen. */ c = findhtchan(ic, ni->ni_chan, IEEE80211_CHAN_HT20); #if 0 IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_11N, ni, "no HT40 channel (freq %u), falling back to HT20", ni->ni_chan->ic_freq); #endif /* XXX stat */ } if (c != NULL && c != ni->ni_chan) { IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_11N, ni, "switch station to HT%d channel %u/0x%x", IEEE80211_IS_CHAN_HT40(c) ? 40 : 20, c->ic_freq, c->ic_flags); ni->ni_chan = c; ret = 1; } /* NB: caller responsible for forcing any channel change */ } /* update node's tx channel width */ ni->ni_chw = IEEE80211_IS_CHAN_HT40(ni->ni_chan)? 40 : 20; return (ret); } /* * Update 11n MIMO PS state according to received htcap. */ static __inline int htcap_update_mimo_ps(struct ieee80211_node *ni) { uint16_t oflags = ni->ni_flags; switch (ni->ni_htcap & IEEE80211_HTCAP_SMPS) { case IEEE80211_HTCAP_SMPS_DYNAMIC: ni->ni_flags |= IEEE80211_NODE_MIMO_PS; ni->ni_flags |= IEEE80211_NODE_MIMO_RTS; break; case IEEE80211_HTCAP_SMPS_ENA: ni->ni_flags |= IEEE80211_NODE_MIMO_PS; ni->ni_flags &= ~IEEE80211_NODE_MIMO_RTS; break; case IEEE80211_HTCAP_SMPS_OFF: default: /* disable on rx of reserved value */ ni->ni_flags &= ~IEEE80211_NODE_MIMO_PS; ni->ni_flags &= ~IEEE80211_NODE_MIMO_RTS; break; } return (oflags ^ ni->ni_flags); } /* * Update short GI state according to received htcap * and local settings. */ static __inline void htcap_update_shortgi(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; ni->ni_flags &= ~(IEEE80211_NODE_SGI20|IEEE80211_NODE_SGI40); if ((ni->ni_htcap & IEEE80211_HTCAP_SHORTGI20) && (vap->iv_flags_ht & IEEE80211_FHT_SHORTGI20)) ni->ni_flags |= IEEE80211_NODE_SGI20; if ((ni->ni_htcap & IEEE80211_HTCAP_SHORTGI40) && (vap->iv_flags_ht & IEEE80211_FHT_SHORTGI40)) ni->ni_flags |= IEEE80211_NODE_SGI40; } /* * Parse and update HT-related state extracted from * the HT cap and info ie's. */ int ieee80211_ht_updateparams(struct ieee80211_node *ni, const uint8_t *htcapie, const uint8_t *htinfoie) { struct ieee80211vap *vap = ni->ni_vap; const struct ieee80211_ie_htinfo *htinfo; int htflags; int ret = 0; ieee80211_parse_htcap(ni, htcapie); if (vap->iv_htcaps & IEEE80211_HTCAP_SMPS) htcap_update_mimo_ps(ni); htcap_update_shortgi(ni); if (htinfoie[0] == IEEE80211_ELEMID_VENDOR) htinfoie += 4; htinfo = (const struct ieee80211_ie_htinfo *) htinfoie; htinfo_parse(ni, htinfo); htflags = (vap->iv_flags_ht & IEEE80211_FHT_HT) ? IEEE80211_CHAN_HT20 : 0; /* NB: honor operating mode constraint */ if ((htinfo->hi_byte1 & IEEE80211_HTINFO_TXWIDTH_2040) && (vap->iv_flags_ht & IEEE80211_FHT_USEHT40)) { if (ni->ni_ht2ndchan == IEEE80211_HTINFO_2NDCHAN_ABOVE) htflags = IEEE80211_CHAN_HT40U; else if (ni->ni_ht2ndchan == IEEE80211_HTINFO_2NDCHAN_BELOW) htflags = IEEE80211_CHAN_HT40D; } if (htinfo_update_chw(ni, htflags)) ret = 1; if ((htinfo->hi_byte1 & IEEE80211_HTINFO_RIFSMODE_PERM) && (vap->iv_flags_ht & IEEE80211_FHT_RIFS)) ni->ni_flags |= IEEE80211_NODE_RIFS; else ni->ni_flags &= ~IEEE80211_NODE_RIFS; return (ret); } /* * Parse and update HT-related state extracted from the HT cap ie * for a station joining an HT BSS. */ void ieee80211_ht_updatehtcap(struct ieee80211_node *ni, const uint8_t *htcapie) { struct ieee80211vap *vap = ni->ni_vap; int htflags; ieee80211_parse_htcap(ni, htcapie); if (vap->iv_htcaps & IEEE80211_HTCAP_SMPS) htcap_update_mimo_ps(ni); htcap_update_shortgi(ni); /* NB: honor operating mode constraint */ /* XXX 40 MHz intolerant */ htflags = (vap->iv_flags_ht & IEEE80211_FHT_HT) ? IEEE80211_CHAN_HT20 : 0; if ((ni->ni_htcap & IEEE80211_HTCAP_CHWIDTH40) && (vap->iv_flags_ht & IEEE80211_FHT_USEHT40)) { if (IEEE80211_IS_CHAN_HT40U(vap->iv_bss->ni_chan)) htflags = IEEE80211_CHAN_HT40U; else if (IEEE80211_IS_CHAN_HT40D(vap->iv_bss->ni_chan)) htflags = IEEE80211_CHAN_HT40D; } (void) htinfo_update_chw(ni, htflags); } /* * Install received HT rate set by parsing the HT cap ie. */ int ieee80211_setup_htrates(struct ieee80211_node *ni, const uint8_t *ie, int flags) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; const struct ieee80211_ie_htcap *htcap; struct ieee80211_htrateset *rs; int i, maxequalmcs, maxunequalmcs; maxequalmcs = ic->ic_txstream * 8 - 1; if (ic->ic_htcaps & IEEE80211_HTC_TXUNEQUAL) { if (ic->ic_txstream >= 2) maxunequalmcs = 38; if (ic->ic_txstream >= 3) maxunequalmcs = 52; if (ic->ic_txstream >= 4) maxunequalmcs = 76; } else maxunequalmcs = 0; rs = &ni->ni_htrates; memset(rs, 0, sizeof(*rs)); if (ie != NULL) { if (ie[0] == IEEE80211_ELEMID_VENDOR) ie += 4; htcap = (const struct ieee80211_ie_htcap *) ie; for (i = 0; i < IEEE80211_HTRATE_MAXSIZE; i++) { if (isclr(htcap->hc_mcsset, i)) continue; if (rs->rs_nrates == IEEE80211_HTRATE_MAXSIZE) { IEEE80211_NOTE(vap, IEEE80211_MSG_XRATE | IEEE80211_MSG_11N, ni, "WARNING, HT rate set too large; only " "using %u rates", IEEE80211_HTRATE_MAXSIZE); vap->iv_stats.is_rx_rstoobig++; break; } if (i <= 31 && i > maxequalmcs) continue; if (i == 32 && (ic->ic_htcaps & IEEE80211_HTC_TXMCS32) == 0) continue; if (i > 32 && i > maxunequalmcs) continue; rs->rs_rates[rs->rs_nrates++] = i; } } return ieee80211_fix_rate(ni, (struct ieee80211_rateset *) rs, flags); } /* * Mark rates in a node's HT rate set as basic according * to the information in the supplied HT info ie. */ void ieee80211_setup_basic_htrates(struct ieee80211_node *ni, const uint8_t *ie) { const struct ieee80211_ie_htinfo *htinfo; struct ieee80211_htrateset *rs; int i, j; if (ie[0] == IEEE80211_ELEMID_VENDOR) ie += 4; htinfo = (const struct ieee80211_ie_htinfo *) ie; rs = &ni->ni_htrates; if (rs->rs_nrates == 0) { IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_XRATE | IEEE80211_MSG_11N, ni, "%s", "WARNING, empty HT rate set"); return; } for (i = 0; i < IEEE80211_HTRATE_MAXSIZE; i++) { if (isclr(htinfo->hi_basicmcsset, i)) continue; for (j = 0; j < rs->rs_nrates; j++) if ((rs->rs_rates[j] & IEEE80211_RATE_VAL) == i) rs->rs_rates[j] |= IEEE80211_RATE_BASIC; } } static void ampdu_tx_setup(struct ieee80211_tx_ampdu *tap) { callout_init(&tap->txa_timer, 1); tap->txa_flags |= IEEE80211_AGGR_SETUP; tap->txa_lastsample = ticks; } static void ampdu_tx_stop(struct ieee80211_tx_ampdu *tap) { struct ieee80211_node *ni = tap->txa_ni; struct ieee80211com *ic = ni->ni_ic; IEEE80211_NOTE(tap->txa_ni->ni_vap, IEEE80211_MSG_11N, tap->txa_ni, "%s: called", __func__); KASSERT(tap->txa_flags & IEEE80211_AGGR_SETUP, ("txa_flags 0x%x tid %d ac %d", tap->txa_flags, tap->txa_tid, TID_TO_WME_AC(tap->txa_tid))); /* * Stop BA stream if setup so driver has a chance * to reclaim any resources it might have allocated. */ ic->ic_addba_stop(ni, tap); /* * Stop any pending BAR transmit. */ bar_stop_timer(tap); /* * Reset packet estimate. */ ieee80211_txampdu_init_pps(tap); /* NB: clearing NAK means we may re-send ADDBA */ tap->txa_flags &= ~(IEEE80211_AGGR_SETUP | IEEE80211_AGGR_NAK); } /* * ADDBA response timeout. * * If software aggregation and per-TID queue management was done here, * that queue would be unpaused after the ADDBA timeout occurs. */ static void addba_timeout(void *arg) { struct ieee80211_tx_ampdu *tap = arg; struct ieee80211_node *ni = tap->txa_ni; struct ieee80211com *ic = ni->ni_ic; /* XXX ? */ tap->txa_flags &= ~IEEE80211_AGGR_XCHGPEND; tap->txa_attempts++; ic->ic_addba_response_timeout(ni, tap); } static void addba_start_timeout(struct ieee80211_tx_ampdu *tap) { /* XXX use CALLOUT_PENDING instead? */ callout_reset(&tap->txa_timer, ieee80211_addba_timeout, addba_timeout, tap); tap->txa_flags |= IEEE80211_AGGR_XCHGPEND; tap->txa_nextrequest = ticks + ieee80211_addba_timeout; } static void addba_stop_timeout(struct ieee80211_tx_ampdu *tap) { /* XXX use CALLOUT_PENDING instead? */ if (tap->txa_flags & IEEE80211_AGGR_XCHGPEND) { callout_stop(&tap->txa_timer); tap->txa_flags &= ~IEEE80211_AGGR_XCHGPEND; } } static void null_addba_response_timeout(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap) { } /* * Default method for requesting A-MPDU tx aggregation. * We setup the specified state block and start a timer * to wait for an ADDBA response frame. */ static int ieee80211_addba_request(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap, int dialogtoken, int baparamset, int batimeout) { int bufsiz; /* XXX locking */ tap->txa_token = dialogtoken; tap->txa_flags |= IEEE80211_AGGR_IMMEDIATE; bufsiz = MS(baparamset, IEEE80211_BAPS_BUFSIZ); tap->txa_wnd = (bufsiz == 0) ? IEEE80211_AGGR_BAWMAX : min(bufsiz, IEEE80211_AGGR_BAWMAX); addba_start_timeout(tap); return 1; } /* * Called by drivers that wish to request an ADDBA session be * setup. This brings it up and starts the request timer. */ int ieee80211_ampdu_tx_request_ext(struct ieee80211_node *ni, int tid) { struct ieee80211_tx_ampdu *tap; if (tid < 0 || tid > 15) return (0); tap = &ni->ni_tx_ampdu[tid]; /* XXX locking */ if ((tap->txa_flags & IEEE80211_AGGR_SETUP) == 0) { /* do deferred setup of state */ ampdu_tx_setup(tap); } /* XXX hack for not doing proper locking */ tap->txa_flags &= ~IEEE80211_AGGR_NAK; addba_start_timeout(tap); return (1); } /* * Called by drivers that have marked a session as active. */ int ieee80211_ampdu_tx_request_active_ext(struct ieee80211_node *ni, int tid, int status) { struct ieee80211_tx_ampdu *tap; if (tid < 0 || tid > 15) return (0); tap = &ni->ni_tx_ampdu[tid]; /* XXX locking */ addba_stop_timeout(tap); if (status == 1) { tap->txa_flags |= IEEE80211_AGGR_RUNNING; tap->txa_attempts = 0; } else { /* mark tid so we don't try again */ tap->txa_flags |= IEEE80211_AGGR_NAK; } return (1); } /* * Default method for processing an A-MPDU tx aggregation * response. We shutdown any pending timer and update the * state block according to the reply. */ static int ieee80211_addba_response(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap, int status, int baparamset, int batimeout) { int bufsiz, tid; /* XXX locking */ addba_stop_timeout(tap); if (status == IEEE80211_STATUS_SUCCESS) { bufsiz = MS(baparamset, IEEE80211_BAPS_BUFSIZ); /* XXX override our request? */ tap->txa_wnd = (bufsiz == 0) ? IEEE80211_AGGR_BAWMAX : min(bufsiz, IEEE80211_AGGR_BAWMAX); /* XXX AC/TID */ tid = MS(baparamset, IEEE80211_BAPS_TID); tap->txa_flags |= IEEE80211_AGGR_RUNNING; tap->txa_attempts = 0; } else { /* mark tid so we don't try again */ tap->txa_flags |= IEEE80211_AGGR_NAK; } return 1; } /* * Default method for stopping A-MPDU tx aggregation. * Any timer is cleared and we drain any pending frames. */ static void ieee80211_addba_stop(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap) { /* XXX locking */ addba_stop_timeout(tap); if (tap->txa_flags & IEEE80211_AGGR_RUNNING) { /* XXX clear aggregation queue */ tap->txa_flags &= ~IEEE80211_AGGR_RUNNING; } tap->txa_attempts = 0; } /* * Process a received action frame using the default aggregation * policy. We intercept ADDBA-related frames and use them to * update our aggregation state. All other frames are passed up * for processing by ieee80211_recv_action. */ static int ht_recv_action_ba_addba_request(struct ieee80211_node *ni, const struct ieee80211_frame *wh, const uint8_t *frm, const uint8_t *efrm) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_rx_ampdu *rap; uint8_t dialogtoken; uint16_t baparamset, batimeout, baseqctl; uint16_t args[5]; int tid; dialogtoken = frm[2]; baparamset = LE_READ_2(frm+3); batimeout = LE_READ_2(frm+5); baseqctl = LE_READ_2(frm+7); tid = MS(baparamset, IEEE80211_BAPS_TID); IEEE80211_NOTE(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, "recv ADDBA request: dialogtoken %u baparamset 0x%x " "(tid %d bufsiz %d) batimeout %d baseqctl %d:%d", dialogtoken, baparamset, tid, MS(baparamset, IEEE80211_BAPS_BUFSIZ), batimeout, MS(baseqctl, IEEE80211_BASEQ_START), MS(baseqctl, IEEE80211_BASEQ_FRAG)); rap = &ni->ni_rx_ampdu[tid]; /* Send ADDBA response */ args[0] = dialogtoken; /* * NB: We ack only if the sta associated with HT and * the ap is configured to do AMPDU rx (the latter * violates the 11n spec and is mostly for testing). */ if ((ni->ni_flags & IEEE80211_NODE_AMPDU_RX) && (vap->iv_flags_ht & IEEE80211_FHT_AMPDU_RX)) { /* XXX handle ampdu_rx_start failure */ ic->ic_ampdu_rx_start(ni, rap, baparamset, batimeout, baseqctl); args[1] = IEEE80211_STATUS_SUCCESS; } else { IEEE80211_NOTE(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, "reject ADDBA request: %s", ni->ni_flags & IEEE80211_NODE_AMPDU_RX ? "administratively disabled" : "not negotiated for station"); vap->iv_stats.is_addba_reject++; args[1] = IEEE80211_STATUS_UNSPECIFIED; } /* XXX honor rap flags? */ args[2] = IEEE80211_BAPS_POLICY_IMMEDIATE | SM(tid, IEEE80211_BAPS_TID) | SM(rap->rxa_wnd, IEEE80211_BAPS_BUFSIZ) ; args[3] = 0; args[4] = 0; ic->ic_send_action(ni, IEEE80211_ACTION_CAT_BA, IEEE80211_ACTION_BA_ADDBA_RESPONSE, args); return 0; } static int ht_recv_action_ba_addba_response(struct ieee80211_node *ni, const struct ieee80211_frame *wh, const uint8_t *frm, const uint8_t *efrm) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_tx_ampdu *tap; uint8_t dialogtoken, policy; uint16_t baparamset, batimeout, code; int tid, bufsiz; dialogtoken = frm[2]; code = LE_READ_2(frm+3); baparamset = LE_READ_2(frm+5); tid = MS(baparamset, IEEE80211_BAPS_TID); bufsiz = MS(baparamset, IEEE80211_BAPS_BUFSIZ); policy = MS(baparamset, IEEE80211_BAPS_POLICY); batimeout = LE_READ_2(frm+7); tap = &ni->ni_tx_ampdu[tid]; if ((tap->txa_flags & IEEE80211_AGGR_XCHGPEND) == 0) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni->ni_macaddr, "ADDBA response", "no pending ADDBA, tid %d dialogtoken %u " "code %d", tid, dialogtoken, code); vap->iv_stats.is_addba_norequest++; return 0; } if (dialogtoken != tap->txa_token) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni->ni_macaddr, "ADDBA response", "dialogtoken mismatch: waiting for %d, " "received %d, tid %d code %d", tap->txa_token, dialogtoken, tid, code); vap->iv_stats.is_addba_badtoken++; return 0; } /* NB: assumes IEEE80211_AGGR_IMMEDIATE is 1 */ if (policy != (tap->txa_flags & IEEE80211_AGGR_IMMEDIATE)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni->ni_macaddr, "ADDBA response", "policy mismatch: expecting %s, " "received %s, tid %d code %d", tap->txa_flags & IEEE80211_AGGR_IMMEDIATE, policy, tid, code); vap->iv_stats.is_addba_badpolicy++; return 0; } #if 0 /* XXX we take MIN in ieee80211_addba_response */ if (bufsiz > IEEE80211_AGGR_BAWMAX) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni->ni_macaddr, "ADDBA response", "BA window too large: max %d, " "received %d, tid %d code %d", bufsiz, IEEE80211_AGGR_BAWMAX, tid, code); vap->iv_stats.is_addba_badbawinsize++; return 0; } #endif IEEE80211_NOTE(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, "recv ADDBA response: dialogtoken %u code %d " "baparamset 0x%x (tid %d bufsiz %d) batimeout %d", dialogtoken, code, baparamset, tid, bufsiz, batimeout); ic->ic_addba_response(ni, tap, code, baparamset, batimeout); return 0; } static int ht_recv_action_ba_delba(struct ieee80211_node *ni, const struct ieee80211_frame *wh, const uint8_t *frm, const uint8_t *efrm) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211_rx_ampdu *rap; struct ieee80211_tx_ampdu *tap; uint16_t baparamset, code; int tid; baparamset = LE_READ_2(frm+2); code = LE_READ_2(frm+4); tid = MS(baparamset, IEEE80211_DELBAPS_TID); IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, "recv DELBA: baparamset 0x%x (tid %d initiator %d) " "code %d", baparamset, tid, MS(baparamset, IEEE80211_DELBAPS_INIT), code); if ((baparamset & IEEE80211_DELBAPS_INIT) == 0) { tap = &ni->ni_tx_ampdu[tid]; ic->ic_addba_stop(ni, tap); } else { rap = &ni->ni_rx_ampdu[tid]; ic->ic_ampdu_rx_stop(ni, rap); } return 0; } static int ht_recv_action_ht_txchwidth(struct ieee80211_node *ni, const struct ieee80211_frame *wh, const uint8_t *frm, const uint8_t *efrm) { int chw; chw = (frm[2] == IEEE80211_A_HT_TXCHWIDTH_2040) ? 40 : 20; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, "%s: HT txchwidth, width %d%s", __func__, chw, ni->ni_chw != chw ? "*" : ""); if (chw != ni->ni_chw) { ni->ni_chw = chw; /* XXX notify on change */ } return 0; } static int ht_recv_action_ht_mimopwrsave(struct ieee80211_node *ni, const struct ieee80211_frame *wh, const uint8_t *frm, const uint8_t *efrm) { const struct ieee80211_action_ht_mimopowersave *mps = (const struct ieee80211_action_ht_mimopowersave *) frm; /* XXX check iv_htcaps */ if (mps->am_control & IEEE80211_A_HT_MIMOPWRSAVE_ENA) ni->ni_flags |= IEEE80211_NODE_MIMO_PS; else ni->ni_flags &= ~IEEE80211_NODE_MIMO_PS; if (mps->am_control & IEEE80211_A_HT_MIMOPWRSAVE_MODE) ni->ni_flags |= IEEE80211_NODE_MIMO_RTS; else ni->ni_flags &= ~IEEE80211_NODE_MIMO_RTS; /* XXX notify on change */ IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, "%s: HT MIMO PS (%s%s)", __func__, (ni->ni_flags & IEEE80211_NODE_MIMO_PS) ? "on" : "off", (ni->ni_flags & IEEE80211_NODE_MIMO_RTS) ? "+rts" : "" ); return 0; } /* * Transmit processing. */ /* * Check if A-MPDU should be requested/enabled for a stream. * We require a traffic rate above a per-AC threshold and we * also handle backoff from previous failed attempts. * * Drivers may override this method to bring in information * such as link state conditions in making the decision. */ static int ieee80211_ampdu_enable(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap) { struct ieee80211vap *vap = ni->ni_vap; if (tap->txa_avgpps < vap->iv_ampdu_mintraffic[TID_TO_WME_AC(tap->txa_tid)]) return 0; /* XXX check rssi? */ if (tap->txa_attempts >= ieee80211_addba_maxtries && ticks < tap->txa_nextrequest) { /* * Don't retry too often; txa_nextrequest is set * to the minimum interval we'll retry after * ieee80211_addba_maxtries failed attempts are made. */ return 0; } IEEE80211_NOTE(vap, IEEE80211_MSG_11N, ni, "enable AMPDU on tid %d (%s), avgpps %d pkts %d attempt %d", tap->txa_tid, ieee80211_wme_acnames[TID_TO_WME_AC(tap->txa_tid)], tap->txa_avgpps, tap->txa_pkts, tap->txa_attempts); return 1; } /* * Request A-MPDU tx aggregation. Setup local state and * issue an ADDBA request. BA use will only happen after * the other end replies with ADDBA response. */ int ieee80211_ampdu_request(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap) { struct ieee80211com *ic = ni->ni_ic; uint16_t args[5]; int tid, dialogtoken; static int tokens = 0; /* XXX */ /* XXX locking */ if ((tap->txa_flags & IEEE80211_AGGR_SETUP) == 0) { /* do deferred setup of state */ ampdu_tx_setup(tap); } /* XXX hack for not doing proper locking */ tap->txa_flags &= ~IEEE80211_AGGR_NAK; dialogtoken = (tokens+1) % 63; /* XXX */ tid = tap->txa_tid; tap->txa_start = ni->ni_txseqs[tid]; args[0] = dialogtoken; args[1] = 0; /* NB: status code not used */ args[2] = IEEE80211_BAPS_POLICY_IMMEDIATE | SM(tid, IEEE80211_BAPS_TID) | SM(IEEE80211_AGGR_BAWMAX, IEEE80211_BAPS_BUFSIZ) ; args[3] = 0; /* batimeout */ /* NB: do first so there's no race against reply */ if (!ic->ic_addba_request(ni, tap, dialogtoken, args[2], args[3])) { /* unable to setup state, don't make request */ IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: could not setup BA stream for TID %d AC %d", __func__, tap->txa_tid, TID_TO_WME_AC(tap->txa_tid)); /* defer next try so we don't slam the driver with requests */ tap->txa_attempts = ieee80211_addba_maxtries; /* NB: check in case driver wants to override */ if (tap->txa_nextrequest <= ticks) tap->txa_nextrequest = ticks + ieee80211_addba_backoff; return 0; } tokens = dialogtoken; /* allocate token */ /* NB: after calling ic_addba_request so driver can set txa_start */ args[4] = SM(tap->txa_start, IEEE80211_BASEQ_START) | SM(0, IEEE80211_BASEQ_FRAG) ; return ic->ic_send_action(ni, IEEE80211_ACTION_CAT_BA, IEEE80211_ACTION_BA_ADDBA_REQUEST, args); } /* * Terminate an AMPDU tx stream. State is reclaimed * and the peer notified with a DelBA Action frame. */ void ieee80211_ampdu_stop(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap, int reason) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; uint16_t args[4]; /* XXX locking */ tap->txa_flags &= ~IEEE80211_AGGR_BARPEND; if (IEEE80211_AMPDU_RUNNING(tap)) { IEEE80211_NOTE(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, "%s: stop BA stream for TID %d (reason %d)", __func__, tap->txa_tid, reason); vap->iv_stats.is_ampdu_stop++; ic->ic_addba_stop(ni, tap); args[0] = tap->txa_tid; args[1] = IEEE80211_DELBAPS_INIT; args[2] = reason; /* XXX reason code */ ic->ic_send_action(ni, IEEE80211_ACTION_CAT_BA, IEEE80211_ACTION_BA_DELBA, args); } else { IEEE80211_NOTE(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, "%s: BA stream for TID %d not running (reason %d)", __func__, tap->txa_tid, reason); vap->iv_stats.is_ampdu_stop_failed++; } } /* XXX */ static void bar_start_timer(struct ieee80211_tx_ampdu *tap); static void bar_timeout(void *arg) { struct ieee80211_tx_ampdu *tap = arg; struct ieee80211_node *ni = tap->txa_ni; KASSERT((tap->txa_flags & IEEE80211_AGGR_XCHGPEND) == 0, ("bar/addba collision, flags 0x%x", tap->txa_flags)); IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: tid %u flags 0x%x attempts %d", __func__, tap->txa_tid, tap->txa_flags, tap->txa_attempts); /* guard against race with bar_tx_complete */ if ((tap->txa_flags & IEEE80211_AGGR_BARPEND) == 0) return; /* XXX ? */ if (tap->txa_attempts >= ieee80211_bar_maxtries) { struct ieee80211com *ic = ni->ni_ic; ni->ni_vap->iv_stats.is_ampdu_bar_tx_fail++; /* * If (at least) the last BAR TX timeout was due to * an ieee80211_send_bar() failures, then we need * to make sure we notify the driver that a BAR * TX did occur and fail. This gives the driver * a chance to undo any queue pause that may * have occured. */ ic->ic_bar_response(ni, tap, 1); ieee80211_ampdu_stop(ni, tap, IEEE80211_REASON_TIMEOUT); } else { ni->ni_vap->iv_stats.is_ampdu_bar_tx_retry++; if (ieee80211_send_bar(ni, tap, tap->txa_seqpending) != 0) { IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: failed to TX, starting timer\n", __func__); /* * If ieee80211_send_bar() fails here, the * timer may have stopped and/or the pending * flag may be clear. Because of this, * fake the BARPEND and reset the timer. * A retransmission attempt will then occur * during the next timeout. */ /* XXX locking */ tap->txa_flags |= IEEE80211_AGGR_BARPEND; bar_start_timer(tap); } } } static void bar_start_timer(struct ieee80211_tx_ampdu *tap) { IEEE80211_NOTE(tap->txa_ni->ni_vap, IEEE80211_MSG_11N, tap->txa_ni, "%s: called", __func__); callout_reset(&tap->txa_timer, ieee80211_bar_timeout, bar_timeout, tap); } static void bar_stop_timer(struct ieee80211_tx_ampdu *tap) { IEEE80211_NOTE(tap->txa_ni->ni_vap, IEEE80211_MSG_11N, tap->txa_ni, "%s: called", __func__); callout_stop(&tap->txa_timer); } static void bar_tx_complete(struct ieee80211_node *ni, void *arg, int status) { struct ieee80211_tx_ampdu *tap = arg; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: tid %u flags 0x%x pending %d status %d", __func__, tap->txa_tid, tap->txa_flags, callout_pending(&tap->txa_timer), status); ni->ni_vap->iv_stats.is_ampdu_bar_tx++; /* XXX locking */ if ((tap->txa_flags & IEEE80211_AGGR_BARPEND) && callout_pending(&tap->txa_timer)) { struct ieee80211com *ic = ni->ni_ic; if (status == 0) /* ACK'd */ bar_stop_timer(tap); ic->ic_bar_response(ni, tap, status); /* NB: just let timer expire so we pace requests */ } } static void ieee80211_bar_response(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap, int status) { IEEE80211_NOTE(tap->txa_ni->ni_vap, IEEE80211_MSG_11N, tap->txa_ni, "%s: called", __func__); if (status == 0) { /* got ACK */ IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "BAR moves BA win <%u:%u> (%u frames) txseq %u tid %u", tap->txa_start, IEEE80211_SEQ_ADD(tap->txa_start, tap->txa_wnd-1), tap->txa_qframes, tap->txa_seqpending, tap->txa_tid); /* NB: timer already stopped in bar_tx_complete */ tap->txa_start = tap->txa_seqpending; tap->txa_flags &= ~IEEE80211_AGGR_BARPEND; } } /* * Transmit a BAR frame to the specified node. The * BAR contents are drawn from the supplied aggregation * state associated with the node. * * NB: we only handle immediate ACK w/ compressed bitmap. */ int ieee80211_send_bar(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap, ieee80211_seq seq) { #define senderr(_x, _v) do { vap->iv_stats._v++; ret = _x; goto bad; } while (0) struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_frame_bar *bar; struct mbuf *m; uint16_t barctl, barseqctl; uint8_t *frm; int tid, ret; IEEE80211_NOTE(tap->txa_ni->ni_vap, IEEE80211_MSG_11N, tap->txa_ni, "%s: called", __func__); if ((tap->txa_flags & IEEE80211_AGGR_RUNNING) == 0) { /* no ADDBA response, should not happen */ /* XXX stat+msg */ return EINVAL; } /* XXX locking */ bar_stop_timer(tap); ieee80211_ref_node(ni); m = ieee80211_getmgtframe(&frm, ic->ic_headroom, sizeof(*bar)); if (m == NULL) senderr(ENOMEM, is_tx_nobuf); if (!ieee80211_add_callback(m, bar_tx_complete, tap)) { m_freem(m); senderr(ENOMEM, is_tx_nobuf); /* XXX */ /* NOTREACHED */ } bar = mtod(m, struct ieee80211_frame_bar *); bar->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_CTL | IEEE80211_FC0_SUBTYPE_BAR; bar->i_fc[1] = 0; IEEE80211_ADDR_COPY(bar->i_ra, ni->ni_macaddr); IEEE80211_ADDR_COPY(bar->i_ta, vap->iv_myaddr); tid = tap->txa_tid; barctl = (tap->txa_flags & IEEE80211_AGGR_IMMEDIATE ? 0 : IEEE80211_BAR_NOACK) | IEEE80211_BAR_COMP | SM(tid, IEEE80211_BAR_TID) ; barseqctl = SM(seq, IEEE80211_BAR_SEQ_START); /* NB: known to have proper alignment */ bar->i_ctl = htole16(barctl); bar->i_seq = htole16(barseqctl); m->m_pkthdr.len = m->m_len = sizeof(struct ieee80211_frame_bar); M_WME_SETAC(m, WME_AC_VO); IEEE80211_NODE_STAT(ni, tx_mgmt); /* XXX tx_ctl? */ /* XXX locking */ /* init/bump attempts counter */ if ((tap->txa_flags & IEEE80211_AGGR_BARPEND) == 0) tap->txa_attempts = 1; else tap->txa_attempts++; tap->txa_seqpending = seq; tap->txa_flags |= IEEE80211_AGGR_BARPEND; IEEE80211_NOTE(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_11N, ni, "send BAR: tid %u ctl 0x%x start %u (attempt %d)", tid, barctl, seq, tap->txa_attempts); /* * ic_raw_xmit will free the node reference * regardless of queue/TX success or failure. */ IEEE80211_TX_LOCK(ic); ret = ieee80211_raw_output(vap, ni, m, NULL); IEEE80211_TX_UNLOCK(ic); if (ret != 0) { IEEE80211_NOTE(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_11N, ni, "send BAR: failed: (ret = %d)\n", ret); /* xmit failed, clear state flag */ tap->txa_flags &= ~IEEE80211_AGGR_BARPEND; vap->iv_stats.is_ampdu_bar_tx_fail++; return ret; } /* XXX hack against tx complete happening before timer is started */ if (tap->txa_flags & IEEE80211_AGGR_BARPEND) bar_start_timer(tap); return 0; bad: IEEE80211_NOTE(tap->txa_ni->ni_vap, IEEE80211_MSG_11N, tap->txa_ni, "%s: bad! ret=%d", __func__, ret); vap->iv_stats.is_ampdu_bar_tx_fail++; ieee80211_free_node(ni); return ret; #undef senderr } static int ht_action_output(struct ieee80211_node *ni, struct mbuf *m) { struct ieee80211_bpf_params params; memset(¶ms, 0, sizeof(params)); params.ibp_pri = WME_AC_VO; params.ibp_rate0 = ni->ni_txparms->mgmtrate; /* NB: we know all frames are unicast */ params.ibp_try0 = ni->ni_txparms->maxretry; params.ibp_power = ni->ni_txpower; return ieee80211_mgmt_output(ni, m, IEEE80211_FC0_SUBTYPE_ACTION, ¶ms); } #define ADDSHORT(frm, v) do { \ frm[0] = (v) & 0xff; \ frm[1] = (v) >> 8; \ frm += 2; \ } while (0) /* * Send an action management frame. The arguments are stuff * into a frame without inspection; the caller is assumed to * prepare them carefully (e.g. based on the aggregation state). */ static int ht_send_action_ba_addba(struct ieee80211_node *ni, int category, int action, void *arg0) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; uint16_t *args = arg0; struct mbuf *m; uint8_t *frm; IEEE80211_NOTE(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, "send ADDBA %s: dialogtoken %d status %d " "baparamset 0x%x (tid %d) batimeout 0x%x baseqctl 0x%x", (action == IEEE80211_ACTION_BA_ADDBA_REQUEST) ? "request" : "response", args[0], args[1], args[2], MS(args[2], IEEE80211_BAPS_TID), args[3], args[4]); IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "ieee80211_ref_node (%s:%u) %p<%s> refcnt %d\n", __func__, __LINE__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)+1); ieee80211_ref_node(ni); m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), sizeof(uint16_t) /* action+category */ /* XXX may action payload */ + sizeof(struct ieee80211_action_ba_addbaresponse) ); if (m != NULL) { *frm++ = category; *frm++ = action; *frm++ = args[0]; /* dialog token */ if (action == IEEE80211_ACTION_BA_ADDBA_RESPONSE) ADDSHORT(frm, args[1]); /* status code */ ADDSHORT(frm, args[2]); /* baparamset */ ADDSHORT(frm, args[3]); /* batimeout */ if (action == IEEE80211_ACTION_BA_ADDBA_REQUEST) ADDSHORT(frm, args[4]); /* baseqctl */ m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); return ht_action_output(ni, m); } else { vap->iv_stats.is_tx_nobuf++; ieee80211_free_node(ni); return ENOMEM; } } static int ht_send_action_ba_delba(struct ieee80211_node *ni, int category, int action, void *arg0) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; uint16_t *args = arg0; struct mbuf *m; uint16_t baparamset; uint8_t *frm; baparamset = SM(args[0], IEEE80211_DELBAPS_TID) | args[1] ; IEEE80211_NOTE(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, "send DELBA action: tid %d, initiator %d reason %d", args[0], args[1], args[2]); IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "ieee80211_ref_node (%s:%u) %p<%s> refcnt %d\n", __func__, __LINE__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)+1); ieee80211_ref_node(ni); m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), sizeof(uint16_t) /* action+category */ /* XXX may action payload */ + sizeof(struct ieee80211_action_ba_addbaresponse) ); if (m != NULL) { *frm++ = category; *frm++ = action; ADDSHORT(frm, baparamset); ADDSHORT(frm, args[2]); /* reason code */ m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); return ht_action_output(ni, m); } else { vap->iv_stats.is_tx_nobuf++; ieee80211_free_node(ni); return ENOMEM; } } static int ht_send_action_ht_txchwidth(struct ieee80211_node *ni, int category, int action, void *arg0) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct mbuf *m; uint8_t *frm; IEEE80211_NOTE(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, "send HT txchwidth: width %d", IEEE80211_IS_CHAN_HT40(ni->ni_chan) ? 40 : 20); IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "ieee80211_ref_node (%s:%u) %p<%s> refcnt %d\n", __func__, __LINE__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)+1); ieee80211_ref_node(ni); m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), sizeof(uint16_t) /* action+category */ /* XXX may action payload */ + sizeof(struct ieee80211_action_ba_addbaresponse) ); if (m != NULL) { *frm++ = category; *frm++ = action; *frm++ = IEEE80211_IS_CHAN_HT40(ni->ni_chan) ? IEEE80211_A_HT_TXCHWIDTH_2040 : IEEE80211_A_HT_TXCHWIDTH_20; m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); return ht_action_output(ni, m); } else { vap->iv_stats.is_tx_nobuf++; ieee80211_free_node(ni); return ENOMEM; } } #undef ADDSHORT /* * Construct the MCS bit mask for inclusion in an HT capabilities * information element. */ static void ieee80211_set_mcsset(struct ieee80211com *ic, uint8_t *frm) { int i; uint8_t txparams; KASSERT((ic->ic_rxstream > 0 && ic->ic_rxstream <= 4), ("ic_rxstream %d out of range", ic->ic_rxstream)); KASSERT((ic->ic_txstream > 0 && ic->ic_txstream <= 4), ("ic_txstream %d out of range", ic->ic_txstream)); for (i = 0; i < ic->ic_rxstream * 8; i++) setbit(frm, i); if ((ic->ic_htcaps & IEEE80211_HTCAP_CHWIDTH40) && (ic->ic_htcaps & IEEE80211_HTC_RXMCS32)) setbit(frm, 32); if (ic->ic_htcaps & IEEE80211_HTC_RXUNEQUAL) { if (ic->ic_rxstream >= 2) { for (i = 33; i <= 38; i++) setbit(frm, i); } if (ic->ic_rxstream >= 3) { for (i = 39; i <= 52; i++) setbit(frm, i); } if (ic->ic_txstream >= 4) { for (i = 53; i <= 76; i++) setbit(frm, i); } } if (ic->ic_rxstream != ic->ic_txstream) { txparams = 0x1; /* TX MCS set defined */ txparams |= 0x2; /* TX RX MCS not equal */ txparams |= (ic->ic_txstream - 1) << 2; /* num TX streams */ if (ic->ic_htcaps & IEEE80211_HTC_TXUNEQUAL) txparams |= 0x16; /* TX unequal modulation sup */ } else txparams = 0; frm[12] = txparams; } /* * Add body of an HTCAP information element. */ static uint8_t * ieee80211_add_htcap_body(uint8_t *frm, struct ieee80211_node *ni) { #define ADDSHORT(frm, v) do { \ frm[0] = (v) & 0xff; \ frm[1] = (v) >> 8; \ frm += 2; \ } while (0) struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; uint16_t caps, extcaps; int rxmax, density; /* HT capabilities */ caps = vap->iv_htcaps & 0xffff; /* * Note channel width depends on whether we are operating as * a sta or not. When operating as a sta we are generating * a request based on our desired configuration. Otherwise * we are operational and the channel attributes identify * how we've been setup (which might be different if a fixed * channel is specified). */ if (vap->iv_opmode == IEEE80211_M_STA) { /* override 20/40 use based on config */ if (vap->iv_flags_ht & IEEE80211_FHT_USEHT40) caps |= IEEE80211_HTCAP_CHWIDTH40; else caps &= ~IEEE80211_HTCAP_CHWIDTH40; /* Start by using the advertised settings */ rxmax = MS(ni->ni_htparam, IEEE80211_HTCAP_MAXRXAMPDU); density = MS(ni->ni_htparam, IEEE80211_HTCAP_MPDUDENSITY); /* Cap at VAP rxmax */ if (rxmax > vap->iv_ampdu_rxmax) rxmax = vap->iv_ampdu_rxmax; /* * If the VAP ampdu density value greater, use that. * * (Larger density value == larger minimum gap between A-MPDU * subframes.) */ if (vap->iv_ampdu_density > density) density = vap->iv_ampdu_density; /* * NB: Hardware might support HT40 on some but not all * channels. We can't determine this earlier because only * after association the channel is upgraded to HT based * on the negotiated capabilities. */ if (ni->ni_chan != IEEE80211_CHAN_ANYC && findhtchan(ic, ni->ni_chan, IEEE80211_CHAN_HT40U) == NULL && findhtchan(ic, ni->ni_chan, IEEE80211_CHAN_HT40D) == NULL) caps &= ~IEEE80211_HTCAP_CHWIDTH40; } else { /* override 20/40 use based on current channel */ if (IEEE80211_IS_CHAN_HT40(ni->ni_chan)) caps |= IEEE80211_HTCAP_CHWIDTH40; else caps &= ~IEEE80211_HTCAP_CHWIDTH40; /* XXX TODO should it start by using advertised settings? */ rxmax = vap->iv_ampdu_rxmax; density = vap->iv_ampdu_density; } /* adjust short GI based on channel and config */ if ((vap->iv_flags_ht & IEEE80211_FHT_SHORTGI20) == 0) caps &= ~IEEE80211_HTCAP_SHORTGI20; if ((vap->iv_flags_ht & IEEE80211_FHT_SHORTGI40) == 0 || (caps & IEEE80211_HTCAP_CHWIDTH40) == 0) caps &= ~IEEE80211_HTCAP_SHORTGI40; ADDSHORT(frm, caps); /* HT parameters */ *frm = SM(rxmax, IEEE80211_HTCAP_MAXRXAMPDU) | SM(density, IEEE80211_HTCAP_MPDUDENSITY) ; frm++; /* pre-zero remainder of ie */ memset(frm, 0, sizeof(struct ieee80211_ie_htcap) - __offsetof(struct ieee80211_ie_htcap, hc_mcsset)); /* supported MCS set */ /* * XXX: For sta mode the rate set should be restricted based * on the AP's capabilities, but ni_htrates isn't setup when * we're called to form an AssocReq frame so for now we're * restricted to the device capabilities. */ ieee80211_set_mcsset(ni->ni_ic, frm); frm += __offsetof(struct ieee80211_ie_htcap, hc_extcap) - __offsetof(struct ieee80211_ie_htcap, hc_mcsset); /* HT extended capabilities */ extcaps = vap->iv_htextcaps & 0xffff; ADDSHORT(frm, extcaps); frm += sizeof(struct ieee80211_ie_htcap) - __offsetof(struct ieee80211_ie_htcap, hc_txbf); return frm; #undef ADDSHORT } /* * Add 802.11n HT capabilities information element */ uint8_t * ieee80211_add_htcap(uint8_t *frm, struct ieee80211_node *ni) { frm[0] = IEEE80211_ELEMID_HTCAP; frm[1] = sizeof(struct ieee80211_ie_htcap) - 2; return ieee80211_add_htcap_body(frm + 2, ni); } /* * Add Broadcom OUI wrapped standard HTCAP ie; this is * used for compatibility w/ pre-draft implementations. */ uint8_t * ieee80211_add_htcap_vendor(uint8_t *frm, struct ieee80211_node *ni) { frm[0] = IEEE80211_ELEMID_VENDOR; frm[1] = 4 + sizeof(struct ieee80211_ie_htcap) - 2; frm[2] = (BCM_OUI >> 0) & 0xff; frm[3] = (BCM_OUI >> 8) & 0xff; frm[4] = (BCM_OUI >> 16) & 0xff; frm[5] = BCM_OUI_HTCAP; return ieee80211_add_htcap_body(frm + 6, ni); } /* * Construct the MCS bit mask of basic rates * for inclusion in an HT information element. */ static void ieee80211_set_basic_htrates(uint8_t *frm, const struct ieee80211_htrateset *rs) { int i; for (i = 0; i < rs->rs_nrates; i++) { int r = rs->rs_rates[i] & IEEE80211_RATE_VAL; if ((rs->rs_rates[i] & IEEE80211_RATE_BASIC) && r < IEEE80211_HTRATE_MAXSIZE) { /* NB: this assumes a particular implementation */ setbit(frm, r); } } } /* * Update the HTINFO ie for a beacon frame. */ void ieee80211_ht_update_beacon(struct ieee80211vap *vap, struct ieee80211_beacon_offsets *bo) { #define PROTMODE (IEEE80211_HTINFO_OPMODE|IEEE80211_HTINFO_NONHT_PRESENT) struct ieee80211_node *ni; const struct ieee80211_channel *bsschan; struct ieee80211com *ic = vap->iv_ic; struct ieee80211_ie_htinfo *ht = (struct ieee80211_ie_htinfo *) bo->bo_htinfo; ni = ieee80211_ref_node(vap->iv_bss); bsschan = ni->ni_chan; /* XXX only update on channel change */ ht->hi_ctrlchannel = ieee80211_chan2ieee(ic, bsschan); if (vap->iv_flags_ht & IEEE80211_FHT_RIFS) ht->hi_byte1 = IEEE80211_HTINFO_RIFSMODE_PERM; else ht->hi_byte1 = IEEE80211_HTINFO_RIFSMODE_PROH; if (IEEE80211_IS_CHAN_HT40U(bsschan)) ht->hi_byte1 |= IEEE80211_HTINFO_2NDCHAN_ABOVE; else if (IEEE80211_IS_CHAN_HT40D(bsschan)) ht->hi_byte1 |= IEEE80211_HTINFO_2NDCHAN_BELOW; else ht->hi_byte1 |= IEEE80211_HTINFO_2NDCHAN_NONE; if (IEEE80211_IS_CHAN_HT40(bsschan)) ht->hi_byte1 |= IEEE80211_HTINFO_TXWIDTH_2040; /* protection mode */ ht->hi_byte2 = (ht->hi_byte2 &~ PROTMODE) | ic->ic_curhtprotmode; ieee80211_free_node(ni); /* XXX propagate to vendor ie's */ #undef PROTMODE } /* * Add body of an HTINFO information element. * * NB: We don't use struct ieee80211_ie_htinfo because we can * be called to fillin both a standard ie and a compat ie that * has a vendor OUI at the front. */ static uint8_t * ieee80211_add_htinfo_body(uint8_t *frm, struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; /* pre-zero remainder of ie */ memset(frm, 0, sizeof(struct ieee80211_ie_htinfo) - 2); /* primary/control channel center */ *frm++ = ieee80211_chan2ieee(ic, ni->ni_chan); if (vap->iv_flags_ht & IEEE80211_FHT_RIFS) frm[0] = IEEE80211_HTINFO_RIFSMODE_PERM; else frm[0] = IEEE80211_HTINFO_RIFSMODE_PROH; if (IEEE80211_IS_CHAN_HT40U(ni->ni_chan)) frm[0] |= IEEE80211_HTINFO_2NDCHAN_ABOVE; else if (IEEE80211_IS_CHAN_HT40D(ni->ni_chan)) frm[0] |= IEEE80211_HTINFO_2NDCHAN_BELOW; else frm[0] |= IEEE80211_HTINFO_2NDCHAN_NONE; if (IEEE80211_IS_CHAN_HT40(ni->ni_chan)) frm[0] |= IEEE80211_HTINFO_TXWIDTH_2040; frm[1] = ic->ic_curhtprotmode; frm += 5; /* basic MCS set */ ieee80211_set_basic_htrates(frm, &ni->ni_htrates); frm += sizeof(struct ieee80211_ie_htinfo) - __offsetof(struct ieee80211_ie_htinfo, hi_basicmcsset); return frm; } /* * Add 802.11n HT information information element. */ uint8_t * ieee80211_add_htinfo(uint8_t *frm, struct ieee80211_node *ni) { frm[0] = IEEE80211_ELEMID_HTINFO; frm[1] = sizeof(struct ieee80211_ie_htinfo) - 2; return ieee80211_add_htinfo_body(frm + 2, ni); } /* * Add Broadcom OUI wrapped standard HTINFO ie; this is * used for compatibility w/ pre-draft implementations. */ uint8_t * ieee80211_add_htinfo_vendor(uint8_t *frm, struct ieee80211_node *ni) { frm[0] = IEEE80211_ELEMID_VENDOR; frm[1] = 4 + sizeof(struct ieee80211_ie_htinfo) - 2; frm[2] = (BCM_OUI >> 0) & 0xff; frm[3] = (BCM_OUI >> 8) & 0xff; frm[4] = (BCM_OUI >> 16) & 0xff; frm[5] = BCM_OUI_HTINFO; return ieee80211_add_htinfo_body(frm + 6, ni); } Index: head/sys/net80211/ieee80211_node.c =================================================================== --- head/sys/net80211/ieee80211_node.c (revision 297404) +++ head/sys/net80211/ieee80211_node.c (revision 297405) @@ -1,2828 +1,2828 @@ /*- * Copyright (c) 2001 Atsushi Onoe * Copyright (c) 2002-2009 Sam Leffler, Errno Consulting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IEEE80211_SUPPORT_SUPERG #include #endif #ifdef IEEE80211_SUPPORT_TDMA #include #endif #include #include #include #include /* * IEEE80211_NODE_HASHSIZE must be a power of 2. */ CTASSERT((IEEE80211_NODE_HASHSIZE & (IEEE80211_NODE_HASHSIZE-1)) == 0); /* * Association id's are managed with a bit vector. */ #define IEEE80211_AID_SET(_vap, b) \ ((_vap)->iv_aid_bitmap[IEEE80211_AID(b) / 32] |= \ (1 << (IEEE80211_AID(b) % 32))) #define IEEE80211_AID_CLR(_vap, b) \ ((_vap)->iv_aid_bitmap[IEEE80211_AID(b) / 32] &= \ ~(1 << (IEEE80211_AID(b) % 32))) #define IEEE80211_AID_ISSET(_vap, b) \ ((_vap)->iv_aid_bitmap[IEEE80211_AID(b) / 32] & (1 << (IEEE80211_AID(b) % 32))) #ifdef IEEE80211_DEBUG_REFCNT #define REFCNT_LOC "%s (%s:%u) %p<%s> refcnt %d\n", __func__, func, line #else #define REFCNT_LOC "%s %p<%s> refcnt %d\n", __func__ #endif static int ieee80211_sta_join1(struct ieee80211_node *); static struct ieee80211_node *node_alloc(struct ieee80211vap *, const uint8_t [IEEE80211_ADDR_LEN]); static void node_cleanup(struct ieee80211_node *); static void node_free(struct ieee80211_node *); static void node_age(struct ieee80211_node *); static int8_t node_getrssi(const struct ieee80211_node *); static void node_getsignal(const struct ieee80211_node *, int8_t *, int8_t *); static void node_getmimoinfo(const struct ieee80211_node *, struct ieee80211_mimo_info *); static void _ieee80211_free_node(struct ieee80211_node *); static void node_reclaim(struct ieee80211_node_table *nt, struct ieee80211_node *ni); static void ieee80211_node_table_init(struct ieee80211com *ic, struct ieee80211_node_table *nt, const char *name, int inact, int keymaxix); static void ieee80211_node_table_reset(struct ieee80211_node_table *, struct ieee80211vap *); static void ieee80211_node_table_cleanup(struct ieee80211_node_table *nt); static void ieee80211_erp_timeout(struct ieee80211com *); MALLOC_DEFINE(M_80211_NODE, "80211node", "802.11 node state"); MALLOC_DEFINE(M_80211_NODE_IE, "80211nodeie", "802.11 node ie"); void ieee80211_node_attach(struct ieee80211com *ic) { /* XXX really want maxlen enforced per-sta */ ieee80211_ageq_init(&ic->ic_stageq, ic->ic_max_keyix * 8, "802.11 staging q"); ieee80211_node_table_init(ic, &ic->ic_sta, "station", IEEE80211_INACT_INIT, ic->ic_max_keyix); callout_init(&ic->ic_inact, 1); callout_reset(&ic->ic_inact, IEEE80211_INACT_WAIT*hz, ieee80211_node_timeout, ic); ic->ic_node_alloc = node_alloc; ic->ic_node_free = node_free; ic->ic_node_cleanup = node_cleanup; ic->ic_node_age = node_age; ic->ic_node_drain = node_age; /* NB: same as age */ ic->ic_node_getrssi = node_getrssi; ic->ic_node_getsignal = node_getsignal; ic->ic_node_getmimoinfo = node_getmimoinfo; /* * Set flags to be propagated to all vap's; * these define default behaviour/configuration. */ ic->ic_flags_ext |= IEEE80211_FEXT_INACT; /* inactivity processing */ } void ieee80211_node_detach(struct ieee80211com *ic) { callout_drain(&ic->ic_inact); ieee80211_node_table_cleanup(&ic->ic_sta); ieee80211_ageq_cleanup(&ic->ic_stageq); } void ieee80211_node_vattach(struct ieee80211vap *vap) { /* NB: driver can override */ vap->iv_max_aid = IEEE80211_AID_DEF; /* default station inactivity timer setings */ vap->iv_inact_init = IEEE80211_INACT_INIT; vap->iv_inact_auth = IEEE80211_INACT_AUTH; vap->iv_inact_run = IEEE80211_INACT_RUN; vap->iv_inact_probe = IEEE80211_INACT_PROBE; IEEE80211_DPRINTF(vap, IEEE80211_MSG_INACT, "%s: init %u auth %u run %u probe %u\n", __func__, vap->iv_inact_init, vap->iv_inact_auth, vap->iv_inact_run, vap->iv_inact_probe); } void ieee80211_node_latevattach(struct ieee80211vap *vap) { if (vap->iv_opmode == IEEE80211_M_HOSTAP) { /* XXX should we allow max aid to be zero? */ if (vap->iv_max_aid < IEEE80211_AID_MIN) { vap->iv_max_aid = IEEE80211_AID_MIN; if_printf(vap->iv_ifp, "WARNING: max aid too small, changed to %d\n", vap->iv_max_aid); } vap->iv_aid_bitmap = (uint32_t *) IEEE80211_MALLOC( howmany(vap->iv_max_aid, 32) * sizeof(uint32_t), M_80211_NODE, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (vap->iv_aid_bitmap == NULL) { /* XXX no way to recover */ printf("%s: no memory for AID bitmap, max aid %d!\n", __func__, vap->iv_max_aid); vap->iv_max_aid = 0; } } ieee80211_reset_bss(vap); vap->iv_auth = ieee80211_authenticator_get(vap->iv_bss->ni_authmode); } void ieee80211_node_vdetach(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; ieee80211_node_table_reset(&ic->ic_sta, vap); if (vap->iv_bss != NULL) { ieee80211_free_node(vap->iv_bss); vap->iv_bss = NULL; } if (vap->iv_aid_bitmap != NULL) { IEEE80211_FREE(vap->iv_aid_bitmap, M_80211_NODE); vap->iv_aid_bitmap = NULL; } } /* * Port authorize/unauthorize interfaces for use by an authenticator. */ void ieee80211_node_authorize(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; ni->ni_flags |= IEEE80211_NODE_AUTH; ni->ni_inact_reload = vap->iv_inact_run; ni->ni_inact = ni->ni_inact_reload; IEEE80211_NOTE(vap, IEEE80211_MSG_INACT, ni, "%s: inact_reload %u", __func__, ni->ni_inact_reload); } void ieee80211_node_unauthorize(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; ni->ni_flags &= ~IEEE80211_NODE_AUTH; ni->ni_inact_reload = vap->iv_inact_auth; if (ni->ni_inact > ni->ni_inact_reload) ni->ni_inact = ni->ni_inact_reload; IEEE80211_NOTE(vap, IEEE80211_MSG_INACT, ni, "%s: inact_reload %u inact %u", __func__, ni->ni_inact_reload, ni->ni_inact); } /* * Fix tx parameters for a node according to ``association state''. */ void ieee80211_node_setuptxparms(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; enum ieee80211_phymode mode; if (ni->ni_flags & IEEE80211_NODE_HT) { if (IEEE80211_IS_CHAN_5GHZ(ni->ni_chan)) mode = IEEE80211_MODE_11NA; else mode = IEEE80211_MODE_11NG; } else { /* legacy rate handling */ if (IEEE80211_IS_CHAN_ST(ni->ni_chan)) mode = IEEE80211_MODE_STURBO_A; else if (IEEE80211_IS_CHAN_HALF(ni->ni_chan)) mode = IEEE80211_MODE_HALF; else if (IEEE80211_IS_CHAN_QUARTER(ni->ni_chan)) mode = IEEE80211_MODE_QUARTER; /* NB: 108A should be handled as 11a */ else if (IEEE80211_IS_CHAN_A(ni->ni_chan)) mode = IEEE80211_MODE_11A; else if (IEEE80211_IS_CHAN_108G(ni->ni_chan) || (ni->ni_flags & IEEE80211_NODE_ERP)) mode = IEEE80211_MODE_11G; else mode = IEEE80211_MODE_11B; } ni->ni_txparms = &vap->iv_txparms[mode]; } /* * Set/change the channel. The rate set is also updated as * to insure a consistent view by drivers. * XXX should be private but hostap needs it to deal with CSA */ void ieee80211_node_set_chan(struct ieee80211_node *ni, struct ieee80211_channel *chan) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; enum ieee80211_phymode mode; KASSERT(chan != IEEE80211_CHAN_ANYC, ("no channel")); ni->ni_chan = chan; mode = ieee80211_chan2mode(chan); if (IEEE80211_IS_CHAN_HT(chan)) { /* * We must install the legacy rate est in ni_rates and the * HT rate set in ni_htrates. */ ni->ni_htrates = *ieee80211_get_suphtrates(ic, chan); /* * Setup bss tx parameters based on operating mode. We * use legacy rates when operating in a mixed HT+non-HT bss * and non-ERP rates in 11g for mixed ERP+non-ERP bss. */ if (mode == IEEE80211_MODE_11NA && (vap->iv_flags_ht & IEEE80211_FHT_PUREN) == 0) mode = IEEE80211_MODE_11A; else if (mode == IEEE80211_MODE_11NG && (vap->iv_flags_ht & IEEE80211_FHT_PUREN) == 0) mode = IEEE80211_MODE_11G; if (mode == IEEE80211_MODE_11G && (vap->iv_flags & IEEE80211_F_PUREG) == 0) mode = IEEE80211_MODE_11B; } ni->ni_txparms = &vap->iv_txparms[mode]; ni->ni_rates = *ieee80211_get_suprates(ic, chan); } static __inline void copy_bss(struct ieee80211_node *nbss, const struct ieee80211_node *obss) { /* propagate useful state */ nbss->ni_authmode = obss->ni_authmode; nbss->ni_txpower = obss->ni_txpower; nbss->ni_vlan = obss->ni_vlan; /* XXX statistics? */ /* XXX legacy WDS bssid? */ } void ieee80211_create_ibss(struct ieee80211vap* vap, struct ieee80211_channel *chan) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: creating %s on channel %u\n", __func__, ieee80211_opmode_name[vap->iv_opmode], ieee80211_chan2ieee(ic, chan)); ni = ieee80211_alloc_node(&ic->ic_sta, vap, vap->iv_myaddr); if (ni == NULL) { /* XXX recovery? */ return; } IEEE80211_ADDR_COPY(ni->ni_bssid, vap->iv_myaddr); ni->ni_esslen = vap->iv_des_ssid[0].len; memcpy(ni->ni_essid, vap->iv_des_ssid[0].ssid, ni->ni_esslen); if (vap->iv_bss != NULL) copy_bss(ni, vap->iv_bss); ni->ni_intval = ic->ic_bintval; if (vap->iv_flags & IEEE80211_F_PRIVACY) ni->ni_capinfo |= IEEE80211_CAPINFO_PRIVACY; if (ic->ic_phytype == IEEE80211_T_FH) { ni->ni_fhdwell = 200; /* XXX */ ni->ni_fhindex = 1; } if (vap->iv_opmode == IEEE80211_M_IBSS) { vap->iv_flags |= IEEE80211_F_SIBSS; ni->ni_capinfo |= IEEE80211_CAPINFO_IBSS; /* XXX */ if (vap->iv_flags & IEEE80211_F_DESBSSID) IEEE80211_ADDR_COPY(ni->ni_bssid, vap->iv_des_bssid); else { get_random_bytes(ni->ni_bssid, IEEE80211_ADDR_LEN); /* clear group bit, add local bit */ ni->ni_bssid[0] = (ni->ni_bssid[0] &~ 0x01) | 0x02; } } else if (vap->iv_opmode == IEEE80211_M_AHDEMO) { if (vap->iv_flags & IEEE80211_F_DESBSSID) IEEE80211_ADDR_COPY(ni->ni_bssid, vap->iv_des_bssid); else #ifdef IEEE80211_SUPPORT_TDMA if ((vap->iv_caps & IEEE80211_C_TDMA) == 0) #endif memset(ni->ni_bssid, 0, IEEE80211_ADDR_LEN); #ifdef IEEE80211_SUPPORT_MESH } else if (vap->iv_opmode == IEEE80211_M_MBSS) { ni->ni_meshidlen = vap->iv_mesh->ms_idlen; memcpy(ni->ni_meshid, vap->iv_mesh->ms_id, ni->ni_meshidlen); #endif } /* * Fix the channel and related attributes. */ /* clear DFS CAC state on previous channel */ if (ic->ic_bsschan != IEEE80211_CHAN_ANYC && ic->ic_bsschan->ic_freq != chan->ic_freq && IEEE80211_IS_CHAN_CACDONE(ic->ic_bsschan)) ieee80211_dfs_cac_clear(ic, ic->ic_bsschan); ic->ic_bsschan = chan; ieee80211_node_set_chan(ni, chan); ic->ic_curmode = ieee80211_chan2mode(chan); /* * Do mode-specific setup. */ if (IEEE80211_IS_CHAN_FULL(chan)) { if (IEEE80211_IS_CHAN_ANYG(chan)) { /* * Use a mixed 11b/11g basic rate set. */ ieee80211_setbasicrates(&ni->ni_rates, IEEE80211_MODE_11G); if (vap->iv_flags & IEEE80211_F_PUREG) { /* * Also mark OFDM rates basic so 11b * stations do not join (WiFi compliance). */ ieee80211_addbasicrates(&ni->ni_rates, IEEE80211_MODE_11A); } } else if (IEEE80211_IS_CHAN_B(chan)) { /* * Force pure 11b rate set. */ ieee80211_setbasicrates(&ni->ni_rates, IEEE80211_MODE_11B); } } (void) ieee80211_sta_join1(ieee80211_ref_node(ni)); } /* * Reset bss state on transition to the INIT state. * Clear any stations from the table (they have been * deauth'd) and reset the bss node (clears key, rate * etc. state). */ void ieee80211_reset_bss(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni, *obss; ieee80211_node_table_reset(&ic->ic_sta, vap); /* XXX multi-bss: wrong */ ieee80211_reset_erp(ic); ni = ieee80211_alloc_node(&ic->ic_sta, vap, vap->iv_myaddr); KASSERT(ni != NULL, ("unable to setup initial BSS node")); obss = vap->iv_bss; vap->iv_bss = ieee80211_ref_node(ni); if (obss != NULL) { copy_bss(ni, obss); ni->ni_intval = ic->ic_bintval; ieee80211_free_node(obss); } else IEEE80211_ADDR_COPY(ni->ni_bssid, vap->iv_myaddr); } static int match_ssid(const struct ieee80211_node *ni, int nssid, const struct ieee80211_scan_ssid ssids[]) { int i; for (i = 0; i < nssid; i++) { if (ni->ni_esslen == ssids[i].len && memcmp(ni->ni_essid, ssids[i].ssid, ni->ni_esslen) == 0) return 1; } return 0; } /* * Test a node for suitability/compatibility. */ static int check_bss(struct ieee80211vap *vap, struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; uint8_t rate; if (isclr(ic->ic_chan_active, ieee80211_chan2ieee(ic, ni->ni_chan))) return 0; if (vap->iv_opmode == IEEE80211_M_IBSS) { if ((ni->ni_capinfo & IEEE80211_CAPINFO_IBSS) == 0) return 0; } else { if ((ni->ni_capinfo & IEEE80211_CAPINFO_ESS) == 0) return 0; } if (vap->iv_flags & IEEE80211_F_PRIVACY) { if ((ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) == 0) return 0; } else { /* XXX does this mean privacy is supported or required? */ if (ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) return 0; } rate = ieee80211_fix_rate(ni, &ni->ni_rates, IEEE80211_F_JOIN | IEEE80211_F_DONEGO | IEEE80211_F_DOFRATE); if (rate & IEEE80211_RATE_BASIC) return 0; if (vap->iv_des_nssid != 0 && !match_ssid(ni, vap->iv_des_nssid, vap->iv_des_ssid)) return 0; if ((vap->iv_flags & IEEE80211_F_DESBSSID) && !IEEE80211_ADDR_EQ(vap->iv_des_bssid, ni->ni_bssid)) return 0; return 1; } #ifdef IEEE80211_DEBUG /* * Display node suitability/compatibility. */ static void check_bss_debug(struct ieee80211vap *vap, struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; uint8_t rate; int fail; fail = 0; if (isclr(ic->ic_chan_active, ieee80211_chan2ieee(ic, ni->ni_chan))) fail |= 0x01; if (vap->iv_opmode == IEEE80211_M_IBSS) { if ((ni->ni_capinfo & IEEE80211_CAPINFO_IBSS) == 0) fail |= 0x02; } else { if ((ni->ni_capinfo & IEEE80211_CAPINFO_ESS) == 0) fail |= 0x02; } if (vap->iv_flags & IEEE80211_F_PRIVACY) { if ((ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) == 0) fail |= 0x04; } else { /* XXX does this mean privacy is supported or required? */ if (ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) fail |= 0x04; } rate = ieee80211_fix_rate(ni, &ni->ni_rates, IEEE80211_F_JOIN | IEEE80211_F_DONEGO | IEEE80211_F_DOFRATE); if (rate & IEEE80211_RATE_BASIC) fail |= 0x08; if (vap->iv_des_nssid != 0 && !match_ssid(ni, vap->iv_des_nssid, vap->iv_des_ssid)) fail |= 0x10; if ((vap->iv_flags & IEEE80211_F_DESBSSID) && !IEEE80211_ADDR_EQ(vap->iv_des_bssid, ni->ni_bssid)) fail |= 0x20; printf(" %c %s", fail ? '-' : '+', ether_sprintf(ni->ni_macaddr)); printf(" %s%c", ether_sprintf(ni->ni_bssid), fail & 0x20 ? '!' : ' '); printf(" %3d%c", ieee80211_chan2ieee(ic, ni->ni_chan), fail & 0x01 ? '!' : ' '); printf(" %2dM%c", (rate & IEEE80211_RATE_VAL) / 2, fail & 0x08 ? '!' : ' '); printf(" %4s%c", (ni->ni_capinfo & IEEE80211_CAPINFO_ESS) ? "ess" : (ni->ni_capinfo & IEEE80211_CAPINFO_IBSS) ? "ibss" : "????", fail & 0x02 ? '!' : ' '); printf(" %3s%c ", (ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) ? "wep" : "no", fail & 0x04 ? '!' : ' '); ieee80211_print_essid(ni->ni_essid, ni->ni_esslen); printf("%s\n", fail & 0x10 ? "!" : ""); } #endif /* IEEE80211_DEBUG */ /* * Handle 802.11 ad hoc network merge. The * convention, set by the Wireless Ethernet Compatibility Alliance * (WECA), is that an 802.11 station will change its BSSID to match * the "oldest" 802.11 ad hoc network, on the same channel, that * has the station's desired SSID. The "oldest" 802.11 network * sends beacons with the greatest TSF timestamp. * * The caller is assumed to validate TSF's before attempting a merge. * * Return !0 if the BSSID changed, 0 otherwise. */ int ieee80211_ibss_merge(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; #ifdef IEEE80211_DEBUG struct ieee80211com *ic = ni->ni_ic; #endif if (ni == vap->iv_bss || IEEE80211_ADDR_EQ(ni->ni_bssid, vap->iv_bss->ni_bssid)) { /* unchanged, nothing to do */ return 0; } if (!check_bss(vap, ni)) { /* capabilities mismatch */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, "%s: merge failed, capabilities mismatch\n", __func__); #ifdef IEEE80211_DEBUG if (ieee80211_msg_assoc(vap)) check_bss_debug(vap, ni); #endif vap->iv_stats.is_ibss_capmismatch++; return 0; } IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, "%s: new bssid %s: %s preamble, %s slot time%s\n", __func__, ether_sprintf(ni->ni_bssid), ic->ic_flags&IEEE80211_F_SHPREAMBLE ? "short" : "long", ic->ic_flags&IEEE80211_F_SHSLOT ? "short" : "long", ic->ic_flags&IEEE80211_F_USEPROT ? ", protection" : "" ); return ieee80211_sta_join1(ieee80211_ref_node(ni)); } /* * Calculate HT channel promotion flags for all vaps. * This assumes ni_chan have been setup for each vap. */ static int gethtadjustflags(struct ieee80211com *ic) { struct ieee80211vap *vap; int flags; flags = 0; /* XXX locking */ TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { if (vap->iv_state < IEEE80211_S_RUN) continue; switch (vap->iv_opmode) { case IEEE80211_M_WDS: case IEEE80211_M_STA: case IEEE80211_M_AHDEMO: case IEEE80211_M_HOSTAP: case IEEE80211_M_IBSS: case IEEE80211_M_MBSS: flags |= ieee80211_htchanflags(vap->iv_bss->ni_chan); break; default: break; } } return flags; } /* * Check if the current channel needs to change based on whether * any vap's are using HT20/HT40. This is used to sync the state * of ic_curchan after a channel width change on a running vap. */ void ieee80211_sync_curchan(struct ieee80211com *ic) { struct ieee80211_channel *c; c = ieee80211_ht_adjust_channel(ic, ic->ic_curchan, gethtadjustflags(ic)); if (c != ic->ic_curchan) { ic->ic_curchan = c; ic->ic_curmode = ieee80211_chan2mode(ic->ic_curchan); ic->ic_rt = ieee80211_get_ratetable(ic->ic_curchan); IEEE80211_UNLOCK(ic); ic->ic_set_channel(ic); ieee80211_radiotap_chan_change(ic); IEEE80211_LOCK(ic); } } /* * Setup the current channel. The request channel may be * promoted if other vap's are operating with HT20/HT40. */ void ieee80211_setupcurchan(struct ieee80211com *ic, struct ieee80211_channel *c) { if (ic->ic_htcaps & IEEE80211_HTC_HT) { int flags = gethtadjustflags(ic); /* * Check for channel promotion required to support the * set of running vap's. This assumes we are called * after ni_chan is setup for each vap. */ /* NB: this assumes IEEE80211_FHT_USEHT40 > IEEE80211_FHT_HT */ if (flags > ieee80211_htchanflags(c)) c = ieee80211_ht_adjust_channel(ic, c, flags); } ic->ic_bsschan = ic->ic_curchan = c; ic->ic_curmode = ieee80211_chan2mode(ic->ic_curchan); ic->ic_rt = ieee80211_get_ratetable(ic->ic_curchan); } /* * Change the current channel. The channel change is guaranteed to have * happened before the next state change. */ void ieee80211_setcurchan(struct ieee80211com *ic, struct ieee80211_channel *c) { ieee80211_setupcurchan(ic, c); ieee80211_runtask(ic, &ic->ic_chan_task); } void ieee80211_update_chw(struct ieee80211com *ic) { ieee80211_setupcurchan(ic, ic->ic_curchan); ieee80211_runtask(ic, &ic->ic_chw_task); } /* * Join the specified IBSS/BSS network. The node is assumed to * be passed in with a held reference. */ static int ieee80211_sta_join1(struct ieee80211_node *selbs) { struct ieee80211vap *vap = selbs->ni_vap; struct ieee80211com *ic = selbs->ni_ic; struct ieee80211_node *obss; int canreassoc; /* * Committed to selbs, setup state. */ obss = vap->iv_bss; /* * Check if old+new node have the same address in which * case we can reassociate when operating in sta mode. */ canreassoc = (obss != NULL && vap->iv_state == IEEE80211_S_RUN && IEEE80211_ADDR_EQ(obss->ni_macaddr, selbs->ni_macaddr)); vap->iv_bss = selbs; /* NB: caller assumed to bump refcnt */ if (obss != NULL) { struct ieee80211_node_table *nt = obss->ni_table; copy_bss(selbs, obss); ieee80211_node_decref(obss); /* iv_bss reference */ IEEE80211_NODE_LOCK(nt); node_reclaim(nt, obss); /* station table reference */ IEEE80211_NODE_UNLOCK(nt); obss = NULL; /* NB: guard against later use */ } /* * Delete unusable rates; we've already checked * that the negotiated rate set is acceptable. */ ieee80211_fix_rate(vap->iv_bss, &vap->iv_bss->ni_rates, IEEE80211_F_DODEL | IEEE80211_F_JOIN); ieee80211_setcurchan(ic, selbs->ni_chan); /* * Set the erp state (mostly the slot time) to deal with * the auto-select case; this should be redundant if the * mode is locked. */ ieee80211_reset_erp(ic); ieee80211_wme_initparams(vap); if (vap->iv_opmode == IEEE80211_M_STA) { if (canreassoc) { /* Reassociate */ ieee80211_new_state(vap, IEEE80211_S_ASSOC, 1); } else { /* * Act as if we received a DEAUTH frame in case we * are invoked from the RUN state. This will cause * us to try to re-authenticate if we are operating * as a station. */ ieee80211_new_state(vap, IEEE80211_S_AUTH, IEEE80211_FC0_SUBTYPE_DEAUTH); } } else ieee80211_new_state(vap, IEEE80211_S_RUN, -1); return 1; } int ieee80211_sta_join(struct ieee80211vap *vap, struct ieee80211_channel *chan, const struct ieee80211_scan_entry *se) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; ni = ieee80211_alloc_node(&ic->ic_sta, vap, se->se_macaddr); if (ni == NULL) { /* XXX msg */ return 0; } /* * Expand scan state into node's format. * XXX may not need all this stuff */ IEEE80211_ADDR_COPY(ni->ni_bssid, se->se_bssid); ni->ni_esslen = se->se_ssid[1]; memcpy(ni->ni_essid, se->se_ssid+2, ni->ni_esslen); ni->ni_tstamp.tsf = se->se_tstamp.tsf; ni->ni_intval = se->se_intval; ni->ni_capinfo = se->se_capinfo; ni->ni_chan = chan; ni->ni_timoff = se->se_timoff; ni->ni_fhdwell = se->se_fhdwell; ni->ni_fhindex = se->se_fhindex; ni->ni_erp = se->se_erp; IEEE80211_RSSI_LPF(ni->ni_avgrssi, se->se_rssi); ni->ni_noise = se->se_noise; if (vap->iv_opmode == IEEE80211_M_STA) { /* NB: only infrastructure mode requires an associd */ ni->ni_flags |= IEEE80211_NODE_ASSOCID; } if (ieee80211_ies_init(&ni->ni_ies, se->se_ies.data, se->se_ies.len)) { ieee80211_ies_expand(&ni->ni_ies); #ifdef IEEE80211_SUPPORT_SUPERG if (ni->ni_ies.ath_ie != NULL) ieee80211_parse_ath(ni, ni->ni_ies.ath_ie); #endif if (ni->ni_ies.htcap_ie != NULL) ieee80211_parse_htcap(ni, ni->ni_ies.htcap_ie); if (ni->ni_ies.htinfo_ie != NULL) ieee80211_parse_htinfo(ni, ni->ni_ies.htinfo_ie); #ifdef IEEE80211_SUPPORT_MESH if (ni->ni_ies.meshid_ie != NULL) ieee80211_parse_meshid(ni, ni->ni_ies.meshid_ie); #endif #ifdef IEEE80211_SUPPORT_TDMA if (ni->ni_ies.tdma_ie != NULL) ieee80211_parse_tdma(ni, ni->ni_ies.tdma_ie); #endif } vap->iv_dtim_period = se->se_dtimperiod; vap->iv_dtim_count = 0; /* NB: must be after ni_chan is setup */ ieee80211_setup_rates(ni, se->se_rates, se->se_xrates, IEEE80211_F_DOSORT); if (ieee80211_iserp_rateset(&ni->ni_rates)) ni->ni_flags |= IEEE80211_NODE_ERP; /* * Setup HT state for this node if it's available, otherwise * non-STA modes won't pick this state up. * * For IBSS and related modes that don't go through an * association request/response, the only appropriate place * to setup the HT state is here. */ if (ni->ni_ies.htinfo_ie != NULL && ni->ni_ies.htcap_ie != NULL && vap->iv_flags_ht & IEEE80211_FHT_HT) { ieee80211_ht_node_init(ni); ieee80211_ht_updateparams(ni, ni->ni_ies.htcap_ie, ni->ni_ies.htinfo_ie); ieee80211_setup_htrates(ni, ni->ni_ies.htcap_ie, IEEE80211_F_JOIN | IEEE80211_F_DOBRS); ieee80211_setup_basic_htrates(ni, ni->ni_ies.htinfo_ie); } /* XXX else check for ath FF? */ /* XXX QoS? Difficult given that WME config is specific to a master */ ieee80211_node_setuptxparms(ni); ieee80211_ratectl_node_init(ni); return ieee80211_sta_join1(ieee80211_ref_node(ni)); } /* * Leave the specified IBSS/BSS network. The node is assumed to * be passed in with a held reference. */ void ieee80211_sta_leave(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; ic->ic_node_cleanup(ni); ieee80211_notify_node_leave(ni); } /* * Send a deauthenticate frame and drop the station. */ void ieee80211_node_deauth(struct ieee80211_node *ni, int reason) { /* NB: bump the refcnt to be sure temporary nodes are not reclaimed */ ieee80211_ref_node(ni); if (ni->ni_associd != 0) IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DEAUTH, reason); ieee80211_node_leave(ni); ieee80211_free_node(ni); } static struct ieee80211_node * node_alloc(struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct ieee80211_node *ni; ni = (struct ieee80211_node *) IEEE80211_MALLOC(sizeof(struct ieee80211_node), M_80211_NODE, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); return ni; } /* * Initialize an ie blob with the specified data. If previous * data exists re-use the data block. As a side effect we clear * all references to specific ie's; the caller is required to * recalculate them. */ int ieee80211_ies_init(struct ieee80211_ies *ies, const uint8_t *data, int len) { /* NB: assumes data+len are the last fields */ memset(ies, 0, offsetof(struct ieee80211_ies, data)); if (ies->data != NULL && ies->len != len) { /* data size changed */ IEEE80211_FREE(ies->data, M_80211_NODE_IE); ies->data = NULL; } if (ies->data == NULL) { ies->data = (uint8_t *) IEEE80211_MALLOC(len, M_80211_NODE_IE, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (ies->data == NULL) { ies->len = 0; /* NB: pointers have already been zero'd above */ return 0; } } memcpy(ies->data, data, len); ies->len = len; return 1; } /* * Reclaim storage for an ie blob. */ void ieee80211_ies_cleanup(struct ieee80211_ies *ies) { if (ies->data != NULL) IEEE80211_FREE(ies->data, M_80211_NODE_IE); } /* * Expand an ie blob data contents and to fillin individual * ie pointers. The data blob is assumed to be well-formed; * we don't do any validity checking of ie lengths. */ void ieee80211_ies_expand(struct ieee80211_ies *ies) { uint8_t *ie; int ielen; ie = ies->data; ielen = ies->len; while (ielen > 0) { switch (ie[0]) { case IEEE80211_ELEMID_VENDOR: if (iswpaoui(ie)) ies->wpa_ie = ie; else if (iswmeoui(ie)) ies->wme_ie = ie; #ifdef IEEE80211_SUPPORT_SUPERG else if (isatherosoui(ie)) ies->ath_ie = ie; #endif #ifdef IEEE80211_SUPPORT_TDMA else if (istdmaoui(ie)) ies->tdma_ie = ie; #endif break; case IEEE80211_ELEMID_RSN: ies->rsn_ie = ie; break; case IEEE80211_ELEMID_HTCAP: ies->htcap_ie = ie; break; case IEEE80211_ELEMID_HTINFO: ies->htinfo_ie = ie; break; #ifdef IEEE80211_SUPPORT_MESH case IEEE80211_ELEMID_MESHID: ies->meshid_ie = ie; break; #endif } ielen -= 2 + ie[1]; ie += 2 + ie[1]; } } /* * Reclaim any resources in a node and reset any critical * state. Typically nodes are free'd immediately after, * but in some cases the storage may be reused so we need * to insure consistent state (should probably fix that). */ static void node_cleanup(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; int i; /* NB: preserve ni_table */ if (ni->ni_flags & IEEE80211_NODE_PWR_MGT) { if (vap->iv_opmode != IEEE80211_M_STA) vap->iv_ps_sta--; ni->ni_flags &= ~IEEE80211_NODE_PWR_MGT; IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, "power save mode off, %u sta's in ps mode", vap->iv_ps_sta); } /* * Cleanup any HT-related state. */ if (ni->ni_flags & IEEE80211_NODE_HT) ieee80211_ht_node_cleanup(ni); #ifdef IEEE80211_SUPPORT_SUPERG else if (ni->ni_ath_flags & IEEE80211_NODE_ATH) ieee80211_ff_node_cleanup(ni); #endif #ifdef IEEE80211_SUPPORT_MESH /* * Cleanup any mesh-related state. */ if (vap->iv_opmode == IEEE80211_M_MBSS) ieee80211_mesh_node_cleanup(ni); #endif /* * Clear any staging queue entries. */ ieee80211_ageq_drain_node(&ic->ic_stageq, ni); /* * Clear AREF flag that marks the authorization refcnt bump * has happened. This is probably not needed as the node * should always be removed from the table so not found but * do it just in case. * Likewise clear the ASSOCID flag as these flags are intended * to be managed in tandem. */ ni->ni_flags &= ~(IEEE80211_NODE_AREF | IEEE80211_NODE_ASSOCID); /* * Drain power save queue and, if needed, clear TIM. */ if (ieee80211_node_psq_drain(ni) != 0 && vap->iv_set_tim != NULL) vap->iv_set_tim(ni, 0); ni->ni_associd = 0; if (ni->ni_challenge != NULL) { IEEE80211_FREE(ni->ni_challenge, M_80211_NODE); ni->ni_challenge = NULL; } /* * Preserve SSID, WPA, and WME ie's so the bss node is * reusable during a re-auth/re-assoc state transition. * If we remove these data they will not be recreated * because they come from a probe-response or beacon frame * which cannot be expected prior to the association-response. * This should not be an issue when operating in other modes * as stations leaving always go through a full state transition * which will rebuild this state. * * XXX does this leave us open to inheriting old state? */ for (i = 0; i < nitems(ni->ni_rxfrag); i++) if (ni->ni_rxfrag[i] != NULL) { m_freem(ni->ni_rxfrag[i]); ni->ni_rxfrag[i] = NULL; } /* * Must be careful here to remove any key map entry w/o a LOR. */ ieee80211_node_delucastkey(ni); } static void node_free(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; ieee80211_ratectl_node_deinit(ni); ic->ic_node_cleanup(ni); ieee80211_ies_cleanup(&ni->ni_ies); ieee80211_psq_cleanup(&ni->ni_psq); IEEE80211_FREE(ni, M_80211_NODE); } static void node_age(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; IEEE80211_NODE_LOCK_ASSERT(&vap->iv_ic->ic_sta); /* * Age frames on the power save queue. */ if (ieee80211_node_psq_age(ni) != 0 && ni->ni_psq.psq_len == 0 && vap->iv_set_tim != NULL) vap->iv_set_tim(ni, 0); /* * Age out HT resources (e.g. frames on the * A-MPDU reorder queues). */ if (ni->ni_associd != 0 && (ni->ni_flags & IEEE80211_NODE_HT)) ieee80211_ht_node_age(ni); } static int8_t node_getrssi(const struct ieee80211_node *ni) { uint32_t avgrssi = ni->ni_avgrssi; int32_t rssi; if (avgrssi == IEEE80211_RSSI_DUMMY_MARKER) return 0; rssi = IEEE80211_RSSI_GET(avgrssi); return rssi < 0 ? 0 : rssi > 127 ? 127 : rssi; } static void node_getsignal(const struct ieee80211_node *ni, int8_t *rssi, int8_t *noise) { *rssi = node_getrssi(ni); *noise = ni->ni_noise; } static void node_getmimoinfo(const struct ieee80211_node *ni, struct ieee80211_mimo_info *info) { int i; uint32_t avgrssi; int32_t rssi; bzero(info, sizeof(*info)); for (i = 0; i < ni->ni_mimo_chains; i++) { avgrssi = ni->ni_mimo_rssi_ctl[i]; if (avgrssi == IEEE80211_RSSI_DUMMY_MARKER) { info->rssi[i] = 0; } else { rssi = IEEE80211_RSSI_GET(avgrssi); info->rssi[i] = rssi < 0 ? 0 : rssi > 127 ? 127 : rssi; } info->noise[i] = ni->ni_mimo_noise_ctl[i]; } /* XXX ext radios? */ /* XXX EVM? */ } struct ieee80211_node * ieee80211_alloc_node(struct ieee80211_node_table *nt, struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct ieee80211com *ic = nt->nt_ic; struct ieee80211_node *ni; int hash; ni = ic->ic_node_alloc(vap, macaddr); if (ni == NULL) { vap->iv_stats.is_rx_nodealloc++; return NULL; } IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "%s %p<%s> in %s table\n", __func__, ni, ether_sprintf(macaddr), nt->nt_name); IEEE80211_ADDR_COPY(ni->ni_macaddr, macaddr); hash = IEEE80211_NODE_HASH(ic, macaddr); ieee80211_node_initref(ni); /* mark referenced */ ni->ni_chan = IEEE80211_CHAN_ANYC; ni->ni_authmode = IEEE80211_AUTH_OPEN; ni->ni_txpower = ic->ic_txpowlimit; /* max power */ ni->ni_txparms = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; ieee80211_crypto_resetkey(vap, &ni->ni_ucastkey, IEEE80211_KEYIX_NONE); ni->ni_avgrssi = IEEE80211_RSSI_DUMMY_MARKER; ni->ni_inact_reload = nt->nt_inact_init; ni->ni_inact = ni->ni_inact_reload; ni->ni_ath_defkeyix = 0x7fff; ieee80211_psq_init(&ni->ni_psq, "unknown"); #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) ieee80211_mesh_node_init(vap, ni); #endif IEEE80211_NODE_LOCK(nt); TAILQ_INSERT_TAIL(&nt->nt_node, ni, ni_list); LIST_INSERT_HEAD(&nt->nt_hash[hash], ni, ni_hash); ni->ni_table = nt; ni->ni_vap = vap; ni->ni_ic = ic; IEEE80211_NODE_UNLOCK(nt); IEEE80211_NOTE(vap, IEEE80211_MSG_INACT, ni, "%s: inact_reload %u", __func__, ni->ni_inact_reload); ieee80211_ratectl_node_init(ni); return ni; } /* * Craft a temporary node suitable for sending a management frame * to the specified station. We craft only as much state as we * need to do the work since the node will be immediately reclaimed * once the send completes. */ struct ieee80211_node * ieee80211_tmp_node(struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; ni = ic->ic_node_alloc(vap, macaddr); if (ni != NULL) { struct ieee80211_node *bss = vap->iv_bss; IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "%s %p<%s>\n", __func__, ni, ether_sprintf(macaddr)); ni->ni_table = NULL; /* NB: pedantic */ ni->ni_ic = ic; /* NB: needed to set channel */ ni->ni_vap = vap; IEEE80211_ADDR_COPY(ni->ni_macaddr, macaddr); IEEE80211_ADDR_COPY(ni->ni_bssid, bss->ni_bssid); ieee80211_node_initref(ni); /* mark referenced */ /* NB: required by ieee80211_fix_rate */ ieee80211_node_set_chan(ni, bss->ni_chan); ieee80211_crypto_resetkey(vap, &ni->ni_ucastkey, IEEE80211_KEYIX_NONE); ni->ni_txpower = bss->ni_txpower; /* XXX optimize away */ ieee80211_psq_init(&ni->ni_psq, "unknown"); ieee80211_ratectl_node_init(ni); } else { /* XXX msg */ vap->iv_stats.is_rx_nodealloc++; } return ni; } struct ieee80211_node * ieee80211_dup_bss(struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; ni = ieee80211_alloc_node(&ic->ic_sta, vap, macaddr); if (ni != NULL) { struct ieee80211_node *bss = vap->iv_bss; /* * Inherit from iv_bss. */ copy_bss(ni, bss); IEEE80211_ADDR_COPY(ni->ni_bssid, bss->ni_bssid); ieee80211_node_set_chan(ni, bss->ni_chan); } return ni; } /* * Create a bss node for a legacy WDS vap. The far end does * not associate so we just create create a new node and * simulate an association. The caller is responsible for * installing the node as the bss node and handling any further * setup work like authorizing the port. */ struct ieee80211_node * ieee80211_node_create_wds(struct ieee80211vap *vap, const uint8_t bssid[IEEE80211_ADDR_LEN], struct ieee80211_channel *chan) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; /* XXX check if node already in sta table? */ ni = ieee80211_alloc_node(&ic->ic_sta, vap, bssid); if (ni != NULL) { ni->ni_wdsvap = vap; IEEE80211_ADDR_COPY(ni->ni_bssid, bssid); /* * Inherit any manually configured settings. */ copy_bss(ni, vap->iv_bss); ieee80211_node_set_chan(ni, chan); /* NB: propagate ssid so available to WPA supplicant */ ni->ni_esslen = vap->iv_des_ssid[0].len; memcpy(ni->ni_essid, vap->iv_des_ssid[0].ssid, ni->ni_esslen); /* NB: no associd for peer */ /* * There are no management frames to use to * discover neighbor capabilities, so blindly * propagate the local configuration. */ if (vap->iv_flags & IEEE80211_F_WME) ni->ni_flags |= IEEE80211_NODE_QOS; #ifdef IEEE80211_SUPPORT_SUPERG if (vap->iv_flags & IEEE80211_F_FF) ni->ni_flags |= IEEE80211_NODE_FF; #endif if ((ic->ic_htcaps & IEEE80211_HTC_HT) && (vap->iv_flags_ht & IEEE80211_FHT_HT)) { /* * Device is HT-capable and HT is enabled for * the vap; setup HT operation. On return * ni_chan will be adjusted to an HT channel. */ ieee80211_ht_wds_init(ni); } else { struct ieee80211_channel *c = ni->ni_chan; /* * Force a legacy channel to be used. */ c = ieee80211_find_channel(ic, c->ic_freq, c->ic_flags &~ IEEE80211_CHAN_HT); KASSERT(c != NULL, ("no legacy channel, %u/%x", ni->ni_chan->ic_freq, ni->ni_chan->ic_flags)); ni->ni_chan = c; } } return ni; } struct ieee80211_node * #ifdef IEEE80211_DEBUG_REFCNT ieee80211_find_node_locked_debug(struct ieee80211_node_table *nt, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func, int line) #else ieee80211_find_node_locked(struct ieee80211_node_table *nt, const uint8_t macaddr[IEEE80211_ADDR_LEN]) #endif { struct ieee80211_node *ni; int hash; IEEE80211_NODE_LOCK_ASSERT(nt); hash = IEEE80211_NODE_HASH(nt->nt_ic, macaddr); LIST_FOREACH(ni, &nt->nt_hash[hash], ni_hash) { if (IEEE80211_ADDR_EQ(ni->ni_macaddr, macaddr)) { ieee80211_ref_node(ni); /* mark referenced */ #ifdef IEEE80211_DEBUG_REFCNT IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s (%s:%u) %p<%s> refcnt %d\n", __func__, func, line, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)); #endif return ni; } } return NULL; } struct ieee80211_node * #ifdef IEEE80211_DEBUG_REFCNT ieee80211_find_node_debug(struct ieee80211_node_table *nt, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func, int line) #else ieee80211_find_node(struct ieee80211_node_table *nt, const uint8_t macaddr[IEEE80211_ADDR_LEN]) #endif { struct ieee80211_node *ni; IEEE80211_NODE_LOCK(nt); ni = ieee80211_find_node_locked(nt, macaddr); IEEE80211_NODE_UNLOCK(nt); return ni; } struct ieee80211_node * #ifdef IEEE80211_DEBUG_REFCNT ieee80211_find_vap_node_locked_debug(struct ieee80211_node_table *nt, const struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func, int line) #else ieee80211_find_vap_node_locked(struct ieee80211_node_table *nt, const struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN]) #endif { struct ieee80211_node *ni; int hash; IEEE80211_NODE_LOCK_ASSERT(nt); hash = IEEE80211_NODE_HASH(nt->nt_ic, macaddr); LIST_FOREACH(ni, &nt->nt_hash[hash], ni_hash) { if (ni->ni_vap == vap && IEEE80211_ADDR_EQ(ni->ni_macaddr, macaddr)) { ieee80211_ref_node(ni); /* mark referenced */ #ifdef IEEE80211_DEBUG_REFCNT IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s (%s:%u) %p<%s> refcnt %d\n", __func__, func, line, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)); #endif return ni; } } return NULL; } struct ieee80211_node * #ifdef IEEE80211_DEBUG_REFCNT ieee80211_find_vap_node_debug(struct ieee80211_node_table *nt, const struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func, int line) #else ieee80211_find_vap_node(struct ieee80211_node_table *nt, const struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN]) #endif { struct ieee80211_node *ni; IEEE80211_NODE_LOCK(nt); ni = ieee80211_find_vap_node_locked(nt, vap, macaddr); IEEE80211_NODE_UNLOCK(nt); return ni; } /* * Fake up a node; this handles node discovery in adhoc mode. * Note that for the driver's benefit we we treat this like * an association so the driver has an opportunity to setup * it's private state. */ struct ieee80211_node * ieee80211_fakeup_adhoc_node(struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct ieee80211_node *ni; IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE | IEEE80211_MSG_ASSOC, "%s: mac<%s>\n", __func__, ether_sprintf(macaddr)); ni = ieee80211_dup_bss(vap, macaddr); if (ni != NULL) { struct ieee80211com *ic = vap->iv_ic; /* XXX no rate negotiation; just dup */ ni->ni_rates = vap->iv_bss->ni_rates; if (ieee80211_iserp_rateset(&ni->ni_rates)) ni->ni_flags |= IEEE80211_NODE_ERP; if (vap->iv_opmode == IEEE80211_M_AHDEMO) { /* * In adhoc demo mode there are no management * frames to use to discover neighbor capabilities, * so blindly propagate the local configuration * so we can do interesting things (e.g. use * WME to disable ACK's). */ if (vap->iv_flags & IEEE80211_F_WME) ni->ni_flags |= IEEE80211_NODE_QOS; #ifdef IEEE80211_SUPPORT_SUPERG if (vap->iv_flags & IEEE80211_F_FF) ni->ni_flags |= IEEE80211_NODE_FF; #endif } ieee80211_node_setuptxparms(ni); ieee80211_ratectl_node_init(ni); if (ic->ic_newassoc != NULL) ic->ic_newassoc(ni, 1); /* XXX not right for 802.1x/WPA */ ieee80211_node_authorize(ni); } return ni; } void ieee80211_init_neighbor(struct ieee80211_node *ni, const struct ieee80211_frame *wh, const struct ieee80211_scanparams *sp) { int do_ht_setup = 0; ni->ni_esslen = sp->ssid[1]; memcpy(ni->ni_essid, sp->ssid + 2, sp->ssid[1]); IEEE80211_ADDR_COPY(ni->ni_bssid, wh->i_addr3); memcpy(ni->ni_tstamp.data, sp->tstamp, sizeof(ni->ni_tstamp)); ni->ni_intval = sp->bintval; ni->ni_capinfo = sp->capinfo; ni->ni_chan = ni->ni_ic->ic_curchan; ni->ni_fhdwell = sp->fhdwell; ni->ni_fhindex = sp->fhindex; ni->ni_erp = sp->erp; ni->ni_timoff = sp->timoff; #ifdef IEEE80211_SUPPORT_MESH if (ni->ni_vap->iv_opmode == IEEE80211_M_MBSS) ieee80211_mesh_init_neighbor(ni, wh, sp); #endif if (ieee80211_ies_init(&ni->ni_ies, sp->ies, sp->ies_len)) { ieee80211_ies_expand(&ni->ni_ies); if (ni->ni_ies.wme_ie != NULL) ni->ni_flags |= IEEE80211_NODE_QOS; else ni->ni_flags &= ~IEEE80211_NODE_QOS; #ifdef IEEE80211_SUPPORT_SUPERG if (ni->ni_ies.ath_ie != NULL) ieee80211_parse_ath(ni, ni->ni_ies.ath_ie); #endif if (ni->ni_ies.htcap_ie != NULL) ieee80211_parse_htcap(ni, ni->ni_ies.htcap_ie); if (ni->ni_ies.htinfo_ie != NULL) ieee80211_parse_htinfo(ni, ni->ni_ies.htinfo_ie); if ((ni->ni_ies.htcap_ie != NULL) && (ni->ni_ies.htinfo_ie != NULL) && (ni->ni_vap->iv_flags_ht & IEEE80211_FHT_HT)) { do_ht_setup = 1; } } /* NB: must be after ni_chan is setup */ ieee80211_setup_rates(ni, sp->rates, sp->xrates, IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE | IEEE80211_F_DONEGO | IEEE80211_F_DODEL); /* * If the neighbor is HT compatible, flip that on. */ if (do_ht_setup) { IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_ASSOC, "%s: doing HT setup\n", __func__); ieee80211_ht_node_init(ni); ieee80211_ht_updateparams(ni, ni->ni_ies.htcap_ie, ni->ni_ies.htinfo_ie); ieee80211_setup_htrates(ni, ni->ni_ies.htcap_ie, IEEE80211_F_JOIN | IEEE80211_F_DOBRS); ieee80211_setup_basic_htrates(ni, ni->ni_ies.htinfo_ie); ieee80211_node_setuptxparms(ni); ieee80211_ratectl_node_init(ni); } } /* * Do node discovery in adhoc mode on receipt of a beacon * or probe response frame. Note that for the driver's * benefit we we treat this like an association so the * driver has an opportunity to setup it's private state. */ struct ieee80211_node * ieee80211_add_neighbor(struct ieee80211vap *vap, const struct ieee80211_frame *wh, const struct ieee80211_scanparams *sp) { struct ieee80211_node *ni; IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, "%s: mac<%s>\n", __func__, ether_sprintf(wh->i_addr2)); ni = ieee80211_dup_bss(vap, wh->i_addr2);/* XXX alloc_node? */ if (ni != NULL) { struct ieee80211com *ic = vap->iv_ic; ieee80211_init_neighbor(ni, wh, sp); if (ieee80211_iserp_rateset(&ni->ni_rates)) ni->ni_flags |= IEEE80211_NODE_ERP; ieee80211_node_setuptxparms(ni); ieee80211_ratectl_node_init(ni); if (ic->ic_newassoc != NULL) ic->ic_newassoc(ni, 1); /* XXX not right for 802.1x/WPA */ ieee80211_node_authorize(ni); } return ni; } #define IS_PROBEREQ(wh) \ ((wh->i_fc[0] & (IEEE80211_FC0_TYPE_MASK|IEEE80211_FC0_SUBTYPE_MASK)) \ == (IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_PROBE_REQ)) #define IS_BCAST_PROBEREQ(wh) \ (IS_PROBEREQ(wh) && IEEE80211_IS_MULTICAST( \ ((const struct ieee80211_frame *)(wh))->i_addr3)) static __inline struct ieee80211_node * _find_rxnode(struct ieee80211_node_table *nt, const struct ieee80211_frame_min *wh) { if (IS_BCAST_PROBEREQ(wh)) return NULL; /* spam bcast probe req to all vap's */ return ieee80211_find_node_locked(nt, wh->i_addr2); } /* * Locate the node for sender, track state, and then pass the * (referenced) node up to the 802.11 layer for its use. Note * we can return NULL if the sender is not in the table. */ struct ieee80211_node * #ifdef IEEE80211_DEBUG_REFCNT ieee80211_find_rxnode_debug(struct ieee80211com *ic, const struct ieee80211_frame_min *wh, const char *func, int line) #else ieee80211_find_rxnode(struct ieee80211com *ic, const struct ieee80211_frame_min *wh) #endif { struct ieee80211_node_table *nt; struct ieee80211_node *ni; nt = &ic->ic_sta; IEEE80211_NODE_LOCK(nt); ni = _find_rxnode(nt, wh); IEEE80211_NODE_UNLOCK(nt); return ni; } /* * Like ieee80211_find_rxnode but use the supplied h/w * key index as a hint to locate the node in the key * mapping table. If an entry is present at the key * index we return it; otherwise do a normal lookup and * update the mapping table if the station has a unicast * key assigned to it. */ struct ieee80211_node * #ifdef IEEE80211_DEBUG_REFCNT ieee80211_find_rxnode_withkey_debug(struct ieee80211com *ic, const struct ieee80211_frame_min *wh, ieee80211_keyix keyix, const char *func, int line) #else ieee80211_find_rxnode_withkey(struct ieee80211com *ic, const struct ieee80211_frame_min *wh, ieee80211_keyix keyix) #endif { struct ieee80211_node_table *nt; struct ieee80211_node *ni; nt = &ic->ic_sta; IEEE80211_NODE_LOCK(nt); if (nt->nt_keyixmap != NULL && keyix < nt->nt_keyixmax) ni = nt->nt_keyixmap[keyix]; else ni = NULL; if (ni == NULL) { ni = _find_rxnode(nt, wh); if (ni != NULL && nt->nt_keyixmap != NULL) { /* * If the station has a unicast key cache slot * assigned update the key->node mapping table. */ keyix = ni->ni_ucastkey.wk_rxkeyix; /* XXX can keyixmap[keyix] != NULL? */ if (keyix < nt->nt_keyixmax && nt->nt_keyixmap[keyix] == NULL) { IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s: add key map entry %p<%s> refcnt %d\n", __func__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)+1); nt->nt_keyixmap[keyix] = ieee80211_ref_node(ni); } } } else { if (IS_BCAST_PROBEREQ(wh)) ni = NULL; /* spam bcast probe req to all vap's */ else ieee80211_ref_node(ni); } IEEE80211_NODE_UNLOCK(nt); return ni; } #undef IS_BCAST_PROBEREQ #undef IS_PROBEREQ /* * Return a reference to the appropriate node for sending * a data frame. This handles node discovery in adhoc networks. */ struct ieee80211_node * #ifdef IEEE80211_DEBUG_REFCNT ieee80211_find_txnode_debug(struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func, int line) #else ieee80211_find_txnode(struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN]) #endif { struct ieee80211_node_table *nt = &vap->iv_ic->ic_sta; struct ieee80211_node *ni; /* * The destination address should be in the node table * unless this is a multicast/broadcast frame. We can * also optimize station mode operation, all frames go * to the bss node. */ /* XXX can't hold lock across dup_bss 'cuz of recursive locking */ IEEE80211_NODE_LOCK(nt); if (vap->iv_opmode == IEEE80211_M_STA || vap->iv_opmode == IEEE80211_M_WDS || IEEE80211_IS_MULTICAST(macaddr)) ni = ieee80211_ref_node(vap->iv_bss); else ni = ieee80211_find_node_locked(nt, macaddr); IEEE80211_NODE_UNLOCK(nt); if (ni == NULL) { if (vap->iv_opmode == IEEE80211_M_IBSS || vap->iv_opmode == IEEE80211_M_AHDEMO) { /* * In adhoc mode cons up a node for the destination. * Note that we need an additional reference for the * caller to be consistent with * ieee80211_find_node_locked. */ ni = ieee80211_fakeup_adhoc_node(vap, macaddr); if (ni != NULL) (void) ieee80211_ref_node(ni); } else { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_OUTPUT, macaddr, "no node, discard frame (%s)", __func__); vap->iv_stats.is_tx_nonode++; } } return ni; } static void _ieee80211_free_node(struct ieee80211_node *ni) { struct ieee80211_node_table *nt = ni->ni_table; /* * NB: careful about referencing the vap as it may be * gone if the last reference was held by a driver. * We know the com will always be present so it's safe * to use ni_ic below to reclaim resources. */ #if 0 IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "%s %p<%s> in %s table\n", __func__, ni, ether_sprintf(ni->ni_macaddr), nt != NULL ? nt->nt_name : ""); #endif if (ni->ni_associd != 0) { struct ieee80211vap *vap = ni->ni_vap; if (vap->iv_aid_bitmap != NULL) IEEE80211_AID_CLR(vap, ni->ni_associd); } if (nt != NULL) { TAILQ_REMOVE(&nt->nt_node, ni, ni_list); LIST_REMOVE(ni, ni_hash); } ni->ni_ic->ic_node_free(ni); } /* * Clear any entry in the unicast key mapping table. */ static int node_clear_keyixmap(struct ieee80211_node_table *nt, struct ieee80211_node *ni) { ieee80211_keyix keyix; keyix = ni->ni_ucastkey.wk_rxkeyix; if (nt->nt_keyixmap != NULL && keyix < nt->nt_keyixmax && nt->nt_keyixmap[keyix] == ni) { IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s: %p<%s> clear key map entry %u\n", __func__, ni, ether_sprintf(ni->ni_macaddr), keyix); nt->nt_keyixmap[keyix] = NULL; ieee80211_node_decref(ni); return 1; } return 0; } void #ifdef IEEE80211_DEBUG_REFCNT ieee80211_free_node_debug(struct ieee80211_node *ni, const char *func, int line) #else ieee80211_free_node(struct ieee80211_node *ni) #endif { struct ieee80211_node_table *nt = ni->ni_table; #ifdef IEEE80211_DEBUG_REFCNT IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s (%s:%u) %p<%s> refcnt %d\n", __func__, func, line, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)-1); #endif if (nt != NULL) { IEEE80211_NODE_LOCK(nt); if (ieee80211_node_dectestref(ni)) { /* * Last reference, reclaim state. */ _ieee80211_free_node(ni); } else if (ieee80211_node_refcnt(ni) == 1) if (node_clear_keyixmap(nt, ni)) _ieee80211_free_node(ni); IEEE80211_NODE_UNLOCK(nt); } else { if (ieee80211_node_dectestref(ni)) _ieee80211_free_node(ni); } } /* * Reclaim a unicast key and clear any key cache state. */ int ieee80211_node_delucastkey(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211_node_table *nt = &ic->ic_sta; struct ieee80211_node *nikey; ieee80211_keyix keyix; int isowned, status; /* * NB: We must beware of LOR here; deleting the key * can cause the crypto layer to block traffic updates * which can generate a LOR against the node table lock; * grab it here and stash the key index for our use below. * * Must also beware of recursion on the node table lock. * When called from node_cleanup we may already have * the node table lock held. Unfortunately there's no * way to separate out this path so we must do this * conditionally. */ isowned = IEEE80211_NODE_IS_LOCKED(nt); if (!isowned) IEEE80211_NODE_LOCK(nt); nikey = NULL; status = 1; /* NB: success */ if (ni->ni_ucastkey.wk_keyix != IEEE80211_KEYIX_NONE) { keyix = ni->ni_ucastkey.wk_rxkeyix; status = ieee80211_crypto_delkey(ni->ni_vap, &ni->ni_ucastkey); if (nt->nt_keyixmap != NULL && keyix < nt->nt_keyixmax) { nikey = nt->nt_keyixmap[keyix]; nt->nt_keyixmap[keyix] = NULL; } } if (!isowned) IEEE80211_NODE_UNLOCK(nt); if (nikey != NULL) { KASSERT(nikey == ni, ("key map out of sync, ni %p nikey %p", ni, nikey)); IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s: delete key map entry %p<%s> refcnt %d\n", __func__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)-1); ieee80211_free_node(ni); } return status; } /* * Reclaim a node. If this is the last reference count then * do the normal free work. Otherwise remove it from the node * table and mark it gone by clearing the back-reference. */ static void node_reclaim(struct ieee80211_node_table *nt, struct ieee80211_node *ni) { IEEE80211_NODE_LOCK_ASSERT(nt); IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s: remove %p<%s> from %s table, refcnt %d\n", __func__, ni, ether_sprintf(ni->ni_macaddr), nt->nt_name, ieee80211_node_refcnt(ni)-1); /* * Clear any entry in the unicast key mapping table. * We need to do it here so rx lookups don't find it * in the mapping table even if it's not in the hash * table. We cannot depend on the mapping table entry * being cleared because the node may not be free'd. */ (void)node_clear_keyixmap(nt, ni); if (!ieee80211_node_dectestref(ni)) { /* * Other references are present, just remove the * node from the table so it cannot be found. When * the references are dropped storage will be * reclaimed. */ TAILQ_REMOVE(&nt->nt_node, ni, ni_list); LIST_REMOVE(ni, ni_hash); ni->ni_table = NULL; /* clear reference */ } else _ieee80211_free_node(ni); } /* * Node table support. */ static void ieee80211_node_table_init(struct ieee80211com *ic, struct ieee80211_node_table *nt, const char *name, int inact, int keyixmax) { nt->nt_ic = ic; IEEE80211_NODE_LOCK_INIT(nt, ic->ic_name); IEEE80211_NODE_ITERATE_LOCK_INIT(nt, ic->ic_name); TAILQ_INIT(&nt->nt_node); nt->nt_name = name; nt->nt_scangen = 1; nt->nt_inact_init = inact; nt->nt_keyixmax = keyixmax; if (nt->nt_keyixmax > 0) { nt->nt_keyixmap = (struct ieee80211_node **) IEEE80211_MALLOC( keyixmax * sizeof(struct ieee80211_node *), M_80211_NODE, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (nt->nt_keyixmap == NULL) ic_printf(ic, "Cannot allocate key index map with %u entries\n", keyixmax); } else nt->nt_keyixmap = NULL; } static void ieee80211_node_table_reset(struct ieee80211_node_table *nt, struct ieee80211vap *match) { struct ieee80211_node *ni, *next; IEEE80211_NODE_LOCK(nt); TAILQ_FOREACH_SAFE(ni, &nt->nt_node, ni_list, next) { if (match != NULL && ni->ni_vap != match) continue; /* XXX can this happen? if so need's work */ if (ni->ni_associd != 0) { struct ieee80211vap *vap = ni->ni_vap; if (vap->iv_auth->ia_node_leave != NULL) vap->iv_auth->ia_node_leave(ni); if (vap->iv_aid_bitmap != NULL) IEEE80211_AID_CLR(vap, ni->ni_associd); } ni->ni_wdsvap = NULL; /* clear reference */ node_reclaim(nt, ni); } if (match != NULL && match->iv_opmode == IEEE80211_M_WDS) { /* * Make a separate pass to clear references to this vap * held by DWDS entries. They will not be matched above * because ni_vap will point to the ap vap but we still * need to clear ni_wdsvap when the WDS vap is destroyed * and/or reset. */ TAILQ_FOREACH_SAFE(ni, &nt->nt_node, ni_list, next) if (ni->ni_wdsvap == match) ni->ni_wdsvap = NULL; } IEEE80211_NODE_UNLOCK(nt); } static void ieee80211_node_table_cleanup(struct ieee80211_node_table *nt) { ieee80211_node_table_reset(nt, NULL); if (nt->nt_keyixmap != NULL) { #ifdef DIAGNOSTIC /* XXX verify all entries are NULL */ int i; for (i = 0; i < nt->nt_keyixmax; i++) if (nt->nt_keyixmap[i] != NULL) printf("%s: %s[%u] still active\n", __func__, nt->nt_name, i); #endif IEEE80211_FREE(nt->nt_keyixmap, M_80211_NODE); nt->nt_keyixmap = NULL; } IEEE80211_NODE_ITERATE_LOCK_DESTROY(nt); IEEE80211_NODE_LOCK_DESTROY(nt); } /* * Timeout inactive stations and do related housekeeping. * Note that we cannot hold the node lock while sending a * frame as this would lead to a LOR. Instead we use a * generation number to mark nodes that we've scanned and * drop the lock and restart a scan if we have to time out * a node. Since we are single-threaded by virtue of * controlling the inactivity timer we can be sure this will * process each node only once. */ static void ieee80211_timeout_stations(struct ieee80211com *ic) { struct ieee80211_node_table *nt = &ic->ic_sta; struct ieee80211vap *vap; struct ieee80211_node *ni; int gen = 0; IEEE80211_NODE_ITERATE_LOCK(nt); gen = ++nt->nt_scangen; restart: IEEE80211_NODE_LOCK(nt); TAILQ_FOREACH(ni, &nt->nt_node, ni_list) { if (ni->ni_scangen == gen) /* previously handled */ continue; ni->ni_scangen = gen; /* * Ignore entries for which have yet to receive an * authentication frame. These are transient and * will be reclaimed when the last reference to them * goes away (when frame xmits complete). */ vap = ni->ni_vap; /* * Only process stations when in RUN state. This * insures, for example, that we don't timeout an * inactive station during CAC. Note that CSA state * is actually handled in ieee80211_node_timeout as * it applies to more than timeout processing. */ if (vap->iv_state != IEEE80211_S_RUN) continue; /* XXX can vap be NULL? */ if ((vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_STA) && (ni->ni_flags & IEEE80211_NODE_AREF) == 0) continue; /* * Free fragment if not needed anymore * (last fragment older than 1s). * XXX doesn't belong here, move to node_age */ if (ni->ni_rxfrag[0] != NULL && ticks > ni->ni_rxfragstamp + hz) { m_freem(ni->ni_rxfrag[0]); ni->ni_rxfrag[0] = NULL; } if (ni->ni_inact > 0) { ni->ni_inact--; IEEE80211_NOTE(vap, IEEE80211_MSG_INACT, ni, "%s: inact %u inact_reload %u nrates %u", __func__, ni->ni_inact, ni->ni_inact_reload, ni->ni_rates.rs_nrates); } /* * Special case ourself; we may be idle for extended periods * of time and regardless reclaiming our state is wrong. * XXX run ic_node_age */ if (ni == vap->iv_bss) continue; if (ni->ni_associd != 0 || (vap->iv_opmode == IEEE80211_M_IBSS || vap->iv_opmode == IEEE80211_M_AHDEMO)) { /* * Age/drain resources held by the station. */ ic->ic_node_age(ni); /* * Probe the station before time it out. We * send a null data frame which may not be * universally supported by drivers (need it * for ps-poll support so it should be...). * * XXX don't probe the station unless we've * received a frame from them (and have * some idea of the rates they are capable * of); this will get fixed more properly * soon with better handling of the rate set. */ if ((vap->iv_flags_ext & IEEE80211_FEXT_INACT) && (0 < ni->ni_inact && ni->ni_inact <= vap->iv_inact_probe) && ni->ni_rates.rs_nrates != 0) { IEEE80211_NOTE(vap, IEEE80211_MSG_INACT | IEEE80211_MSG_NODE, ni, "%s", "probe station due to inactivity"); /* * Grab a reference before unlocking the table * so the node cannot be reclaimed before we * send the frame. ieee80211_send_nulldata * understands we've done this and reclaims the * ref for us as needed. */ ieee80211_ref_node(ni); IEEE80211_NODE_UNLOCK(nt); ieee80211_send_nulldata(ni); /* XXX stat? */ goto restart; } } if ((vap->iv_flags_ext & IEEE80211_FEXT_INACT) && ni->ni_inact <= 0) { IEEE80211_NOTE(vap, IEEE80211_MSG_INACT | IEEE80211_MSG_NODE, ni, "station timed out due to inactivity " "(refcnt %u)", ieee80211_node_refcnt(ni)); /* * Send a deauthenticate frame and drop the station. * This is somewhat complicated due to reference counts * and locking. At this point a station will typically * have a reference count of 1. ieee80211_node_leave * will do a "free" of the node which will drop the * reference count. But in the meantime a reference * wil be held by the deauth frame. The actual reclaim * of the node will happen either after the tx is * completed or by ieee80211_node_leave. * * Separately we must drop the node lock before sending * in case the driver takes a lock, as this can result * in a LOR between the node lock and the driver lock. */ ieee80211_ref_node(ni); IEEE80211_NODE_UNLOCK(nt); if (ni->ni_associd != 0) { IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DEAUTH, IEEE80211_REASON_AUTH_EXPIRE); } ieee80211_node_leave(ni); ieee80211_free_node(ni); vap->iv_stats.is_node_timeout++; goto restart; } } IEEE80211_NODE_UNLOCK(nt); IEEE80211_NODE_ITERATE_UNLOCK(nt); } /* * Aggressively reclaim resources. This should be used * only in a critical situation to reclaim mbuf resources. */ void ieee80211_drain(struct ieee80211com *ic) { struct ieee80211_node_table *nt = &ic->ic_sta; struct ieee80211vap *vap; struct ieee80211_node *ni; IEEE80211_NODE_LOCK(nt); TAILQ_FOREACH(ni, &nt->nt_node, ni_list) { /* * Ignore entries for which have yet to receive an * authentication frame. These are transient and * will be reclaimed when the last reference to them * goes away (when frame xmits complete). */ vap = ni->ni_vap; /* * Only process stations when in RUN state. This * insures, for example, that we don't timeout an * inactive station during CAC. Note that CSA state * is actually handled in ieee80211_node_timeout as * it applies to more than timeout processing. */ if (vap->iv_state != IEEE80211_S_RUN) continue; /* XXX can vap be NULL? */ if ((vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_STA) && (ni->ni_flags & IEEE80211_NODE_AREF) == 0) continue; /* * Free fragments. * XXX doesn't belong here, move to node_drain */ if (ni->ni_rxfrag[0] != NULL) { m_freem(ni->ni_rxfrag[0]); ni->ni_rxfrag[0] = NULL; } /* * Drain resources held by the station. */ ic->ic_node_drain(ni); } IEEE80211_NODE_UNLOCK(nt); } /* * Per-ieee80211com inactivity timer callback. */ void ieee80211_node_timeout(void *arg) { struct ieee80211com *ic = arg; /* * Defer timeout processing if a channel switch is pending. * We typically need to be mute so not doing things that * might generate frames is good to handle in one place. * Supressing the station timeout processing may extend the * lifetime of inactive stations (by not decrementing their * idle counters) but this should be ok unless the CSA is * active for an unusually long time. */ if ((ic->ic_flags & IEEE80211_F_CSAPENDING) == 0) { ieee80211_scan_timeout(ic); ieee80211_timeout_stations(ic); ieee80211_ageq_age(&ic->ic_stageq, IEEE80211_INACT_WAIT); IEEE80211_LOCK(ic); ieee80211_erp_timeout(ic); ieee80211_ht_timeout(ic); IEEE80211_UNLOCK(ic); } callout_reset(&ic->ic_inact, IEEE80211_INACT_WAIT*hz, ieee80211_node_timeout, ic); } /* * Iterate over the node table and return an array of ref'ed nodes. * * This is separated out from calling the actual node function so that * no LORs will occur. * * If there are too many nodes (ie, the number of nodes doesn't fit * within 'max_aid' entries) then the node references will be freed * and an error will be returned. * * The responsibility of allocating and freeing "ni_arr" is up to * the caller. */ int ieee80211_iterate_nt(struct ieee80211_node_table *nt, struct ieee80211_node **ni_arr, uint16_t max_aid) { u_int gen; int i, j, ret; struct ieee80211_node *ni; IEEE80211_NODE_ITERATE_LOCK(nt); IEEE80211_NODE_LOCK(nt); gen = ++nt->nt_scangen; i = ret = 0; /* * We simply assume here that since the node * scan generation doesn't change (as * we are holding both the node table and * node table iteration locks), we can simply * assign it to the node here. */ TAILQ_FOREACH(ni, &nt->nt_node, ni_list) { if (i >= max_aid) { ret = E2BIG; ic_printf(nt->nt_ic, "Node array overflow: max=%u", max_aid); break; } ni_arr[i] = ieee80211_ref_node(ni); ni_arr[i]->ni_scangen = gen; i++; } /* * It's safe to unlock here. * * If we're successful, the list is returned. * If we're unsuccessful, the list is ignored * and we remove our references. * * This avoids any potential LOR with * ieee80211_free_node(). */ IEEE80211_NODE_UNLOCK(nt); IEEE80211_NODE_ITERATE_UNLOCK(nt); /* * If ret is non-zero, we hit some kind of error. * Rather than walking some nodes, we'll walk none * of them. */ if (ret) { for (j = 0; j < i; j++) { /* ieee80211_free_node() locks by itself */ ieee80211_free_node(ni_arr[j]); } } return (ret); } /* * Just a wrapper, so we don't have to change every ieee80211_iterate_nodes() * reference in the source. * * Note that this fetches 'max_aid' from the first VAP, rather than finding * the largest max_aid from all VAPs. */ void ieee80211_iterate_nodes(struct ieee80211_node_table *nt, ieee80211_iter_func *f, void *arg) { struct ieee80211_node **ni_arr; size_t size; int i; uint16_t max_aid; struct ieee80211vap *vap; /* Overdoing it default */ max_aid = IEEE80211_AID_MAX; /* Handle the case of there being no vaps just yet */ vap = TAILQ_FIRST(&nt->nt_ic->ic_vaps); if (vap != NULL) max_aid = vap->iv_max_aid; size = max_aid * sizeof(struct ieee80211_node *); ni_arr = (struct ieee80211_node **) IEEE80211_MALLOC(size, M_80211_NODE, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (ni_arr == NULL) return; /* * If this fails, the node table won't have any * valid entries - ieee80211_iterate_nt() frees * the references to them. So don't try walking * the table; just skip to the end and free the * temporary memory. */ if (ieee80211_iterate_nt(nt, ni_arr, max_aid) != 0) goto done; for (i = 0; i < max_aid; i++) { if (ni_arr[i] == NULL) /* end of the list */ break; (*f)(arg, ni_arr[i]); /* ieee80211_free_node() locks by itself */ ieee80211_free_node(ni_arr[i]); } done: IEEE80211_FREE(ni_arr, M_80211_NODE); } void ieee80211_dump_node(struct ieee80211_node_table *nt, struct ieee80211_node *ni) { printf("0x%p: mac %s refcnt %d\n", ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)); printf("\tscangen %u authmode %u flags 0x%x\n", ni->ni_scangen, ni->ni_authmode, ni->ni_flags); printf("\tassocid 0x%x txpower %u vlan %u\n", ni->ni_associd, ni->ni_txpower, ni->ni_vlan); printf("\ttxseq %u rxseq %u fragno %u rxfragstamp %u\n", ni->ni_txseqs[IEEE80211_NONQOS_TID], ni->ni_rxseqs[IEEE80211_NONQOS_TID] >> IEEE80211_SEQ_SEQ_SHIFT, ni->ni_rxseqs[IEEE80211_NONQOS_TID] & IEEE80211_SEQ_FRAG_MASK, ni->ni_rxfragstamp); printf("\trssi %d noise %d intval %u capinfo 0x%x\n", node_getrssi(ni), ni->ni_noise, ni->ni_intval, ni->ni_capinfo); printf("\tbssid %s essid \"%.*s\" channel %u:0x%x\n", ether_sprintf(ni->ni_bssid), ni->ni_esslen, ni->ni_essid, ni->ni_chan->ic_freq, ni->ni_chan->ic_flags); printf("\tinact %u inact_reload %u txrate %u\n", ni->ni_inact, ni->ni_inact_reload, ni->ni_txrate); printf("\thtcap %x htparam %x htctlchan %u ht2ndchan %u\n", ni->ni_htcap, ni->ni_htparam, ni->ni_htctlchan, ni->ni_ht2ndchan); printf("\thtopmode %x htstbc %x chw %u\n", ni->ni_htopmode, ni->ni_htstbc, ni->ni_chw); } void ieee80211_dump_nodes(struct ieee80211_node_table *nt) { ieee80211_iterate_nodes(nt, (ieee80211_iter_func *) ieee80211_dump_node, nt); } static void ieee80211_notify_erp_locked(struct ieee80211com *ic) { struct ieee80211vap *vap; IEEE80211_LOCK_ASSERT(ic); TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) if (vap->iv_opmode == IEEE80211_M_HOSTAP) ieee80211_beacon_notify(vap, IEEE80211_BEACON_ERP); } void ieee80211_notify_erp(struct ieee80211com *ic) { IEEE80211_LOCK(ic); ieee80211_notify_erp_locked(ic); IEEE80211_UNLOCK(ic); } /* * Handle a station joining an 11g network. */ static void ieee80211_node_join_11g(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; IEEE80211_LOCK_ASSERT(ic); /* * Station isn't capable of short slot time. Bump * the count of long slot time stations and disable * use of short slot time. Note that the actual switch * over to long slot time use may not occur until the * next beacon transmission (per sec. 7.3.1.4 of 11g). */ if ((ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME) == 0) { ic->ic_longslotsta++; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC, ni, "station needs long slot time, count %d", ic->ic_longslotsta); /* XXX vap's w/ conflicting needs won't work */ if (!IEEE80211_IS_CHAN_108G(ic->ic_bsschan)) { /* * Don't force slot time when switched to turbo * mode as non-ERP stations won't be present; this * need only be done when on the normal G channel. */ ieee80211_set_shortslottime(ic, 0); } } /* * If the new station is not an ERP station * then bump the counter and enable protection * if configured. */ if (!ieee80211_iserp_rateset(&ni->ni_rates)) { ic->ic_nonerpsta++; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC, ni, "station is !ERP, %d non-ERP stations associated", ic->ic_nonerpsta); /* * If station does not support short preamble * then we must enable use of Barker preamble. */ if ((ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE) == 0) { IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC, ni, "%s", "station needs long preamble"); ic->ic_flags |= IEEE80211_F_USEBARKER; ic->ic_flags &= ~IEEE80211_F_SHPREAMBLE; } /* * If protection is configured and this is the first * indication we should use protection, enable it. */ if (ic->ic_protmode != IEEE80211_PROT_NONE && ic->ic_nonerpsta == 1 && (ic->ic_flags_ext & IEEE80211_FEXT_NONERP_PR) == 0) { IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_ASSOC, "%s: enable use of protection\n", __func__); ic->ic_flags |= IEEE80211_F_USEPROT; ieee80211_notify_erp_locked(ic); } } else ni->ni_flags |= IEEE80211_NODE_ERP; } void ieee80211_node_join(struct ieee80211_node *ni, int resp) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; int newassoc; if (ni->ni_associd == 0) { uint16_t aid; KASSERT(vap->iv_aid_bitmap != NULL, ("no aid bitmap")); /* * It would be good to search the bitmap * more efficiently, but this will do for now. */ for (aid = 1; aid < vap->iv_max_aid; aid++) { if (!IEEE80211_AID_ISSET(vap, aid)) break; } if (aid >= vap->iv_max_aid) { IEEE80211_SEND_MGMT(ni, resp, IEEE80211_STATUS_TOOMANY); ieee80211_node_leave(ni); return; } ni->ni_associd = aid | 0xc000; ni->ni_jointime = time_uptime; IEEE80211_LOCK(ic); IEEE80211_AID_SET(vap, ni->ni_associd); vap->iv_sta_assoc++; ic->ic_sta_assoc++; if (IEEE80211_IS_CHAN_HT(ic->ic_bsschan)) ieee80211_ht_node_join(ni); if (IEEE80211_IS_CHAN_ANYG(ic->ic_bsschan) && IEEE80211_IS_CHAN_FULL(ic->ic_bsschan)) ieee80211_node_join_11g(ni); IEEE80211_UNLOCK(ic); newassoc = 1; } else newassoc = 0; IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_DEBUG, ni, "station associated at aid %d: %s preamble, %s slot time%s%s%s%s%s%s%s%s", IEEE80211_NODE_AID(ni), ic->ic_flags & IEEE80211_F_SHPREAMBLE ? "short" : "long", ic->ic_flags & IEEE80211_F_SHSLOT ? "short" : "long", ic->ic_flags & IEEE80211_F_USEPROT ? ", protection" : "", ni->ni_flags & IEEE80211_NODE_QOS ? ", QoS" : "", ni->ni_flags & IEEE80211_NODE_HT ? (ni->ni_chw == 40 ? ", HT40" : ", HT20") : "", ni->ni_flags & IEEE80211_NODE_AMPDU ? " (+AMPDU)" : "", ni->ni_flags & IEEE80211_NODE_MIMO_RTS ? " (+SMPS-DYN)" : ni->ni_flags & IEEE80211_NODE_MIMO_PS ? " (+SMPS)" : "", ni->ni_flags & IEEE80211_NODE_RIFS ? " (+RIFS)" : "", IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_FF) ? ", fast-frames" : "", IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_TURBOP) ? ", turbo" : "" ); ieee80211_node_setuptxparms(ni); ieee80211_ratectl_node_init(ni); /* give driver a chance to setup state like ni_txrate */ if (ic->ic_newassoc != NULL) ic->ic_newassoc(ni, newassoc); IEEE80211_SEND_MGMT(ni, resp, IEEE80211_STATUS_SUCCESS); /* tell the authenticator about new station */ if (vap->iv_auth->ia_node_join != NULL) vap->iv_auth->ia_node_join(ni); ieee80211_notify_node_join(ni, resp == IEEE80211_FC0_SUBTYPE_ASSOC_RESP); } static void disable_protection(struct ieee80211com *ic) { KASSERT(ic->ic_nonerpsta == 0 && (ic->ic_flags_ext & IEEE80211_FEXT_NONERP_PR) == 0, ("%d non ERP stations, flags 0x%x", ic->ic_nonerpsta, ic->ic_flags_ext)); ic->ic_flags &= ~IEEE80211_F_USEPROT; /* XXX verify mode? */ if (ic->ic_caps & IEEE80211_C_SHPREAMBLE) { ic->ic_flags |= IEEE80211_F_SHPREAMBLE; ic->ic_flags &= ~IEEE80211_F_USEBARKER; } ieee80211_notify_erp_locked(ic); } /* * Handle a station leaving an 11g network. */ static void ieee80211_node_leave_11g(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; IEEE80211_LOCK_ASSERT(ic); KASSERT(IEEE80211_IS_CHAN_ANYG(ic->ic_bsschan), ("not in 11g, bss %u:0x%x", ic->ic_bsschan->ic_freq, ic->ic_bsschan->ic_flags)); /* * If a long slot station do the slot time bookkeeping. */ if ((ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME) == 0) { KASSERT(ic->ic_longslotsta > 0, ("bogus long slot station count %d", ic->ic_longslotsta)); ic->ic_longslotsta--; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC, ni, "long slot time station leaves, count now %d", ic->ic_longslotsta); if (ic->ic_longslotsta == 0) { /* * Re-enable use of short slot time if supported * and not operating in IBSS mode (per spec). */ if ((ic->ic_caps & IEEE80211_C_SHSLOT) && ic->ic_opmode != IEEE80211_M_IBSS) { IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_ASSOC, "%s: re-enable use of short slot time\n", __func__); ieee80211_set_shortslottime(ic, 1); } } } /* * If a non-ERP station do the protection-related bookkeeping. */ if ((ni->ni_flags & IEEE80211_NODE_ERP) == 0) { KASSERT(ic->ic_nonerpsta > 0, ("bogus non-ERP station count %d", ic->ic_nonerpsta)); ic->ic_nonerpsta--; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC, ni, "non-ERP station leaves, count now %d%s", ic->ic_nonerpsta, (ic->ic_flags_ext & IEEE80211_FEXT_NONERP_PR) ? " (non-ERP sta present)" : ""); if (ic->ic_nonerpsta == 0 && (ic->ic_flags_ext & IEEE80211_FEXT_NONERP_PR) == 0) { IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_ASSOC, "%s: disable use of protection\n", __func__); disable_protection(ic); } } } /* * Time out presence of an overlapping bss with non-ERP * stations. When operating in hostap mode we listen for * beacons from other stations and if we identify a non-ERP * station is present we enable protection. To identify * when all non-ERP stations are gone we time out this * condition. */ static void ieee80211_erp_timeout(struct ieee80211com *ic) { IEEE80211_LOCK_ASSERT(ic); if ((ic->ic_flags_ext & IEEE80211_FEXT_NONERP_PR) && - time_after(ticks, ic->ic_lastnonerp + IEEE80211_NONERP_PRESENT_AGE)) { + ieee80211_time_after(ticks, ic->ic_lastnonerp + IEEE80211_NONERP_PRESENT_AGE)) { #if 0 IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC, ni, "%s", "age out non-ERP sta present on channel"); #endif ic->ic_flags_ext &= ~IEEE80211_FEXT_NONERP_PR; if (ic->ic_nonerpsta == 0) disable_protection(ic); } } /* * Handle bookkeeping for station deauthentication/disassociation * when operating as an ap. */ void ieee80211_node_leave(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_node_table *nt = ni->ni_table; IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_DEBUG, ni, "station with aid %d leaves", IEEE80211_NODE_AID(ni)); KASSERT(vap->iv_opmode != IEEE80211_M_STA, ("unexpected operating mode %u", vap->iv_opmode)); /* * If node wasn't previously associated all * we need to do is reclaim the reference. */ /* XXX ibss mode bypasses 11g and notification */ if (ni->ni_associd == 0) goto done; /* * Tell the authenticator the station is leaving. * Note that we must do this before yanking the * association id as the authenticator uses the * associd to locate it's state block. */ if (vap->iv_auth->ia_node_leave != NULL) vap->iv_auth->ia_node_leave(ni); IEEE80211_LOCK(ic); IEEE80211_AID_CLR(vap, ni->ni_associd); vap->iv_sta_assoc--; ic->ic_sta_assoc--; if (IEEE80211_IS_CHAN_HT(ic->ic_bsschan)) ieee80211_ht_node_leave(ni); if (IEEE80211_IS_CHAN_ANYG(ic->ic_bsschan) && IEEE80211_IS_CHAN_FULL(ic->ic_bsschan)) ieee80211_node_leave_11g(ni); IEEE80211_UNLOCK(ic); /* * Cleanup station state. In particular clear various * state that might otherwise be reused if the node * is reused before the reference count goes to zero * (and memory is reclaimed). */ ieee80211_sta_leave(ni); done: /* * Remove the node from any table it's recorded in and * drop the caller's reference. Removal from the table * is important to insure the node is not reprocessed * for inactivity. */ if (nt != NULL) { IEEE80211_NODE_LOCK(nt); node_reclaim(nt, ni); IEEE80211_NODE_UNLOCK(nt); } else ieee80211_free_node(ni); } struct rssiinfo { struct ieee80211vap *vap; int rssi_samples; uint32_t rssi_total; }; static void get_hostap_rssi(void *arg, struct ieee80211_node *ni) { struct rssiinfo *info = arg; struct ieee80211vap *vap = ni->ni_vap; int8_t rssi; if (info->vap != vap) return; /* only associated stations */ if (ni->ni_associd == 0) return; rssi = vap->iv_ic->ic_node_getrssi(ni); if (rssi != 0) { info->rssi_samples++; info->rssi_total += rssi; } } static void get_adhoc_rssi(void *arg, struct ieee80211_node *ni) { struct rssiinfo *info = arg; struct ieee80211vap *vap = ni->ni_vap; int8_t rssi; if (info->vap != vap) return; /* only neighbors */ /* XXX check bssid */ if ((ni->ni_capinfo & IEEE80211_CAPINFO_IBSS) == 0) return; rssi = vap->iv_ic->ic_node_getrssi(ni); if (rssi != 0) { info->rssi_samples++; info->rssi_total += rssi; } } #ifdef IEEE80211_SUPPORT_MESH static void get_mesh_rssi(void *arg, struct ieee80211_node *ni) { struct rssiinfo *info = arg; struct ieee80211vap *vap = ni->ni_vap; int8_t rssi; if (info->vap != vap) return; /* only neighbors that peered successfully */ if (ni->ni_mlstate != IEEE80211_NODE_MESH_ESTABLISHED) return; rssi = vap->iv_ic->ic_node_getrssi(ni); if (rssi != 0) { info->rssi_samples++; info->rssi_total += rssi; } } #endif /* IEEE80211_SUPPORT_MESH */ int8_t ieee80211_getrssi(struct ieee80211vap *vap) { #define NZ(x) ((x) == 0 ? 1 : (x)) struct ieee80211com *ic = vap->iv_ic; struct rssiinfo info; info.rssi_total = 0; info.rssi_samples = 0; info.vap = vap; switch (vap->iv_opmode) { case IEEE80211_M_IBSS: /* average of all ibss neighbors */ case IEEE80211_M_AHDEMO: /* average of all neighbors */ ieee80211_iterate_nodes(&ic->ic_sta, get_adhoc_rssi, &info); break; case IEEE80211_M_HOSTAP: /* average of all associated stations */ ieee80211_iterate_nodes(&ic->ic_sta, get_hostap_rssi, &info); break; #ifdef IEEE80211_SUPPORT_MESH case IEEE80211_M_MBSS: /* average of all mesh neighbors */ ieee80211_iterate_nodes(&ic->ic_sta, get_mesh_rssi, &info); break; #endif case IEEE80211_M_MONITOR: /* XXX */ case IEEE80211_M_STA: /* use stats from associated ap */ default: if (vap->iv_bss != NULL) info.rssi_total = ic->ic_node_getrssi(vap->iv_bss); info.rssi_samples = 1; break; } return info.rssi_total / NZ(info.rssi_samples); #undef NZ } void ieee80211_getsignal(struct ieee80211vap *vap, int8_t *rssi, int8_t *noise) { if (vap->iv_bss == NULL) /* NB: shouldn't happen */ return; vap->iv_ic->ic_node_getsignal(vap->iv_bss, rssi, noise); /* for non-station mode return avg'd rssi accounting */ if (vap->iv_opmode != IEEE80211_M_STA) *rssi = ieee80211_getrssi(vap); } Index: head/sys/net80211/ieee80211_power.c =================================================================== --- head/sys/net80211/ieee80211_power.c (revision 297404) +++ head/sys/net80211/ieee80211_power.c (revision 297405) @@ -1,650 +1,650 @@ /*- * Copyright (c) 2002-2008 Sam Leffler, Errno Consulting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); /* * IEEE 802.11 power save support. */ #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include static void ieee80211_update_ps(struct ieee80211vap *, int); static int ieee80211_set_tim(struct ieee80211_node *, int); static MALLOC_DEFINE(M_80211_POWER, "80211power", "802.11 power save state"); void ieee80211_power_attach(struct ieee80211com *ic) { } void ieee80211_power_detach(struct ieee80211com *ic) { } void ieee80211_power_vattach(struct ieee80211vap *vap) { if (vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_IBSS) { /* NB: driver should override */ vap->iv_update_ps = ieee80211_update_ps; vap->iv_set_tim = ieee80211_set_tim; } vap->iv_node_ps = ieee80211_node_pwrsave; vap->iv_sta_ps = ieee80211_sta_pwrsave; } void ieee80211_power_latevattach(struct ieee80211vap *vap) { /* * Allocate these only if needed. Beware that we * know adhoc mode doesn't support ATIM yet... */ if (vap->iv_opmode == IEEE80211_M_HOSTAP) { vap->iv_tim_len = howmany(vap->iv_max_aid,8) * sizeof(uint8_t); vap->iv_tim_bitmap = (uint8_t *) IEEE80211_MALLOC(vap->iv_tim_len, M_80211_POWER, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (vap->iv_tim_bitmap == NULL) { printf("%s: no memory for TIM bitmap!\n", __func__); /* XXX good enough to keep from crashing? */ vap->iv_tim_len = 0; } } } void ieee80211_power_vdetach(struct ieee80211vap *vap) { if (vap->iv_tim_bitmap != NULL) { IEEE80211_FREE(vap->iv_tim_bitmap, M_80211_POWER); vap->iv_tim_bitmap = NULL; } } void ieee80211_psq_init(struct ieee80211_psq *psq, const char *name) { memset(psq, 0, sizeof(*psq)); psq->psq_maxlen = IEEE80211_PS_MAX_QUEUE; IEEE80211_PSQ_INIT(psq, name); /* OS-dependent setup */ } void ieee80211_psq_cleanup(struct ieee80211_psq *psq) { #if 0 psq_drain(psq); /* XXX should not be needed? */ #else KASSERT(psq->psq_len == 0, ("%d frames on ps q", psq->psq_len)); #endif IEEE80211_PSQ_DESTROY(psq); /* OS-dependent cleanup */ } /* * Return the highest priority frame in the ps queue. */ struct mbuf * ieee80211_node_psq_dequeue(struct ieee80211_node *ni, int *qlen) { struct ieee80211_psq *psq = &ni->ni_psq; struct ieee80211_psq_head *qhead; struct mbuf *m; IEEE80211_PSQ_LOCK(psq); qhead = &psq->psq_head[0]; again: if ((m = qhead->head) != NULL) { if ((qhead->head = m->m_nextpkt) == NULL) qhead->tail = NULL; KASSERT(qhead->len > 0, ("qhead len %d", qhead->len)); qhead->len--; KASSERT(psq->psq_len > 0, ("psq len %d", psq->psq_len)); psq->psq_len--; m->m_nextpkt = NULL; } if (m == NULL && qhead == &psq->psq_head[0]) { /* Algol-68 style for loop */ qhead = &psq->psq_head[1]; goto again; } if (qlen != NULL) *qlen = psq->psq_len; IEEE80211_PSQ_UNLOCK(psq); return m; } /* * Reclaim an mbuf from the ps q. If marked with M_ENCAP * we assume there is a node reference that must be relcaimed. */ static void psq_mfree(struct mbuf *m) { if (m->m_flags & M_ENCAP) { struct ieee80211_node *ni = (void *) m->m_pkthdr.rcvif; ieee80211_free_node(ni); } m->m_nextpkt = NULL; m_freem(m); } /* * Clear any frames queued in the power save queue. * The number of frames that were present is returned. */ static int psq_drain(struct ieee80211_psq *psq) { struct ieee80211_psq_head *qhead; struct mbuf *m; int qlen; IEEE80211_PSQ_LOCK(psq); qlen = psq->psq_len; qhead = &psq->psq_head[0]; again: while ((m = qhead->head) != NULL) { qhead->head = m->m_nextpkt; psq_mfree(m); } qhead->tail = NULL; qhead->len = 0; if (qhead == &psq->psq_head[0]) { /* Algol-68 style for loop */ qhead = &psq->psq_head[1]; goto again; } psq->psq_len = 0; IEEE80211_PSQ_UNLOCK(psq); return qlen; } /* * Clear any frames queued in the power save queue. * The number of frames that were present is returned. */ int ieee80211_node_psq_drain(struct ieee80211_node *ni) { return psq_drain(&ni->ni_psq); } /* * Age frames on the power save queue. The aging interval is * 4 times the listen interval specified by the station. This * number is factored into the age calculations when the frame * is placed on the queue. We store ages as time differences * so we can check and/or adjust only the head of the list. * If a frame's age exceeds the threshold then discard it. * The number of frames discarded is returned so the caller * can check if it needs to adjust the tim. */ int ieee80211_node_psq_age(struct ieee80211_node *ni) { struct ieee80211_psq *psq = &ni->ni_psq; int discard = 0; if (psq->psq_len != 0) { #ifdef IEEE80211_DEBUG struct ieee80211vap *vap = ni->ni_vap; #endif struct ieee80211_psq_head *qhead; struct mbuf *m; IEEE80211_PSQ_LOCK(psq); qhead = &psq->psq_head[0]; again: while ((m = qhead->head) != NULL && M_AGE_GET(m) < IEEE80211_INACT_WAIT) { IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, "discard frame, age %u", M_AGE_GET(m)); if ((qhead->head = m->m_nextpkt) == NULL) qhead->tail = NULL; KASSERT(qhead->len > 0, ("qhead len %d", qhead->len)); qhead->len--; KASSERT(psq->psq_len > 0, ("psq len %d", psq->psq_len)); psq->psq_len--; psq_mfree(m); discard++; } if (qhead == &psq->psq_head[0]) { /* Algol-68 style for loop */ qhead = &psq->psq_head[1]; goto again; } if (m != NULL) M_AGE_SUB(m, IEEE80211_INACT_WAIT); IEEE80211_PSQ_UNLOCK(psq); IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, "discard %u frames for age", discard); IEEE80211_NODE_STAT_ADD(ni, ps_discard, discard); } return discard; } /* * Handle a change in the PS station occupancy. */ static void ieee80211_update_ps(struct ieee80211vap *vap, int nsta) { KASSERT(vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_IBSS, ("operating mode %u", vap->iv_opmode)); } /* * Indicate whether there are frames queued for a station in power-save mode. */ static int ieee80211_set_tim(struct ieee80211_node *ni, int set) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; uint16_t aid; int changed; KASSERT(vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_IBSS, ("operating mode %u", vap->iv_opmode)); aid = IEEE80211_AID(ni->ni_associd); KASSERT(aid < vap->iv_max_aid, ("bogus aid %u, max %u", aid, vap->iv_max_aid)); IEEE80211_LOCK(ic); changed = (set != (isset(vap->iv_tim_bitmap, aid) != 0)); if (changed) { if (set) { setbit(vap->iv_tim_bitmap, aid); vap->iv_ps_pending++; } else { clrbit(vap->iv_tim_bitmap, aid); vap->iv_ps_pending--; } /* NB: we know vap is in RUN state so no need to check */ vap->iv_update_beacon(vap, IEEE80211_BEACON_TIM); } IEEE80211_UNLOCK(ic); return changed; } /* * Save an outbound packet for a node in power-save sleep state. * The new packet is placed on the node's saved queue, and the TIM * is changed, if necessary. */ int ieee80211_pwrsave(struct ieee80211_node *ni, struct mbuf *m) { struct ieee80211_psq *psq = &ni->ni_psq; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_psq_head *qhead; int qlen, age; IEEE80211_PSQ_LOCK(psq); if (psq->psq_len >= psq->psq_maxlen) { psq->psq_drops++; IEEE80211_PSQ_UNLOCK(psq); IEEE80211_NOTE(vap, IEEE80211_MSG_ANY, ni, "pwr save q overflow, drops %d (size %d)", psq->psq_drops, psq->psq_len); #ifdef IEEE80211_DEBUG if (ieee80211_msg_dumppkts(vap)) ieee80211_dump_pkt(ni->ni_ic, mtod(m, caddr_t), m->m_len, -1, -1); #endif psq_mfree(m); return ENOSPC; } /* * Tag the frame with it's expiry time and insert it in * the appropriate queue. The aging interval is 4 times * the listen interval specified by the station. Frames * that sit around too long are reclaimed using this * information. */ /* TU -> secs. XXX handle overflow? */ age = IEEE80211_TU_TO_MS((ni->ni_intval * ic->ic_bintval) << 2) / 1000; /* * Encapsulated frames go on the high priority queue, * other stuff goes on the low priority queue. We use * this to order frames returned out of the driver * ahead of frames we collect in ieee80211_start. */ if (m->m_flags & M_ENCAP) qhead = &psq->psq_head[0]; else qhead = &psq->psq_head[1]; if (qhead->tail == NULL) { struct mbuf *mh; qhead->head = m; /* * Take care to adjust age when inserting the first * frame of a queue and the other queue already has * frames. We need to preserve the age difference * relationship so ieee80211_node_psq_age works. */ if (qhead == &psq->psq_head[1]) { mh = psq->psq_head[0].head; if (mh != NULL) age-= M_AGE_GET(mh); } else { mh = psq->psq_head[1].head; if (mh != NULL) { int nage = M_AGE_GET(mh) - age; /* XXX is clamping to zero good 'nuf? */ M_AGE_SET(mh, nage < 0 ? 0 : nage); } } } else { qhead->tail->m_nextpkt = m; age -= M_AGE_GET(qhead->head); } KASSERT(age >= 0, ("age %d", age)); M_AGE_SET(m, age); m->m_nextpkt = NULL; qhead->tail = m; qhead->len++; qlen = ++(psq->psq_len); IEEE80211_PSQ_UNLOCK(psq); IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, "save frame with age %d, %u now queued", age, qlen); if (qlen == 1 && vap->iv_set_tim != NULL) vap->iv_set_tim(ni, 1); return 0; } /* * Move frames from the ps q to the vap's send queue * and/or the driver's send queue; and kick the start * method for each, as appropriate. Note we're careful * to preserve packet ordering here. */ static void pwrsave_flushq(struct ieee80211_node *ni) { struct ieee80211_psq *psq = &ni->ni_psq; struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_psq_head *qhead; struct mbuf *parent_q = NULL, *ifp_q = NULL; struct mbuf *m; IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, "flush ps queue, %u packets queued", psq->psq_len); IEEE80211_PSQ_LOCK(psq); qhead = &psq->psq_head[0]; /* 802.11 frames */ if (qhead->head != NULL) { /* XXX could dispatch through vap and check M_ENCAP */ /* XXX need different driver interface */ /* XXX bypasses q max and OACTIVE */ parent_q = qhead->head; qhead->head = qhead->tail = NULL; qhead->len = 0; } qhead = &psq->psq_head[1]; /* 802.3 frames */ if (qhead->head != NULL) { /* XXX need different driver interface */ /* XXX bypasses q max and OACTIVE */ ifp_q = qhead->head; qhead->head = qhead->tail = NULL; qhead->len = 0; } psq->psq_len = 0; IEEE80211_PSQ_UNLOCK(psq); /* NB: do this outside the psq lock */ /* XXX packets might get reordered if parent is OACTIVE */ /* parent frames, should be encapsulated */ while (parent_q != NULL) { m = parent_q; parent_q = m->m_nextpkt; m->m_nextpkt = NULL; /* must be encapsulated */ KASSERT((m->m_flags & M_ENCAP), ("%s: parentq with non-M_ENCAP frame!\n", __func__)); (void) ieee80211_parent_xmitpkt(ic, m); } /* VAP frames, aren't encapsulated */ while (ifp_q != NULL) { m = ifp_q; ifp_q = m->m_nextpkt; m->m_nextpkt = NULL; KASSERT((!(m->m_flags & M_ENCAP)), ("%s: vapq with M_ENCAP frame!\n", __func__)); (void) ieee80211_vap_xmitpkt(vap, m); } } /* * Handle station power-save state change. */ void ieee80211_node_pwrsave(struct ieee80211_node *ni, int enable) { struct ieee80211vap *vap = ni->ni_vap; int update; update = 0; if (enable) { if ((ni->ni_flags & IEEE80211_NODE_PWR_MGT) == 0) { vap->iv_ps_sta++; update = 1; } ni->ni_flags |= IEEE80211_NODE_PWR_MGT; IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, "power save mode on, %u sta's in ps mode", vap->iv_ps_sta); if (update) vap->iv_update_ps(vap, vap->iv_ps_sta); } else { if (ni->ni_flags & IEEE80211_NODE_PWR_MGT) { vap->iv_ps_sta--; update = 1; } ni->ni_flags &= ~IEEE80211_NODE_PWR_MGT; IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, "power save mode off, %u sta's in ps mode", vap->iv_ps_sta); /* NB: order here is intentional so TIM is clear before flush */ if (vap->iv_set_tim != NULL) vap->iv_set_tim(ni, 0); if (update) { /* NB if no sta's in ps, driver should flush mc q */ vap->iv_update_ps(vap, vap->iv_ps_sta); } if (ni->ni_psq.psq_len != 0) pwrsave_flushq(ni); } } /* * Handle power-save state change in station mode. */ void ieee80211_sta_pwrsave(struct ieee80211vap *vap, int enable) { struct ieee80211_node *ni = vap->iv_bss; if (!((enable != 0) ^ ((ni->ni_flags & IEEE80211_NODE_PWR_MGT) != 0))) return; IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, "sta power save mode %s", enable ? "on" : "off"); if (!enable) { ni->ni_flags &= ~IEEE80211_NODE_PWR_MGT; ieee80211_send_nulldata(ieee80211_ref_node(ni)); /* * Flush any queued frames; we can do this immediately * because we know they'll be queued behind the null * data frame we send the ap. * XXX can we use a data frame to take us out of ps? */ if (ni->ni_psq.psq_len != 0) pwrsave_flushq(ni); } else { ni->ni_flags |= IEEE80211_NODE_PWR_MGT; ieee80211_send_nulldata(ieee80211_ref_node(ni)); } } /* * Handle being notified that we have data available for us in a TIM/ATIM. * * This may schedule a transition from _SLEEP -> _RUN if it's appropriate. * * In STA mode, we may have put to sleep during scan and need to be dragged * back out of powersave mode. */ void ieee80211_sta_tim_notify(struct ieee80211vap *vap, int set) { struct ieee80211com *ic = vap->iv_ic; /* * Schedule the driver state change. It'll happen at some point soon. * Since the hardware shouldn't know that we're running just yet * (and thus tell the peer that we're awake before we actually wake * up said hardware), we leave the actual node state transition * up to the transition to RUN. * * XXX TODO: verify that the transition to RUN will wake up the * BSS node! */ IEEE80211_LOCK(vap->iv_ic); if (set == 1 && vap->iv_state == IEEE80211_S_SLEEP) { ieee80211_new_state_locked(vap, IEEE80211_S_RUN, 0); IEEE80211_DPRINTF(vap, IEEE80211_MSG_POWER, "%s: TIM=%d; wakeup\n", __func__, set); } else if ((set == 1) && (ic->ic_flags_ext & IEEE80211_FEXT_BGSCAN)) { /* * XXX only do this if we're in RUN state? */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_POWER, "%s: wake up from bgscan vap sleep\n", __func__); /* * We may be in BGSCAN mode - this means the VAP is is in STA * mode powersave. If it is, we need to wake it up so we * can process outbound traffic. */ vap->iv_sta_ps(vap, 0); } IEEE80211_UNLOCK(vap->iv_ic); } /* * Timer check on whether the VAP has had any transmit activity. * * This may schedule a transition from _RUN -> _SLEEP if it's appropriate. */ void ieee80211_sta_ps_timer_check(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; /* XXX lock assert */ /* For no, only do this in STA mode */ if (! (vap->iv_caps & IEEE80211_C_SWSLEEP)) goto out; if (vap->iv_opmode != IEEE80211_M_STA) goto out; /* If we're not at run state, bail */ if (vap->iv_state != IEEE80211_S_RUN) goto out; IEEE80211_DPRINTF(vap, IEEE80211_MSG_POWER, "%s: lastdata=%llu, ticks=%llu\n", __func__, (unsigned long long) ic->ic_lastdata, (unsigned long long) ticks); /* If powersave is disabled on the VAP, don't bother */ if (! (vap->iv_flags & IEEE80211_F_PMGTON)) goto out; /* If we've done any data within our idle interval, bail */ /* XXX hard-coded to one second for now, ew! */ - if (time_after(ic->ic_lastdata + 500, ticks)) + if (ieee80211_time_after(ic->ic_lastdata + 500, ticks)) goto out; /* * Signify we're going into power save and transition the * node to powersave. */ if ((vap->iv_bss->ni_flags & IEEE80211_NODE_PWR_MGT) == 0) vap->iv_sta_ps(vap, 1); /* * XXX The driver has to handle the fact that we're going * to sleep but frames may still be transmitted; * hopefully it and/or us will do the right thing and mark any * transmitted frames with PWRMGT set to 1. */ ieee80211_new_state_locked(vap, IEEE80211_S_SLEEP, 0); IEEE80211_DPRINTF(vap, IEEE80211_MSG_POWER, "%s: time delta=%d msec\n", __func__, (int) ticks_to_msecs(ticks - ic->ic_lastdata)); out: return; } Index: head/sys/net80211/ieee80211_scan_sta.c =================================================================== --- head/sys/net80211/ieee80211_scan_sta.c (revision 297404) +++ head/sys/net80211/ieee80211_scan_sta.c (revision 297405) @@ -1,1933 +1,1933 @@ /*- * Copyright (c) 2002-2009 Sam Leffler, Errno Consulting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); /* * IEEE 802.11 station scanning support. */ #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IEEE80211_SUPPORT_TDMA #include #endif #ifdef IEEE80211_SUPPORT_MESH #include #endif #include #include /* * Parameters for managing cache entries: * * o a station with STA_FAILS_MAX failures is not considered * when picking a candidate * o a station that hasn't had an update in STA_PURGE_SCANS * (background) scans is discarded * o after STA_FAILS_AGE seconds we clear the failure count */ #define STA_FAILS_MAX 2 /* assoc failures before ignored */ #define STA_FAILS_AGE (2*60) /* time before clearing fails (secs) */ #define STA_PURGE_SCANS 2 /* age for purging entries (scans) */ /* XXX tunable */ #define STA_RSSI_MIN 8 /* min acceptable rssi */ #define STA_RSSI_MAX 40 /* max rssi for comparison */ struct sta_entry { struct ieee80211_scan_entry base; TAILQ_ENTRY(sta_entry) se_list; LIST_ENTRY(sta_entry) se_hash; uint8_t se_fails; /* failure to associate count */ uint8_t se_seen; /* seen during current scan */ uint8_t se_notseen; /* not seen in previous scans */ uint8_t se_flags; #define STA_DEMOTE11B 0x01 /* match w/ demoted 11b chan */ uint32_t se_avgrssi; /* LPF rssi state */ unsigned long se_lastupdate; /* time of last update */ unsigned long se_lastfail; /* time of last failure */ unsigned long se_lastassoc; /* time of last association */ u_int se_scangen; /* iterator scan gen# */ u_int se_countrygen; /* gen# of last cc notify */ }; #define STA_HASHSIZE 32 /* simple hash is enough for variation of macaddr */ #define STA_HASH(addr) \ (((const uint8_t *)(addr))[IEEE80211_ADDR_LEN - 1] % STA_HASHSIZE) #define MAX_IEEE_CHAN 256 /* max acceptable IEEE chan # */ CTASSERT(MAX_IEEE_CHAN >= 256); struct sta_table { ieee80211_scan_table_lock_t st_lock; /* on scan table */ TAILQ_HEAD(, sta_entry) st_entry; /* all entries */ LIST_HEAD(, sta_entry) st_hash[STA_HASHSIZE]; ieee80211_scan_iter_lock_t st_scanlock; /* on st_scaniter */ u_int st_scaniter; /* gen# for iterator */ u_int st_scangen; /* scan generation # */ int st_newscan; /* ap-related state */ int st_maxrssi[MAX_IEEE_CHAN]; }; static void sta_flush_table(struct sta_table *); /* * match_bss returns a bitmask describing if an entry is suitable * for use. If non-zero the entry was deemed not suitable and it's * contents explains why. The following flags are or'd to to this * mask and can be used to figure out why the entry was rejected. */ #define MATCH_CHANNEL 0x00001 /* channel mismatch */ #define MATCH_CAPINFO 0x00002 /* capabilities mismatch, e.g. no ess */ #define MATCH_PRIVACY 0x00004 /* privacy mismatch */ #define MATCH_RATE 0x00008 /* rate set mismatch */ #define MATCH_SSID 0x00010 /* ssid mismatch */ #define MATCH_BSSID 0x00020 /* bssid mismatch */ #define MATCH_FAILS 0x00040 /* too many failed auth attempts */ #define MATCH_NOTSEEN 0x00080 /* not seen in recent scans */ #define MATCH_RSSI 0x00100 /* rssi deemed too low to use */ #define MATCH_CC 0x00200 /* country code mismatch */ #define MATCH_TDMA_NOIE 0x00400 /* no TDMA ie */ #define MATCH_TDMA_NOTMASTER 0x00800 /* not TDMA master */ #define MATCH_TDMA_NOSLOT 0x01000 /* all TDMA slots occupied */ #define MATCH_TDMA_LOCAL 0x02000 /* local address */ #define MATCH_TDMA_VERSION 0x04000 /* protocol version mismatch */ #define MATCH_MESH_NOID 0x10000 /* no MESHID ie */ #define MATCH_MESHID 0x20000 /* meshid mismatch */ static int match_bss(struct ieee80211vap *, const struct ieee80211_scan_state *, struct sta_entry *, int); static void adhoc_age(struct ieee80211_scan_state *); static __inline int isocmp(const uint8_t cc1[], const uint8_t cc2[]) { return (cc1[0] == cc2[0] && cc1[1] == cc2[1]); } /* number of references from net80211 layer */ static int nrefs = 0; /* * Module glue. */ IEEE80211_SCANNER_MODULE(sta, 1); /* * Attach prior to any scanning work. */ static int sta_attach(struct ieee80211_scan_state *ss) { struct sta_table *st; st = (struct sta_table *) IEEE80211_MALLOC(sizeof(struct sta_table), M_80211_SCAN, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (st == NULL) return 0; IEEE80211_SCAN_TABLE_LOCK_INIT(st, "scantable"); IEEE80211_SCAN_ITER_LOCK_INIT(st, "scangen"); TAILQ_INIT(&st->st_entry); ss->ss_priv = st; nrefs++; /* NB: we assume caller locking */ return 1; } /* * Cleanup any private state. */ static int sta_detach(struct ieee80211_scan_state *ss) { struct sta_table *st = ss->ss_priv; if (st != NULL) { sta_flush_table(st); IEEE80211_SCAN_TABLE_LOCK_DESTROY(st); IEEE80211_SCAN_ITER_LOCK_DESTROY(st); IEEE80211_FREE(st, M_80211_SCAN); KASSERT(nrefs > 0, ("imbalanced attach/detach")); nrefs--; /* NB: we assume caller locking */ } return 1; } /* * Flush all per-scan state. */ static int sta_flush(struct ieee80211_scan_state *ss) { struct sta_table *st = ss->ss_priv; IEEE80211_SCAN_TABLE_LOCK(st); sta_flush_table(st); IEEE80211_SCAN_TABLE_UNLOCK(st); ss->ss_last = 0; return 0; } /* * Flush all entries in the scan cache. */ static void sta_flush_table(struct sta_table *st) { struct sta_entry *se, *next; TAILQ_FOREACH_SAFE(se, &st->st_entry, se_list, next) { TAILQ_REMOVE(&st->st_entry, se, se_list); LIST_REMOVE(se, se_hash); ieee80211_ies_cleanup(&se->base.se_ies); IEEE80211_FREE(se, M_80211_SCAN); } memset(st->st_maxrssi, 0, sizeof(st->st_maxrssi)); } /* * Process a beacon or probe response frame; create an * entry in the scan cache or update any previous entry. */ static int sta_add(struct ieee80211_scan_state *ss, struct ieee80211_channel *curchan, const struct ieee80211_scanparams *sp, const struct ieee80211_frame *wh, int subtype, int rssi, int noise) { #define ISPROBE(_st) ((_st) == IEEE80211_FC0_SUBTYPE_PROBE_RESP) #define PICK1ST(_ss) \ ((ss->ss_flags & (IEEE80211_SCAN_PICK1ST | IEEE80211_SCAN_GOTPICK)) == \ IEEE80211_SCAN_PICK1ST) struct sta_table *st = ss->ss_priv; const uint8_t *macaddr = wh->i_addr2; struct ieee80211vap *vap = ss->ss_vap; struct ieee80211com *ic = vap->iv_ic; struct ieee80211_channel *c; struct sta_entry *se; struct ieee80211_scan_entry *ise; int hash; hash = STA_HASH(macaddr); IEEE80211_SCAN_TABLE_LOCK(st); LIST_FOREACH(se, &st->st_hash[hash], se_hash) if (IEEE80211_ADDR_EQ(se->base.se_macaddr, macaddr)) goto found; se = (struct sta_entry *) IEEE80211_MALLOC(sizeof(struct sta_entry), M_80211_SCAN, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (se == NULL) { IEEE80211_SCAN_TABLE_UNLOCK(st); return 0; } se->se_scangen = st->st_scaniter-1; se->se_avgrssi = IEEE80211_RSSI_DUMMY_MARKER; IEEE80211_ADDR_COPY(se->base.se_macaddr, macaddr); TAILQ_INSERT_TAIL(&st->st_entry, se, se_list); LIST_INSERT_HEAD(&st->st_hash[hash], se, se_hash); found: ise = &se->base; /* XXX ap beaconing multiple ssid w/ same bssid */ if (sp->ssid[1] != 0 && (ISPROBE(subtype) || ise->se_ssid[1] == 0)) memcpy(ise->se_ssid, sp->ssid, 2+sp->ssid[1]); KASSERT(sp->rates[1] <= IEEE80211_RATE_MAXSIZE, ("rate set too large: %u", sp->rates[1])); memcpy(ise->se_rates, sp->rates, 2+sp->rates[1]); if (sp->xrates != NULL) { /* XXX validate xrates[1] */ KASSERT(sp->xrates[1] <= IEEE80211_RATE_MAXSIZE, ("xrate set too large: %u", sp->xrates[1])); memcpy(ise->se_xrates, sp->xrates, 2+sp->xrates[1]); } else ise->se_xrates[1] = 0; IEEE80211_ADDR_COPY(ise->se_bssid, wh->i_addr3); if ((sp->status & IEEE80211_BPARSE_OFFCHAN) == 0) { /* * Record rssi data using extended precision LPF filter. * * NB: use only on-channel data to insure we get a good * estimate of the signal we'll see when associated. */ IEEE80211_RSSI_LPF(se->se_avgrssi, rssi); ise->se_rssi = IEEE80211_RSSI_GET(se->se_avgrssi); ise->se_noise = noise; } memcpy(ise->se_tstamp.data, sp->tstamp, sizeof(ise->se_tstamp)); ise->se_intval = sp->bintval; ise->se_capinfo = sp->capinfo; #ifdef IEEE80211_SUPPORT_MESH if (sp->meshid != NULL && sp->meshid[1] != 0) memcpy(ise->se_meshid, sp->meshid, 2+sp->meshid[1]); #endif /* * Beware of overriding se_chan for frames seen * off-channel; this can cause us to attempt an * association on the wrong channel. */ if (sp->status & IEEE80211_BPARSE_OFFCHAN) { /* * Off-channel, locate the home/bss channel for the sta * using the value broadcast in the DSPARMS ie. We know * sp->chan has this value because it's used to calculate * IEEE80211_BPARSE_OFFCHAN. */ c = ieee80211_find_channel_byieee(ic, sp->chan, curchan->ic_flags); if (c != NULL) { ise->se_chan = c; } else if (ise->se_chan == NULL) { /* should not happen, pick something */ ise->se_chan = curchan; } } else ise->se_chan = curchan; if (IEEE80211_IS_CHAN_HT(ise->se_chan) && sp->htcap == NULL) { /* Demote legacy networks to a non-HT channel. */ c = ieee80211_find_channel(ic, ise->se_chan->ic_freq, ise->se_chan->ic_flags & ~IEEE80211_CHAN_HT); KASSERT(c != NULL, ("no legacy channel %u", ise->se_chan->ic_ieee)); ise->se_chan = c; } ise->se_fhdwell = sp->fhdwell; ise->se_fhindex = sp->fhindex; ise->se_erp = sp->erp; ise->se_timoff = sp->timoff; if (sp->tim != NULL) { const struct ieee80211_tim_ie *tim = (const struct ieee80211_tim_ie *) sp->tim; ise->se_dtimperiod = tim->tim_period; } if (sp->country != NULL) { const struct ieee80211_country_ie *cie = (const struct ieee80211_country_ie *) sp->country; /* * If 11d is enabled and we're attempting to join a bss * that advertises it's country code then compare our * current settings to what we fetched from the country ie. * If our country code is unspecified or different then * dispatch an event to user space that identifies the * country code so our regdomain config can be changed. */ /* XXX only for STA mode? */ if ((IEEE80211_IS_CHAN_11D(ise->se_chan) || (vap->iv_flags_ext & IEEE80211_FEXT_DOTD)) && (ic->ic_regdomain.country == CTRY_DEFAULT || !isocmp(cie->cc, ic->ic_regdomain.isocc))) { /* only issue one notify event per scan */ if (se->se_countrygen != st->st_scangen) { ieee80211_notify_country(vap, ise->se_bssid, cie->cc); se->se_countrygen = st->st_scangen; } } ise->se_cc[0] = cie->cc[0]; ise->se_cc[1] = cie->cc[1]; } /* NB: no need to setup ie ptrs; they are not (currently) used */ (void) ieee80211_ies_init(&ise->se_ies, sp->ies, sp->ies_len); /* clear failure count after STA_FAIL_AGE passes */ if (se->se_fails && (ticks - se->se_lastfail) > STA_FAILS_AGE*hz) { se->se_fails = 0; IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_SCAN, macaddr, "%s: fails %u", __func__, se->se_fails); } se->se_lastupdate = ticks; /* update time */ se->se_seen = 1; se->se_notseen = 0; KASSERT(sizeof(sp->bchan) == 1, ("bchan size")); if (rssi > st->st_maxrssi[sp->bchan]) st->st_maxrssi[sp->bchan] = rssi; IEEE80211_SCAN_TABLE_UNLOCK(st); /* * If looking for a quick choice and nothing's * been found check here. */ if (PICK1ST(ss) && match_bss(vap, ss, se, IEEE80211_MSG_SCAN) == 0) ss->ss_flags |= IEEE80211_SCAN_GOTPICK; return 1; #undef PICK1ST #undef ISPROBE } /* * Check if a channel is excluded by user request. */ static int isexcluded(struct ieee80211vap *vap, const struct ieee80211_channel *c) { return (isclr(vap->iv_ic->ic_chan_active, c->ic_ieee) || (vap->iv_des_chan != IEEE80211_CHAN_ANYC && c->ic_freq != vap->iv_des_chan->ic_freq)); } static struct ieee80211_channel * find11gchannel(struct ieee80211com *ic, int i, int freq) { struct ieee80211_channel *c; int j; /* * The normal ordering in the channel list is b channel * immediately followed by g so optimize the search for * this. We'll still do a full search just in case. */ for (j = i+1; j < ic->ic_nchans; j++) { c = &ic->ic_channels[j]; if (c->ic_freq == freq && IEEE80211_IS_CHAN_G(c)) return c; } for (j = 0; j < i; j++) { c = &ic->ic_channels[j]; if (c->ic_freq == freq && IEEE80211_IS_CHAN_G(c)) return c; } return NULL; } static const u_int chanflags[IEEE80211_MODE_MAX] = { [IEEE80211_MODE_AUTO] = IEEE80211_CHAN_B, [IEEE80211_MODE_11A] = IEEE80211_CHAN_A, [IEEE80211_MODE_11B] = IEEE80211_CHAN_B, [IEEE80211_MODE_11G] = IEEE80211_CHAN_G, [IEEE80211_MODE_FH] = IEEE80211_CHAN_FHSS, /* check base channel */ [IEEE80211_MODE_TURBO_A] = IEEE80211_CHAN_A, [IEEE80211_MODE_TURBO_G] = IEEE80211_CHAN_G, [IEEE80211_MODE_STURBO_A] = IEEE80211_CHAN_ST, [IEEE80211_MODE_HALF] = IEEE80211_CHAN_HALF, [IEEE80211_MODE_QUARTER] = IEEE80211_CHAN_QUARTER, /* check legacy */ [IEEE80211_MODE_11NA] = IEEE80211_CHAN_A, [IEEE80211_MODE_11NG] = IEEE80211_CHAN_G, }; static void add_channels(struct ieee80211vap *vap, struct ieee80211_scan_state *ss, enum ieee80211_phymode mode, const uint16_t freq[], int nfreq) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_channel *c, *cg; u_int modeflags; int i; KASSERT(mode < nitems(chanflags), ("Unexpected mode %u", mode)); modeflags = chanflags[mode]; for (i = 0; i < nfreq; i++) { if (ss->ss_last >= IEEE80211_SCAN_MAX) break; c = ieee80211_find_channel(ic, freq[i], modeflags); if (c == NULL || isexcluded(vap, c)) continue; if (mode == IEEE80211_MODE_AUTO) { /* * XXX special-case 11b/g channels so we select * the g channel if both are present. */ if (IEEE80211_IS_CHAN_B(c) && (cg = find11gchannel(ic, i, c->ic_freq)) != NULL) c = cg; } ss->ss_chans[ss->ss_last++] = c; } } struct scanlist { uint16_t mode; uint16_t count; const uint16_t *list; }; static int checktable(const struct scanlist *scan, const struct ieee80211_channel *c) { int i; for (; scan->list != NULL; scan++) { for (i = 0; i < scan->count; i++) if (scan->list[i] == c->ic_freq) return 1; } return 0; } static int onscanlist(const struct ieee80211_scan_state *ss, const struct ieee80211_channel *c) { int i; for (i = 0; i < ss->ss_last; i++) if (ss->ss_chans[i] == c) return 1; return 0; } static void sweepchannels(struct ieee80211_scan_state *ss, struct ieee80211vap *vap, const struct scanlist table[]) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_channel *c; int i; for (i = 0; i < ic->ic_nchans; i++) { if (ss->ss_last >= IEEE80211_SCAN_MAX) break; c = &ic->ic_channels[i]; /* * Ignore dynamic turbo channels; we scan them * in normal mode (i.e. not boosted). Likewise * for HT channels, they get scanned using * legacy rates. */ if (IEEE80211_IS_CHAN_DTURBO(c) || IEEE80211_IS_CHAN_HT(c)) continue; /* * If a desired mode was specified, scan only * channels that satisfy that constraint. */ if (vap->iv_des_mode != IEEE80211_MODE_AUTO && vap->iv_des_mode != ieee80211_chan2mode(c)) continue; /* * Skip channels excluded by user request. */ if (isexcluded(vap, c)) continue; /* * Add the channel unless it is listed in the * fixed scan order tables. This insures we * don't sweep back in channels we filtered out * above. */ if (checktable(table, c)) continue; /* Add channel to scanning list. */ ss->ss_chans[ss->ss_last++] = c; } /* * Explicitly add any desired channel if: * - not already on the scan list * - allowed by any desired mode constraint * - there is space in the scan list * This allows the channel to be used when the filtering * mechanisms would otherwise elide it (e.g HT, turbo). */ c = vap->iv_des_chan; if (c != IEEE80211_CHAN_ANYC && !onscanlist(ss, c) && (vap->iv_des_mode == IEEE80211_MODE_AUTO || vap->iv_des_mode == ieee80211_chan2mode(c)) && ss->ss_last < IEEE80211_SCAN_MAX) ss->ss_chans[ss->ss_last++] = c; } static void makescanlist(struct ieee80211_scan_state *ss, struct ieee80211vap *vap, const struct scanlist table[]) { const struct scanlist *scan; enum ieee80211_phymode mode; ss->ss_last = 0; /* * Use the table of ordered channels to construct the list * of channels for scanning. Any channels in the ordered * list not in the master list will be discarded. */ for (scan = table; scan->list != NULL; scan++) { mode = scan->mode; if (vap->iv_des_mode != IEEE80211_MODE_AUTO) { /* * If a desired mode was specified, scan only * channels that satisfy that constraint. */ if (vap->iv_des_mode != mode) { /* * The scan table marks 2.4Ghz channels as b * so if the desired mode is 11g, then use * the 11b channel list but upgrade the mode. */ if (vap->iv_des_mode == IEEE80211_MODE_11G) { if (mode == IEEE80211_MODE_11G) /* Skip the G check */ continue; else if (mode == IEEE80211_MODE_11B) mode = IEEE80211_MODE_11G; /* upgrade */ } } } else { /* * This lets add_channels upgrade an 11b channel * to 11g if available. */ if (mode == IEEE80211_MODE_11B) mode = IEEE80211_MODE_AUTO; } #ifdef IEEE80211_F_XR /* XR does not operate on turbo channels */ if ((vap->iv_flags & IEEE80211_F_XR) && (mode == IEEE80211_MODE_TURBO_A || mode == IEEE80211_MODE_TURBO_G || mode == IEEE80211_MODE_STURBO_A)) continue; #endif /* * Add the list of the channels; any that are not * in the master channel list will be discarded. */ add_channels(vap, ss, mode, scan->list, scan->count); } /* * Add the channels from the ic that are not present * in the table. */ sweepchannels(ss, vap, table); } static const uint16_t rcl1[] = /* 8 FCC channel: 52, 56, 60, 64, 36, 40, 44, 48 */ { 5260, 5280, 5300, 5320, 5180, 5200, 5220, 5240 }; static const uint16_t rcl2[] = /* 4 MKK channels: 34, 38, 42, 46 */ { 5170, 5190, 5210, 5230 }; static const uint16_t rcl3[] = /* 2.4Ghz ch: 1,6,11,7,13 */ { 2412, 2437, 2462, 2442, 2472 }; static const uint16_t rcl4[] = /* 5 FCC channel: 149, 153, 161, 165 */ { 5745, 5765, 5785, 5805, 5825 }; static const uint16_t rcl7[] = /* 11 ETSI channel: 100,104,108,112,116,120,124,128,132,136,140 */ { 5500, 5520, 5540, 5560, 5580, 5600, 5620, 5640, 5660, 5680, 5700 }; static const uint16_t rcl8[] = /* 2.4Ghz ch: 2,3,4,5,8,9,10,12 */ { 2417, 2422, 2427, 2432, 2447, 2452, 2457, 2467 }; static const uint16_t rcl9[] = /* 2.4Ghz ch: 14 */ { 2484 }; static const uint16_t rcl10[] = /* Added Korean channels 2312-2372 */ { 2312, 2317, 2322, 2327, 2332, 2337, 2342, 2347, 2352, 2357, 2362, 2367, 2372 }; static const uint16_t rcl11[] = /* Added Japan channels in 4.9/5.0 spectrum */ { 5040, 5060, 5080, 4920, 4940, 4960, 4980 }; #ifdef ATH_TURBO_SCAN static const uint16_t rcl5[] = /* 3 static turbo channels */ { 5210, 5250, 5290 }; static const uint16_t rcl6[] = /* 2 static turbo channels */ { 5760, 5800 }; static const uint16_t rcl6x[] = /* 4 FCC3 turbo channels */ { 5540, 5580, 5620, 5660 }; static const uint16_t rcl12[] = /* 2.4Ghz Turbo channel 6 */ { 2437 }; static const uint16_t rcl13[] = /* dynamic Turbo channels */ { 5200, 5240, 5280, 5765, 5805 }; #endif /* ATH_TURBO_SCAN */ #define X(a) .count = sizeof(a)/sizeof(a[0]), .list = a static const struct scanlist staScanTable[] = { { IEEE80211_MODE_11B, X(rcl3) }, { IEEE80211_MODE_11A, X(rcl1) }, { IEEE80211_MODE_11A, X(rcl2) }, { IEEE80211_MODE_11B, X(rcl8) }, { IEEE80211_MODE_11B, X(rcl9) }, { IEEE80211_MODE_11A, X(rcl4) }, #ifdef ATH_TURBO_SCAN { IEEE80211_MODE_STURBO_A, X(rcl5) }, { IEEE80211_MODE_STURBO_A, X(rcl6) }, { IEEE80211_MODE_TURBO_A, X(rcl6x) }, { IEEE80211_MODE_TURBO_A, X(rcl13) }, #endif /* ATH_TURBO_SCAN */ { IEEE80211_MODE_11A, X(rcl7) }, { IEEE80211_MODE_11B, X(rcl10) }, { IEEE80211_MODE_11A, X(rcl11) }, #ifdef ATH_TURBO_SCAN { IEEE80211_MODE_TURBO_G, X(rcl12) }, #endif /* ATH_TURBO_SCAN */ { .list = NULL } }; /* * Start a station-mode scan by populating the channel list. */ static int sta_start(struct ieee80211_scan_state *ss, struct ieee80211vap *vap) { struct sta_table *st = ss->ss_priv; makescanlist(ss, vap, staScanTable); if (ss->ss_mindwell == 0) ss->ss_mindwell = msecs_to_ticks(20); /* 20ms */ if (ss->ss_maxdwell == 0) ss->ss_maxdwell = msecs_to_ticks(200); /* 200ms */ st->st_scangen++; st->st_newscan = 1; return 0; } /* * Restart a scan, typically a bg scan but can * also be a fg scan that came up empty. */ static int sta_restart(struct ieee80211_scan_state *ss, struct ieee80211vap *vap) { struct sta_table *st = ss->ss_priv; st->st_newscan = 1; return 0; } /* * Cancel an ongoing scan. */ static int sta_cancel(struct ieee80211_scan_state *ss, struct ieee80211vap *vap) { return 0; } /* * Demote any supplied 11g channel to 11b. There should * always be an 11b channel but we check anyway... */ static struct ieee80211_channel * demote11b(struct ieee80211vap *vap, struct ieee80211_channel *chan) { struct ieee80211_channel *c; if (IEEE80211_IS_CHAN_ANYG(chan) && vap->iv_des_mode == IEEE80211_MODE_AUTO) { c = ieee80211_find_channel(vap->iv_ic, chan->ic_freq, (chan->ic_flags &~ (IEEE80211_CHAN_PUREG | IEEE80211_CHAN_G)) | IEEE80211_CHAN_B); if (c != NULL) chan = c; } return chan; } static int maxrate(const struct ieee80211_scan_entry *se) { const struct ieee80211_ie_htcap *htcap = (const struct ieee80211_ie_htcap *) se->se_ies.htcap_ie; int rmax, r, i, txstream; uint16_t caps; uint8_t txparams; rmax = 0; if (htcap != NULL) { /* * HT station; inspect supported MCS and then adjust * rate by channel width. */ txparams = htcap->hc_mcsset[12]; if (txparams & 0x3) { /* * TX MCS parameters defined and not equal to RX, * extract the number of spartial streams and * map it to the highest MCS rate. */ txstream = ((txparams & 0xc) >> 2) + 1; i = txstream * 8 - 1; } else for (i = 31; i >= 0 && isclr(htcap->hc_mcsset, i); i--); if (i >= 0) { caps = LE_READ_2(&htcap->hc_cap); if ((caps & IEEE80211_HTCAP_CHWIDTH40) && (caps & IEEE80211_HTCAP_SHORTGI40)) rmax = ieee80211_htrates[i].ht40_rate_400ns; else if (caps & IEEE80211_HTCAP_CHWIDTH40) rmax = ieee80211_htrates[i].ht40_rate_800ns; else if (caps & IEEE80211_HTCAP_SHORTGI20) rmax = ieee80211_htrates[i].ht20_rate_400ns; else rmax = ieee80211_htrates[i].ht20_rate_800ns; } } for (i = 0; i < se->se_rates[1]; i++) { r = se->se_rates[2+i] & IEEE80211_RATE_VAL; if (r > rmax) rmax = r; } for (i = 0; i < se->se_xrates[1]; i++) { r = se->se_xrates[2+i] & IEEE80211_RATE_VAL; if (r > rmax) rmax = r; } return rmax; } /* * Compare the capabilities of two entries and decide which is * more desirable (return >0 if a is considered better). Note * that we assume compatibility/usability has already been checked * so we don't need to (e.g. validate whether privacy is supported). * Used to select the best scan candidate for association in a BSS. */ static int sta_compare(const struct sta_entry *a, const struct sta_entry *b) { #define PREFER(_a,_b,_what) do { \ if (((_a) ^ (_b)) & (_what)) \ return ((_a) & (_what)) ? 1 : -1; \ } while (0) int maxa, maxb; int8_t rssia, rssib; int weight; /* privacy support */ PREFER(a->base.se_capinfo, b->base.se_capinfo, IEEE80211_CAPINFO_PRIVACY); /* compare count of previous failures */ weight = b->se_fails - a->se_fails; if (abs(weight) > 1) return weight; /* * Compare rssi. If the two are considered equivalent * then fallback to other criteria. We threshold the * comparisons to avoid selecting an ap purely by rssi * when both values may be good but one ap is otherwise * more desirable (e.g. an 11b-only ap with stronger * signal than an 11g ap). */ rssia = MIN(a->base.se_rssi, STA_RSSI_MAX); rssib = MIN(b->base.se_rssi, STA_RSSI_MAX); if (abs(rssib - rssia) < 5) { /* best/max rate preferred if signal level close enough XXX */ maxa = maxrate(&a->base); maxb = maxrate(&b->base); if (maxa != maxb) return maxa - maxb; /* XXX use freq for channel preference */ /* for now just prefer 5Ghz band to all other bands */ PREFER(IEEE80211_IS_CHAN_5GHZ(a->base.se_chan), IEEE80211_IS_CHAN_5GHZ(b->base.se_chan), 1); } /* all things being equal, use signal level */ return a->base.se_rssi - b->base.se_rssi; #undef PREFER } /* * Check rate set suitability and return the best supported rate. * XXX inspect MCS for HT */ static int check_rate(struct ieee80211vap *vap, const struct ieee80211_channel *chan, const struct ieee80211_scan_entry *se) { const struct ieee80211_rateset *srs; int i, j, nrs, r, okrate, badrate, fixedrate, ucastrate; const uint8_t *rs; okrate = badrate = 0; srs = ieee80211_get_suprates(vap->iv_ic, chan); nrs = se->se_rates[1]; rs = se->se_rates+2; /* XXX MCS */ ucastrate = vap->iv_txparms[ieee80211_chan2mode(chan)].ucastrate; fixedrate = IEEE80211_FIXED_RATE_NONE; again: for (i = 0; i < nrs; i++) { r = IEEE80211_RV(rs[i]); badrate = r; /* * Check any fixed rate is included. */ if (r == ucastrate) fixedrate = r; /* * Check against our supported rates. */ for (j = 0; j < srs->rs_nrates; j++) if (r == IEEE80211_RV(srs->rs_rates[j])) { if (r > okrate) /* NB: track max */ okrate = r; break; } if (j == srs->rs_nrates && (rs[i] & IEEE80211_RATE_BASIC)) { /* * Don't try joining a BSS, if we don't support * one of its basic rates. */ okrate = 0; goto back; } } if (rs == se->se_rates+2) { /* scan xrates too; sort of an algol68-style for loop */ nrs = se->se_xrates[1]; rs = se->se_xrates+2; goto again; } back: if (okrate == 0 || ucastrate != fixedrate) return badrate | IEEE80211_RATE_BASIC; else return IEEE80211_RV(okrate); } static __inline int match_id(const uint8_t *ie, const uint8_t *val, int len) { return (ie[1] == len && memcmp(ie+2, val, len) == 0); } static int match_ssid(const uint8_t *ie, int nssid, const struct ieee80211_scan_ssid ssids[]) { int i; for (i = 0; i < nssid; i++) { if (match_id(ie, ssids[i].ssid, ssids[i].len)) return 1; } return 0; } #ifdef IEEE80211_SUPPORT_TDMA static int tdma_isfull(const struct ieee80211_tdma_param *tdma) { int slot, slotcnt; slotcnt = tdma->tdma_slotcnt; for (slot = slotcnt-1; slot >= 0; slot--) if (isclr(tdma->tdma_inuse, slot)) return 0; return 1; } #endif /* IEEE80211_SUPPORT_TDMA */ /* * Test a scan candidate for suitability/compatibility. */ static int match_bss(struct ieee80211vap *vap, const struct ieee80211_scan_state *ss, struct sta_entry *se0, int debug) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_scan_entry *se = &se0->base; uint8_t rate; int fail; fail = 0; if (isclr(ic->ic_chan_active, ieee80211_chan2ieee(ic, se->se_chan))) fail |= MATCH_CHANNEL; /* * NB: normally the desired mode is used to construct * the channel list, but it's possible for the scan * cache to include entries for stations outside this * list so we check the desired mode here to weed them * out. */ if (vap->iv_des_mode != IEEE80211_MODE_AUTO && (se->se_chan->ic_flags & IEEE80211_CHAN_ALLTURBO) != chanflags[vap->iv_des_mode]) fail |= MATCH_CHANNEL; if (vap->iv_opmode == IEEE80211_M_IBSS) { if ((se->se_capinfo & IEEE80211_CAPINFO_IBSS) == 0) fail |= MATCH_CAPINFO; #ifdef IEEE80211_SUPPORT_TDMA } else if (vap->iv_opmode == IEEE80211_M_AHDEMO) { /* * Adhoc demo network setup shouldn't really be scanning * but just in case skip stations operating in IBSS or * BSS mode. */ if (se->se_capinfo & (IEEE80211_CAPINFO_IBSS|IEEE80211_CAPINFO_ESS)) fail |= MATCH_CAPINFO; /* * TDMA operation cannot coexist with a normal 802.11 network; * skip if IBSS or ESS capabilities are marked and require * the beacon have a TDMA ie present. */ if (vap->iv_caps & IEEE80211_C_TDMA) { const struct ieee80211_tdma_param *tdma = (const struct ieee80211_tdma_param *)se->se_ies.tdma_ie; const struct ieee80211_tdma_state *ts = vap->iv_tdma; if (tdma == NULL) fail |= MATCH_TDMA_NOIE; else if (tdma->tdma_version != ts->tdma_version) fail |= MATCH_TDMA_VERSION; else if (tdma->tdma_slot != 0) fail |= MATCH_TDMA_NOTMASTER; else if (tdma_isfull(tdma)) fail |= MATCH_TDMA_NOSLOT; #if 0 else if (ieee80211_local_address(se->se_macaddr)) fail |= MATCH_TDMA_LOCAL; #endif } #endif /* IEEE80211_SUPPORT_TDMA */ #ifdef IEEE80211_SUPPORT_MESH } else if (vap->iv_opmode == IEEE80211_M_MBSS) { const struct ieee80211_mesh_state *ms = vap->iv_mesh; /* * Mesh nodes have IBSS & ESS bits in capinfo turned off * and two special ie's that must be present. */ if (se->se_capinfo & (IEEE80211_CAPINFO_IBSS|IEEE80211_CAPINFO_ESS)) fail |= MATCH_CAPINFO; else if (se->se_meshid[0] != IEEE80211_ELEMID_MESHID) fail |= MATCH_MESH_NOID; else if (ms->ms_idlen != 0 && match_id(se->se_meshid, ms->ms_id, ms->ms_idlen)) fail |= MATCH_MESHID; #endif } else { if ((se->se_capinfo & IEEE80211_CAPINFO_ESS) == 0) fail |= MATCH_CAPINFO; /* * If 11d is enabled and we're attempting to join a bss * that advertises it's country code then compare our * current settings to what we fetched from the country ie. * If our country code is unspecified or different then do * not attempt to join the bss. We should have already * dispatched an event to user space that identifies the * new country code so our regdomain config should match. */ if ((IEEE80211_IS_CHAN_11D(se->se_chan) || (vap->iv_flags_ext & IEEE80211_FEXT_DOTD)) && se->se_cc[0] != 0 && (ic->ic_regdomain.country == CTRY_DEFAULT || !isocmp(se->se_cc, ic->ic_regdomain.isocc))) fail |= MATCH_CC; } if (vap->iv_flags & IEEE80211_F_PRIVACY) { if ((se->se_capinfo & IEEE80211_CAPINFO_PRIVACY) == 0) fail |= MATCH_PRIVACY; } else { /* XXX does this mean privacy is supported or required? */ if (se->se_capinfo & IEEE80211_CAPINFO_PRIVACY) fail |= MATCH_PRIVACY; } se0->se_flags &= ~STA_DEMOTE11B; rate = check_rate(vap, se->se_chan, se); if (rate & IEEE80211_RATE_BASIC) { fail |= MATCH_RATE; /* * An 11b-only ap will give a rate mismatch if there is an * OFDM fixed tx rate for 11g. Try downgrading the channel * in the scan list to 11b and retry the rate check. */ if (IEEE80211_IS_CHAN_ANYG(se->se_chan)) { rate = check_rate(vap, demote11b(vap, se->se_chan), se); if ((rate & IEEE80211_RATE_BASIC) == 0) { fail &= ~MATCH_RATE; se0->se_flags |= STA_DEMOTE11B; } } } else if (rate < 2*24) { /* * This is an 11b-only ap. Check the desired mode in * case that needs to be honored (mode 11g filters out * 11b-only ap's). Otherwise force any 11g channel used * in scanning to be demoted. * * NB: we cheat a bit here by looking at the max rate; * we could/should check the rates. */ if (!(vap->iv_des_mode == IEEE80211_MODE_AUTO || vap->iv_des_mode == IEEE80211_MODE_11B)) fail |= MATCH_RATE; else se0->se_flags |= STA_DEMOTE11B; } if (ss->ss_nssid != 0 && !match_ssid(se->se_ssid, ss->ss_nssid, ss->ss_ssid)) fail |= MATCH_SSID; if ((vap->iv_flags & IEEE80211_F_DESBSSID) && !IEEE80211_ADDR_EQ(vap->iv_des_bssid, se->se_bssid)) fail |= MATCH_BSSID; if (se0->se_fails >= STA_FAILS_MAX) fail |= MATCH_FAILS; if (se0->se_notseen >= STA_PURGE_SCANS) fail |= MATCH_NOTSEEN; if (se->se_rssi < STA_RSSI_MIN) fail |= MATCH_RSSI; #ifdef IEEE80211_DEBUG if (ieee80211_msg(vap, debug)) { printf(" %c %s", fail & MATCH_FAILS ? '=' : fail & MATCH_NOTSEEN ? '^' : fail & MATCH_CC ? '$' : #ifdef IEEE80211_SUPPORT_TDMA fail & MATCH_TDMA_NOIE ? '&' : fail & MATCH_TDMA_VERSION ? 'v' : fail & MATCH_TDMA_NOTMASTER ? 's' : fail & MATCH_TDMA_NOSLOT ? 'f' : fail & MATCH_TDMA_LOCAL ? 'l' : #endif fail & MATCH_MESH_NOID ? 'm' : fail ? '-' : '+', ether_sprintf(se->se_macaddr)); printf(" %s%c", ether_sprintf(se->se_bssid), fail & MATCH_BSSID ? '!' : ' '); printf(" %3d%c", ieee80211_chan2ieee(ic, se->se_chan), fail & MATCH_CHANNEL ? '!' : ' '); printf(" %+4d%c", se->se_rssi, fail & MATCH_RSSI ? '!' : ' '); printf(" %2dM%c", (rate & IEEE80211_RATE_VAL) / 2, fail & MATCH_RATE ? '!' : ' '); printf(" %4s%c", (se->se_capinfo & IEEE80211_CAPINFO_ESS) ? "ess" : (se->se_capinfo & IEEE80211_CAPINFO_IBSS) ? "ibss" : "", fail & MATCH_CAPINFO ? '!' : ' '); printf(" %3s%c ", (se->se_capinfo & IEEE80211_CAPINFO_PRIVACY) ? "wep" : "no", fail & MATCH_PRIVACY ? '!' : ' '); ieee80211_print_essid(se->se_ssid+2, se->se_ssid[1]); printf("%s\n", fail & (MATCH_SSID | MATCH_MESHID) ? "!" : ""); } #endif return fail; } static void sta_update_notseen(struct sta_table *st) { struct sta_entry *se; IEEE80211_SCAN_TABLE_LOCK(st); TAILQ_FOREACH(se, &st->st_entry, se_list) { /* * If seen the reset and don't bump the count; * otherwise bump the ``not seen'' count. Note * that this insures that stations for which we * see frames while not scanning but not during * this scan will not be penalized. */ if (se->se_seen) se->se_seen = 0; else se->se_notseen++; } IEEE80211_SCAN_TABLE_UNLOCK(st); } static void sta_dec_fails(struct sta_table *st) { struct sta_entry *se; IEEE80211_SCAN_TABLE_LOCK(st); TAILQ_FOREACH(se, &st->st_entry, se_list) if (se->se_fails) se->se_fails--; IEEE80211_SCAN_TABLE_UNLOCK(st); } static struct sta_entry * select_bss(struct ieee80211_scan_state *ss, struct ieee80211vap *vap, int debug) { struct sta_table *st = ss->ss_priv; struct sta_entry *se, *selbs = NULL; IEEE80211_DPRINTF(vap, debug, " %s\n", "macaddr bssid chan rssi rate flag wep essid"); IEEE80211_SCAN_TABLE_LOCK(st); TAILQ_FOREACH(se, &st->st_entry, se_list) { ieee80211_ies_expand(&se->base.se_ies); if (match_bss(vap, ss, se, debug) == 0) { if (selbs == NULL) selbs = se; else if (sta_compare(se, selbs) > 0) selbs = se; } } IEEE80211_SCAN_TABLE_UNLOCK(st); return selbs; } /* * Pick an ap or ibss network to join or find a channel * to use to start an ibss network. */ static int sta_pick_bss(struct ieee80211_scan_state *ss, struct ieee80211vap *vap) { struct sta_table *st = ss->ss_priv; struct sta_entry *selbs; struct ieee80211_channel *chan; KASSERT(vap->iv_opmode == IEEE80211_M_STA, ("wrong mode %u", vap->iv_opmode)); if (st->st_newscan) { sta_update_notseen(st); st->st_newscan = 0; } if (ss->ss_flags & IEEE80211_SCAN_NOPICK) { /* * Manual/background scan, don't select+join the * bss, just return. The scanning framework will * handle notification that this has completed. */ ss->ss_flags &= ~IEEE80211_SCAN_NOPICK; return 1; } /* * Automatic sequencing; look for a candidate and * if found join the network. */ /* NB: unlocked read should be ok */ if (TAILQ_FIRST(&st->st_entry) == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: no scan candidate\n", __func__); if (ss->ss_flags & IEEE80211_SCAN_NOJOIN) return 0; notfound: /* * If nothing suitable was found decrement * the failure counts so entries will be * reconsidered the next time around. We * really want to do this only for sta's * where we've previously had some success. */ sta_dec_fails(st); st->st_newscan = 1; return 0; /* restart scan */ } selbs = select_bss(ss, vap, IEEE80211_MSG_SCAN); if (ss->ss_flags & IEEE80211_SCAN_NOJOIN) return (selbs != NULL); if (selbs == NULL) goto notfound; chan = selbs->base.se_chan; if (selbs->se_flags & STA_DEMOTE11B) chan = demote11b(vap, chan); if (!ieee80211_sta_join(vap, chan, &selbs->base)) goto notfound; return 1; /* terminate scan */ } /* * Lookup an entry in the scan cache. We assume we're * called from the bottom half or such that we don't need * to block the bottom half so that it's safe to return * a reference to an entry w/o holding the lock on the table. */ static struct sta_entry * sta_lookup(struct sta_table *st, const uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct sta_entry *se; int hash = STA_HASH(macaddr); IEEE80211_SCAN_TABLE_LOCK(st); LIST_FOREACH(se, &st->st_hash[hash], se_hash) if (IEEE80211_ADDR_EQ(se->base.se_macaddr, macaddr)) break; IEEE80211_SCAN_TABLE_UNLOCK(st); return se; /* NB: unlocked */ } static void sta_roam_check(struct ieee80211_scan_state *ss, struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni = vap->iv_bss; struct sta_table *st = ss->ss_priv; enum ieee80211_phymode mode; struct sta_entry *se, *selbs; uint8_t roamRate, curRate, ucastRate; int8_t roamRssi, curRssi; se = sta_lookup(st, ni->ni_macaddr); if (se == NULL) { /* XXX something is wrong */ return; } mode = ieee80211_chan2mode(ic->ic_bsschan); roamRate = vap->iv_roamparms[mode].rate; roamRssi = vap->iv_roamparms[mode].rssi; ucastRate = vap->iv_txparms[mode].ucastrate; /* NB: the most up to date rssi is in the node, not the scan cache */ curRssi = ic->ic_node_getrssi(ni); if (ucastRate == IEEE80211_FIXED_RATE_NONE) { curRate = ni->ni_txrate; roamRate &= IEEE80211_RATE_VAL; IEEE80211_DPRINTF(vap, IEEE80211_MSG_ROAM, "%s: currssi %d currate %u roamrssi %d roamrate %u\n", __func__, curRssi, curRate, roamRssi, roamRate); } else { curRate = roamRate; /* NB: insure compare below fails */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_ROAM, "%s: currssi %d roamrssi %d\n", __func__, curRssi, roamRssi); } /* * Check if a new ap should be used and switch. * XXX deauth current ap */ if (curRate < roamRate || curRssi < roamRssi) { - if (time_after(ticks, ic->ic_lastscan + vap->iv_scanvalid)) { + if (ieee80211_time_after(ticks, ic->ic_lastscan + vap->iv_scanvalid)) { /* * Scan cache contents are too old; force a scan now * if possible so we have current state to make a * decision with. We don't kick off a bg scan if * we're using dynamic turbo and boosted or if the * channel is busy. * XXX force immediate switch on scan complete */ if (!IEEE80211_IS_CHAN_DTURBO(ic->ic_curchan) && - time_after(ticks, ic->ic_lastdata + vap->iv_bgscanidle)) + ieee80211_time_after(ticks, ic->ic_lastdata + vap->iv_bgscanidle)) ieee80211_bg_scan(vap, 0); return; } se->base.se_rssi = curRssi; selbs = select_bss(ss, vap, IEEE80211_MSG_ROAM); if (selbs != NULL && selbs != se) { struct ieee80211_channel *chan; IEEE80211_DPRINTF(vap, IEEE80211_MSG_ROAM | IEEE80211_MSG_DEBUG, "%s: ROAM: curRate %u, roamRate %u, " "curRssi %d, roamRssi %d\n", __func__, curRate, roamRate, curRssi, roamRssi); chan = selbs->base.se_chan; if (selbs->se_flags & STA_DEMOTE11B) chan = demote11b(vap, chan); (void) ieee80211_sta_join(vap, chan, &selbs->base); } } } /* * Age entries in the scan cache. * XXX also do roaming since it's convenient */ static void sta_age(struct ieee80211_scan_state *ss) { struct ieee80211vap *vap = ss->ss_vap; adhoc_age(ss); /* * If rate control is enabled check periodically to see if * we should roam from our current connection to one that * might be better. This only applies when we're operating * in sta mode and automatic roaming is set. * XXX defer if busy * XXX repeater station * XXX do when !bgscan? */ KASSERT(vap->iv_opmode == IEEE80211_M_STA, ("wrong mode %u", vap->iv_opmode)); if (vap->iv_roaming == IEEE80211_ROAMING_AUTO && (vap->iv_flags & IEEE80211_F_BGSCAN) && vap->iv_state >= IEEE80211_S_RUN) /* XXX vap is implicit */ sta_roam_check(ss, vap); } /* * Iterate over the entries in the scan cache, invoking * the callback function on each one. */ static void sta_iterate(struct ieee80211_scan_state *ss, ieee80211_scan_iter_func *f, void *arg) { struct sta_table *st = ss->ss_priv; struct sta_entry *se; u_int gen; IEEE80211_SCAN_ITER_LOCK(st); gen = st->st_scaniter++; restart: IEEE80211_SCAN_TABLE_LOCK(st); TAILQ_FOREACH(se, &st->st_entry, se_list) { if (se->se_scangen != gen) { se->se_scangen = gen; /* update public state */ se->base.se_age = ticks - se->se_lastupdate; IEEE80211_SCAN_TABLE_UNLOCK(st); (*f)(arg, &se->base); goto restart; } } IEEE80211_SCAN_TABLE_UNLOCK(st); IEEE80211_SCAN_ITER_UNLOCK(st); } static void sta_assoc_fail(struct ieee80211_scan_state *ss, const uint8_t macaddr[IEEE80211_ADDR_LEN], int reason) { struct sta_table *st = ss->ss_priv; struct sta_entry *se; se = sta_lookup(st, macaddr); if (se != NULL) { se->se_fails++; se->se_lastfail = ticks; IEEE80211_NOTE_MAC(ss->ss_vap, IEEE80211_MSG_SCAN, macaddr, "%s: reason %u fails %u", __func__, reason, se->se_fails); } } static void sta_assoc_success(struct ieee80211_scan_state *ss, const uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct sta_table *st = ss->ss_priv; struct sta_entry *se; se = sta_lookup(st, macaddr); if (se != NULL) { #if 0 se->se_fails = 0; IEEE80211_NOTE_MAC(ss->ss_vap, IEEE80211_MSG_SCAN, macaddr, "%s: fails %u", __func__, se->se_fails); #endif se->se_lastassoc = ticks; } } static const struct ieee80211_scanner sta_default = { .scan_name = "default", .scan_attach = sta_attach, .scan_detach = sta_detach, .scan_start = sta_start, .scan_restart = sta_restart, .scan_cancel = sta_cancel, .scan_end = sta_pick_bss, .scan_flush = sta_flush, .scan_add = sta_add, .scan_age = sta_age, .scan_iterate = sta_iterate, .scan_assoc_fail = sta_assoc_fail, .scan_assoc_success = sta_assoc_success, }; IEEE80211_SCANNER_ALG(sta, IEEE80211_M_STA, sta_default); /* * Adhoc mode-specific support. */ static const uint16_t adhocWorld[] = /* 36, 40, 44, 48 */ { 5180, 5200, 5220, 5240 }; static const uint16_t adhocFcc3[] = /* 36, 40, 44, 48 145, 149, 153, 157, 161, 165 */ { 5180, 5200, 5220, 5240, 5725, 5745, 5765, 5785, 5805, 5825 }; static const uint16_t adhocMkk[] = /* 34, 38, 42, 46 */ { 5170, 5190, 5210, 5230 }; static const uint16_t adhoc11b[] = /* 10, 11 */ { 2457, 2462 }; static const struct scanlist adhocScanTable[] = { { IEEE80211_MODE_11B, X(adhoc11b) }, { IEEE80211_MODE_11A, X(adhocWorld) }, { IEEE80211_MODE_11A, X(adhocFcc3) }, { IEEE80211_MODE_11B, X(adhocMkk) }, { .list = NULL } }; #undef X /* * Start an adhoc-mode scan by populating the channel list. */ static int adhoc_start(struct ieee80211_scan_state *ss, struct ieee80211vap *vap) { struct sta_table *st = ss->ss_priv; makescanlist(ss, vap, adhocScanTable); if (ss->ss_mindwell == 0) ss->ss_mindwell = msecs_to_ticks(200); /* 200ms */ if (ss->ss_maxdwell == 0) ss->ss_maxdwell = msecs_to_ticks(200); /* 200ms */ st->st_scangen++; st->st_newscan = 1; return 0; } /* * Select a channel to start an adhoc network on. * The channel list was populated with appropriate * channels so select one that looks least occupied. */ static struct ieee80211_channel * adhoc_pick_channel(struct ieee80211_scan_state *ss, int flags) { struct sta_table *st = ss->ss_priv; struct sta_entry *se; struct ieee80211_channel *c, *bestchan; int i, bestrssi, maxrssi; bestchan = NULL; bestrssi = -1; IEEE80211_SCAN_TABLE_LOCK(st); for (i = 0; i < ss->ss_last; i++) { c = ss->ss_chans[i]; /* never consider a channel with radar */ if (IEEE80211_IS_CHAN_RADAR(c)) continue; /* skip channels disallowed by regulatory settings */ if (IEEE80211_IS_CHAN_NOADHOC(c)) continue; /* check channel attributes for band compatibility */ if (flags != 0 && (c->ic_flags & flags) != flags) continue; maxrssi = 0; TAILQ_FOREACH(se, &st->st_entry, se_list) { if (se->base.se_chan != c) continue; if (se->base.se_rssi > maxrssi) maxrssi = se->base.se_rssi; } if (bestchan == NULL || maxrssi < bestrssi) bestchan = c; } IEEE80211_SCAN_TABLE_UNLOCK(st); return bestchan; } /* * Pick an ibss network to join or find a channel * to use to start an ibss network. */ static int adhoc_pick_bss(struct ieee80211_scan_state *ss, struct ieee80211vap *vap) { struct sta_table *st = ss->ss_priv; struct sta_entry *selbs; struct ieee80211_channel *chan; struct ieee80211com *ic = vap->iv_ic; KASSERT(vap->iv_opmode == IEEE80211_M_IBSS || vap->iv_opmode == IEEE80211_M_AHDEMO || vap->iv_opmode == IEEE80211_M_MBSS, ("wrong opmode %u", vap->iv_opmode)); if (st->st_newscan) { sta_update_notseen(st); st->st_newscan = 0; } if (ss->ss_flags & IEEE80211_SCAN_NOPICK) { /* * Manual/background scan, don't select+join the * bss, just return. The scanning framework will * handle notification that this has completed. */ ss->ss_flags &= ~IEEE80211_SCAN_NOPICK; return 1; } /* * Automatic sequencing; look for a candidate and * if found join the network. */ /* NB: unlocked read should be ok */ if (TAILQ_FIRST(&st->st_entry) == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: no scan candidate\n", __func__); if (ss->ss_flags & IEEE80211_SCAN_NOJOIN) return 0; notfound: /* NB: never auto-start a tdma network for slot !0 */ #ifdef IEEE80211_SUPPORT_TDMA if (vap->iv_des_nssid && ((vap->iv_caps & IEEE80211_C_TDMA) == 0 || ieee80211_tdma_getslot(vap) == 0)) { #else if (vap->iv_des_nssid) { #endif /* * No existing adhoc network to join and we have * an ssid; start one up. If no channel was * specified, try to select a channel. */ if (vap->iv_des_chan == IEEE80211_CHAN_ANYC || IEEE80211_IS_CHAN_RADAR(vap->iv_des_chan)) { chan = adhoc_pick_channel(ss, 0); } else chan = vap->iv_des_chan; if (chan != NULL) { struct ieee80211com *ic = vap->iv_ic; /* * Create a HT capable IBSS; the per-node * probe request/response will result in * "correct" rate control capabilities being * negotiated. */ chan = ieee80211_ht_adjust_channel(ic, chan, vap->iv_flags_ht); ieee80211_create_ibss(vap, chan); return 1; } } /* * If nothing suitable was found decrement * the failure counts so entries will be * reconsidered the next time around. We * really want to do this only for sta's * where we've previously had some success. */ sta_dec_fails(st); st->st_newscan = 1; return 0; /* restart scan */ } selbs = select_bss(ss, vap, IEEE80211_MSG_SCAN); if (ss->ss_flags & IEEE80211_SCAN_NOJOIN) return (selbs != NULL); if (selbs == NULL) goto notfound; chan = selbs->base.se_chan; if (selbs->se_flags & STA_DEMOTE11B) chan = demote11b(vap, chan); /* * If HT is available, make it a possibility here. * The intent is to enable HT20/HT40 when joining a non-HT * IBSS node; we can then advertise HT IEs and speak HT * to any subsequent nodes that support it. */ chan = ieee80211_ht_adjust_channel(ic, chan, vap->iv_flags_ht); if (!ieee80211_sta_join(vap, chan, &selbs->base)) goto notfound; return 1; /* terminate scan */ } /* * Age entries in the scan cache. */ static void adhoc_age(struct ieee80211_scan_state *ss) { struct sta_table *st = ss->ss_priv; struct sta_entry *se, *next; IEEE80211_SCAN_TABLE_LOCK(st); TAILQ_FOREACH_SAFE(se, &st->st_entry, se_list, next) { if (se->se_notseen > STA_PURGE_SCANS) { TAILQ_REMOVE(&st->st_entry, se, se_list); LIST_REMOVE(se, se_hash); ieee80211_ies_cleanup(&se->base.se_ies); IEEE80211_FREE(se, M_80211_SCAN); } } IEEE80211_SCAN_TABLE_UNLOCK(st); } static const struct ieee80211_scanner adhoc_default = { .scan_name = "default", .scan_attach = sta_attach, .scan_detach = sta_detach, .scan_start = adhoc_start, .scan_restart = sta_restart, .scan_cancel = sta_cancel, .scan_end = adhoc_pick_bss, .scan_flush = sta_flush, .scan_pickchan = adhoc_pick_channel, .scan_add = sta_add, .scan_age = adhoc_age, .scan_iterate = sta_iterate, .scan_assoc_fail = sta_assoc_fail, .scan_assoc_success = sta_assoc_success, }; IEEE80211_SCANNER_ALG(ibss, IEEE80211_M_IBSS, adhoc_default); IEEE80211_SCANNER_ALG(ahdemo, IEEE80211_M_AHDEMO, adhoc_default); static int ap_start(struct ieee80211_scan_state *ss, struct ieee80211vap *vap) { struct sta_table *st = ss->ss_priv; makescanlist(ss, vap, staScanTable); if (ss->ss_mindwell == 0) ss->ss_mindwell = msecs_to_ticks(200); /* 200ms */ if (ss->ss_maxdwell == 0) ss->ss_maxdwell = msecs_to_ticks(200); /* 200ms */ st->st_scangen++; st->st_newscan = 1; return 0; } /* * Cancel an ongoing scan. */ static int ap_cancel(struct ieee80211_scan_state *ss, struct ieee80211vap *vap) { return 0; } /* * Pick a quiet channel to use for ap operation. */ static struct ieee80211_channel * ap_pick_channel(struct ieee80211_scan_state *ss, int flags) { struct sta_table *st = ss->ss_priv; struct ieee80211_channel *bestchan = NULL; int i; /* XXX select channel more intelligently, e.g. channel spread, power */ /* NB: use scan list order to preserve channel preference */ for (i = 0; i < ss->ss_last; i++) { struct ieee80211_channel *chan = ss->ss_chans[i]; /* * If the channel is unoccupied the max rssi * should be zero; just take it. Otherwise * track the channel with the lowest rssi and * use that when all channels appear occupied. */ if (IEEE80211_IS_CHAN_RADAR(chan)) continue; if (IEEE80211_IS_CHAN_NOHOSTAP(chan)) continue; /* check channel attributes for band compatibility */ if (flags != 0 && (chan->ic_flags & flags) != flags) continue; KASSERT(sizeof(chan->ic_ieee) == 1, ("ic_chan size")); /* XXX channel have interference */ if (st->st_maxrssi[chan->ic_ieee] == 0) { /* XXX use other considerations */ return chan; } if (bestchan == NULL || st->st_maxrssi[chan->ic_ieee] < st->st_maxrssi[bestchan->ic_ieee]) bestchan = chan; } return bestchan; } /* * Pick a quiet channel to use for ap operation. */ static int ap_end(struct ieee80211_scan_state *ss, struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_channel *bestchan; KASSERT(vap->iv_opmode == IEEE80211_M_HOSTAP, ("wrong opmode %u", vap->iv_opmode)); bestchan = ap_pick_channel(ss, 0); if (bestchan == NULL) { /* no suitable channel, should not happen */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: no suitable channel! (should not happen)\n", __func__); /* XXX print something? */ return 0; /* restart scan */ } /* * If this is a dynamic turbo channel, start with the unboosted one. */ if (IEEE80211_IS_CHAN_TURBO(bestchan)) { bestchan = ieee80211_find_channel(ic, bestchan->ic_freq, bestchan->ic_flags & ~IEEE80211_CHAN_TURBO); if (bestchan == NULL) { /* should never happen ?? */ return 0; } } if (ss->ss_flags & (IEEE80211_SCAN_NOPICK | IEEE80211_SCAN_NOJOIN)) { /* * Manual/background scan, don't select+join the * bss, just return. The scanning framework will * handle notification that this has completed. */ ss->ss_flags &= ~IEEE80211_SCAN_NOPICK; return 1; } ieee80211_create_ibss(vap, ieee80211_ht_adjust_channel(ic, bestchan, vap->iv_flags_ht)); return 1; } static const struct ieee80211_scanner ap_default = { .scan_name = "default", .scan_attach = sta_attach, .scan_detach = sta_detach, .scan_start = ap_start, .scan_restart = sta_restart, .scan_cancel = ap_cancel, .scan_end = ap_end, .scan_flush = sta_flush, .scan_pickchan = ap_pick_channel, .scan_add = sta_add, .scan_age = adhoc_age, .scan_iterate = sta_iterate, .scan_assoc_success = sta_assoc_success, .scan_assoc_fail = sta_assoc_fail, }; IEEE80211_SCANNER_ALG(ap, IEEE80211_M_HOSTAP, ap_default); #ifdef IEEE80211_SUPPORT_MESH /* * Pick an mbss network to join or find a channel * to use to start an mbss network. */ static int mesh_pick_bss(struct ieee80211_scan_state *ss, struct ieee80211vap *vap) { struct sta_table *st = ss->ss_priv; struct ieee80211_mesh_state *ms = vap->iv_mesh; struct sta_entry *selbs; struct ieee80211_channel *chan; KASSERT(vap->iv_opmode == IEEE80211_M_MBSS, ("wrong opmode %u", vap->iv_opmode)); if (st->st_newscan) { sta_update_notseen(st); st->st_newscan = 0; } if (ss->ss_flags & IEEE80211_SCAN_NOPICK) { /* * Manual/background scan, don't select+join the * bss, just return. The scanning framework will * handle notification that this has completed. */ ss->ss_flags &= ~IEEE80211_SCAN_NOPICK; return 1; } /* * Automatic sequencing; look for a candidate and * if found join the network. */ /* NB: unlocked read should be ok */ if (TAILQ_FIRST(&st->st_entry) == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: no scan candidate\n", __func__); if (ss->ss_flags & IEEE80211_SCAN_NOJOIN) return 0; notfound: if (ms->ms_idlen != 0) { /* * No existing mbss network to join and we have * a meshid; start one up. If no channel was * specified, try to select a channel. */ if (vap->iv_des_chan == IEEE80211_CHAN_ANYC || IEEE80211_IS_CHAN_RADAR(vap->iv_des_chan)) { struct ieee80211com *ic = vap->iv_ic; chan = adhoc_pick_channel(ss, 0); if (chan != NULL) chan = ieee80211_ht_adjust_channel(ic, chan, vap->iv_flags_ht); } else chan = vap->iv_des_chan; if (chan != NULL) { ieee80211_create_ibss(vap, chan); return 1; } } /* * If nothing suitable was found decrement * the failure counts so entries will be * reconsidered the next time around. We * really want to do this only for sta's * where we've previously had some success. */ sta_dec_fails(st); st->st_newscan = 1; return 0; /* restart scan */ } selbs = select_bss(ss, vap, IEEE80211_MSG_SCAN); if (ss->ss_flags & IEEE80211_SCAN_NOJOIN) return (selbs != NULL); if (selbs == NULL) goto notfound; chan = selbs->base.se_chan; if (selbs->se_flags & STA_DEMOTE11B) chan = demote11b(vap, chan); if (!ieee80211_sta_join(vap, chan, &selbs->base)) goto notfound; return 1; /* terminate scan */ } static const struct ieee80211_scanner mesh_default = { .scan_name = "default", .scan_attach = sta_attach, .scan_detach = sta_detach, .scan_start = adhoc_start, .scan_restart = sta_restart, .scan_cancel = sta_cancel, .scan_end = mesh_pick_bss, .scan_flush = sta_flush, .scan_pickchan = adhoc_pick_channel, .scan_add = sta_add, .scan_age = adhoc_age, .scan_iterate = sta_iterate, .scan_assoc_fail = sta_assoc_fail, .scan_assoc_success = sta_assoc_success, }; IEEE80211_SCANNER_ALG(mesh, IEEE80211_M_MBSS, mesh_default); #endif /* IEEE80211_SUPPORT_MESH */ Index: head/sys/net80211/ieee80211_scan_sw.c =================================================================== --- head/sys/net80211/ieee80211_scan_sw.c (revision 297404) +++ head/sys/net80211/ieee80211_scan_sw.c (revision 297405) @@ -1,989 +1,989 @@ /*- * Copyright (c) 2002-2008 Sam Leffler, Errno Consulting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); /* * IEEE 802.11 scanning support. */ #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct scan_state { struct ieee80211_scan_state base; /* public state */ u_int ss_iflags; /* flags used internally */ #define ISCAN_MINDWELL 0x0001 /* min dwell time reached */ #define ISCAN_DISCARD 0x0002 /* discard rx'd frames */ #define ISCAN_CANCEL 0x0004 /* cancel current scan */ #define ISCAN_ABORT 0x0008 /* end the scan immediately */ #define ISCAN_RUNNING 0x0010 /* scan was started */ unsigned long ss_chanmindwell; /* min dwell on curchan */ unsigned long ss_scanend; /* time scan must stop */ u_int ss_duration; /* duration for next scan */ struct task ss_scan_start; /* scan start */ struct timeout_task ss_scan_curchan; /* scan execution */ }; #define SCAN_PRIVATE(ss) ((struct scan_state *) ss) /* * Amount of time to go off-channel during a background * scan. This value should be large enough to catch most * ap's but short enough that we can return on-channel * before our listen interval expires. * * XXX tunable * XXX check against configured listen interval */ #define IEEE80211_SCAN_OFFCHANNEL msecs_to_ticks(150) /* * Roaming-related defaults. RSSI thresholds are as returned by the * driver (.5dBm). Transmit rate thresholds are IEEE rate codes (i.e * .5M units) or MCS. */ /* rssi thresholds */ #define ROAM_RSSI_11A_DEFAULT 14 /* 11a bss */ #define ROAM_RSSI_11B_DEFAULT 14 /* 11b bss */ #define ROAM_RSSI_11BONLY_DEFAULT 14 /* 11b-only bss */ /* transmit rate thresholds */ #define ROAM_RATE_11A_DEFAULT 2*12 /* 11a bss */ #define ROAM_RATE_11B_DEFAULT 2*5 /* 11b bss */ #define ROAM_RATE_11BONLY_DEFAULT 2*1 /* 11b-only bss */ #define ROAM_RATE_HALF_DEFAULT 2*6 /* half-width 11a/g bss */ #define ROAM_RATE_QUARTER_DEFAULT 2*3 /* quarter-width 11a/g bss */ #define ROAM_MCS_11N_DEFAULT (1 | IEEE80211_RATE_MCS) /* 11n bss */ static void scan_curchan(struct ieee80211_scan_state *, unsigned long); static void scan_mindwell(struct ieee80211_scan_state *); static void scan_signal(struct ieee80211_scan_state *, int); static void scan_signal_locked(struct ieee80211_scan_state *, int); static void scan_start(void *, int); static void scan_curchan_task(void *, int); static void scan_end(struct ieee80211_scan_state *, int); static void scan_done(struct ieee80211_scan_state *, int); MALLOC_DEFINE(M_80211_SCAN, "80211scan", "802.11 scan state"); static void ieee80211_swscan_detach(struct ieee80211com *ic) { struct ieee80211_scan_state *ss = ic->ic_scan; if (ss != NULL) { scan_signal(ss, ISCAN_ABORT); ieee80211_draintask(ic, &SCAN_PRIVATE(ss)->ss_scan_start); taskqueue_drain_timeout(ic->ic_tq, &SCAN_PRIVATE(ss)->ss_scan_curchan); KASSERT((ic->ic_flags & IEEE80211_F_SCAN) == 0, ("scan still running")); /* * For now, do the ss_ops detach here rather * than ieee80211_scan_detach(). * * I'll figure out how to cleanly split things up * at a later date. */ if (ss->ss_ops != NULL) { ss->ss_ops->scan_detach(ss); ss->ss_ops = NULL; } ic->ic_scan = NULL; IEEE80211_FREE(SCAN_PRIVATE(ss), M_80211_SCAN); } } static void ieee80211_swscan_vattach(struct ieee80211vap *vap) { /* nothing to do for now */ /* * TODO: all of the vap scan calls should be methods! */ } static void ieee80211_swscan_vdetach(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_scan_state *ss = ic->ic_scan; IEEE80211_LOCK_ASSERT(ic); if (ss != NULL && ss->ss_vap == vap && (ic->ic_flags & IEEE80211_F_SCAN)) scan_signal_locked(ss, ISCAN_ABORT); } static void ieee80211_swscan_set_scan_duration(struct ieee80211vap *vap, u_int duration) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_scan_state *ss = ic->ic_scan; IEEE80211_LOCK_ASSERT(ic); /* NB: flush frames rx'd before 1st channel change */ SCAN_PRIVATE(ss)->ss_iflags |= ISCAN_DISCARD; SCAN_PRIVATE(ss)->ss_duration = duration; } /* * Start a scan unless one is already going. */ static int ieee80211_swscan_start_scan_locked(const struct ieee80211_scanner *scan, struct ieee80211vap *vap, int flags, u_int duration, u_int mindwell, u_int maxdwell, u_int nssid, const struct ieee80211_scan_ssid ssids[]) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_scan_state *ss = ic->ic_scan; IEEE80211_LOCK_ASSERT(ic); if (ic->ic_flags & IEEE80211_F_CSAPENDING) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: scan inhibited by pending channel change\n", __func__); } else if ((ic->ic_flags & IEEE80211_F_SCAN) == 0) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: %s scan, duration %u mindwell %u maxdwell %u, desired mode %s, %s%s%s%s%s%s\n" , __func__ , flags & IEEE80211_SCAN_ACTIVE ? "active" : "passive" , duration, mindwell, maxdwell , ieee80211_phymode_name[vap->iv_des_mode] , flags & IEEE80211_SCAN_FLUSH ? "flush" : "append" , flags & IEEE80211_SCAN_NOPICK ? ", nopick" : "" , flags & IEEE80211_SCAN_NOJOIN ? ", nojoin" : "" , flags & IEEE80211_SCAN_NOBCAST ? ", nobcast" : "" , flags & IEEE80211_SCAN_PICK1ST ? ", pick1st" : "" , flags & IEEE80211_SCAN_ONCE ? ", once" : "" ); ieee80211_scan_update_locked(vap, scan); if (ss->ss_ops != NULL) { if ((flags & IEEE80211_SCAN_NOSSID) == 0) ieee80211_scan_copy_ssid(vap, ss, nssid, ssids); /* NB: top 4 bits for internal use */ ss->ss_flags = flags & 0xfff; if (ss->ss_flags & IEEE80211_SCAN_ACTIVE) vap->iv_stats.is_scan_active++; else vap->iv_stats.is_scan_passive++; if (flags & IEEE80211_SCAN_FLUSH) ss->ss_ops->scan_flush(ss); if (flags & IEEE80211_SCAN_BGSCAN) ic->ic_flags_ext |= IEEE80211_FEXT_BGSCAN; /* Set duration for this particular scan */ ieee80211_swscan_set_scan_duration(vap, duration); ss->ss_next = 0; ss->ss_mindwell = mindwell; ss->ss_maxdwell = maxdwell; /* NB: scan_start must be before the scan runtask */ ss->ss_ops->scan_start(ss, vap); #ifdef IEEE80211_DEBUG if (ieee80211_msg_scan(vap)) ieee80211_scan_dump(ss); #endif /* IEEE80211_DEBUG */ ic->ic_flags |= IEEE80211_F_SCAN; /* Start scan task */ ieee80211_runtask(ic, &SCAN_PRIVATE(ss)->ss_scan_start); } return 1; } else { IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: %s scan already in progress\n", __func__, ss->ss_flags & IEEE80211_SCAN_ACTIVE ? "active" : "passive"); } return 0; } /* * Start a scan unless one is already going. * * Called without the comlock held; grab the comlock as appropriate. */ static int ieee80211_swscan_start_scan(const struct ieee80211_scanner *scan, struct ieee80211vap *vap, int flags, u_int duration, u_int mindwell, u_int maxdwell, u_int nssid, const struct ieee80211_scan_ssid ssids[]) { struct ieee80211com *ic = vap->iv_ic; int result; IEEE80211_UNLOCK_ASSERT(ic); IEEE80211_LOCK(ic); result = ieee80211_swscan_start_scan_locked(scan, vap, flags, duration, mindwell, maxdwell, nssid, ssids); IEEE80211_UNLOCK(ic); return result; } /* * Check the scan cache for an ap/channel to use; if that * fails then kick off a new scan. * * Called with the comlock held. * * XXX TODO: split out! */ static int ieee80211_swscan_check_scan(const struct ieee80211_scanner *scan, struct ieee80211vap *vap, int flags, u_int duration, u_int mindwell, u_int maxdwell, u_int nssid, const struct ieee80211_scan_ssid ssids[]) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_scan_state *ss = ic->ic_scan; int result; IEEE80211_LOCK_ASSERT(ic); if (ss->ss_ops != NULL) { /* XXX verify ss_ops matches vap->iv_opmode */ if ((flags & IEEE80211_SCAN_NOSSID) == 0) { /* * Update the ssid list and mark flags so if * we call start_scan it doesn't duplicate work. */ ieee80211_scan_copy_ssid(vap, ss, nssid, ssids); flags |= IEEE80211_SCAN_NOSSID; } if ((ic->ic_flags & IEEE80211_F_SCAN) == 0 && (flags & IEEE80211_SCAN_FLUSH) == 0 && - time_before(ticks, ic->ic_lastscan + vap->iv_scanvalid)) { + ieee80211_time_before(ticks, ic->ic_lastscan + vap->iv_scanvalid)) { /* * We're not currently scanning and the cache is * deemed hot enough to consult. Lock out others * by marking IEEE80211_F_SCAN while we decide if * something is already in the scan cache we can * use. Also discard any frames that might come * in while temporarily marked as scanning. */ SCAN_PRIVATE(ss)->ss_iflags |= ISCAN_DISCARD; ic->ic_flags |= IEEE80211_F_SCAN; /* NB: need to use supplied flags in check */ ss->ss_flags = flags & 0xff; result = ss->ss_ops->scan_end(ss, vap); ic->ic_flags &= ~IEEE80211_F_SCAN; SCAN_PRIVATE(ss)->ss_iflags &= ~ISCAN_DISCARD; if (result) { ieee80211_notify_scan_done(vap); return 1; } } } result = ieee80211_swscan_start_scan_locked(scan, vap, flags, duration, mindwell, maxdwell, nssid, ssids); return result; } /* * Restart a previous scan. If the previous scan completed * then we start again using the existing channel list. */ static int ieee80211_swscan_bg_scan(const struct ieee80211_scanner *scan, struct ieee80211vap *vap, int flags) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_scan_state *ss = ic->ic_scan; /* XXX assert unlocked? */ // IEEE80211_UNLOCK_ASSERT(ic); IEEE80211_LOCK(ic); if ((ic->ic_flags & IEEE80211_F_SCAN) == 0) { u_int duration; /* * Go off-channel for a fixed interval that is large * enough to catch most ap's but short enough that * we can return on-channel before our listen interval * expires. */ duration = IEEE80211_SCAN_OFFCHANNEL; IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: %s scan, ticks %u duration %u\n", __func__, ss->ss_flags & IEEE80211_SCAN_ACTIVE ? "active" : "passive", ticks, duration); ieee80211_scan_update_locked(vap, scan); if (ss->ss_ops != NULL) { ss->ss_vap = vap; /* * A background scan does not select a new sta; it * just refreshes the scan cache. Also, indicate * the scan logic should follow the beacon schedule: * we go off-channel and scan for a while, then * return to the bss channel to receive a beacon, * then go off-channel again. All during this time * we notify the ap we're in power save mode. When * the scan is complete we leave power save mode. * If any beacon indicates there are frames pending * for us then we drop out of power save mode * (and background scan) automatically by way of the * usual sta power save logic. */ ss->ss_flags |= IEEE80211_SCAN_NOPICK | IEEE80211_SCAN_BGSCAN | flags ; /* if previous scan completed, restart */ if (ss->ss_next >= ss->ss_last) { if (ss->ss_flags & IEEE80211_SCAN_ACTIVE) vap->iv_stats.is_scan_active++; else vap->iv_stats.is_scan_passive++; /* * NB: beware of the scan cache being flushed; * if the channel list is empty use the * scan_start method to populate it. */ ss->ss_next = 0; if (ss->ss_last != 0) ss->ss_ops->scan_restart(ss, vap); else { ss->ss_ops->scan_start(ss, vap); #ifdef IEEE80211_DEBUG if (ieee80211_msg_scan(vap)) ieee80211_scan_dump(ss); #endif /* IEEE80211_DEBUG */ } } ieee80211_swscan_set_scan_duration(vap, duration); ss->ss_maxdwell = duration; ic->ic_flags |= IEEE80211_F_SCAN; ic->ic_flags_ext |= IEEE80211_FEXT_BGSCAN; ieee80211_runtask(ic, &SCAN_PRIVATE(ss)->ss_scan_start); } else { /* XXX msg+stat */ } } else { IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: %s scan already in progress\n", __func__, ss->ss_flags & IEEE80211_SCAN_ACTIVE ? "active" : "passive"); } IEEE80211_UNLOCK(ic); /* NB: racey, does it matter? */ return (ic->ic_flags & IEEE80211_F_SCAN); } static void cancel_scan(struct ieee80211vap *vap, int any, const char *func) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_scan_state *ss = ic->ic_scan; IEEE80211_LOCK(ic); if ((ic->ic_flags & IEEE80211_F_SCAN) && (any || ss->ss_vap == vap) && (SCAN_PRIVATE(ss)->ss_iflags & ISCAN_CANCEL) == 0) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: cancel %s scan\n", func, ss->ss_flags & IEEE80211_SCAN_ACTIVE ? "active" : "passive"); /* clear bg scan NOPICK */ ss->ss_flags &= ~IEEE80211_SCAN_NOPICK; /* mark cancel request and wake up the scan task */ scan_signal_locked(ss, ISCAN_CANCEL); } else { IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: called; F_SCAN=%d, vap=%s, CANCEL=%d\n", func, !! (ic->ic_flags & IEEE80211_F_SCAN), (ss->ss_vap == vap ? "match" : "nomatch"), !! (SCAN_PRIVATE(ss)->ss_iflags & ISCAN_CANCEL)); } IEEE80211_UNLOCK(ic); } /* * Cancel any scan currently going on for the specified vap. */ static void ieee80211_swscan_cancel_scan(struct ieee80211vap *vap) { cancel_scan(vap, 0, __func__); } /* * Cancel any scan currently going on. */ static void ieee80211_swscan_cancel_anyscan(struct ieee80211vap *vap) { cancel_scan(vap, 1, __func__); } /* * Public access to scan_next for drivers that manage * scanning themselves (e.g. for firmware-based devices). */ static void ieee80211_swscan_scan_next(struct ieee80211vap *vap) { struct ieee80211_scan_state *ss = vap->iv_ic->ic_scan; IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: called\n", __func__); /* wake up the scan task */ scan_signal(ss, 0); } /* * Public access to scan_next for drivers that are not able to scan single * channels (e.g. for firmware-based devices). */ static void ieee80211_swscan_scan_done(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_scan_state *ss = ic->ic_scan; IEEE80211_LOCK_ASSERT(ic); scan_signal_locked(ss, 0); } /* * Probe the curent channel, if allowed, while scanning. * If the channel is not marked passive-only then send * a probe request immediately. Otherwise mark state and * listen for beacons on the channel; if we receive something * then we'll transmit a probe request. */ static void ieee80211_swscan_probe_curchan(struct ieee80211vap *vap, int force) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_scan_state *ss = ic->ic_scan; struct ifnet *ifp = vap->iv_ifp; int i; /* * Send directed probe requests followed by any * broadcast probe request. * XXX remove dependence on ic/vap->iv_bss */ for (i = 0; i < ss->ss_nssid; i++) ieee80211_send_probereq(vap->iv_bss, vap->iv_myaddr, ifp->if_broadcastaddr, ifp->if_broadcastaddr, ss->ss_ssid[i].ssid, ss->ss_ssid[i].len); if ((ss->ss_flags & IEEE80211_SCAN_NOBCAST) == 0) ieee80211_send_probereq(vap->iv_bss, vap->iv_myaddr, ifp->if_broadcastaddr, ifp->if_broadcastaddr, "", 0); } /* * Scan curchan. If this is an active scan and the channel * is not marked passive then send probe request frame(s). * Arrange for the channel change after maxdwell ticks. */ static void scan_curchan(struct ieee80211_scan_state *ss, unsigned long maxdwell) { struct ieee80211vap *vap = ss->ss_vap; struct ieee80211com *ic = ss->ss_ic; IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: calling; maxdwell=%lu\n", __func__, maxdwell); IEEE80211_LOCK(ic); if (ss->ss_flags & IEEE80211_SCAN_ACTIVE) ieee80211_probe_curchan(vap, 0); taskqueue_enqueue_timeout(ic->ic_tq, &SCAN_PRIVATE(ss)->ss_scan_curchan, maxdwell); IEEE80211_UNLOCK(ic); } static void scan_signal(struct ieee80211_scan_state *ss, int iflags) { struct ieee80211com *ic = ss->ss_ic; IEEE80211_UNLOCK_ASSERT(ic); IEEE80211_LOCK(ic); scan_signal_locked(ss, iflags); IEEE80211_UNLOCK(ic); } static void scan_signal_locked(struct ieee80211_scan_state *ss, int iflags) { struct scan_state *ss_priv = SCAN_PRIVATE(ss); struct timeout_task *scan_task = &ss_priv->ss_scan_curchan; struct ieee80211com *ic = ss->ss_ic; IEEE80211_LOCK_ASSERT(ic); ss_priv->ss_iflags |= iflags; if (ss_priv->ss_iflags & ISCAN_RUNNING) { if (taskqueue_cancel_timeout(ic->ic_tq, scan_task, NULL) == 0) taskqueue_enqueue_timeout(ic->ic_tq, scan_task, 0); } } /* * Handle mindwell requirements completed; initiate a channel * change to the next channel asap. */ static void scan_mindwell(struct ieee80211_scan_state *ss) { IEEE80211_DPRINTF(ss->ss_vap, IEEE80211_MSG_SCAN, "%s: called\n", __func__); scan_signal(ss, 0); } static void scan_start(void *arg, int pending) { #define ISCAN_REP (ISCAN_MINDWELL | ISCAN_DISCARD) struct ieee80211_scan_state *ss = (struct ieee80211_scan_state *) arg; struct scan_state *ss_priv = SCAN_PRIVATE(ss); struct ieee80211vap *vap = ss->ss_vap; struct ieee80211com *ic = ss->ss_ic; IEEE80211_LOCK(ic); if (vap == NULL || (ic->ic_flags & IEEE80211_F_SCAN) == 0 || (ss_priv->ss_iflags & ISCAN_ABORT)) { /* Cancelled before we started */ scan_done(ss, 0); return; } if (ss->ss_next == ss->ss_last) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: no channels to scan\n", __func__); scan_done(ss, 1); return; } if (vap->iv_opmode == IEEE80211_M_STA && vap->iv_state == IEEE80211_S_RUN) { if ((vap->iv_bss->ni_flags & IEEE80211_NODE_PWR_MGT) == 0) { /* Enable station power save mode */ vap->iv_sta_ps(vap, 1); /* Wait until null data frame will be ACK'ed */ mtx_sleep(vap, IEEE80211_LOCK_OBJ(ic), PCATCH, "sta_ps", msecs_to_ticks(10)); if (ss_priv->ss_iflags & ISCAN_ABORT) { scan_done(ss, 0); return; } } } ss_priv->ss_scanend = ticks + ss_priv->ss_duration; /* XXX scan state can change! Re-validate scan state! */ IEEE80211_UNLOCK(ic); ic->ic_scan_start(ic); /* notify driver */ scan_curchan_task(ss, 0); } static void scan_curchan_task(void *arg, int pending) { struct ieee80211_scan_state *ss = arg; struct scan_state *ss_priv = SCAN_PRIVATE(ss); struct ieee80211com *ic = ss->ss_ic; struct ieee80211_channel *chan; unsigned long maxdwell; int scandone; IEEE80211_LOCK(ic); end: scandone = (ss->ss_next >= ss->ss_last) || (ss_priv->ss_iflags & ISCAN_CANCEL) != 0; IEEE80211_DPRINTF(ss->ss_vap, IEEE80211_MSG_SCAN, "%s: loop start; scandone=%d\n", __func__, scandone); if (scandone || (ss->ss_flags & IEEE80211_SCAN_GOTPICK) || (ss_priv->ss_iflags & ISCAN_ABORT) || - time_after(ticks + ss->ss_mindwell, ss_priv->ss_scanend)) { + ieee80211_time_after(ticks + ss->ss_mindwell, ss_priv->ss_scanend)) { ss_priv->ss_iflags &= ~ISCAN_RUNNING; scan_end(ss, scandone); return; } else ss_priv->ss_iflags |= ISCAN_RUNNING; chan = ss->ss_chans[ss->ss_next++]; /* * Watch for truncation due to the scan end time. */ - if (time_after(ticks + ss->ss_maxdwell, ss_priv->ss_scanend)) + if (ieee80211_time_after(ticks + ss->ss_maxdwell, ss_priv->ss_scanend)) maxdwell = ss_priv->ss_scanend - ticks; else maxdwell = ss->ss_maxdwell; IEEE80211_DPRINTF(ss->ss_vap, IEEE80211_MSG_SCAN, "%s: chan %3d%c -> %3d%c [%s, dwell min %lums max %lums]\n", __func__, ieee80211_chan2ieee(ic, ic->ic_curchan), ieee80211_channel_type_char(ic->ic_curchan), ieee80211_chan2ieee(ic, chan), ieee80211_channel_type_char(chan), (ss->ss_flags & IEEE80211_SCAN_ACTIVE) && (chan->ic_flags & IEEE80211_CHAN_PASSIVE) == 0 ? "active" : "passive", ticks_to_msecs(ss->ss_mindwell), ticks_to_msecs(maxdwell)); /* * Potentially change channel and phy mode. */ ic->ic_curchan = chan; ic->ic_rt = ieee80211_get_ratetable(chan); IEEE80211_UNLOCK(ic); /* * Perform the channel change and scan unlocked so the driver * may sleep. Once set_channel returns the hardware has * completed the channel change. */ ic->ic_set_channel(ic); ieee80211_radiotap_chan_change(ic); /* * Scan curchan. Drivers for "intelligent hardware" * override ic_scan_curchan to tell the device to do * the work. Otherwise we manage the work ourselves; * sending a probe request (as needed), and arming the * timeout to switch channels after maxdwell ticks. * * scan_curchan should only pause for the time required to * prepare/initiate the hardware for the scan (if at all). */ ic->ic_scan_curchan(ss, maxdwell); IEEE80211_LOCK(ic); /* XXX scan state can change! Re-validate scan state! */ ss_priv->ss_chanmindwell = ticks + ss->ss_mindwell; /* clear mindwell lock and initial channel change flush */ ss_priv->ss_iflags &= ~ISCAN_REP; if (ss_priv->ss_iflags & (ISCAN_CANCEL|ISCAN_ABORT)) goto end; IEEE80211_DPRINTF(ss->ss_vap, IEEE80211_MSG_SCAN, "%s: waiting\n", __func__); IEEE80211_UNLOCK(ic); } static void scan_end(struct ieee80211_scan_state *ss, int scandone) { struct scan_state *ss_priv = SCAN_PRIVATE(ss); struct ieee80211vap *vap = ss->ss_vap; struct ieee80211com *ic = ss->ss_ic; IEEE80211_LOCK_ASSERT(ic); IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: out\n", __func__); if (ss_priv->ss_iflags & ISCAN_ABORT) { scan_done(ss, scandone); return; } IEEE80211_UNLOCK(ic); ic->ic_scan_end(ic); /* notify driver */ IEEE80211_LOCK(ic); /* XXX scan state can change! Re-validate scan state! */ /* * Since a cancellation may have occured during one of the * driver calls (whilst unlocked), update scandone. */ if (scandone == 0 && (ss_priv->ss_iflags & ISCAN_CANCEL) != 0) { /* XXX printf? */ if_printf(vap->iv_ifp, "%s: OOPS! scan cancelled during driver call (1)!\n", __func__); scandone = 1; } /* * Record scan complete time. Note that we also do * this when canceled so any background scan will * not be restarted for a while. */ if (scandone) ic->ic_lastscan = ticks; /* return to the bss channel */ if (ic->ic_bsschan != IEEE80211_CHAN_ANYC && ic->ic_curchan != ic->ic_bsschan) { ieee80211_setupcurchan(ic, ic->ic_bsschan); IEEE80211_UNLOCK(ic); ic->ic_set_channel(ic); ieee80211_radiotap_chan_change(ic); IEEE80211_LOCK(ic); } /* clear internal flags and any indication of a pick */ ss_priv->ss_iflags &= ~ISCAN_REP; ss->ss_flags &= ~IEEE80211_SCAN_GOTPICK; /* * If not canceled and scan completed, do post-processing. * If the callback function returns 0, then it wants to * continue/restart scanning. Unfortunately we needed to * notify the driver to end the scan above to avoid having * rx frames alter the scan candidate list. */ if ((ss_priv->ss_iflags & ISCAN_CANCEL) == 0 && !ss->ss_ops->scan_end(ss, vap) && (ss->ss_flags & IEEE80211_SCAN_ONCE) == 0 && - time_before(ticks + ss->ss_mindwell, ss_priv->ss_scanend)) { + ieee80211_time_before(ticks + ss->ss_mindwell, ss_priv->ss_scanend)) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: done, restart " "[ticks %u, dwell min %lu scanend %lu]\n", __func__, ticks, ss->ss_mindwell, ss_priv->ss_scanend); ss->ss_next = 0; /* reset to begining */ if (ss->ss_flags & IEEE80211_SCAN_ACTIVE) vap->iv_stats.is_scan_active++; else vap->iv_stats.is_scan_passive++; ss->ss_ops->scan_restart(ss, vap); /* XXX? */ ieee80211_runtask(ic, &ss_priv->ss_scan_start); IEEE80211_UNLOCK(ic); return; } /* past here, scandone is ``true'' if not in bg mode */ if ((ss->ss_flags & IEEE80211_SCAN_BGSCAN) == 0) scandone = 1; IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: %s, [ticks %u, dwell min %lu scanend %lu]\n", __func__, scandone ? "done" : "stopped", ticks, ss->ss_mindwell, ss_priv->ss_scanend); /* * Since a cancellation may have occured during one of the * driver calls (whilst unlocked), update scandone. */ if (scandone == 0 && (ss_priv->ss_iflags & ISCAN_CANCEL) != 0) { /* XXX printf? */ if_printf(vap->iv_ifp, "%s: OOPS! scan cancelled during driver call (2)!\n", __func__); scandone = 1; } scan_done(ss, scandone); } static void scan_done(struct ieee80211_scan_state *ss, int scandone) { struct scan_state *ss_priv = SCAN_PRIVATE(ss); struct ieee80211com *ic = ss->ss_ic; struct ieee80211vap *vap = ss->ss_vap; IEEE80211_LOCK_ASSERT(ic); /* * Clear the SCAN bit first in case frames are * pending on the station power save queue. If * we defer this then the dispatch of the frames * may generate a request to cancel scanning. */ ic->ic_flags &= ~IEEE80211_F_SCAN; /* * Drop out of power save mode when a scan has * completed. If this scan was prematurely terminated * because it is a background scan then don't notify * the ap; we'll either return to scanning after we * receive the beacon frame or we'll drop out of power * save mode because the beacon indicates we have frames * waiting for us. */ if (scandone) { vap->iv_sta_ps(vap, 0); if (ss->ss_next >= ss->ss_last) { ieee80211_notify_scan_done(vap); ic->ic_flags_ext &= ~IEEE80211_FEXT_BGSCAN; } } ss_priv->ss_iflags &= ~(ISCAN_CANCEL|ISCAN_ABORT); ss_priv->ss_scanend = 0; ss->ss_flags &= ~(IEEE80211_SCAN_ONCE | IEEE80211_SCAN_PICK1ST); IEEE80211_UNLOCK(ic); #undef ISCAN_REP } /* * Process a beacon or probe response frame. */ static void ieee80211_swscan_add_scan(struct ieee80211vap *vap, struct ieee80211_channel *curchan, const struct ieee80211_scanparams *sp, const struct ieee80211_frame *wh, int subtype, int rssi, int noise) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_scan_state *ss = ic->ic_scan; /* XXX locking */ /* * Frames received during startup are discarded to avoid * using scan state setup on the initial entry to the timer * callback. This can occur because the device may enable * rx prior to our doing the initial channel change in the * timer routine. */ if (SCAN_PRIVATE(ss)->ss_iflags & ISCAN_DISCARD) return; #ifdef IEEE80211_DEBUG if (ieee80211_msg_scan(vap) && (ic->ic_flags & IEEE80211_F_SCAN)) ieee80211_scan_dump_probe_beacon(subtype, 1, wh->i_addr2, sp, rssi); #endif if (ss->ss_ops != NULL && ss->ss_ops->scan_add(ss, curchan, sp, wh, subtype, rssi, noise)) { /* * If we've reached the min dwell time terminate * the timer so we'll switch to the next channel. */ if ((SCAN_PRIVATE(ss)->ss_iflags & ISCAN_MINDWELL) == 0 && - time_after_eq(ticks, SCAN_PRIVATE(ss)->ss_chanmindwell)) { + ieee80211_time_after_eq(ticks, SCAN_PRIVATE(ss)->ss_chanmindwell)) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: chan %3d%c min dwell met (%u > %lu)\n", __func__, ieee80211_chan2ieee(ic, ic->ic_curchan), ieee80211_channel_type_char(ic->ic_curchan), ticks, SCAN_PRIVATE(ss)->ss_chanmindwell); SCAN_PRIVATE(ss)->ss_iflags |= ISCAN_MINDWELL; /* * NB: trigger at next clock tick or wait for the * hardware. */ ic->ic_scan_mindwell(ss); } } } static struct ieee80211_scan_methods swscan_methods = { .sc_attach = ieee80211_swscan_attach, .sc_detach = ieee80211_swscan_detach, .sc_vattach = ieee80211_swscan_vattach, .sc_vdetach = ieee80211_swscan_vdetach, .sc_set_scan_duration = ieee80211_swscan_set_scan_duration, .sc_start_scan = ieee80211_swscan_start_scan, .sc_check_scan = ieee80211_swscan_check_scan, .sc_bg_scan = ieee80211_swscan_bg_scan, .sc_cancel_scan = ieee80211_swscan_cancel_scan, .sc_cancel_anyscan = ieee80211_swscan_cancel_anyscan, .sc_scan_next = ieee80211_swscan_scan_next, .sc_scan_done = ieee80211_swscan_scan_done, .sc_scan_probe_curchan = ieee80211_swscan_probe_curchan, .sc_add_scan = ieee80211_swscan_add_scan }; /* * Default scan attach method. */ void ieee80211_swscan_attach(struct ieee80211com *ic) { struct scan_state *ss; /* * Setup the default methods */ ic->ic_scan_methods = &swscan_methods; /* Allocate initial scan state */ ss = (struct scan_state *) IEEE80211_MALLOC(sizeof(struct scan_state), M_80211_SCAN, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (ss == NULL) { ic->ic_scan = NULL; return; } TASK_INIT(&ss->ss_scan_start, 0, scan_start, ss); TIMEOUT_TASK_INIT(ic->ic_tq, &ss->ss_scan_curchan, 0, scan_curchan_task, ss); ic->ic_scan = &ss->base; ss->base.ss_ic = ic; ic->ic_scan_curchan = scan_curchan; ic->ic_scan_mindwell = scan_mindwell; } Index: head/sys/net80211/ieee80211_sta.c =================================================================== --- head/sys/net80211/ieee80211_sta.c (revision 297404) +++ head/sys/net80211/ieee80211_sta.c (revision 297405) @@ -1,1871 +1,1871 @@ /*- * Copyright (c) 2007-2008 Sam Leffler, Errno Consulting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #ifdef __FreeBSD__ __FBSDID("$FreeBSD$"); #endif /* * IEEE 802.11 Station mode support. */ #include "opt_inet.h" #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IEEE80211_SUPPORT_SUPERG #include #endif #include #include #define IEEE80211_RATE2MBS(r) (((r) & IEEE80211_RATE_VAL) / 2) static void sta_vattach(struct ieee80211vap *); static void sta_beacon_miss(struct ieee80211vap *); static int sta_newstate(struct ieee80211vap *, enum ieee80211_state, int); static int sta_input(struct ieee80211_node *, struct mbuf *, const struct ieee80211_rx_stats *, int, int); static void sta_recv_mgmt(struct ieee80211_node *, struct mbuf *, int subtype, const struct ieee80211_rx_stats *, int rssi, int nf); static void sta_recv_ctl(struct ieee80211_node *, struct mbuf *, int subtype); void ieee80211_sta_attach(struct ieee80211com *ic) { ic->ic_vattach[IEEE80211_M_STA] = sta_vattach; } void ieee80211_sta_detach(struct ieee80211com *ic) { } static void sta_vdetach(struct ieee80211vap *vap) { } static void sta_vattach(struct ieee80211vap *vap) { vap->iv_newstate = sta_newstate; vap->iv_input = sta_input; vap->iv_recv_mgmt = sta_recv_mgmt; vap->iv_recv_ctl = sta_recv_ctl; vap->iv_opdetach = sta_vdetach; vap->iv_bmiss = sta_beacon_miss; } /* * Handle a beacon miss event. The common code filters out * spurious events that can happen when scanning and/or before * reaching RUN state. */ static void sta_beacon_miss(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; IEEE80211_LOCK_ASSERT(ic); KASSERT((ic->ic_flags & IEEE80211_F_SCAN) == 0, ("scanning")); KASSERT(vap->iv_state >= IEEE80211_S_RUN, ("wrong state %s", ieee80211_state_name[vap->iv_state])); IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE | IEEE80211_MSG_DEBUG, "beacon miss, mode %s state %s\n", ieee80211_opmode_name[vap->iv_opmode], ieee80211_state_name[vap->iv_state]); if (vap->iv_state == IEEE80211_S_CSA) { /* * A Channel Switch is pending; assume we missed the * beacon that would've completed the process and just * force the switch. If we made a mistake we'll not * find the AP on the new channel and fall back to a * normal scan. */ ieee80211_csa_completeswitch(ic); return; } if (++vap->iv_bmiss_count < vap->iv_bmiss_max) { /* * Send a directed probe req before falling back to a * scan; if we receive a response ic_bmiss_count will * be reset. Some cards mistakenly report beacon miss * so this avoids the expensive scan if the ap is * still there. */ ieee80211_send_probereq(vap->iv_bss, vap->iv_myaddr, vap->iv_bss->ni_bssid, vap->iv_bss->ni_bssid, vap->iv_bss->ni_essid, vap->iv_bss->ni_esslen); return; } callout_stop(&vap->iv_swbmiss); vap->iv_bmiss_count = 0; vap->iv_stats.is_beacon_miss++; if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) { #ifdef IEEE80211_SUPPORT_SUPERG struct ieee80211com *ic = vap->iv_ic; /* * If we receive a beacon miss interrupt when using * dynamic turbo, attempt to switch modes before * reassociating. */ if (IEEE80211_ATH_CAP(vap, vap->iv_bss, IEEE80211_NODE_TURBOP)) ieee80211_dturbo_switch(vap, ic->ic_bsschan->ic_flags ^ IEEE80211_CHAN_TURBO); #endif /* * Try to reassociate before scanning for a new ap. */ ieee80211_new_state(vap, IEEE80211_S_ASSOC, 1); } else { /* * Somebody else is controlling state changes (e.g. * a user-mode app) don't do anything that would * confuse them; just drop into scan mode so they'll * notified of the state change and given control. */ ieee80211_new_state(vap, IEEE80211_S_SCAN, 0); } } /* * Handle deauth with reason. We retry only for * the cases where we might succeed. Otherwise * we downgrade the ap and scan. */ static void sta_authretry(struct ieee80211vap *vap, struct ieee80211_node *ni, int reason) { switch (reason) { case IEEE80211_STATUS_SUCCESS: /* NB: MLME assoc */ case IEEE80211_STATUS_TIMEOUT: case IEEE80211_REASON_ASSOC_EXPIRE: case IEEE80211_REASON_NOT_AUTHED: case IEEE80211_REASON_NOT_ASSOCED: case IEEE80211_REASON_ASSOC_LEAVE: case IEEE80211_REASON_ASSOC_NOT_AUTHED: IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, 1); break; default: ieee80211_scan_assoc_fail(vap, vap->iv_bss->ni_macaddr, reason); if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) ieee80211_check_scan_current(vap); break; } } static void sta_swbmiss_start(struct ieee80211vap *vap) { if (vap->iv_flags_ext & IEEE80211_FEXT_SWBMISS) { /* * Start s/w beacon miss timer for devices w/o * hardware support. We fudge a bit here since * we're doing this in software. */ vap->iv_swbmiss_period = IEEE80211_TU_TO_TICKS( 2 * vap->iv_bmissthreshold * vap->iv_bss->ni_intval); vap->iv_swbmiss_count = 0; callout_reset(&vap->iv_swbmiss, vap->iv_swbmiss_period, ieee80211_swbmiss, vap); } } /* * IEEE80211_M_STA vap state machine handler. * This routine handles the main states in the 802.11 protocol. */ static int sta_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; enum ieee80211_state ostate; IEEE80211_LOCK_ASSERT(ic); ostate = vap->iv_state; IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: %s -> %s (%d)\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate], arg); vap->iv_state = nstate; /* state transition */ callout_stop(&vap->iv_mgtsend); /* XXX callout_drain */ if (ostate != IEEE80211_S_SCAN) ieee80211_cancel_scan(vap); /* background scan */ ni = vap->iv_bss; /* NB: no reference held */ if (vap->iv_flags_ext & IEEE80211_FEXT_SWBMISS) callout_stop(&vap->iv_swbmiss); switch (nstate) { case IEEE80211_S_INIT: switch (ostate) { case IEEE80211_S_SLEEP: /* XXX wakeup */ /* XXX driver hook to wakeup the hardware? */ case IEEE80211_S_RUN: IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DISASSOC, IEEE80211_REASON_ASSOC_LEAVE); ieee80211_sta_leave(ni); break; case IEEE80211_S_ASSOC: IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DEAUTH, IEEE80211_REASON_AUTH_LEAVE); break; case IEEE80211_S_SCAN: ieee80211_cancel_scan(vap); break; default: break; } if (ostate != IEEE80211_S_INIT) { /* NB: optimize INIT -> INIT case */ ieee80211_reset_bss(vap); } if (vap->iv_auth->ia_detach != NULL) vap->iv_auth->ia_detach(vap); break; case IEEE80211_S_SCAN: switch (ostate) { case IEEE80211_S_INIT: /* * Initiate a scan. We can come here as a result * of an IEEE80211_IOC_SCAN_REQ too in which case * the vap will be marked with IEEE80211_FEXT_SCANREQ * and the scan request parameters will be present * in iv_scanreq. Otherwise we do the default. */ if (vap->iv_flags_ext & IEEE80211_FEXT_SCANREQ) { ieee80211_check_scan(vap, vap->iv_scanreq_flags, vap->iv_scanreq_duration, vap->iv_scanreq_mindwell, vap->iv_scanreq_maxdwell, vap->iv_scanreq_nssid, vap->iv_scanreq_ssid); vap->iv_flags_ext &= ~IEEE80211_FEXT_SCANREQ; } else ieee80211_check_scan_current(vap); break; case IEEE80211_S_SCAN: case IEEE80211_S_AUTH: case IEEE80211_S_ASSOC: /* * These can happen either because of a timeout * on an assoc/auth response or because of a * change in state that requires a reset. For * the former we're called with a non-zero arg * that is the cause for the failure; pass this * to the scan code so it can update state. * Otherwise trigger a new scan unless we're in * manual roaming mode in which case an application * must issue an explicit scan request. */ if (arg != 0) ieee80211_scan_assoc_fail(vap, vap->iv_bss->ni_macaddr, arg); if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) ieee80211_check_scan_current(vap); break; case IEEE80211_S_SLEEP: /* beacon miss */ /* * XXX if in sleep we need to wakeup the hardware. */ /* FALLTHROUGH */ case IEEE80211_S_RUN: /* beacon miss */ /* * Beacon miss. Notify user space and if not * under control of a user application (roaming * manual) kick off a scan to re-connect. */ ieee80211_sta_leave(ni); if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) ieee80211_check_scan_current(vap); break; default: goto invalid; } break; case IEEE80211_S_AUTH: switch (ostate) { case IEEE80211_S_INIT: case IEEE80211_S_SCAN: IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, 1); break; case IEEE80211_S_AUTH: case IEEE80211_S_ASSOC: switch (arg & 0xff) { case IEEE80211_FC0_SUBTYPE_AUTH: /* ??? */ IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, 2); break; case IEEE80211_FC0_SUBTYPE_DEAUTH: sta_authretry(vap, ni, arg>>8); break; } break; case IEEE80211_S_SLEEP: case IEEE80211_S_RUN: switch (arg & 0xff) { case IEEE80211_FC0_SUBTYPE_AUTH: IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, 2); vap->iv_state = IEEE80211_S_RUN; /* stay RUN */ break; case IEEE80211_FC0_SUBTYPE_DEAUTH: ieee80211_sta_leave(ni); if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) { /* try to reauth */ IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, 1); } break; } break; default: goto invalid; } break; case IEEE80211_S_ASSOC: switch (ostate) { case IEEE80211_S_AUTH: case IEEE80211_S_ASSOC: IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_ASSOC_REQ, 0); break; case IEEE80211_S_SLEEP: /* cannot happen */ case IEEE80211_S_RUN: ieee80211_sta_leave(ni); if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) { IEEE80211_SEND_MGMT(ni, arg ? IEEE80211_FC0_SUBTYPE_REASSOC_REQ : IEEE80211_FC0_SUBTYPE_ASSOC_REQ, 0); } break; default: goto invalid; } break; case IEEE80211_S_RUN: if (vap->iv_flags & IEEE80211_F_WPA) { /* XXX validate prerequisites */ } switch (ostate) { case IEEE80211_S_RUN: case IEEE80211_S_CSA: break; case IEEE80211_S_AUTH: /* when join is done in fw */ case IEEE80211_S_ASSOC: #ifdef IEEE80211_DEBUG if (ieee80211_msg_debug(vap)) { ieee80211_note(vap, "%s with %s ssid ", (vap->iv_opmode == IEEE80211_M_STA ? "associated" : "synchronized"), ether_sprintf(ni->ni_bssid)); ieee80211_print_essid(vap->iv_bss->ni_essid, ni->ni_esslen); /* XXX MCS/HT */ printf(" channel %d start %uMb\n", ieee80211_chan2ieee(ic, ic->ic_curchan), IEEE80211_RATE2MBS(ni->ni_txrate)); } #endif ieee80211_scan_assoc_success(vap, ni->ni_macaddr); ieee80211_notify_node_join(ni, arg == IEEE80211_FC0_SUBTYPE_ASSOC_RESP); break; case IEEE80211_S_SLEEP: /* Wake up from sleep */ vap->iv_sta_ps(vap, 0); break; default: goto invalid; } ieee80211_sync_curchan(ic); if (ostate != IEEE80211_S_RUN) sta_swbmiss_start(vap); /* * When 802.1x is not in use mark the port authorized * at this point so traffic can flow. */ if (ni->ni_authmode != IEEE80211_AUTH_8021X) ieee80211_node_authorize(ni); /* * Fake association when joining an existing bss. * * Don't do this if we're doing SLEEP->RUN. */ if (ic->ic_newassoc != NULL && ostate != IEEE80211_S_SLEEP) ic->ic_newassoc(vap->iv_bss, (ostate != IEEE80211_S_RUN)); break; case IEEE80211_S_CSA: if (ostate != IEEE80211_S_RUN) goto invalid; break; case IEEE80211_S_SLEEP: sta_swbmiss_start(vap); vap->iv_sta_ps(vap, 1); break; default: invalid: IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: unexpected state transition %s -> %s\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate]); break; } return 0; } /* * Return non-zero if the frame is an echo of a multicast * frame sent by ourself. The dir is known to be DSTODS. */ static __inline int isdstods_mcastecho(struct ieee80211vap *vap, const struct ieee80211_frame *wh) { #define QWH4(wh) ((const struct ieee80211_qosframe_addr4 *)wh) #define WH4(wh) ((const struct ieee80211_frame_addr4 *)wh) const uint8_t *sa; KASSERT(vap->iv_opmode == IEEE80211_M_STA, ("wrong mode")); if (!IEEE80211_IS_MULTICAST(wh->i_addr3)) return 0; sa = IEEE80211_QOS_HAS_SEQ(wh) ? QWH4(wh)->i_addr4 : WH4(wh)->i_addr4; return IEEE80211_ADDR_EQ(sa, vap->iv_myaddr); #undef WH4 #undef QWH4 } /* * Return non-zero if the frame is an echo of a multicast * frame sent by ourself. The dir is known to be FROMDS. */ static __inline int isfromds_mcastecho(struct ieee80211vap *vap, const struct ieee80211_frame *wh) { KASSERT(vap->iv_opmode == IEEE80211_M_STA, ("wrong mode")); if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) return 0; return IEEE80211_ADDR_EQ(wh->i_addr3, vap->iv_myaddr); } /* * Decide if a received management frame should be * printed when debugging is enabled. This filters some * of the less interesting frames that come frequently * (e.g. beacons). */ static __inline int doprint(struct ieee80211vap *vap, int subtype) { switch (subtype) { case IEEE80211_FC0_SUBTYPE_BEACON: return (vap->iv_ic->ic_flags & IEEE80211_F_SCAN); case IEEE80211_FC0_SUBTYPE_PROBE_REQ: return 0; } return 1; } /* * Process a received frame. The node associated with the sender * should be supplied. If nothing was found in the node table then * the caller is assumed to supply a reference to iv_bss instead. * The RSSI and a timestamp are also supplied. The RSSI data is used * during AP scanning to select a AP to associate with; it can have * any units so long as values have consistent units and higher values * mean ``better signal''. The receive timestamp is currently not used * by the 802.11 layer. */ static int sta_input(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_rx_stats *rxs, int rssi, int nf) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ifnet *ifp = vap->iv_ifp; struct ieee80211_frame *wh; struct ieee80211_key *key; struct ether_header *eh; int hdrspace, need_tap = 1; /* mbuf need to be tapped. */ uint8_t dir, type, subtype, qos; uint8_t *bssid; if (m->m_flags & M_AMPDU_MPDU) { /* * Fastpath for A-MPDU reorder q resubmission. Frames * w/ M_AMPDU_MPDU marked have already passed through * here but were received out of order and been held on * the reorder queue. When resubmitted they are marked * with the M_AMPDU_MPDU flag and we can bypass most of * the normal processing. */ wh = mtod(m, struct ieee80211_frame *); type = IEEE80211_FC0_TYPE_DATA; dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK; subtype = IEEE80211_FC0_SUBTYPE_QOS; hdrspace = ieee80211_hdrspace(ic, wh); /* XXX optimize? */ goto resubmit_ampdu; } KASSERT(ni != NULL, ("null node")); ni->ni_inact = ni->ni_inact_reload; type = -1; /* undefined */ if (m->m_pkthdr.len < sizeof(struct ieee80211_frame_min)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "too short (1): len %u", m->m_pkthdr.len); vap->iv_stats.is_rx_tooshort++; goto out; } /* * Bit of a cheat here, we use a pointer for a 3-address * frame format but don't reference fields past outside * ieee80211_frame_min w/o first validating the data is * present. */ wh = mtod(m, struct ieee80211_frame *); if ((wh->i_fc[0] & IEEE80211_FC0_VERSION_MASK) != IEEE80211_FC0_VERSION_0) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "wrong version, fc %02x:%02x", wh->i_fc[0], wh->i_fc[1]); vap->iv_stats.is_rx_badversion++; goto err; } dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK; type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; if ((ic->ic_flags & IEEE80211_F_SCAN) == 0) { bssid = wh->i_addr2; if (!IEEE80211_ADDR_EQ(bssid, ni->ni_bssid)) { /* not interested in */ IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, bssid, NULL, "%s", "not to bss"); vap->iv_stats.is_rx_wrongbss++; goto out; } /* * Some devices may be in a promiscuous mode * where they receive frames for multiple station * addresses. * * If we receive a data frame that isn't * destined to our VAP MAC, drop it. * * XXX TODO: This is only enforced when not scanning; * XXX it assumes a software-driven scan will put the NIC * XXX into a "no data frames" mode before setting this * XXX flag. Otherwise it may be possible that we'll still * XXX process data frames whilst scanning. */ if ((! IEEE80211_IS_MULTICAST(wh->i_addr1)) && (! IEEE80211_ADDR_EQ(wh->i_addr1, IF_LLADDR(ifp)))) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, bssid, NULL, "not to cur sta: lladdr=%6D, addr1=%6D", IF_LLADDR(ifp), ":", wh->i_addr1, ":"); vap->iv_stats.is_rx_wrongbss++; goto out; } IEEE80211_RSSI_LPF(ni->ni_avgrssi, rssi); ni->ni_noise = nf; if ( IEEE80211_HAS_SEQ(type, subtype) && !IEEE80211_IS_MULTICAST(wh->i_addr1)) { uint8_t tid = ieee80211_gettid(wh); if (IEEE80211_QOS_HAS_SEQ(wh) && TID_TO_WME_AC(tid) >= WME_AC_VI) ic->ic_wme.wme_hipri_traffic++; if (! ieee80211_check_rxseq(ni, wh, bssid)) goto out; } } switch (type) { case IEEE80211_FC0_TYPE_DATA: hdrspace = ieee80211_hdrspace(ic, wh); if (m->m_len < hdrspace && (m = m_pullup(m, hdrspace)) == NULL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "data too short: expecting %u", hdrspace); vap->iv_stats.is_rx_tooshort++; goto out; /* XXX */ } /* * Handle A-MPDU re-ordering. If the frame is to be * processed directly then ieee80211_ampdu_reorder * will return 0; otherwise it has consumed the mbuf * and we should do nothing more with it. */ if ((m->m_flags & M_AMPDU) && (dir == IEEE80211_FC1_DIR_FROMDS || dir == IEEE80211_FC1_DIR_DSTODS) && ieee80211_ampdu_reorder(ni, m) != 0) { m = NULL; goto out; } resubmit_ampdu: if (dir == IEEE80211_FC1_DIR_FROMDS) { if ((ifp->if_flags & IFF_SIMPLEX) && isfromds_mcastecho(vap, wh)) { /* * In IEEE802.11 network, multicast * packets sent from "me" are broadcast * from the AP; silently discard for * SIMPLEX interface. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "data", "%s", "multicast echo"); vap->iv_stats.is_rx_mcastecho++; goto out; } if ((vap->iv_flags & IEEE80211_F_DWDS) && IEEE80211_IS_MULTICAST(wh->i_addr1)) { /* * DWDS sta's must drop 3-address mcast frames * as they will be sent separately as a 4-addr * frame. Accepting the 3-addr frame will * confuse the bridge into thinking the sending * sta is located at the end of WDS link. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "3-address data", "%s", "DWDS enabled"); vap->iv_stats.is_rx_mcastecho++; goto out; } } else if (dir == IEEE80211_FC1_DIR_DSTODS) { if ((vap->iv_flags & IEEE80211_F_DWDS) == 0) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "4-address data", "%s", "DWDS not enabled"); vap->iv_stats.is_rx_wrongdir++; goto out; } if ((ifp->if_flags & IFF_SIMPLEX) && isdstods_mcastecho(vap, wh)) { /* * In IEEE802.11 network, multicast * packets sent from "me" are broadcast * from the AP; silently discard for * SIMPLEX interface. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "4-address data", "%s", "multicast echo"); vap->iv_stats.is_rx_mcastecho++; goto out; } } else { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "data", "incorrect dir 0x%x", dir); vap->iv_stats.is_rx_wrongdir++; goto out; } /* * Handle privacy requirements. Note that we * must not be preempted from here until after * we (potentially) call ieee80211_crypto_demic; * otherwise we may violate assumptions in the * crypto cipher modules used to do delayed update * of replay sequence numbers. */ if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0) { /* * Discard encrypted frames when privacy is off. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "WEP", "%s", "PRIVACY off"); vap->iv_stats.is_rx_noprivacy++; IEEE80211_NODE_STAT(ni, rx_noprivacy); goto out; } key = ieee80211_crypto_decap(ni, m, hdrspace); if (key == NULL) { /* NB: stats+msgs handled in crypto_decap */ IEEE80211_NODE_STAT(ni, rx_wepfail); goto out; } wh = mtod(m, struct ieee80211_frame *); wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED; } else { /* XXX M_WEP and IEEE80211_F_PRIVACY */ key = NULL; } /* * Save QoS bits for use below--before we strip the header. */ if (subtype == IEEE80211_FC0_SUBTYPE_QOS) { qos = (dir == IEEE80211_FC1_DIR_DSTODS) ? ((struct ieee80211_qosframe_addr4 *)wh)->i_qos[0] : ((struct ieee80211_qosframe *)wh)->i_qos[0]; } else qos = 0; /* * Next up, any fragmentation. */ if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { m = ieee80211_defrag(ni, m, hdrspace); if (m == NULL) { /* Fragment dropped or frame not complete yet */ goto out; } } wh = NULL; /* no longer valid, catch any uses */ /* * Next strip any MSDU crypto bits. */ if (key != NULL && !ieee80211_crypto_demic(vap, key, m, 0)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, ni->ni_macaddr, "data", "%s", "demic error"); vap->iv_stats.is_rx_demicfail++; IEEE80211_NODE_STAT(ni, rx_demicfail); goto out; } /* copy to listener after decrypt */ if (ieee80211_radiotap_active_vap(vap)) ieee80211_radiotap_rx(vap, m); need_tap = 0; /* * Finally, strip the 802.11 header. */ m = ieee80211_decap(vap, m, hdrspace); if (m == NULL) { /* XXX mask bit to check for both */ /* don't count Null data frames as errors */ if (subtype == IEEE80211_FC0_SUBTYPE_NODATA || subtype == IEEE80211_FC0_SUBTYPE_QOS_NULL) goto out; IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, ni->ni_macaddr, "data", "%s", "decap error"); vap->iv_stats.is_rx_decap++; IEEE80211_NODE_STAT(ni, rx_decap); goto err; } eh = mtod(m, struct ether_header *); if (!ieee80211_node_is_authorized(ni)) { /* * Deny any non-PAE frames received prior to * authorization. For open/shared-key * authentication the port is mark authorized * after authentication completes. For 802.1x * the port is not marked authorized by the * authenticator until the handshake has completed. */ if (eh->ether_type != htons(ETHERTYPE_PAE)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, eh->ether_shost, "data", "unauthorized port: ether type 0x%x len %u", eh->ether_type, m->m_pkthdr.len); vap->iv_stats.is_rx_unauth++; IEEE80211_NODE_STAT(ni, rx_unauth); goto err; } } else { /* * When denying unencrypted frames, discard * any non-PAE frames received without encryption. */ if ((vap->iv_flags & IEEE80211_F_DROPUNENC) && (key == NULL && (m->m_flags & M_WEP) == 0) && eh->ether_type != htons(ETHERTYPE_PAE)) { /* * Drop unencrypted frames. */ vap->iv_stats.is_rx_unencrypted++; IEEE80211_NODE_STAT(ni, rx_unencrypted); goto out; } } /* XXX require HT? */ if (qos & IEEE80211_QOS_AMSDU) { m = ieee80211_decap_amsdu(ni, m); if (m == NULL) return IEEE80211_FC0_TYPE_DATA; } else { #ifdef IEEE80211_SUPPORT_SUPERG m = ieee80211_decap_fastframe(vap, ni, m); if (m == NULL) return IEEE80211_FC0_TYPE_DATA; #endif } ieee80211_deliver_data(vap, ni, m); return IEEE80211_FC0_TYPE_DATA; case IEEE80211_FC0_TYPE_MGT: vap->iv_stats.is_rx_mgmt++; IEEE80211_NODE_STAT(ni, rx_mgmt); if (dir != IEEE80211_FC1_DIR_NODS) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "data", "incorrect dir 0x%x", dir); vap->iv_stats.is_rx_wrongdir++; goto err; } if (m->m_pkthdr.len < sizeof(struct ieee80211_frame)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, "mgt", "too short: len %u", m->m_pkthdr.len); vap->iv_stats.is_rx_tooshort++; goto out; } #ifdef IEEE80211_DEBUG if ((ieee80211_msg_debug(vap) && doprint(vap, subtype)) || ieee80211_msg_dumppkts(vap)) { if_printf(ifp, "received %s from %s rssi %d\n", ieee80211_mgt_subtype_name[subtype >> IEEE80211_FC0_SUBTYPE_SHIFT], ether_sprintf(wh->i_addr2), rssi); } #endif if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { if (subtype != IEEE80211_FC0_SUBTYPE_AUTH) { /* * Only shared key auth frames with a challenge * should be encrypted, discard all others. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, ieee80211_mgt_subtype_name[subtype >> IEEE80211_FC0_SUBTYPE_SHIFT], "%s", "WEP set but not permitted"); vap->iv_stats.is_rx_mgtdiscard++; /* XXX */ goto out; } if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0) { /* * Discard encrypted frames when privacy is off. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "mgt", "%s", "WEP set but PRIVACY off"); vap->iv_stats.is_rx_noprivacy++; goto out; } hdrspace = ieee80211_hdrspace(ic, wh); key = ieee80211_crypto_decap(ni, m, hdrspace); if (key == NULL) { /* NB: stats+msgs handled in crypto_decap */ goto out; } wh = mtod(m, struct ieee80211_frame *); wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED; } vap->iv_recv_mgmt(ni, m, subtype, rxs, rssi, nf); goto out; case IEEE80211_FC0_TYPE_CTL: vap->iv_stats.is_rx_ctl++; IEEE80211_NODE_STAT(ni, rx_ctrl); vap->iv_recv_ctl(ni, m, subtype); goto out; default: IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, NULL, "bad frame type 0x%x", type); /* should not come here */ break; } err: if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); out: if (m != NULL) { if (need_tap && ieee80211_radiotap_active_vap(vap)) ieee80211_radiotap_rx(vap, m); m_freem(m); } return type; } static void sta_auth_open(struct ieee80211_node *ni, struct ieee80211_frame *wh, int rssi, int nf, uint16_t seq, uint16_t status) { struct ieee80211vap *vap = ni->ni_vap; if (ni->ni_authmode == IEEE80211_AUTH_SHARED) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "open auth", "bad sta auth mode %u", ni->ni_authmode); vap->iv_stats.is_rx_bad_auth++; /* XXX */ return; } if (vap->iv_state != IEEE80211_S_AUTH || seq != IEEE80211_AUTH_OPEN_RESPONSE) { vap->iv_stats.is_rx_bad_auth++; return; } if (status != 0) { IEEE80211_NOTE(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, ni, "open auth failed (reason %d)", status); vap->iv_stats.is_rx_auth_fail++; vap->iv_stats.is_rx_authfail_code = status; ieee80211_new_state(vap, IEEE80211_S_SCAN, IEEE80211_SCAN_FAIL_STATUS); } else ieee80211_new_state(vap, IEEE80211_S_ASSOC, 0); } static void sta_auth_shared(struct ieee80211_node *ni, struct ieee80211_frame *wh, uint8_t *frm, uint8_t *efrm, int rssi, int nf, uint16_t seq, uint16_t status) { struct ieee80211vap *vap = ni->ni_vap; uint8_t *challenge; int estatus; /* * NB: this can happen as we allow pre-shared key * authentication to be enabled w/o wep being turned * on so that configuration of these can be done * in any order. It may be better to enforce the * ordering in which case this check would just be * for sanity/consistency. */ if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "%s", " PRIVACY is disabled"); estatus = IEEE80211_STATUS_ALG; goto bad; } /* * Pre-shared key authentication is evil; accept * it only if explicitly configured (it is supported * mainly for compatibility with clients like OS X). */ if (ni->ni_authmode != IEEE80211_AUTH_AUTO && ni->ni_authmode != IEEE80211_AUTH_SHARED) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "bad sta auth mode %u", ni->ni_authmode); vap->iv_stats.is_rx_bad_auth++; /* XXX maybe a unique error? */ estatus = IEEE80211_STATUS_ALG; goto bad; } challenge = NULL; if (frm + 1 < efrm) { if ((frm[1] + 2) > (efrm - frm)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "ie %d/%d too long", frm[0], (frm[1] + 2) - (efrm - frm)); vap->iv_stats.is_rx_bad_auth++; estatus = IEEE80211_STATUS_CHALLENGE; goto bad; } if (*frm == IEEE80211_ELEMID_CHALLENGE) challenge = frm; frm += frm[1] + 2; } switch (seq) { case IEEE80211_AUTH_SHARED_CHALLENGE: case IEEE80211_AUTH_SHARED_RESPONSE: if (challenge == NULL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "%s", "no challenge"); vap->iv_stats.is_rx_bad_auth++; estatus = IEEE80211_STATUS_CHALLENGE; goto bad; } if (challenge[1] != IEEE80211_CHALLENGE_LEN) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "bad challenge len %d", challenge[1]); vap->iv_stats.is_rx_bad_auth++; estatus = IEEE80211_STATUS_CHALLENGE; goto bad; } default: break; } if (vap->iv_state != IEEE80211_S_AUTH) return; switch (seq) { case IEEE80211_AUTH_SHARED_PASS: if (ni->ni_challenge != NULL) { IEEE80211_FREE(ni->ni_challenge, M_80211_NODE); ni->ni_challenge = NULL; } if (status != 0) { IEEE80211_NOTE_FRAME(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, wh, "shared key auth failed (reason %d)", status); vap->iv_stats.is_rx_auth_fail++; vap->iv_stats.is_rx_authfail_code = status; return; } ieee80211_new_state(vap, IEEE80211_S_ASSOC, 0); break; case IEEE80211_AUTH_SHARED_CHALLENGE: if (!ieee80211_alloc_challenge(ni)) return; /* XXX could optimize by passing recvd challenge */ memcpy(ni->ni_challenge, &challenge[2], challenge[1]); IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, seq + 1); break; default: IEEE80211_DISCARD(vap, IEEE80211_MSG_AUTH, wh, "shared key auth", "bad seq %d", seq); vap->iv_stats.is_rx_bad_auth++; return; } return; bad: /* * Kick the state machine. This short-circuits * using the mgt frame timeout to trigger the * state transition. */ if (vap->iv_state == IEEE80211_S_AUTH) ieee80211_new_state(vap, IEEE80211_S_SCAN, IEEE80211_SCAN_FAIL_STATUS); } int ieee80211_parse_wmeparams(struct ieee80211vap *vap, uint8_t *frm, const struct ieee80211_frame *wh) { #define MS(_v, _f) (((_v) & _f) >> _f##_S) struct ieee80211_wme_state *wme = &vap->iv_ic->ic_wme; u_int len = frm[1], qosinfo; int i; if (len < sizeof(struct ieee80211_wme_param)-2) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WME, wh, "WME", "too short, len %u", len); return -1; } qosinfo = frm[__offsetof(struct ieee80211_wme_param, param_qosInfo)]; qosinfo &= WME_QOSINFO_COUNT; /* XXX do proper check for wraparound */ if (qosinfo == wme->wme_wmeChanParams.cap_info) return 0; frm += __offsetof(struct ieee80211_wme_param, params_acParams); for (i = 0; i < WME_NUM_AC; i++) { struct wmeParams *wmep = &wme->wme_wmeChanParams.cap_wmeParams[i]; /* NB: ACI not used */ wmep->wmep_acm = MS(frm[0], WME_PARAM_ACM); wmep->wmep_aifsn = MS(frm[0], WME_PARAM_AIFSN); wmep->wmep_logcwmin = MS(frm[1], WME_PARAM_LOGCWMIN); wmep->wmep_logcwmax = MS(frm[1], WME_PARAM_LOGCWMAX); wmep->wmep_txopLimit = LE_READ_2(frm+2); frm += 4; } wme->wme_wmeChanParams.cap_info = qosinfo; return 1; #undef MS } /* * Process 11h Channel Switch Announcement (CSA) ie. If this * is the first CSA then initiate the switch. Otherwise we * track state and trigger completion and/or cancel of the switch. * XXX should be public for IBSS use */ static void ieee80211_parse_csaparams(struct ieee80211vap *vap, uint8_t *frm, const struct ieee80211_frame *wh) { struct ieee80211com *ic = vap->iv_ic; const struct ieee80211_csa_ie *csa = (const struct ieee80211_csa_ie *) frm; KASSERT(vap->iv_state >= IEEE80211_S_RUN, ("state %s", ieee80211_state_name[vap->iv_state])); if (csa->csa_mode > 1) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_DOTH, wh, "CSA", "invalid mode %u", csa->csa_mode); return; } IEEE80211_LOCK(ic); if ((ic->ic_flags & IEEE80211_F_CSAPENDING) == 0) { /* * Convert the channel number to a channel reference. We * try first to preserve turbo attribute of the current * channel then fallback. Note this will not work if the * CSA specifies a channel that requires a band switch (e.g. * 11a => 11g). This is intentional as 11h is defined only * for 5GHz/11a and because the switch does not involve a * reassociation, protocol state (capabilities, negotated * rates, etc) may/will be wrong. */ struct ieee80211_channel *c = ieee80211_find_channel_byieee(ic, csa->csa_newchan, (ic->ic_bsschan->ic_flags & IEEE80211_CHAN_ALLTURBO)); if (c == NULL) { c = ieee80211_find_channel_byieee(ic, csa->csa_newchan, (ic->ic_bsschan->ic_flags & IEEE80211_CHAN_ALL)); if (c == NULL) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_DOTH, wh, "CSA", "invalid channel %u", csa->csa_newchan); goto done; } } #if IEEE80211_CSA_COUNT_MIN > 0 if (csa->csa_count < IEEE80211_CSA_COUNT_MIN) { /* * Require at least IEEE80211_CSA_COUNT_MIN count to * reduce the risk of being redirected by a fabricated * CSA. If a valid CSA is dropped we'll still get a * beacon miss when the AP leaves the channel so we'll * eventually follow to the new channel. * * NOTE: this violates the 11h spec that states that * count may be any value and if 0 then a switch * should happen asap. */ IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_DOTH, wh, "CSA", "count %u too small, must be >= %u", csa->csa_count, IEEE80211_CSA_COUNT_MIN); goto done; } #endif ieee80211_csa_startswitch(ic, c, csa->csa_mode, csa->csa_count); } else { /* * Validate this ie against the initial CSA. We require * mode and channel not change and the count must be * monotonically decreasing. This may be pointless and * canceling the switch as a result may be too paranoid but * in the worst case if we drop out of CSA because of this * and the AP does move then we'll just end up taking a * beacon miss and scan to find the AP. * * XXX may want <= on count as we also process ProbeResp * frames and those may come in w/ the same count as the * previous beacon; but doing so leaves us open to a stuck * count until we add a dead-man timer */ if (!(csa->csa_count < ic->ic_csa_count && csa->csa_mode == ic->ic_csa_mode && csa->csa_newchan == ieee80211_chan2ieee(ic, ic->ic_csa_newchan))) { IEEE80211_NOTE_FRAME(vap, IEEE80211_MSG_DOTH, wh, "CSA ie mismatch, initial ie <%d,%d,%d>, " "this ie <%d,%d,%d>", ic->ic_csa_mode, ic->ic_csa_newchan, ic->ic_csa_count, csa->csa_mode, csa->csa_newchan, csa->csa_count); ieee80211_csa_cancelswitch(ic); } else { if (csa->csa_count <= 1) ieee80211_csa_completeswitch(ic); else ic->ic_csa_count = csa->csa_count; } } done: IEEE80211_UNLOCK(ic); } /* * Return non-zero if a background scan may be continued: * o bg scan is active * o no channel switch is pending * o there has not been any traffic recently * * Note we do not check if there is an administrative enable; * this is only done to start the scan. We assume that any * change in state will be accompanied by a request to cancel * active scans which will otherwise cause this test to fail. */ static __inline int contbgscan(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; return ((ic->ic_flags_ext & IEEE80211_FEXT_BGSCAN) && (ic->ic_flags & IEEE80211_F_CSAPENDING) == 0 && vap->iv_state == IEEE80211_S_RUN && /* XXX? */ - time_after(ticks, ic->ic_lastdata + vap->iv_bgscanidle)); + ieee80211_time_after(ticks, ic->ic_lastdata + vap->iv_bgscanidle)); } /* * Return non-zero if a backgrond scan may be started: * o bg scanning is administratively enabled * o no channel switch is pending * o we are not boosted on a dynamic turbo channel * o there has not been a scan recently * o there has not been any traffic recently */ static __inline int startbgscan(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; return ((vap->iv_flags & IEEE80211_F_BGSCAN) && (ic->ic_flags & IEEE80211_F_CSAPENDING) == 0 && #ifdef IEEE80211_SUPPORT_SUPERG !IEEE80211_IS_CHAN_DTURBO(ic->ic_curchan) && #endif - time_after(ticks, ic->ic_lastscan + vap->iv_bgscanintvl) && - time_after(ticks, ic->ic_lastdata + vap->iv_bgscanidle)); + ieee80211_time_after(ticks, ic->ic_lastscan + vap->iv_bgscanintvl) && + ieee80211_time_after(ticks, ic->ic_lastdata + vap->iv_bgscanidle)); } static void sta_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m0, int subtype, const struct ieee80211_rx_stats *rxs, int rssi, int nf) { #define ISPROBE(_st) ((_st) == IEEE80211_FC0_SUBTYPE_PROBE_RESP) #define ISREASSOC(_st) ((_st) == IEEE80211_FC0_SUBTYPE_REASSOC_RESP) struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_channel *rxchan = ic->ic_curchan; struct ieee80211_frame *wh; uint8_t *frm, *efrm; uint8_t *rates, *xrates, *wme, *htcap, *htinfo; uint8_t rate; int ht_state_change = 0; wh = mtod(m0, struct ieee80211_frame *); frm = (uint8_t *)&wh[1]; efrm = mtod(m0, uint8_t *) + m0->m_len; switch (subtype) { case IEEE80211_FC0_SUBTYPE_PROBE_RESP: case IEEE80211_FC0_SUBTYPE_BEACON: { struct ieee80211_scanparams scan; struct ieee80211_channel *c; /* * We process beacon/probe response frames: * o when scanning, or * o station mode when associated (to collect state * updates such as 802.11g slot time) * Frames otherwise received are discarded. */ if (!((ic->ic_flags & IEEE80211_F_SCAN) || ni->ni_associd)) { vap->iv_stats.is_rx_mgtdiscard++; return; } /* Override RX channel as appropriate */ if (rxs != NULL) { c = ieee80211_lookup_channel_rxstatus(vap, rxs); if (c != NULL) rxchan = c; } /* XXX probe response in sta mode when !scanning? */ if (ieee80211_parse_beacon(ni, m0, rxchan, &scan) != 0) { if (! (ic->ic_flags & IEEE80211_F_SCAN)) vap->iv_stats.is_beacon_bad++; return; } /* * Count frame now that we know it's to be processed. */ if (subtype == IEEE80211_FC0_SUBTYPE_BEACON) { vap->iv_stats.is_rx_beacon++; /* XXX remove */ IEEE80211_NODE_STAT(ni, rx_beacons); } else IEEE80211_NODE_STAT(ni, rx_proberesp); /* * When operating in station mode, check for state updates. * Be careful to ignore beacons received while doing a * background scan. We consider only 11g/WMM stuff right now. */ if (ni->ni_associd != 0 && ((ic->ic_flags & IEEE80211_F_SCAN) == 0 || IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_bssid))) { /* record tsf of last beacon */ memcpy(ni->ni_tstamp.data, scan.tstamp, sizeof(ni->ni_tstamp)); /* count beacon frame for s/w bmiss handling */ vap->iv_swbmiss_count++; vap->iv_bmiss_count = 0; if (ni->ni_erp != scan.erp) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC, wh->i_addr2, "erp change: was 0x%x, now 0x%x", ni->ni_erp, scan.erp); if (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan) && (ni->ni_erp & IEEE80211_ERP_USE_PROTECTION)) ic->ic_flags |= IEEE80211_F_USEPROT; else ic->ic_flags &= ~IEEE80211_F_USEPROT; ni->ni_erp = scan.erp; /* XXX statistic */ /* XXX driver notification */ } if ((ni->ni_capinfo ^ scan.capinfo) & IEEE80211_CAPINFO_SHORT_SLOTTIME) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC, wh->i_addr2, "capabilities change: was 0x%x, now 0x%x", ni->ni_capinfo, scan.capinfo); /* * NB: we assume short preamble doesn't * change dynamically */ ieee80211_set_shortslottime(ic, IEEE80211_IS_CHAN_A(ic->ic_bsschan) || (scan.capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME)); ni->ni_capinfo = (ni->ni_capinfo &~ IEEE80211_CAPINFO_SHORT_SLOTTIME) | (scan.capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME); /* XXX statistic */ } if (scan.wme != NULL && (ni->ni_flags & IEEE80211_NODE_QOS) && ieee80211_parse_wmeparams(vap, scan.wme, wh) > 0) ieee80211_wme_updateparams(vap); #ifdef IEEE80211_SUPPORT_SUPERG if (scan.ath != NULL) ieee80211_parse_athparams(ni, scan.ath, wh); #endif if (scan.htcap != NULL && scan.htinfo != NULL && (vap->iv_flags_ht & IEEE80211_FHT_HT)) { /* XXX state changes? */ if (ieee80211_ht_updateparams(ni, scan.htcap, scan.htinfo)) ht_state_change = 1; } if (scan.quiet) ic->ic_set_quiet(ni, scan.quiet); if (scan.tim != NULL) { struct ieee80211_tim_ie *tim = (struct ieee80211_tim_ie *) scan.tim; /* * XXX Check/debug this code; see if it's about * the right time to force the VAP awake if we * receive a frame destined for us? */ int aid = IEEE80211_AID(ni->ni_associd); int ix = aid / NBBY; int min = tim->tim_bitctl &~ 1; int max = tim->tim_len + min - 4; int tim_ucast = 0, tim_mcast = 0; /* * Only do this for unicast traffic in the TIM * The multicast traffic notification for * the scan notification stuff should occur * differently. */ if (min <= ix && ix <= max && isset(tim->tim_bitmap - min, aid)) { tim_ucast = 1; } /* * Do a separate notification * for the multicast bit being set. */ if (tim->tim_bitctl & 1) { tim_mcast = 1; } /* * If the TIM indicates there's traffic for * us then get us out of STA mode powersave. */ if (tim_ucast == 1) { /* * Wake us out of SLEEP state if we're * in it; and if we're doing bgscan * then wake us out of STA powersave. */ ieee80211_sta_tim_notify(vap, 1); /* * This is preventing us from * continuing a bgscan; because it * tricks the contbgscan() * routine to think there's always * traffic for us. * * I think we need both an RX and * TX ic_lastdata field. */ ic->ic_lastdata = ticks; } ni->ni_dtim_count = tim->tim_count; ni->ni_dtim_period = tim->tim_period; } if (scan.csa != NULL && (vap->iv_flags & IEEE80211_F_DOTH)) ieee80211_parse_csaparams(vap, scan.csa, wh); else if (ic->ic_flags & IEEE80211_F_CSAPENDING) { /* * No CSA ie or 11h disabled, but a channel * switch is pending; drop out so we aren't * stuck in CSA state. If the AP really is * moving we'll get a beacon miss and scan. */ IEEE80211_LOCK(ic); ieee80211_csa_cancelswitch(ic); IEEE80211_UNLOCK(ic); } /* * If scanning, pass the info to the scan module. * Otherwise, check if it's the right time to do * a background scan. Background scanning must * be enabled and we must not be operating in the * turbo phase of dynamic turbo mode. Then, * it's been a while since the last background * scan and if no data frames have come through * recently, kick off a scan. Note that this * is the mechanism by which a background scan * is started _and_ continued each time we * return on-channel to receive a beacon from * our ap. */ if (ic->ic_flags & IEEE80211_F_SCAN) { ieee80211_add_scan(vap, rxchan, &scan, wh, subtype, rssi, nf); } else if (contbgscan(vap)) { ieee80211_bg_scan(vap, 0); } else if (startbgscan(vap)) { vap->iv_stats.is_scan_bg++; #if 0 /* wakeup if we are sleeing */ ieee80211_set_pwrsave(vap, 0); #endif ieee80211_bg_scan(vap, 0); } /* * Put the station to sleep if we haven't seen * traffic in a while. */ IEEE80211_LOCK(ic); ieee80211_sta_ps_timer_check(vap); IEEE80211_UNLOCK(ic); /* * If we've had a channel width change (eg HT20<->HT40) * then schedule a delayed driver notification. */ if (ht_state_change) ieee80211_update_chw(ic); return; } /* * If scanning, just pass information to the scan module. */ if (ic->ic_flags & IEEE80211_F_SCAN) { if (ic->ic_flags_ext & IEEE80211_FEXT_PROBECHAN) { /* * Actively scanning a channel marked passive; * send a probe request now that we know there * is 802.11 traffic present. * * XXX check if the beacon we recv'd gives * us what we need and suppress the probe req */ ieee80211_probe_curchan(vap, 1); ic->ic_flags_ext &= ~IEEE80211_FEXT_PROBECHAN; } ieee80211_add_scan(vap, rxchan, &scan, wh, subtype, rssi, nf); return; } break; } case IEEE80211_FC0_SUBTYPE_AUTH: { uint16_t algo, seq, status; /* * auth frame format * [2] algorithm * [2] sequence * [2] status * [tlv*] challenge */ IEEE80211_VERIFY_LENGTH(efrm - frm, 6, return); algo = le16toh(*(uint16_t *)frm); seq = le16toh(*(uint16_t *)(frm + 2)); status = le16toh(*(uint16_t *)(frm + 4)); IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_AUTH, wh->i_addr2, "recv auth frame with algorithm %d seq %d", algo, seq); if (vap->iv_flags & IEEE80211_F_COUNTERM) { IEEE80211_DISCARD(vap, IEEE80211_MSG_AUTH | IEEE80211_MSG_CRYPTO, wh, "auth", "%s", "TKIP countermeasures enabled"); vap->iv_stats.is_rx_auth_countermeasures++; if (vap->iv_opmode == IEEE80211_M_HOSTAP) { ieee80211_send_error(ni, wh->i_addr2, IEEE80211_FC0_SUBTYPE_AUTH, IEEE80211_REASON_MIC_FAILURE); } return; } if (algo == IEEE80211_AUTH_ALG_SHARED) sta_auth_shared(ni, wh, frm + 6, efrm, rssi, nf, seq, status); else if (algo == IEEE80211_AUTH_ALG_OPEN) sta_auth_open(ni, wh, rssi, nf, seq, status); else { IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, "auth", "unsupported alg %d", algo); vap->iv_stats.is_rx_auth_unsupported++; return; } break; } case IEEE80211_FC0_SUBTYPE_ASSOC_RESP: case IEEE80211_FC0_SUBTYPE_REASSOC_RESP: { uint16_t capinfo, associd; uint16_t status; if (vap->iv_state != IEEE80211_S_ASSOC) { vap->iv_stats.is_rx_mgtdiscard++; return; } /* * asresp frame format * [2] capability information * [2] status * [2] association ID * [tlv] supported rates * [tlv] extended supported rates * [tlv] WME * [tlv] HT capabilities * [tlv] HT info */ IEEE80211_VERIFY_LENGTH(efrm - frm, 6, return); ni = vap->iv_bss; capinfo = le16toh(*(uint16_t *)frm); frm += 2; status = le16toh(*(uint16_t *)frm); frm += 2; if (status != 0) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC, wh->i_addr2, "%sassoc failed (reason %d)", ISREASSOC(subtype) ? "re" : "", status); vap->iv_stats.is_rx_auth_fail++; /* XXX */ return; } associd = le16toh(*(uint16_t *)frm); frm += 2; rates = xrates = wme = htcap = htinfo = NULL; while (efrm - frm > 1) { IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2, return); switch (*frm) { case IEEE80211_ELEMID_RATES: rates = frm; break; case IEEE80211_ELEMID_XRATES: xrates = frm; break; case IEEE80211_ELEMID_HTCAP: htcap = frm; break; case IEEE80211_ELEMID_HTINFO: htinfo = frm; break; case IEEE80211_ELEMID_VENDOR: if (iswmeoui(frm)) wme = frm; else if (vap->iv_flags_ht & IEEE80211_FHT_HTCOMPAT) { /* * Accept pre-draft HT ie's if the * standard ones have not been seen. */ if (ishtcapoui(frm)) { if (htcap == NULL) htcap = frm; } else if (ishtinfooui(frm)) { if (htinfo == NULL) htinfo = frm; } } /* XXX Atheros OUI support */ break; } frm += frm[1] + 2; } IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE, return); if (xrates != NULL) IEEE80211_VERIFY_ELEMENT(xrates, IEEE80211_RATE_MAXSIZE - rates[1], return); rate = ieee80211_setup_rates(ni, rates, xrates, IEEE80211_F_JOIN | IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE | IEEE80211_F_DONEGO | IEEE80211_F_DODEL); if (rate & IEEE80211_RATE_BASIC) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC, wh->i_addr2, "%sassoc failed (rate set mismatch)", ISREASSOC(subtype) ? "re" : ""); vap->iv_stats.is_rx_assoc_norate++; ieee80211_new_state(vap, IEEE80211_S_SCAN, IEEE80211_SCAN_FAIL_STATUS); return; } ni->ni_capinfo = capinfo; ni->ni_associd = associd; if (ni->ni_jointime == 0) ni->ni_jointime = time_uptime; if (wme != NULL && ieee80211_parse_wmeparams(vap, wme, wh) >= 0) { ni->ni_flags |= IEEE80211_NODE_QOS; ieee80211_wme_updateparams(vap); } else ni->ni_flags &= ~IEEE80211_NODE_QOS; /* * Setup HT state according to the negotiation. * * NB: shouldn't need to check if HT use is enabled but some * ap's send back HT ie's even when we don't indicate we * are HT capable in our AssocReq. */ if (htcap != NULL && htinfo != NULL && (vap->iv_flags_ht & IEEE80211_FHT_HT)) { ieee80211_ht_node_init(ni); ieee80211_ht_updateparams(ni, htcap, htinfo); ieee80211_setup_htrates(ni, htcap, IEEE80211_F_JOIN | IEEE80211_F_DOBRS); ieee80211_setup_basic_htrates(ni, htinfo); ieee80211_node_setuptxparms(ni); ieee80211_ratectl_node_init(ni); } else { #ifdef IEEE80211_SUPPORT_SUPERG if (IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_ATH)) ieee80211_ff_node_init(ni); #endif } /* * Configure state now that we are associated. * * XXX may need different/additional driver callbacks? */ if (IEEE80211_IS_CHAN_A(ic->ic_curchan) || (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE)) { ic->ic_flags |= IEEE80211_F_SHPREAMBLE; ic->ic_flags &= ~IEEE80211_F_USEBARKER; } else { ic->ic_flags &= ~IEEE80211_F_SHPREAMBLE; ic->ic_flags |= IEEE80211_F_USEBARKER; } ieee80211_set_shortslottime(ic, IEEE80211_IS_CHAN_A(ic->ic_curchan) || (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME)); /* * Honor ERP protection. * * NB: ni_erp should zero for non-11g operation. */ if (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan) && (ni->ni_erp & IEEE80211_ERP_USE_PROTECTION)) ic->ic_flags |= IEEE80211_F_USEPROT; else ic->ic_flags &= ~IEEE80211_F_USEPROT; IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_DEBUG, wh->i_addr2, "%sassoc success at aid %d: %s preamble, %s slot time%s%s%s%s%s%s%s%s", ISREASSOC(subtype) ? "re" : "", IEEE80211_NODE_AID(ni), ic->ic_flags&IEEE80211_F_SHPREAMBLE ? "short" : "long", ic->ic_flags&IEEE80211_F_SHSLOT ? "short" : "long", ic->ic_flags&IEEE80211_F_USEPROT ? ", protection" : "", ni->ni_flags & IEEE80211_NODE_QOS ? ", QoS" : "", ni->ni_flags & IEEE80211_NODE_HT ? (ni->ni_chw == 40 ? ", HT40" : ", HT20") : "", ni->ni_flags & IEEE80211_NODE_AMPDU ? " (+AMPDU)" : "", ni->ni_flags & IEEE80211_NODE_MIMO_RTS ? " (+SMPS-DYN)" : ni->ni_flags & IEEE80211_NODE_MIMO_PS ? " (+SMPS)" : "", ni->ni_flags & IEEE80211_NODE_RIFS ? " (+RIFS)" : "", IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_FF) ? ", fast-frames" : "", IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_TURBOP) ? ", turbo" : "" ); ieee80211_new_state(vap, IEEE80211_S_RUN, subtype); break; } case IEEE80211_FC0_SUBTYPE_DEAUTH: { uint16_t reason; if (vap->iv_state == IEEE80211_S_SCAN) { vap->iv_stats.is_rx_mgtdiscard++; return; } if (!IEEE80211_ADDR_EQ(wh->i_addr1, vap->iv_myaddr)) { /* NB: can happen when in promiscuous mode */ vap->iv_stats.is_rx_mgtdiscard++; break; } /* * deauth frame format * [2] reason */ IEEE80211_VERIFY_LENGTH(efrm - frm, 2, return); reason = le16toh(*(uint16_t *)frm); vap->iv_stats.is_rx_deauth++; vap->iv_stats.is_rx_deauth_code = reason; IEEE80211_NODE_STAT(ni, rx_deauth); IEEE80211_NOTE(vap, IEEE80211_MSG_AUTH, ni, "recv deauthenticate (reason %d)", reason); ieee80211_new_state(vap, IEEE80211_S_AUTH, (reason << 8) | IEEE80211_FC0_SUBTYPE_DEAUTH); break; } case IEEE80211_FC0_SUBTYPE_DISASSOC: { uint16_t reason; if (vap->iv_state != IEEE80211_S_RUN && vap->iv_state != IEEE80211_S_ASSOC && vap->iv_state != IEEE80211_S_AUTH) { vap->iv_stats.is_rx_mgtdiscard++; return; } if (!IEEE80211_ADDR_EQ(wh->i_addr1, vap->iv_myaddr)) { /* NB: can happen when in promiscuous mode */ vap->iv_stats.is_rx_mgtdiscard++; break; } /* * disassoc frame format * [2] reason */ IEEE80211_VERIFY_LENGTH(efrm - frm, 2, return); reason = le16toh(*(uint16_t *)frm); vap->iv_stats.is_rx_disassoc++; vap->iv_stats.is_rx_disassoc_code = reason; IEEE80211_NODE_STAT(ni, rx_disassoc); IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC, ni, "recv disassociate (reason %d)", reason); ieee80211_new_state(vap, IEEE80211_S_ASSOC, 0); break; } case IEEE80211_FC0_SUBTYPE_ACTION: case IEEE80211_FC0_SUBTYPE_ACTION_NOACK: if (!IEEE80211_ADDR_EQ(vap->iv_myaddr, wh->i_addr1) && !IEEE80211_IS_MULTICAST(wh->i_addr1)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "not for us"); vap->iv_stats.is_rx_mgtdiscard++; } else if (vap->iv_state != IEEE80211_S_RUN) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "wrong state %s", ieee80211_state_name[vap->iv_state]); vap->iv_stats.is_rx_mgtdiscard++; } else { if (ieee80211_parse_action(ni, m0) == 0) (void)ic->ic_recv_action(ni, wh, frm, efrm); } break; case IEEE80211_FC0_SUBTYPE_ASSOC_REQ: case IEEE80211_FC0_SUBTYPE_REASSOC_REQ: case IEEE80211_FC0_SUBTYPE_PROBE_REQ: case IEEE80211_FC0_SUBTYPE_TIMING_ADV: case IEEE80211_FC0_SUBTYPE_ATIM: IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "not handled"); vap->iv_stats.is_rx_mgtdiscard++; break; default: IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, "mgt", "subtype 0x%x not handled", subtype); vap->iv_stats.is_rx_badsubtype++; break; } #undef ISREASSOC #undef ISPROBE } static void sta_recv_ctl(struct ieee80211_node *ni, struct mbuf *m, int subtype) { switch (subtype) { case IEEE80211_FC0_SUBTYPE_BAR: ieee80211_recv_bar(ni, m); break; } }