diff options
Diffstat (limited to 'net/mac80211/sta_info.c')
| -rw-r--r-- | net/mac80211/sta_info.c | 226 | 
1 files changed, 125 insertions, 101 deletions
| diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c index 00ca8dcc2bcf..12971b71d0fa 100644 --- a/net/mac80211/sta_info.c +++ b/net/mac80211/sta_info.c @@ -64,32 +64,20 @@   * freed before they are done using it.   */ +static const struct rhashtable_params sta_rht_params = { +	.nelem_hint = 3, /* start small */ +	.head_offset = offsetof(struct sta_info, hash_node), +	.key_offset = offsetof(struct sta_info, sta.addr), +	.key_len = ETH_ALEN, +	.hashfn = sta_addr_hash, +}; +  /* Caller must hold local->sta_mtx */  static int sta_info_hash_del(struct ieee80211_local *local,  			     struct sta_info *sta)  { -	struct sta_info *s; - -	s = rcu_dereference_protected(local->sta_hash[STA_HASH(sta->sta.addr)], -				      lockdep_is_held(&local->sta_mtx)); -	if (!s) -		return -ENOENT; -	if (s == sta) { -		rcu_assign_pointer(local->sta_hash[STA_HASH(sta->sta.addr)], -				   s->hnext); -		return 0; -	} - -	while (rcu_access_pointer(s->hnext) && -	       rcu_access_pointer(s->hnext) != sta) -		s = rcu_dereference_protected(s->hnext, -					lockdep_is_held(&local->sta_mtx)); -	if (rcu_access_pointer(s->hnext)) { -		rcu_assign_pointer(s->hnext, sta->hnext); -		return 0; -	} - -	return -ENOENT; +	return rhashtable_remove_fast(&local->sta_hash, &sta->hash_node, +				      sta_rht_params);  }  static void __cleanup_single_sta(struct sta_info *sta) @@ -118,6 +106,16 @@ static void __cleanup_single_sta(struct sta_info *sta)  		atomic_dec(&ps->num_sta_ps);  	} +	if (sta->sta.txq[0]) { +		for (i = 0; i < ARRAY_SIZE(sta->sta.txq); i++) { +			struct txq_info *txqi = to_txq_info(sta->sta.txq[i]); +			int n = skb_queue_len(&txqi->queue); + +			ieee80211_purge_tx_queue(&local->hw, &txqi->queue); +			atomic_sub(n, &sdata->txqs_len[txqi->txq.ac]); +		} +	} +  	for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {  		local->total_ps_buffered -= skb_queue_len(&sta->ps_tx_buf[ac]);  		ieee80211_purge_tx_queue(&local->hw, &sta->ps_tx_buf[ac]); @@ -159,18 +157,8 @@ struct sta_info *sta_info_get(struct ieee80211_sub_if_data *sdata,  			      const u8 *addr)  {  	struct ieee80211_local *local = sdata->local; -	struct sta_info *sta; -	sta = rcu_dereference_check(local->sta_hash[STA_HASH(addr)], -				    lockdep_is_held(&local->sta_mtx)); -	while (sta) { -		if (sta->sdata == sdata && -		    ether_addr_equal(sta->sta.addr, addr)) -			break; -		sta = rcu_dereference_check(sta->hnext, -					    lockdep_is_held(&local->sta_mtx)); -	} -	return sta; +	return rhashtable_lookup_fast(&local->sta_hash, addr, sta_rht_params);  }  /* @@ -182,18 +170,24 @@ struct sta_info *sta_info_get_bss(struct ieee80211_sub_if_data *sdata,  {  	struct ieee80211_local *local = sdata->local;  	struct sta_info *sta; +	struct rhash_head *tmp; +	const struct bucket_table *tbl; -	sta = rcu_dereference_check(local->sta_hash[STA_HASH(addr)], -				    lockdep_is_held(&local->sta_mtx)); -	while (sta) { -		if ((sta->sdata == sdata || -		     (sta->sdata->bss && sta->sdata->bss == sdata->bss)) && -		    ether_addr_equal(sta->sta.addr, addr)) -			break; -		sta = rcu_dereference_check(sta->hnext, -					    lockdep_is_held(&local->sta_mtx)); +	rcu_read_lock(); +	tbl = rht_dereference_rcu(local->sta_hash.tbl, &local->sta_hash); + +	for_each_sta_info(local, tbl, addr, sta, tmp) { +		if (sta->sdata == sdata || +		    (sta->sdata->bss && sta->sdata->bss == sdata->bss)) { +			rcu_read_unlock(); +			/* this is safe as the caller must already hold +			 * another rcu read section or the mutex +			 */ +			return sta; +		}  	} -	return sta; +	rcu_read_unlock(); +	return NULL;  }  struct sta_info *sta_info_get_by_idx(struct ieee80211_sub_if_data *sdata, @@ -229,19 +223,13 @@ struct sta_info *sta_info_get_by_idx(struct ieee80211_sub_if_data *sdata,   */  void sta_info_free(struct ieee80211_local *local, struct sta_info *sta)  { -	int i; -  	if (sta->rate_ctrl)  		rate_control_free_sta(sta); -	if (sta->tx_lat) { -		for (i = 0; i < IEEE80211_NUM_TIDS; i++) -			kfree(sta->tx_lat[i].bins); -		kfree(sta->tx_lat); -	} -  	sta_dbg(sta->sdata, "Destroyed STA %pM\n", sta->sta.addr); +	if (sta->sta.txq[0]) +		kfree(to_txq_info(sta->sta.txq[0]));  	kfree(rcu_dereference_raw(sta->sta.rates));  	kfree(sta);  } @@ -250,9 +238,8 @@ void sta_info_free(struct ieee80211_local *local, struct sta_info *sta)  static void sta_info_hash_add(struct ieee80211_local *local,  			      struct sta_info *sta)  { -	lockdep_assert_held(&local->sta_mtx); -	sta->hnext = local->sta_hash[STA_HASH(sta->sta.addr)]; -	rcu_assign_pointer(local->sta_hash[STA_HASH(sta->sta.addr)], sta); +	rhashtable_insert_fast(&local->sta_hash, &sta->hash_node, +			       sta_rht_params);  }  static void sta_deliver_ps_frames(struct work_struct *wk) @@ -293,44 +280,15 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,  				const u8 *addr, gfp_t gfp)  {  	struct ieee80211_local *local = sdata->local; +	struct ieee80211_hw *hw = &local->hw;  	struct sta_info *sta;  	struct timespec uptime; -	struct ieee80211_tx_latency_bin_ranges *tx_latency;  	int i; -	sta = kzalloc(sizeof(*sta) + local->hw.sta_data_size, gfp); +	sta = kzalloc(sizeof(*sta) + hw->sta_data_size, gfp);  	if (!sta)  		return NULL; -	rcu_read_lock(); -	tx_latency = rcu_dereference(local->tx_latency); -	/* init stations Tx latency statistics && TID bins */ -	if (tx_latency) { -		sta->tx_lat = kzalloc(IEEE80211_NUM_TIDS * -				      sizeof(struct ieee80211_tx_latency_stat), -				      GFP_ATOMIC); -		if (!sta->tx_lat) { -			rcu_read_unlock(); -			goto free; -		} - -		if (tx_latency->n_ranges) { -			for (i = 0; i < IEEE80211_NUM_TIDS; i++) { -				/* size of bins is size of the ranges +1 */ -				sta->tx_lat[i].bin_count = -					tx_latency->n_ranges + 1; -				sta->tx_lat[i].bins = -					kcalloc(sta->tx_lat[i].bin_count, -						sizeof(u32), GFP_ATOMIC); -				if (!sta->tx_lat[i].bins) { -					rcu_read_unlock(); -					goto free; -				} -			} -		} -	} -	rcu_read_unlock(); -  	spin_lock_init(&sta->lock);  	spin_lock_init(&sta->ps_lock);  	INIT_WORK(&sta->drv_deliver_wk, sta_deliver_ps_frames); @@ -359,8 +317,24 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,  	for (i = 0; i < ARRAY_SIZE(sta->chain_signal_avg); i++)  		ewma_init(&sta->chain_signal_avg[i], 1024, 8); +	if (local->ops->wake_tx_queue) { +		void *txq_data; +		int size = sizeof(struct txq_info) + +			   ALIGN(hw->txq_data_size, sizeof(void *)); + +		txq_data = kcalloc(ARRAY_SIZE(sta->sta.txq), size, gfp); +		if (!txq_data) +			goto free; + +		for (i = 0; i < ARRAY_SIZE(sta->sta.txq); i++) { +			struct txq_info *txq = txq_data + i * size; + +			ieee80211_init_tx_queue(sdata, sta, txq, i); +		} +	} +  	if (sta_prepare_rate_control(local, sta, gfp)) -		goto free; +		goto free_txq;  	for (i = 0; i < IEEE80211_NUM_TIDS; i++) {  		/* @@ -382,7 +356,7 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,  	if (sdata->vif.type == NL80211_IFTYPE_AP ||  	    sdata->vif.type == NL80211_IFTYPE_AP_VLAN) {  		struct ieee80211_supported_band *sband = -			local->hw.wiphy->bands[ieee80211_get_sdata_band(sdata)]; +			hw->wiphy->bands[ieee80211_get_sdata_band(sdata)];  		u8 smps = (sband->ht_cap.cap & IEEE80211_HT_CAP_SM_PS) >>  				IEEE80211_HT_CAP_SM_PS_SHIFT;  		/* @@ -405,14 +379,13 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,  	}  	sta_dbg(sdata, "Allocated STA %pM\n", sta->sta.addr); +  	return sta; +free_txq: +	if (sta->sta.txq[0]) +		kfree(to_txq_info(sta->sta.txq[0]));  free: -	if (sta->tx_lat) { -		for (i = 0; i < IEEE80211_NUM_TIDS; i++) -			kfree(sta->tx_lat[i].bins); -		kfree(sta->tx_lat); -	}  	kfree(sta);  	return NULL;  } @@ -684,6 +657,8 @@ static void __sta_info_recalc_tim(struct sta_info *sta, bool ignore_pending)  		indicate_tim |=  			sta->driver_buffered_tids & tids; +		indicate_tim |= +			sta->txq_buffered_tids & tids;  	}   done: @@ -992,19 +967,32 @@ static void sta_info_cleanup(unsigned long data)  		  round_jiffies(jiffies + STA_INFO_CLEANUP_INTERVAL));  } -void sta_info_init(struct ieee80211_local *local) +u32 sta_addr_hash(const void *key, u32 length, u32 seed) +{ +	return jhash(key, ETH_ALEN, seed); +} + +int sta_info_init(struct ieee80211_local *local)  { +	int err; + +	err = rhashtable_init(&local->sta_hash, &sta_rht_params); +	if (err) +		return err; +  	spin_lock_init(&local->tim_lock);  	mutex_init(&local->sta_mtx);  	INIT_LIST_HEAD(&local->sta_list);  	setup_timer(&local->sta_cleanup, sta_info_cleanup,  		    (unsigned long)local); +	return 0;  }  void sta_info_stop(struct ieee80211_local *local)  {  	del_timer_sync(&local->sta_cleanup); +	rhashtable_destroy(&local->sta_hash);  } @@ -1068,16 +1056,21 @@ void ieee80211_sta_expire(struct ieee80211_sub_if_data *sdata,  }  struct ieee80211_sta *ieee80211_find_sta_by_ifaddr(struct ieee80211_hw *hw, -					       const u8 *addr, -					       const u8 *localaddr) +						   const u8 *addr, +						   const u8 *localaddr)  { -	struct sta_info *sta, *nxt; +	struct ieee80211_local *local = hw_to_local(hw); +	struct sta_info *sta; +	struct rhash_head *tmp; +	const struct bucket_table *tbl; + +	tbl = rht_dereference_rcu(local->sta_hash.tbl, &local->sta_hash);  	/*  	 * Just return a random station if localaddr is NULL  	 * ... first in list.  	 */ -	for_each_sta_info(hw_to_local(hw), addr, sta, nxt) { +	for_each_sta_info(local, tbl, addr, sta, tmp) {  		if (localaddr &&  		    !ether_addr_equal(sta->sdata->vif.addr, localaddr))  			continue; @@ -1115,7 +1108,7 @@ void ieee80211_sta_ps_deliver_wakeup(struct sta_info *sta)  	struct ieee80211_sub_if_data *sdata = sta->sdata;  	struct ieee80211_local *local = sdata->local;  	struct sk_buff_head pending; -	int filtered = 0, buffered = 0, ac; +	int filtered = 0, buffered = 0, ac, i;  	unsigned long flags;  	struct ps_data *ps; @@ -1134,10 +1127,22 @@ void ieee80211_sta_ps_deliver_wakeup(struct sta_info *sta)  	BUILD_BUG_ON(BITS_TO_LONGS(IEEE80211_NUM_TIDS) > 1);  	sta->driver_buffered_tids = 0; +	sta->txq_buffered_tids = 0;  	if (!(local->hw.flags & IEEE80211_HW_AP_LINK_PS))  		drv_sta_notify(local, sdata, STA_NOTIFY_AWAKE, &sta->sta); +	if (sta->sta.txq[0]) { +		for (i = 0; i < ARRAY_SIZE(sta->sta.txq); i++) { +			struct txq_info *txqi = to_txq_info(sta->sta.txq[i]); + +			if (!skb_queue_len(&txqi->queue)) +				continue; + +			drv_wake_tx_queue(local, txqi); +		} +	} +  	skb_queue_head_init(&pending);  	/* sync with ieee80211_tx_h_unicast_ps_buf */ @@ -1275,7 +1280,7 @@ static void ieee80211_send_null_response(struct ieee80211_sub_if_data *sdata,  	}  	info->band = chanctx_conf->def.chan->band; -	ieee80211_xmit(sdata, skb); +	ieee80211_xmit(sdata, sta, skb);  	rcu_read_unlock();  } @@ -1319,8 +1324,10 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta,  		/* if we already have frames from software, then we can't also  		 * release from hardware queues  		 */ -		if (skb_queue_empty(&frames)) +		if (skb_queue_empty(&frames)) {  			driver_release_tids |= sta->driver_buffered_tids & tids; +			driver_release_tids |= sta->txq_buffered_tids & tids; +		}  		if (driver_release_tids) {  			/* If the driver has data on more than one TID then @@ -1491,6 +1498,9 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta,  		sta_info_recalc_tim(sta);  	} else { +		unsigned long tids = sta->txq_buffered_tids & driver_release_tids; +		int tid; +  		/*  		 * We need to release a frame that is buffered somewhere in the  		 * driver ... it'll have to handle that. @@ -1510,8 +1520,22 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta,  		 * that the TID(s) became empty before returning here from the  		 * release function.  		 * Either way, however, when the driver tells us that the TID(s) -		 * became empty we'll do the TIM recalculation. +		 * became empty or we find that a txq became empty, we'll do the +		 * TIM recalculation.  		 */ + +		if (!sta->sta.txq[0]) +			return; + +		for (tid = 0; tid < ARRAY_SIZE(sta->sta.txq); tid++) { +			struct txq_info *txqi = to_txq_info(sta->sta.txq[tid]); + +			if (!(tids & BIT(tid)) || skb_queue_len(&txqi->queue)) +				continue; + +			sta_info_recalc_tim(sta); +			break; +		}  	}  } |