diff --git a/drivers/net/wireless/iwlwifi/iwl-3945-rs.c b/drivers/net/wireless/iwlwifi/iwl-3945-rs.c index a8223c4cc97c..c4bfba6f3c2b 100644 --- a/drivers/net/wireless/iwlwifi/iwl-3945-rs.c +++ b/drivers/net/wireless/iwlwifi/iwl-3945-rs.c @@ -471,10 +471,11 @@ static void rs_tx_status(void *priv_rate, return; } + rcu_read_lock(); + sta = sta_info_get(local, hdr->addr1); if (!sta || !sta->rate_ctrl_priv) { - if (sta) - sta_info_put(sta); + rcu_read_unlock(); IWL_DEBUG_RATE("leave: No STA priv data to update!\n"); return; } @@ -547,7 +548,7 @@ static void rs_tx_status(void *priv_rate, spin_unlock_irqrestore(&rs_sta->lock, flags); - sta_info_put(sta); + rcu_read_unlock(); IWL_DEBUG_RATE("leave\n"); @@ -658,6 +659,8 @@ static void rs_get_rate(void *priv_rate, struct net_device *dev, IWL_DEBUG_RATE("enter\n"); + rcu_read_lock(); + sta = sta_info_get(local, hdr->addr1); /* Send management frames and broadcast/multicast data using lowest @@ -668,8 +671,7 @@ static void rs_get_rate(void *priv_rate, struct net_device *dev, !sta || !sta->rate_ctrl_priv) { IWL_DEBUG_RATE("leave: No STA priv data to update!\n"); sel->rate = rate_lowest(local, band, sta); - if (sta) - sta_info_put(sta); + rcu_read_unlock(); return; } @@ -811,7 +813,7 @@ static void rs_get_rate(void *priv_rate, struct net_device *dev, else sta->txrate_idx = sta->last_txrate_idx; - sta_info_put(sta); + rcu_read_unlock(); IWL_DEBUG_RATE("leave: %d\n", index); @@ -843,13 +845,15 @@ int iwl3945_fill_rs_info(struct ieee80211_hw *hw, char *buf, u8 sta_id) unsigned long now = jiffies; u32 max_time = 0; + rcu_read_lock(); + sta = sta_info_get(local, priv->stations[sta_id].sta.sta.addr); if (!sta || !sta->rate_ctrl_priv) { - if (sta) { - sta_info_put(sta); + if (sta) IWL_DEBUG_RATE("leave - no private rate data!\n"); - } else + else IWL_DEBUG_RATE("leave - no station!\n"); + rcu_read_unlock(); return sprintf(buf, "station %d not found\n", sta_id); } @@ -890,7 +894,7 @@ int iwl3945_fill_rs_info(struct ieee80211_hw *hw, char *buf, u8 sta_id) i = j; } spin_unlock_irqrestore(&rs_sta->lock, flags); - sta_info_put(sta); + rcu_read_unlock(); /* Display the average rate of all samples taken. * @@ -927,11 +931,12 @@ void iwl3945_rate_scale_init(struct ieee80211_hw *hw, s32 sta_id) return; } + rcu_read_lock(); + sta = sta_info_get(local, priv->stations[sta_id].sta.sta.addr); if (!sta || !sta->rate_ctrl_priv) { - if (sta) - sta_info_put(sta); IWL_DEBUG_RATE("leave - no private rate data!\n"); + rcu_read_unlock(); return; } @@ -958,7 +963,7 @@ void iwl3945_rate_scale_init(struct ieee80211_hw *hw, s32 sta_id) break; } - sta_info_put(sta); + rcu_read_unlock(); spin_unlock_irqrestore(&rs_sta->lock, flags); rssi = priv->last_rx_rssi; diff --git a/drivers/net/wireless/iwlwifi/iwl-4965-rs.c b/drivers/net/wireless/iwlwifi/iwl-4965-rs.c index 48a6a85355ec..46d85fd07faa 100644 --- a/drivers/net/wireless/iwlwifi/iwl-4965-rs.c +++ b/drivers/net/wireless/iwlwifi/iwl-4965-rs.c @@ -847,12 +847,12 @@ static void rs_tx_status(void *priv_rate, struct net_device *dev, if (retries > 15) retries = 15; + rcu_read_lock(); sta = sta_info_get(local, hdr->addr1); if (!sta || !sta->rate_ctrl_priv) { - if (sta) - sta_info_put(sta); + rcu_read_unlock(); return; } @@ -891,7 +891,7 @@ static void rs_tx_status(void *priv_rate, struct net_device *dev, if ((rs_index < 0) || (rs_index >= IWL_RATE_COUNT)) { IWL_DEBUG_RATE("bad rate index at: %d rate 0x%X\n", rs_index, tx_mcs.rate_n_flags); - sta_info_put(sta); + rcu_read_unlock(); return; } @@ -909,7 +909,7 @@ static void rs_tx_status(void *priv_rate, struct net_device *dev, IWL_DEBUG_RATE("initial rate does not match 0x%x 0x%x\n", tx_mcs.rate_n_flags, le32_to_cpu(table->rs_table[0].rate_n_flags)); - sta_info_put(sta); + rcu_read_unlock(); return; } @@ -1025,7 +1025,7 @@ static void rs_tx_status(void *priv_rate, struct net_device *dev, /* See if there's a better rate or modulation mode to try. */ rs_rate_scale_perform(priv, dev, hdr, sta); - sta_info_put(sta); + rcu_read_unlock(); return; } @@ -2219,6 +2219,8 @@ static void rs_get_rate(void *priv_rate, struct net_device *dev, IWL_DEBUG_RATE_LIMIT("rate scale calculate new rate for skb\n"); + rcu_read_lock(); + sta = sta_info_get(local, hdr->addr1); /* Send management frames and broadcast/multicast data using lowest @@ -2227,8 +2229,7 @@ static void rs_get_rate(void *priv_rate, struct net_device *dev, if (!ieee80211_is_data(fc) || is_multicast_ether_addr(hdr->addr1) || !sta || !sta->rate_ctrl_priv) { sel->rate = rate_lowest(local, sband, sta); - if (sta) - sta_info_put(sta); + rcu_read_unlock(); return; } @@ -2261,7 +2262,7 @@ static void rs_get_rate(void *priv_rate, struct net_device *dev, sel->rate = rate_lowest(local, sband, sta); return; } - sta_info_put(sta); + rcu_read_unlock(); sel->rate = &priv->ieee_rates[i]; } @@ -2735,13 +2736,15 @@ int iwl4965_fill_rs_info(struct ieee80211_hw *hw, char *buf, u8 sta_id) u32 max_time = 0; u8 lq_type, antenna; + rcu_read_lock(); + sta = sta_info_get(local, priv->stations[sta_id].sta.sta.addr); if (!sta || !sta->rate_ctrl_priv) { - if (sta) { - sta_info_put(sta); + if (sta) IWL_DEBUG_RATE("leave - no private rate data!\n"); - } else + else IWL_DEBUG_RATE("leave - no station!\n"); + rcu_read_unlock(); return sprintf(buf, "station %d not found\n", sta_id); } @@ -2808,7 +2811,7 @@ int iwl4965_fill_rs_info(struct ieee80211_hw *hw, char *buf, u8 sta_id) "active_search %d rate index %d\n", lq_type, antenna, lq_sta->search_better_tbl, sta->last_txrate_idx); - sta_info_put(sta); + rcu_read_unlock(); return cnt; } diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 6ac49231efa9..e9ba6fcc0e45 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -136,7 +136,6 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct net_device *dev, struct ieee80211_sub_if_data *sdata; struct sta_info *sta = NULL; enum ieee80211_key_alg alg; - int ret; struct ieee80211_key *key; sdata = IEEE80211_DEV_TO_SUB_IF(dev); @@ -170,12 +169,7 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct net_device *dev, ieee80211_key_link(key, sdata, sta); - ret = 0; - - if (sta) - sta_info_put(sta); - - return ret; + return 0; } static int ieee80211_del_key(struct wiphy *wiphy, struct net_device *dev, @@ -184,7 +178,6 @@ static int ieee80211_del_key(struct wiphy *wiphy, struct net_device *dev, struct ieee80211_sub_if_data *sdata; struct sta_info *sta; int ret; - struct ieee80211_key *key; sdata = IEEE80211_DEV_TO_SUB_IF(dev); @@ -195,21 +188,18 @@ static int ieee80211_del_key(struct wiphy *wiphy, struct net_device *dev, ret = 0; if (sta->key) { - key = sta->key; - ieee80211_key_free(key); + ieee80211_key_free(sta->key); WARN_ON(sta->key); } else ret = -ENOENT; - sta_info_put(sta); return ret; } if (!sdata->keys[key_idx]) return -ENOENT; - key = sdata->keys[key_idx]; - ieee80211_key_free(key); + ieee80211_key_free(sdata->keys[key_idx]); WARN_ON(sdata->keys[key_idx]); return 0; @@ -292,8 +282,6 @@ static int ieee80211_get_key(struct wiphy *wiphy, struct net_device *dev, err = 0; out: - if (sta) - sta_info_put(sta); return err; } @@ -311,7 +299,7 @@ static int ieee80211_config_default_key(struct wiphy *wiphy, static void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo) { - struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev); + struct ieee80211_sub_if_data *sdata = sta->sdata; sinfo->filled = STATION_INFO_INACTIVE_TIME | STATION_INFO_RX_BYTES | @@ -340,16 +328,20 @@ static int ieee80211_dump_station(struct wiphy *wiphy, struct net_device *dev, { struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); struct sta_info *sta; + int ret = -ENOENT; + + rcu_read_lock(); sta = sta_info_get_by_idx(local, idx, dev); - if (!sta) - return -ENOENT; + if (sta) { + ret = 0; + memcpy(mac, sta->addr, ETH_ALEN); + sta_set_sinfo(sta, sinfo); + } - memcpy(mac, sta->addr, ETH_ALEN); - sta_set_sinfo(sta, sinfo); - sta_info_put(sta); + rcu_read_unlock(); - return 0; + return ret; } static int ieee80211_get_station(struct wiphy *wiphy, struct net_device *dev, @@ -357,16 +349,21 @@ static int ieee80211_get_station(struct wiphy *wiphy, struct net_device *dev, { struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); struct sta_info *sta; + int ret = -ENOENT; - sta = sta_info_get(local, mac); - if (!sta) - return -ENOENT; + rcu_read_lock(); /* XXX: verify sta->dev == dev */ - sta_set_sinfo(sta, sinfo); - sta_info_put(sta); - return 0; + sta = sta_info_get(local, mac); + if (sta) { + ret = 0; + sta_set_sinfo(sta, sinfo); + } + + rcu_read_unlock(); + + return ret; } /* @@ -559,8 +556,8 @@ static void ieee80211_send_layer2_update(struct sta_info *sta) msg->xid_info[1] = 1; /* LLC types/classes: Type 1 LLC */ msg->xid_info[2] = 0; /* XID sender's receive window size (RW) */ - skb->dev = sta->dev; - skb->protocol = eth_type_trans(skb, sta->dev); + skb->dev = sta->sdata->dev; + skb->protocol = eth_type_trans(skb, sta->sdata->dev); memset(skb->cb, 0, sizeof(skb->cb)); netif_rx(skb); } @@ -572,7 +569,7 @@ static void sta_apply_parameters(struct ieee80211_local *local, u32 rates; int i, j; struct ieee80211_supported_band *sband; - struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev); + struct ieee80211_sub_if_data *sdata = sta->sdata; if (params->station_flags & STATION_FLAG_CHANGED) { sta->flags &= ~WLAN_STA_AUTHORIZED; @@ -644,14 +641,13 @@ static int ieee80211_add_station(struct wiphy *wiphy, struct net_device *dev, sdata = IEEE80211_DEV_TO_SUB_IF(dev); if (ieee80211_vif_is_mesh(&sdata->vif)) - sta = mesh_plink_add(mac, DEFAULT_RATES, dev); + sta = mesh_plink_add(mac, DEFAULT_RATES, sdata); else - sta = sta_info_add(local, dev, mac, GFP_KERNEL); + sta = sta_info_add(sdata, mac); if (IS_ERR(sta)) return PTR_ERR(sta); - sta->dev = sdata->dev; if (sdata->vif.type == IEEE80211_IF_TYPE_VLAN || sdata->vif.type == IEEE80211_IF_TYPE_AP) ieee80211_send_layer2_update(sta); @@ -662,15 +658,14 @@ static int ieee80211_add_station(struct wiphy *wiphy, struct net_device *dev, rate_control_rate_init(sta, local); - sta_info_put(sta); - return 0; } static int ieee80211_del_station(struct wiphy *wiphy, struct net_device *dev, u8 *mac) { - struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct ieee80211_local *local = sdata->local; struct sta_info *sta; if (mac) { @@ -679,10 +674,14 @@ static int ieee80211_del_station(struct wiphy *wiphy, struct net_device *dev, if (!sta) return -ENOENT; - sta_info_free(sta); - sta_info_put(sta); + sta_info_unlink(&sta); + + if (sta) { + synchronize_rcu(); + sta_info_destroy(sta); + } } else - sta_info_flush(local, dev); + sta_info_flush(local, sdata); return 0; } @@ -701,21 +700,19 @@ static int ieee80211_change_station(struct wiphy *wiphy, if (!sta) return -ENOENT; - if (params->vlan && params->vlan != sta->dev) { + if (params->vlan && params->vlan != sta->sdata->dev) { vlansdata = IEEE80211_DEV_TO_SUB_IF(params->vlan); if (vlansdata->vif.type != IEEE80211_IF_TYPE_VLAN || vlansdata->vif.type != IEEE80211_IF_TYPE_AP) return -EINVAL; - sta->dev = params->vlan; + sta->sdata = IEEE80211_DEV_TO_SUB_IF(params->vlan); ieee80211_send_layer2_update(sta); } sta_apply_parameters(local, sta, params); - sta_info_put(sta); - return 0; } @@ -735,23 +732,26 @@ static int ieee80211_add_mpath(struct wiphy *wiphy, struct net_device *dev, if (sdata->vif.type != IEEE80211_IF_TYPE_MESH_POINT) return -ENOTSUPP; + rcu_read_lock(); sta = sta_info_get(local, next_hop); - if (!sta) + if (!sta) { + rcu_read_unlock(); return -ENOENT; + } err = mesh_path_add(dst, dev); - if (err) + if (err) { + rcu_read_unlock(); return err; + } - rcu_read_lock(); mpath = mesh_path_lookup(dst, dev); if (!mpath) { rcu_read_unlock(); - sta_info_put(sta); return -ENXIO; } mesh_path_fix_nexthop(mpath, sta); - sta_info_put(sta); + rcu_read_unlock(); return 0; } @@ -760,7 +760,7 @@ static int ieee80211_del_mpath(struct wiphy *wiphy, struct net_device *dev, u8 *dst) { if (dst) - return mesh_path_del(dst, dev); + return mesh_path_del(dst, dev, false); mesh_path_flush(dev); return 0; @@ -781,20 +781,22 @@ static int ieee80211_change_mpath(struct wiphy *wiphy, if (sdata->vif.type != IEEE80211_IF_TYPE_MESH_POINT) return -ENOTSUPP; - sta = sta_info_get(local, next_hop); - if (!sta) - return -ENOENT; - rcu_read_lock(); + + sta = sta_info_get(local, next_hop); + if (!sta) { + rcu_read_unlock(); + return -ENOENT; + } + mpath = mesh_path_lookup(dst, dev); if (!mpath) { rcu_read_unlock(); - sta_info_put(sta); return -ENOENT; } mesh_path_fix_nexthop(mpath, sta); - sta_info_put(sta); + rcu_read_unlock(); return 0; } diff --git a/net/mac80211/debugfs_sta.c b/net/mac80211/debugfs_sta.c index ed7c9f3b4602..73cfb4da464d 100644 --- a/net/mac80211/debugfs_sta.c +++ b/net/mac80211/debugfs_sta.c @@ -51,7 +51,7 @@ static const struct file_operations sta_ ##name## _ops = { \ STA_OPS(name) STA_FILE(aid, aid, D); -STA_FILE(dev, dev->name, S); +STA_FILE(dev, sdata->dev->name, S); STA_FILE(rx_packets, rx_packets, LU); STA_FILE(tx_packets, tx_packets, LU); STA_FILE(rx_bytes, rx_bytes, LU); @@ -200,7 +200,7 @@ static ssize_t sta_agg_status_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { struct sta_info *sta = file->private_data; - struct net_device *dev = sta->dev; + struct net_device *dev = sta->sdata->dev; struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); struct ieee80211_hw *hw = &local->hw; u8 *da = sta->addr; diff --git a/net/mac80211/debugfs_sta.h b/net/mac80211/debugfs_sta.h index 574a1cd54b96..8b608903259f 100644 --- a/net/mac80211/debugfs_sta.h +++ b/net/mac80211/debugfs_sta.h @@ -1,6 +1,8 @@ #ifndef __MAC80211_DEBUGFS_STA_H #define __MAC80211_DEBUGFS_STA_H +#include "sta_info.h" + #ifdef CONFIG_MAC80211_DEBUGFS void ieee80211_sta_debugfs_add(struct sta_info *sta); void ieee80211_sta_debugfs_remove(struct sta_info *sta); diff --git a/net/mac80211/ieee80211.c b/net/mac80211/ieee80211.c index 727af295c969..85b1391375c0 100644 --- a/net/mac80211/ieee80211.c +++ b/net/mac80211/ieee80211.c @@ -375,15 +375,19 @@ static int ieee80211_stop(struct net_device *dev) sdata = IEEE80211_DEV_TO_SUB_IF(dev); - list_for_each_entry(sta, &local->sta_list, list) { - if (sta->dev == dev) + rcu_read_lock(); + + list_for_each_entry_rcu(sta, &local->sta_list, list) { + if (sta->sdata == sdata) for (i = 0; i < STA_TID_NUM; i++) - ieee80211_sta_stop_rx_ba_session(sta->dev, + ieee80211_sta_stop_rx_ba_session(sdata->dev, sta->addr, i, WLAN_BACK_RECIPIENT, WLAN_REASON_QSTA_LEAVE_QBSS); } + rcu_read_unlock(); + netif_stop_queue(dev); /* @@ -449,7 +453,7 @@ static int ieee80211_stop(struct net_device *dev) netif_tx_unlock_bh(local->mdev); break; case IEEE80211_IF_TYPE_MESH_POINT: - sta_info_flush(local, dev); + sta_info_flush(local, sdata); /* fall through */ case IEEE80211_IF_TYPE_STA: case IEEE80211_IF_TYPE_IBSS: @@ -522,9 +526,12 @@ int ieee80211_start_tx_ba_session(struct ieee80211_hw *hw, u8 *ra, u16 tid) print_mac(mac, ra), tid); #endif /* CONFIG_MAC80211_HT_DEBUG */ + rcu_read_lock(); + sta = sta_info_get(local, ra); if (!sta) { printk(KERN_DEBUG "Could not find the station\n"); + rcu_read_unlock(); return -ENOENT; } @@ -564,7 +571,7 @@ int ieee80211_start_tx_ba_session(struct ieee80211_hw *hw, u8 *ra, u16 tid) spin_unlock_bh(&local->mdev->queue_lock); goto start_ba_exit; } - sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev); + sdata = sta->sdata; /* Ok, the Addba frame hasn't been sent yet, but if the driver calls the * call back right away, it must see that the flow has begun */ @@ -601,7 +608,7 @@ int ieee80211_start_tx_ba_session(struct ieee80211_hw *hw, u8 *ra, u16 tid) sta->ampdu_mlme.dialog_token_allocator; sta->ampdu_mlme.tid_tx[tid].ssn = start_seq_num; - ieee80211_send_addba_request(sta->dev, ra, tid, + ieee80211_send_addba_request(sta->sdata->dev, ra, tid, sta->ampdu_mlme.tid_tx[tid].dialog_token, sta->ampdu_mlme.tid_tx[tid].ssn, 0x40, 5000); @@ -614,7 +621,7 @@ int ieee80211_start_tx_ba_session(struct ieee80211_hw *hw, u8 *ra, u16 tid) start_ba_exit: spin_unlock_bh(&sta->ampdu_mlme.ampdu_tx); - sta_info_put(sta); + rcu_read_unlock(); return ret; } EXPORT_SYMBOL(ieee80211_start_tx_ba_session); @@ -637,9 +644,12 @@ int ieee80211_stop_tx_ba_session(struct ieee80211_hw *hw, print_mac(mac, ra), tid); #endif /* CONFIG_MAC80211_HT_DEBUG */ + rcu_read_lock(); sta = sta_info_get(local, ra); - if (!sta) + if (!sta) { + rcu_read_unlock(); return -ENOENT; + } /* check if the TID is in aggregation */ state = &sta->ampdu_mlme.tid_tx[tid].state; @@ -673,7 +683,7 @@ int ieee80211_stop_tx_ba_session(struct ieee80211_hw *hw, stop_BA_exit: spin_unlock_bh(&sta->ampdu_mlme.ampdu_tx); - sta_info_put(sta); + rcu_read_unlock(); return ret; } EXPORT_SYMBOL(ieee80211_stop_tx_ba_session); @@ -691,8 +701,10 @@ void ieee80211_start_tx_ba_cb(struct ieee80211_hw *hw, u8 *ra, u16 tid) return; } + rcu_read_lock(); sta = sta_info_get(local, ra); if (!sta) { + rcu_read_unlock(); printk(KERN_DEBUG "Could not find station: %s\n", print_mac(mac, ra)); return; @@ -705,7 +717,7 @@ void ieee80211_start_tx_ba_cb(struct ieee80211_hw *hw, u8 *ra, u16 tid) printk(KERN_DEBUG "addBA was not requested yet, state is %d\n", *state); spin_unlock_bh(&sta->ampdu_mlme.ampdu_tx); - sta_info_put(sta); + rcu_read_unlock(); return; } @@ -718,7 +730,7 @@ void ieee80211_start_tx_ba_cb(struct ieee80211_hw *hw, u8 *ra, u16 tid) ieee80211_wake_queue(hw, sta->tid_to_tx_q[tid]); } spin_unlock_bh(&sta->ampdu_mlme.ampdu_tx); - sta_info_put(sta); + rcu_read_unlock(); } EXPORT_SYMBOL(ieee80211_start_tx_ba_cb); @@ -739,10 +751,12 @@ void ieee80211_stop_tx_ba_cb(struct ieee80211_hw *hw, u8 *ra, u8 tid) printk(KERN_DEBUG "Stop a BA session requested on DA %s tid %d\n", print_mac(mac, ra), tid); + rcu_read_lock(); sta = sta_info_get(local, ra); if (!sta) { printk(KERN_DEBUG "Could not find station: %s\n", print_mac(mac, ra)); + rcu_read_unlock(); return; } state = &sta->ampdu_mlme.tid_tx[tid].state; @@ -750,13 +764,13 @@ void ieee80211_stop_tx_ba_cb(struct ieee80211_hw *hw, u8 *ra, u8 tid) spin_lock_bh(&sta->ampdu_mlme.ampdu_tx); if ((*state & HT_AGG_STATE_REQ_STOP_BA_MSK) == 0) { printk(KERN_DEBUG "unexpected callback to A-MPDU stop\n"); - sta_info_put(sta); spin_unlock_bh(&sta->ampdu_mlme.ampdu_tx); + rcu_read_unlock(); return; } if (*state & HT_AGG_STATE_INITIATOR_MSK) - ieee80211_send_delba(sta->dev, ra, tid, + ieee80211_send_delba(sta->sdata->dev, ra, tid, WLAN_BACK_INITIATOR, WLAN_REASON_QSTA_NOT_USE); agg_queue = sta->tid_to_tx_q[tid]; @@ -777,7 +791,7 @@ void ieee80211_stop_tx_ba_cb(struct ieee80211_hw *hw, u8 *ra, u8 tid) sta->ampdu_mlme.tid_tx[tid].addba_req_num = 0; spin_unlock_bh(&sta->ampdu_mlme.ampdu_tx); - sta_info_put(sta); + rcu_read_unlock(); } EXPORT_SYMBOL(ieee80211_stop_tx_ba_cb); @@ -887,32 +901,41 @@ int ieee80211_if_update_wds(struct net_device *dev, u8 *remote_addr) struct sta_info *sta; DECLARE_MAC_BUF(mac); + might_sleep(); + if (compare_ether_addr(remote_addr, sdata->u.wds.remote_addr) == 0) return 0; + rcu_read_lock(); + /* Create STA entry for the new peer */ - sta = sta_info_add(local, dev, remote_addr, GFP_KERNEL); - if (IS_ERR(sta)) + sta = sta_info_add(sdata, remote_addr); + if (IS_ERR(sta)) { + rcu_read_unlock(); return PTR_ERR(sta); + } sta->flags |= WLAN_STA_AUTHORIZED; - sta_info_put(sta); - /* Remove STA entry for the old peer */ sta = sta_info_get(local, sdata->u.wds.remote_addr); - if (sta) { - sta_info_free(sta); - sta_info_put(sta); - } else { + if (sta) + sta_info_unlink(&sta); + else printk(KERN_DEBUG "%s: could not find STA entry for WDS link " "peer %s\n", dev->name, print_mac(mac, sdata->u.wds.remote_addr)); - } /* Update WDS link data */ memcpy(&sdata->u.wds.remote_addr, remote_addr, ETH_ALEN); + rcu_read_unlock(); + + if (sta) { + synchronize_rcu(); + sta_info_destroy(sta); + } + return 0; } @@ -1330,6 +1353,8 @@ void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb, return; } + rcu_read_lock(); + if (status->excessive_retries) { struct sta_info *sta; sta = sta_info_get(local, hdr->addr1); @@ -1343,10 +1368,9 @@ void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb, status->flags |= IEEE80211_TX_STATUS_TX_FILTERED; ieee80211_handle_filtered_frame(local, sta, skb, status); - sta_info_put(sta); + rcu_read_unlock(); return; } - sta_info_put(sta); } } @@ -1356,12 +1380,14 @@ void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb, if (sta) { ieee80211_handle_filtered_frame(local, sta, skb, status); - sta_info_put(sta); + rcu_read_unlock(); return; } } else rate_control_tx_status(local->mdev, skb, status); + rcu_read_unlock(); + ieee80211_led_tx(local, 0); /* SNMP counters diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index d3b5cc57af40..8e440c5706dd 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -574,6 +574,7 @@ struct ieee80211_local { unsigned int filter_flags; /* FIF_* */ struct iw_statistics wstats; u8 wstats_flags; + bool tim_in_locked_section; /* see ieee80211_beacon_get() */ int tx_headroom; /* required headroom for hardware/radiotap */ enum { @@ -591,9 +592,15 @@ struct ieee80211_local { struct sk_buff_head skb_queue; struct sk_buff_head skb_queue_unreliable; - /* Station data structures */ - rwlock_t sta_lock; /* protects STA data structures */ - int num_sta; /* number of stations in sta_list */ + /* Station data */ + /* + * The lock only protects the list, hash, timer and counter + * against manipulation, reads are done in RCU. Additionally, + * the lock protects each BSS's TIM bitmap and a few items + * in a STA info structure. + */ + spinlock_t sta_lock; + unsigned long num_sta; struct list_head sta_list; struct sta_info *sta_hash[STA_HASH_SIZE]; struct timer_list sta_cleanup; diff --git a/net/mac80211/ieee80211_iface.c b/net/mac80211/ieee80211_iface.c index b0f17a2b1a42..98b22736e883 100644 --- a/net/mac80211/ieee80211_iface.c +++ b/net/mac80211/ieee80211_iface.c @@ -240,16 +240,21 @@ void ieee80211_if_reinit(struct net_device *dev) break; } case IEEE80211_IF_TYPE_WDS: + rcu_read_lock(); sta = sta_info_get(local, sdata->u.wds.remote_addr); if (sta) { - sta_info_free(sta); - sta_info_put(sta); + sta_info_unlink(&sta); } else { #ifdef CONFIG_MAC80211_VERBOSE_DEBUG printk(KERN_DEBUG "%s: Someone had deleted my STA " "entry for the WDS link\n", dev->name); #endif /* CONFIG_MAC80211_VERBOSE_DEBUG */ } + rcu_read_unlock(); + if (sta) { + synchronize_rcu(); + sta_info_destroy(sta); + } break; case IEEE80211_IF_TYPE_MESH_POINT: case IEEE80211_IF_TYPE_STA: @@ -275,7 +280,7 @@ void ieee80211_if_reinit(struct net_device *dev) } /* remove all STAs that are bound to this virtual interface */ - sta_info_flush(local, dev); + sta_info_flush(local, sdata); memset(&sdata->u, 0, sizeof(sdata->u)); ieee80211_if_sdata_init(sdata); diff --git a/net/mac80211/ieee80211_ioctl.c b/net/mac80211/ieee80211_ioctl.c index 38e2d83e15f4..5147152b9268 100644 --- a/net/mac80211/ieee80211_ioctl.c +++ b/net/mac80211/ieee80211_ioctl.c @@ -33,8 +33,7 @@ static int ieee80211_set_encryption(struct net_device *dev, u8 *sta_addr, size_t key_len) { struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); - int ret; - struct sta_info *sta = NULL; + struct sta_info *sta; struct ieee80211_key *key; struct ieee80211_sub_if_data *sdata; @@ -51,24 +50,23 @@ static int ieee80211_set_encryption(struct net_device *dev, u8 *sta_addr, key = sdata->keys[idx]; } else { sta = sta_info_get(local, sta_addr); - if (!sta) { - ret = -ENOENT; - key = NULL; - goto err_out; - } - + if (!sta) + return -ENOENT; key = sta->key; } if (!key) - ret = -ENOENT; - else - ret = 0; + return -ENOENT; + + ieee80211_key_free(key); + return 0; } else { key = ieee80211_key_alloc(alg, idx, key_len, _key); if (!key) return -ENOMEM; + sta = NULL; + if (!is_broadcast_ether_addr(sta_addr)) { set_tx_key = 0; /* @@ -78,14 +76,14 @@ static int ieee80211_set_encryption(struct net_device *dev, u8 *sta_addr, * work around this. */ if (idx != 0 && alg != ALG_WEP) { - ret = -EINVAL; - goto err_out; + ieee80211_key_free(key); + return -EINVAL; } sta = sta_info_get(local, sta_addr); if (!sta) { - ret = -ENOENT; - goto err_out; + ieee80211_key_free(key); + return -ENOENT; } } @@ -93,18 +91,9 @@ static int ieee80211_set_encryption(struct net_device *dev, u8 *sta_addr, if (set_tx_key || (!sta && !sdata->default_key && key)) ieee80211_set_default_key(sdata, idx); - - /* don't free key later */ - key = NULL; - - ret = 0; } - err_out: - if (sta) - sta_info_put(sta); - ieee80211_key_free(key); - return ret; + return 0; } static int ieee80211_ioctl_siwgenie(struct net_device *dev, @@ -625,7 +614,7 @@ static int ieee80211_ioctl_giwrate(struct net_device *dev, else rate->value = 0; rate->value *= 100000; - sta_info_put(sta); + return 0; } @@ -1000,7 +989,6 @@ static struct iw_statistics *ieee80211_get_wireless_stats(struct net_device *dev wstats->qual.qual = sta->last_signal; wstats->qual.noise = sta->last_noise; wstats->qual.updated = local->wstats_flags; - sta_info_put(sta); } return wstats; } diff --git a/net/mac80211/ieee80211_rate.c b/net/mac80211/ieee80211_rate.c index ebe29b716b27..4de06f128d90 100644 --- a/net/mac80211/ieee80211_rate.c +++ b/net/mac80211/ieee80211_rate.c @@ -170,9 +170,12 @@ void rate_control_get_rate(struct net_device *dev, struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); struct rate_control_ref *ref = local->rate_ctrl; struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data; - struct sta_info *sta = sta_info_get(local, hdr->addr1); + struct sta_info *sta; int i; + rcu_read_lock(); + sta = sta_info_get(local, hdr->addr1); + memset(sel, 0, sizeof(struct rate_selection)); ref->ops->get_rate(ref->priv, dev, sband, skb, sel); @@ -190,8 +193,7 @@ void rate_control_get_rate(struct net_device *dev, } } - if (sta) - sta_info_put(sta); + rcu_read_unlock(); } struct rate_control_ref *rate_control_get(struct rate_control_ref *ref) diff --git a/net/mac80211/ieee80211_rate.h b/net/mac80211/ieee80211_rate.h index 5f9a2ca49a57..bfd0a1982e4a 100644 --- a/net/mac80211/ieee80211_rate.h +++ b/net/mac80211/ieee80211_rate.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include "ieee80211_i.h" #include "sta_info.h" diff --git a/net/mac80211/ieee80211_sta.c b/net/mac80211/ieee80211_sta.c index 9f933aeca719..a3e96eb59eb0 100644 --- a/net/mac80211/ieee80211_sta.c +++ b/net/mac80211/ieee80211_sta.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -845,6 +846,8 @@ static void ieee80211_associated(struct net_device *dev, ifsta->state = IEEE80211_ASSOCIATED; + rcu_read_lock(); + sta = sta_info_get(local, ifsta->bssid); if (!sta) { printk(KERN_DEBUG "%s: No STA entry for own AP %s\n", @@ -860,7 +863,7 @@ static void ieee80211_associated(struct net_device *dev, "range\n", dev->name, print_mac(mac, ifsta->bssid)); disassoc = 1; - sta_info_free(sta); + sta_info_unlink(&sta); } else ieee80211_send_probe_req(dev, ifsta->bssid, local->scan_ssid, @@ -876,8 +879,17 @@ static void ieee80211_associated(struct net_device *dev, ifsta->ssid_len); } } - sta_info_put(sta); } + + rcu_read_unlock(); + + if (disassoc && sta) { + synchronize_rcu(); + rtnl_lock(); + sta_info_destroy(sta); + rtnl_unlock(); + } + if (disassoc) { ifsta->state = IEEE80211_DISABLED; ieee80211_set_associated(dev, ifsta, 0); @@ -1103,9 +1115,13 @@ static void ieee80211_sta_process_addba_request(struct net_device *dev, int ret = -EOPNOTSUPP; DECLARE_MAC_BUF(mac); + rcu_read_lock(); + sta = sta_info_get(local, mgmt->sa); - if (!sta) + if (!sta) { + rcu_read_unlock(); return; + } /* extract session parameters from addba request frame */ dialog_token = mgmt->u.action.u.addba_req.dialog_token; @@ -1197,9 +1213,9 @@ end: spin_unlock_bh(&sta->ampdu_mlme.ampdu_rx); end_no_lock: - ieee80211_send_addba_resp(sta->dev, sta->addr, tid, dialog_token, - status, 1, buf_size, timeout); - sta_info_put(sta); + ieee80211_send_addba_resp(sta->sdata->dev, sta->addr, tid, + dialog_token, status, 1, buf_size, timeout); + rcu_read_unlock(); } static void ieee80211_sta_process_addba_resp(struct net_device *dev, @@ -1213,9 +1229,13 @@ static void ieee80211_sta_process_addba_resp(struct net_device *dev, u16 tid; u8 *state; + rcu_read_lock(); + sta = sta_info_get(local, mgmt->sa); - if (!sta) + if (!sta) { + rcu_read_unlock(); return; + } capab = le16_to_cpu(mgmt->u.action.u.addba_resp.capab); tid = (capab & IEEE80211_ADDBA_PARAM_TID_MASK) >> 2; @@ -1230,7 +1250,7 @@ static void ieee80211_sta_process_addba_resp(struct net_device *dev, #ifdef CONFIG_MAC80211_HT_DEBUG printk(KERN_DEBUG "wrong addBA response token, tid %d\n", tid); #endif /* CONFIG_MAC80211_HT_DEBUG */ - sta_info_put(sta); + rcu_read_unlock(); return; } @@ -1244,7 +1264,7 @@ static void ieee80211_sta_process_addba_resp(struct net_device *dev, spin_unlock_bh(&sta->ampdu_mlme.ampdu_tx); printk(KERN_DEBUG "state not HT_ADDBA_REQUESTED_MSK:" "%d\n", *state); - sta_info_put(sta); + rcu_read_unlock(); return; } @@ -1271,7 +1291,7 @@ static void ieee80211_sta_process_addba_resp(struct net_device *dev, ieee80211_stop_tx_ba_session(hw, sta->addr, tid, WLAN_BACK_INITIATOR); } - sta_info_put(sta); + rcu_read_unlock(); } void ieee80211_send_delba(struct net_device *dev, const u8 *da, u16 tid, @@ -1326,16 +1346,20 @@ void ieee80211_sta_stop_rx_ba_session(struct net_device *dev, u8 *ra, u16 tid, struct sta_info *sta; int ret, i; + rcu_read_lock(); + sta = sta_info_get(local, ra); - if (!sta) + if (!sta) { + rcu_read_unlock(); return; + } /* check if TID is in operational state */ spin_lock_bh(&sta->ampdu_mlme.ampdu_rx); if (sta->ampdu_mlme.tid_rx[tid].state != HT_AGG_STATE_OPERATIONAL) { spin_unlock_bh(&sta->ampdu_mlme.ampdu_rx); - sta_info_put(sta); + rcu_read_unlock(); return; } sta->ampdu_mlme.tid_rx[tid].state = @@ -1374,7 +1398,7 @@ void ieee80211_sta_stop_rx_ba_session(struct net_device *dev, u8 *ra, u16 tid, kfree(sta->ampdu_mlme.tid_rx[tid].reorder_buf); sta->ampdu_mlme.tid_rx[tid].state = HT_AGG_STATE_IDLE; - sta_info_put(sta); + rcu_read_unlock(); } @@ -1387,9 +1411,13 @@ static void ieee80211_sta_process_delba(struct net_device *dev, u16 initiator; DECLARE_MAC_BUF(mac); + rcu_read_lock(); + sta = sta_info_get(local, mgmt->sa); - if (!sta) + if (!sta) { + rcu_read_unlock(); return; + } params = le16_to_cpu(mgmt->u.action.u.delba.params); tid = (params & IEEE80211_DELBA_PARAM_TID_MASK) >> 12; @@ -1414,7 +1442,7 @@ static void ieee80211_sta_process_delba(struct net_device *dev, ieee80211_stop_tx_ba_session(&local->hw, sta->addr, tid, WLAN_BACK_RECIPIENT); } - sta_info_put(sta); + rcu_read_unlock(); } /* @@ -1437,9 +1465,13 @@ void sta_addba_resp_timer_expired(unsigned long data) struct sta_info *sta; u8 *state; + rcu_read_lock(); + sta = sta_info_get(local, temp_sta->addr); - if (!sta) + if (!sta) { + rcu_read_unlock(); return; + } state = &sta->ampdu_mlme.tid_tx[tid].state; /* check if the TID waits for addBA response */ @@ -1461,7 +1493,7 @@ void sta_addba_resp_timer_expired(unsigned long data) WLAN_BACK_INITIATOR); timer_expired_exit: - sta_info_put(sta); + rcu_read_unlock(); } /* @@ -1481,8 +1513,8 @@ void sta_rx_agg_session_timer_expired(unsigned long data) timer_to_tid[0]); printk(KERN_DEBUG "rx session timer expired on tid %d\n", (u16)*ptid); - ieee80211_sta_stop_rx_ba_session(sta->dev, sta->addr, (u16)*ptid, - WLAN_BACK_TIMER, + ieee80211_sta_stop_rx_ba_session(sta->sdata->dev, sta->addr, + (u16)*ptid, WLAN_BACK_TIMER, WLAN_REASON_QSTA_TIMEOUT); } @@ -1791,14 +1823,18 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata, if (ifsta->assocresp_ies) memcpy(ifsta->assocresp_ies, pos, ifsta->assocresp_ies_len); + rcu_read_lock(); + /* Add STA entry for the AP */ sta = sta_info_get(local, ifsta->bssid); if (!sta) { struct ieee80211_sta_bss *bss; - sta = sta_info_add(local, dev, ifsta->bssid, GFP_KERNEL); + + sta = sta_info_add(sdata, ifsta->bssid); if (IS_ERR(sta)) { printk(KERN_DEBUG "%s: failed to add STA entry for the" " AP (error %ld)\n", dev->name, PTR_ERR(sta)); + rcu_read_unlock(); return; } bss = ieee80211_rx_bss_get(dev, ifsta->bssid, @@ -1812,7 +1848,6 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata, } } - sta->dev = dev; sta->flags |= WLAN_STA_AUTH | WLAN_STA_ASSOC | WLAN_STA_ASSOC_AP | WLAN_STA_AUTHORIZED; @@ -1883,7 +1918,7 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata, bss_conf->aid = aid; ieee80211_set_associated(dev, ifsta, 1); - sta_info_put(sta); + rcu_read_unlock(); ieee80211_associated(dev, ifsta); } @@ -2329,6 +2364,8 @@ static void ieee80211_rx_bss_info(struct net_device *dev, mesh_peer_accepts_plinks(&elems, dev)); } + rcu_read_lock(); + if (sdata->vif.type == IEEE80211_IF_TYPE_IBSS && elems.supp_rates && memcmp(mgmt->bssid, sdata->u.sta.bssid, ETH_ALEN) == 0 && (sta = sta_info_get(local, mgmt->sa))) { @@ -2354,9 +2391,10 @@ static void ieee80211_rx_bss_info(struct net_device *dev, (unsigned long long) supp_rates, (unsigned long long) sta->supp_rates[rx_status->band]); } - sta_info_put(sta); } + rcu_read_unlock(); + if (elems.ds_params && elems.ds_params_len == 1) freq = ieee80211_channel_to_frequency(elems.ds_params[0]); else @@ -2550,8 +2588,10 @@ static void ieee80211_rx_bss_info(struct net_device *dev, "local TSF - IBSS merge with BSSID %s\n", dev->name, print_mac(mac, mgmt->bssid)); ieee80211_sta_join_ibss(dev, &sdata->u.sta, bss); + rcu_read_lock(); ieee80211_ibss_add_sta(dev, NULL, mgmt->bssid, mgmt->sa); + rcu_read_unlock(); } } @@ -2893,17 +2933,20 @@ static int ieee80211_sta_active_ibss(struct net_device *dev) struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); int active = 0; struct sta_info *sta; + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); - read_lock_bh(&local->sta_lock); - list_for_each_entry(sta, &local->sta_list, list) { - if (sta->dev == dev && + rcu_read_lock(); + + list_for_each_entry_rcu(sta, &local->sta_list, list) { + if (sta->sdata == sdata && time_after(sta->last_rx + IEEE80211_IBSS_MERGE_INTERVAL, jiffies)) { active++; break; } } - read_unlock_bh(&local->sta_lock); + + rcu_read_unlock(); return active; } @@ -2915,22 +2958,25 @@ static void ieee80211_sta_expire(struct net_device *dev, unsigned long exp_time) struct sta_info *sta, *tmp; LIST_HEAD(tmp_list); DECLARE_MAC_BUF(mac); + unsigned long flags; - write_lock_bh(&local->sta_lock); + spin_lock_irqsave(&local->sta_lock, flags); list_for_each_entry_safe(sta, tmp, &local->sta_list, list) if (time_after(jiffies, sta->last_rx + exp_time)) { printk(KERN_DEBUG "%s: expiring inactive STA %s\n", dev->name, print_mac(mac, sta->addr)); - __sta_info_get(sta); - sta_info_remove(sta); - list_add(&sta->list, &tmp_list); + sta_info_unlink(&sta); + if (sta) + list_add(&sta->list, &tmp_list); } - write_unlock_bh(&local->sta_lock); + spin_unlock_irqrestore(&local->sta_lock, flags); - list_for_each_entry_safe(sta, tmp, &tmp_list, list) { - sta_info_free(sta); - sta_info_put(sta); - } + synchronize_rcu(); + + rtnl_lock(); + list_for_each_entry_safe(sta, tmp, &tmp_list, list) + sta_info_destroy(sta); + rtnl_unlock(); } @@ -3977,6 +4023,7 @@ int ieee80211_sta_set_extra_ie(struct net_device *dev, char *ie, size_t len) } +/* must be called under RCU read lock */ struct sta_info * ieee80211_ibss_add_sta(struct net_device *dev, struct sk_buff *skb, u8 *bssid, u8 *addr) @@ -3999,7 +4046,7 @@ struct sta_info * ieee80211_ibss_add_sta(struct net_device *dev, printk(KERN_DEBUG "%s: Adding new IBSS station %s (dev=%s)\n", wiphy_name(local->hw.wiphy), print_mac(mac, addr), dev->name); - sta = sta_info_add(local, dev, addr, GFP_ATOMIC); + sta = sta_info_add(sdata, addr); if (IS_ERR(sta)) return NULL; @@ -4010,7 +4057,7 @@ struct sta_info * ieee80211_ibss_add_sta(struct net_device *dev, rate_control_rate_init(sta, local); - return sta; /* caller will call sta_info_put() */ + return sta; } diff --git a/net/mac80211/key.c b/net/mac80211/key.c index eac9c59dbc4d..df0c04cedbe4 100644 --- a/net/mac80211/key.c +++ b/net/mac80211/key.c @@ -240,14 +240,17 @@ void ieee80211_key_link(struct ieee80211_key *key, if (sdata->vif.type == IEEE80211_IF_TYPE_STA) { struct sta_info *ap; + rcu_read_lock(); + /* same here, the AP could be using QoS */ ap = sta_info_get(key->local, key->sdata->u.sta.bssid); if (ap) { if (ap->flags & WLAN_STA_WME) key->conf.flags |= IEEE80211_KEY_FLAG_WMM_STA; - sta_info_put(ap); } + + rcu_read_unlock(); } } @@ -290,6 +293,9 @@ void ieee80211_key_free(struct ieee80211_key *key) __ieee80211_key_replace(key->sdata, key->sta, key, NULL); + /* + * Do NOT remove this without looking at sta_info_destroy() + */ synchronize_rcu(); /* diff --git a/net/mac80211/mesh.c b/net/mac80211/mesh.c index ebe1a7a80bad..9de1ccc11cf9 100644 --- a/net/mac80211/mesh.c +++ b/net/mac80211/mesh.c @@ -83,11 +83,10 @@ bool mesh_peer_accepts_plinks(struct ieee802_11_elems *ie, /** * mesh_accept_plinks_update: update accepting_plink in local mesh beacons * - * @dev: mesh interface in which mesh beacons are going to be updated + * @sdata: mesh interface in which mesh beacons are going to be updated */ -void mesh_accept_plinks_update(struct net_device *dev) +void mesh_accept_plinks_update(struct ieee80211_sub_if_data *sdata) { - struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); bool free_plinks; /* In case mesh_plink_free_count > 0 and mesh_plinktbl_capacity == 0, diff --git a/net/mac80211/mesh.h b/net/mac80211/mesh.h index d565b3fb9e6a..576eee83d859 100644 --- a/net/mac80211/mesh.h +++ b/net/mac80211/mesh.h @@ -65,9 +65,10 @@ enum mesh_path_flags { * @state_lock: mesh pat state lock * * - * The combination of dst and dev is unique in the mesh path table. A reference - * to the next_hop sta will be kept and in case this sta is removed, the - * mesh_path structure must be also removed or substitued in a rcu safe way + * The combination of dst and dev is unique in the mesh path table. Since the + * next_hop STA is only protected by RCU as well, deleting the STA must also + * remove/substitute the mesh_path structure and wait until that is no longer + * reachable before destroying the STA completely. */ struct mesh_path { u8 dst[ETH_ALEN]; @@ -230,8 +231,9 @@ void mesh_neighbour_update(u8 *hw_addr, u64 rates, struct net_device *dev, bool add); bool mesh_peer_accepts_plinks(struct ieee802_11_elems *ie, struct net_device *dev); -void mesh_accept_plinks_update(struct net_device *dev); -struct sta_info *mesh_plink_add(u8 *hw_addr, u64 rates, struct net_device *dev); +void mesh_accept_plinks_update(struct ieee80211_sub_if_data *sdata); +struct sta_info *mesh_plink_add(u8 *hw_addr, u64 rates, + struct ieee80211_sub_if_data *sdata); void mesh_plink_broken(struct sta_info *sta); void mesh_plink_deactivate(struct sta_info *sta); int mesh_plink_open(struct sta_info *sta); @@ -254,7 +256,7 @@ void mesh_path_flush_pending(struct mesh_path *mpath); void mesh_path_tx_pending(struct mesh_path *mpath); int mesh_pathtbl_init(void); void mesh_pathtbl_unregister(void); -int mesh_path_del(u8 *addr, struct net_device *dev); +int mesh_path_del(u8 *addr, struct net_device *dev, bool force); void mesh_path_timer(unsigned long data); void mesh_path_flush_by_nexthop(struct sta_info *sta); void mesh_path_discard_frame(struct sk_buff *skb, struct net_device *dev); @@ -270,7 +272,7 @@ static inline int mesh_plink_free_count(struct ieee80211_sub_if_data *sdata) static inline bool mesh_plink_availables(struct ieee80211_sub_if_data *sdata) { - return (min(mesh_plink_free_count(sdata), + return (min_t(long, mesh_plink_free_count(sdata), MESH_MAX_PLINKS - sdata->local->num_sta)) > 0; } diff --git a/net/mac80211/mesh_hwmp.c b/net/mac80211/mesh_hwmp.c index c2f40ef418cf..d8530fe7a0b3 100644 --- a/net/mac80211/mesh_hwmp.c +++ b/net/mac80211/mesh_hwmp.c @@ -294,7 +294,6 @@ static u32 hwmp_route_info_get(struct net_device *dev, orig_metric = PREP_IE_METRIC(hwmp_ie); break; default: - sta_info_put(sta); rcu_read_unlock(); return 0; } @@ -330,7 +329,6 @@ static u32 hwmp_route_info_get(struct net_device *dev, mpath = mesh_path_lookup(orig_addr, dev); if (!mpath) { rcu_read_unlock(); - sta_info_put(sta); return 0; } spin_lock_bh(&mpath->state_lock); @@ -372,7 +370,6 @@ static u32 hwmp_route_info_get(struct net_device *dev, mpath = mesh_path_lookup(ta, dev); if (!mpath) { rcu_read_unlock(); - sta_info_put(sta); return 0; } spin_lock_bh(&mpath->state_lock); @@ -391,7 +388,6 @@ static u32 hwmp_route_info_get(struct net_device *dev, spin_unlock_bh(&mpath->state_lock); } - sta_info_put(sta); rcu_read_unlock(); return process ? new_metric : 0; @@ -861,5 +857,5 @@ void mesh_path_timer(unsigned long data) endmpathtimer: rcu_read_unlock(); if (delete) - mesh_path_del(mpath->dst, mpath->dev); + mesh_path_del(mpath->dst, mpath->dev, false); } diff --git a/net/mac80211/mesh_pathtbl.c b/net/mac80211/mesh_pathtbl.c index 3cbdbb23d75a..a17f2b299045 100644 --- a/net/mac80211/mesh_pathtbl.c +++ b/net/mac80211/mesh_pathtbl.c @@ -55,10 +55,7 @@ static DEFINE_RWLOCK(pathtbl_resize_lock); */ void mesh_path_assign_nexthop(struct mesh_path *mpath, struct sta_info *sta) { - __sta_info_get(sta); - if (mpath->next_hop) - sta_info_put(mpath->next_hop); - mpath->next_hop = sta; + rcu_assign_pointer(mpath->next_hop, sta); } @@ -236,7 +233,7 @@ void mesh_plink_broken(struct sta_info *sta) struct mesh_path *mpath; struct mpath_node *node; struct hlist_node *p; - struct net_device *dev = sta->dev; + struct net_device *dev = sta->sdata->dev; int i; rcu_read_lock(); @@ -266,9 +263,9 @@ EXPORT_SYMBOL(mesh_plink_broken); * * RCU notes: this function is called when a mesh plink transitions from ESTAB * to any other state, since ESTAB state is the only one that allows path - * creation. This will happen before the sta can be freed (since we hold - * a reference to it) so any reader in a rcu read block will be protected - * against the plink dissapearing. + * creation. This will happen before the sta can be freed (because + * sta_info_destroy() calls this) so any reader in a rcu read block will be + * protected against the plink disappearing. */ void mesh_path_flush_by_nexthop(struct sta_info *sta) { @@ -280,7 +277,7 @@ void mesh_path_flush_by_nexthop(struct sta_info *sta) for_each_mesh_entry(mesh_paths, p, node, i) { mpath = node->mpath; if (mpath->next_hop == sta) - mesh_path_del(mpath->dst, mpath->dev); + mesh_path_del(mpath->dst, mpath->dev, true); } } @@ -294,7 +291,7 @@ void mesh_path_flush(struct net_device *dev) for_each_mesh_entry(mesh_paths, p, node, i) { mpath = node->mpath; if (mpath->dev == dev) - mesh_path_del(mpath->dst, mpath->dev); + mesh_path_del(mpath->dst, mpath->dev, false); } } @@ -303,8 +300,8 @@ static void mesh_path_node_reclaim(struct rcu_head *rp) struct mpath_node *node = container_of(rp, struct mpath_node, rcu); struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(node->mpath->dev); - if (node->mpath->next_hop) - sta_info_put(node->mpath->next_hop); + + rcu_assign_pointer(node->mpath->next_hop, NULL); atomic_dec(&sdata->u.sta.mpaths); kfree(node->mpath); kfree(node); @@ -319,9 +316,10 @@ static void mesh_path_node_reclaim(struct rcu_head *rp) * Returns: 0 if succesful * * State: if the path is being resolved, the deletion will be postponed until - * the path resolution completes or times out. + * the path resolution completes or times out, unless the force parameter + * is given. */ -int mesh_path_del(u8 *addr, struct net_device *dev) +int mesh_path_del(u8 *addr, struct net_device *dev, bool force) { struct mesh_path *mpath; struct mpath_node *node; @@ -340,7 +338,7 @@ int mesh_path_del(u8 *addr, struct net_device *dev) if (mpath->dev == dev && memcmp(addr, mpath->dst, ETH_ALEN) == 0) { spin_lock_bh(&mpath->state_lock); - if (mpath->flags & MESH_PATH_RESOLVING) { + if (!force && mpath->flags & MESH_PATH_RESOLVING) { mpath->flags |= MESH_PATH_DELETE; } else { mpath->flags |= MESH_PATH_RESOLVING; @@ -510,7 +508,7 @@ void mesh_path_expire(struct net_device *dev) time_after(jiffies, mpath->exp_time + MESH_PATH_EXPIRE)) { spin_unlock_bh(&mpath->state_lock); - mesh_path_del(mpath->dst, mpath->dev); + mesh_path_del(mpath->dst, mpath->dev, false); } else spin_unlock_bh(&mpath->state_lock); } diff --git a/net/mac80211/mesh_plink.c b/net/mac80211/mesh_plink.c index b5fbe970e48f..c2b80500ae72 100644 --- a/net/mac80211/mesh_plink.c +++ b/net/mac80211/mesh_plink.c @@ -65,14 +65,14 @@ static inline void mesh_plink_inc_estab_count(struct ieee80211_sub_if_data *sdata) { atomic_inc(&sdata->u.sta.mshstats.estab_plinks); - mesh_accept_plinks_update(sdata->dev); + mesh_accept_plinks_update(sdata); } static inline void mesh_plink_dec_estab_count(struct ieee80211_sub_if_data *sdata) { atomic_dec(&sdata->u.sta.mshstats.estab_plinks); - mesh_accept_plinks_update(sdata->dev); + mesh_accept_plinks_update(sdata); } /** @@ -99,12 +99,13 @@ static inline void mesh_plink_fsm_restart(struct sta_info *sta) * * Returns: non-NULL on success, ERR_PTR() on error. */ -struct sta_info *mesh_plink_add(u8 *hw_addr, u64 rates, struct net_device *dev) +struct sta_info *mesh_plink_add(u8 *hw_addr, u64 rates, + struct ieee80211_sub_if_data *sdata) { - struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); + struct ieee80211_local *local = sdata->local; struct sta_info *sta; - if (memcmp(hw_addr, dev->dev_addr, ETH_ALEN) == 0) + if (compare_ether_addr(hw_addr, sdata->dev->dev_addr) == 0) /* never add ourselves as neighbours */ return ERR_PTR(-EINVAL); @@ -114,7 +115,7 @@ struct sta_info *mesh_plink_add(u8 *hw_addr, u64 rates, struct net_device *dev) if (local->num_sta >= MESH_MAX_PLINKS) return ERR_PTR(-ENOSPC); - sta = sta_info_add(local, dev, hw_addr, GFP_KERNEL); + sta = sta_info_add(sdata, hw_addr); if (IS_ERR(sta)) return sta; @@ -125,7 +126,7 @@ struct sta_info *mesh_plink_add(u8 *hw_addr, u64 rates, struct net_device *dev) sta->supp_rates[local->hw.conf.channel->band] = rates; rate_control_rate_init(sta, local); - mesh_accept_plinks_update(dev); + mesh_accept_plinks_update(sdata); return sta; } @@ -141,7 +142,8 @@ struct sta_info *mesh_plink_add(u8 *hw_addr, u64 rates, struct net_device *dev) */ static void __mesh_plink_deactivate(struct sta_info *sta) { - struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev); + struct ieee80211_sub_if_data *sdata = sta->sdata; + if (sta->plink_state == ESTAB) mesh_plink_dec_estab_count(sdata); sta->plink_state = BLOCKED; @@ -246,11 +248,15 @@ void mesh_neighbour_update(u8 *hw_addr, u64 rates, struct net_device *dev, struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); struct sta_info *sta; + rcu_read_lock(); + sta = sta_info_get(local, hw_addr); if (!sta) { - sta = mesh_plink_add(hw_addr, rates, dev); - if (IS_ERR(sta)) + sta = mesh_plink_add(hw_addr, rates, sdata); + if (IS_ERR(sta)) { + rcu_read_unlock(); return; + } } sta->last_rx = jiffies; @@ -260,7 +266,7 @@ void mesh_neighbour_update(u8 *hw_addr, u64 rates, struct net_device *dev, sdata->u.sta.mshcfg.auto_open_plinks) mesh_plink_open(sta); - sta_info_put(sta); + rcu_read_unlock(); } static void mesh_plink_timer(unsigned long data) @@ -273,6 +279,11 @@ static void mesh_plink_timer(unsigned long data) DECLARE_MAC_BUF(mac); #endif + /* + * This STA is valid because sta_info_destroy() will + * del_timer_sync() this timer after having made sure + * it cannot be readded (by deleting the plink.) + */ sta = (struct sta_info *) data; spin_lock_bh(&sta->plink_lock); @@ -286,8 +297,8 @@ static void mesh_plink_timer(unsigned long data) reason = 0; llid = sta->llid; plid = sta->plid; - dev = sta->dev; - sdata = IEEE80211_DEV_TO_SUB_IF(dev); + sdata = sta->sdata; + dev = sdata->dev; switch (sta->plink_state) { case OPN_RCVD: @@ -302,8 +313,7 @@ static void mesh_plink_timer(unsigned long data) sta->plink_timeout = sta->plink_timeout + rand % sta->plink_timeout; ++sta->plink_retries; - if (!mod_plink_timer(sta, sta->plink_timeout)) - __sta_info_get(sta); + mod_plink_timer(sta, sta->plink_timeout); spin_unlock_bh(&sta->plink_lock); mesh_plink_frame_tx(dev, PLINK_OPEN, sta->addr, llid, 0, 0); @@ -316,16 +326,14 @@ static void mesh_plink_timer(unsigned long data) if (!reason) reason = cpu_to_le16(MESH_CONFIRM_TIMEOUT); sta->plink_state = HOLDING; - if (!mod_plink_timer(sta, dot11MeshHoldingTimeout(sdata))) - __sta_info_get(sta); + mod_plink_timer(sta, dot11MeshHoldingTimeout(sdata)); spin_unlock_bh(&sta->plink_lock); mesh_plink_frame_tx(dev, PLINK_CLOSE, sta->addr, llid, plid, reason); break; case HOLDING: /* holding timer */ - if (del_timer(&sta->plink_timer)) - sta_info_put(sta); + del_timer(&sta->plink_timer); mesh_plink_fsm_restart(sta); spin_unlock_bh(&sta->plink_lock); break; @@ -333,8 +341,6 @@ static void mesh_plink_timer(unsigned long data) spin_unlock_bh(&sta->plink_lock); break; } - - sta_info_put(sta); } static inline void mesh_plink_timer_set(struct sta_info *sta, int timeout) @@ -343,14 +349,13 @@ static inline void mesh_plink_timer_set(struct sta_info *sta, int timeout) sta->plink_timer.data = (unsigned long) sta; sta->plink_timer.function = mesh_plink_timer; sta->plink_timeout = timeout; - __sta_info_get(sta); add_timer(&sta->plink_timer); } int mesh_plink_open(struct sta_info *sta) { __le16 llid; - struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev); + struct ieee80211_sub_if_data *sdata = sta->sdata; #ifdef CONFIG_MAC80211_VERBOSE_MPL_DEBUG DECLARE_MAC_BUF(mac); #endif @@ -360,7 +365,6 @@ int mesh_plink_open(struct sta_info *sta) sta->llid = llid; if (sta->plink_state != LISTEN) { spin_unlock_bh(&sta->plink_lock); - sta_info_put(sta); return -EBUSY; } sta->plink_state = OPN_SNT; @@ -369,7 +373,8 @@ int mesh_plink_open(struct sta_info *sta) mpl_dbg("Mesh plink: starting establishment with %s\n", print_mac(mac, sta->addr)); - return mesh_plink_frame_tx(sta->dev, PLINK_OPEN, sta->addr, llid, 0, 0); + return mesh_plink_frame_tx(sdata->dev, PLINK_OPEN, + sta->addr, llid, 0, 0); } void mesh_plink_block(struct sta_info *sta) @@ -386,7 +391,7 @@ void mesh_plink_block(struct sta_info *sta) int mesh_plink_close(struct sta_info *sta) { - struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev); + struct ieee80211_sub_if_data *sdata = sta->sdata; int llid, plid, reason; #ifdef CONFIG_MAC80211_VERBOSE_MPL_DEBUG DECLARE_MAC_BUF(mac); @@ -401,13 +406,11 @@ int mesh_plink_close(struct sta_info *sta) if (sta->plink_state == LISTEN || sta->plink_state == BLOCKED) { mesh_plink_fsm_restart(sta); spin_unlock_bh(&sta->plink_lock); - sta_info_put(sta); return 0; } else if (sta->plink_state == ESTAB) { __mesh_plink_deactivate(sta); /* The timer should not be running */ - if (!mod_plink_timer(sta, dot11MeshHoldingTimeout(sdata))) - __sta_info_get(sta); + mod_plink_timer(sta, dot11MeshHoldingTimeout(sdata)); } else if (!mod_plink_timer(sta, dot11MeshHoldingTimeout(sdata))) sta->ignore_plink_timer = true; @@ -415,15 +418,16 @@ int mesh_plink_close(struct sta_info *sta) llid = sta->llid; plid = sta->plid; spin_unlock_bh(&sta->plink_lock); - mesh_plink_frame_tx(sta->dev, PLINK_CLOSE, sta->addr, llid, plid, - reason); + mesh_plink_frame_tx(sta->sdata->dev, PLINK_CLOSE, sta->addr, llid, + plid, reason); return 0; } void mesh_rx_plink_frame(struct net_device *dev, struct ieee80211_mgmt *mgmt, size_t len, struct ieee80211_rx_status *rx_status) { - struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct ieee80211_local *local = sdata->local; struct ieee802_11_elems elems; struct sta_info *sta; enum plink_event event; @@ -435,7 +439,6 @@ void mesh_rx_plink_frame(struct net_device *dev, struct ieee80211_mgmt *mgmt, #ifdef CONFIG_MAC80211_VERBOSE_MPL_DEBUG DECLARE_MAC_BUF(mac); #endif - struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); if (is_multicast_ether_addr(mgmt->da)) { mpl_dbg("Mesh plink: ignore frame from multicast address"); @@ -474,14 +477,17 @@ void mesh_rx_plink_frame(struct net_device *dev, struct ieee80211_mgmt *mgmt, if (ftype == PLINK_CONFIRM || (ftype == PLINK_CLOSE && ie_len == 7)) memcpy(&llid, PLINK_GET_PLID(elems.peer_link), 2); + rcu_read_lock(); + sta = sta_info_get(local, mgmt->sa); if (!sta && ftype != PLINK_OPEN) { mpl_dbg("Mesh plink: cls or cnf from unknown peer\n"); + rcu_read_unlock(); return; } if (sta && sta->plink_state == BLOCKED) { - sta_info_put(sta); + rcu_read_unlock(); return; } @@ -505,13 +511,15 @@ void mesh_rx_plink_frame(struct net_device *dev, struct ieee80211_mgmt *mgmt, u64 rates; if (!mesh_plink_free_count(sdata)) { mpl_dbg("Mesh plink error: no more free plinks\n"); + rcu_read_unlock(); return; } rates = ieee80211_sta_get_rates(local, &elems, rx_status->band); - sta = mesh_plink_add(mgmt->sa, rates, dev); + sta = mesh_plink_add(mgmt->sa, rates, sdata); if (IS_ERR(sta)) { mpl_dbg("Mesh plink error: plink table full\n"); + rcu_read_unlock(); return; } event = OPN_ACPT; @@ -521,14 +529,14 @@ void mesh_rx_plink_frame(struct net_device *dev, struct ieee80211_mgmt *mgmt, switch (ftype) { case PLINK_OPEN: if (!mesh_plink_free_count(sdata) || - (sta->plid && sta->plid != plid)) + (sta->plid && sta->plid != plid)) event = OPN_IGNR; else event = OPN_ACPT; break; case PLINK_CONFIRM: if (!mesh_plink_free_count(sdata) || - (sta->llid != llid || sta->plid != plid)) + (sta->llid != llid || sta->plid != plid)) event = CNF_IGNR; else event = CNF_ACPT; @@ -555,7 +563,7 @@ void mesh_rx_plink_frame(struct net_device *dev, struct ieee80211_mgmt *mgmt, default: mpl_dbg("Mesh plink: unknown frame subtype\n"); spin_unlock_bh(&sta->plink_lock); - sta_info_put(sta); + rcu_read_unlock(); return; } } @@ -659,8 +667,7 @@ void mesh_rx_plink_frame(struct net_device *dev, struct ieee80211_mgmt *mgmt, plid, 0); break; case CNF_ACPT: - if (del_timer(&sta->plink_timer)) - sta_info_put(sta); + del_timer(&sta->plink_timer); sta->plink_state = ESTAB; mesh_plink_inc_estab_count(sdata); spin_unlock_bh(&sta->plink_lock); @@ -693,8 +700,7 @@ void mesh_rx_plink_frame(struct net_device *dev, struct ieee80211_mgmt *mgmt, plid, reason); break; case OPN_ACPT: - if (del_timer(&sta->plink_timer)) - sta_info_put(sta); + del_timer(&sta->plink_timer); sta->plink_state = ESTAB; mesh_plink_inc_estab_count(sdata); spin_unlock_bh(&sta->plink_lock); @@ -717,9 +723,7 @@ void mesh_rx_plink_frame(struct net_device *dev, struct ieee80211_mgmt *mgmt, __mesh_plink_deactivate(sta); sta->plink_state = HOLDING; llid = sta->llid; - if (!mod_plink_timer(sta, - dot11MeshHoldingTimeout(sdata))) - __sta_info_get(sta); + mod_plink_timer(sta, dot11MeshHoldingTimeout(sdata)); spin_unlock_bh(&sta->plink_lock); mesh_plink_frame_tx(dev, PLINK_CLOSE, sta->addr, llid, plid, reason); @@ -738,10 +742,8 @@ void mesh_rx_plink_frame(struct net_device *dev, struct ieee80211_mgmt *mgmt, case HOLDING: switch (event) { case CLS_ACPT: - if (del_timer(&sta->plink_timer)) { + if (del_timer(&sta->plink_timer)) sta->ignore_plink_timer = 1; - sta_info_put(sta); - } mesh_plink_fsm_restart(sta); spin_unlock_bh(&sta->plink_lock); break; @@ -766,5 +768,6 @@ void mesh_rx_plink_frame(struct net_device *dev, struct ieee80211_mgmt *mgmt, spin_unlock_bh(&sta->plink_lock); break; } - sta_info_put(sta); + + rcu_read_unlock(); } diff --git a/net/mac80211/rc80211_pid_algo.c b/net/mac80211/rc80211_pid_algo.c index 217c0f487bba..a1993161de99 100644 --- a/net/mac80211/rc80211_pid_algo.c +++ b/net/mac80211/rc80211_pid_algo.c @@ -77,7 +77,7 @@ static void rate_control_pid_adjust_rate(struct ieee80211_local *local, int cur_sorted, new_sorted, probe, tmp, n_bitrates, band; int cur = sta->txrate_idx; - sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev); + sdata = sta->sdata; sband = local->hw.wiphy->bands[local->hw.conf.channel->band]; band = sband->band; n_bitrates = sband->n_bitrates; @@ -149,7 +149,7 @@ static void rate_control_pid_sample(struct rc_pid_info *pinfo, struct sta_info *sta) { #ifdef CONFIG_MAC80211_MESH - struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev); + struct ieee80211_sub_if_data *sdata = sta->sdata; #endif struct rc_pid_sta_info *spinfo = sta->rate_ctrl_priv; struct rc_pid_rateinfo *rinfo = pinfo->rinfo; @@ -249,23 +249,25 @@ static void rate_control_pid_tx_status(void *priv, struct net_device *dev, unsigned long period; struct ieee80211_supported_band *sband; + rcu_read_lock(); + sta = sta_info_get(local, hdr->addr1); sband = local->hw.wiphy->bands[local->hw.conf.channel->band]; if (!sta) - return; + goto unlock; /* Don't update the state if we're not controlling the rate. */ - sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev); + sdata = sta->sdata; if (sdata->bss && sdata->bss->force_unicast_rateidx > -1) { sta->txrate_idx = sdata->bss->max_ratectrl_rateidx; - return; + goto unlock; } /* Ignore all frames that were sent with a different rate than the rate * we currently advise mac80211 to use. */ if (status->control.tx_rate != &sband->bitrates[sta->txrate_idx]) - goto ignore; + goto unlock; spinfo = sta->rate_ctrl_priv; spinfo->tx_num_xmit++; @@ -303,8 +305,8 @@ static void rate_control_pid_tx_status(void *priv, struct net_device *dev, if (time_after(jiffies, spinfo->last_sample + period)) rate_control_pid_sample(pinfo, local, sta); -ignore: - sta_info_put(sta); + unlock: + rcu_read_unlock(); } static void rate_control_pid_get_rate(void *priv, struct net_device *dev, @@ -319,6 +321,8 @@ static void rate_control_pid_get_rate(void *priv, struct net_device *dev, int rateidx; u16 fc; + rcu_read_lock(); + sta = sta_info_get(local, hdr->addr1); /* Send management frames and broadcast/multicast data using lowest @@ -327,8 +331,7 @@ static void rate_control_pid_get_rate(void *priv, struct net_device *dev, if ((fc & IEEE80211_FCTL_FTYPE) != IEEE80211_FTYPE_DATA || is_multicast_ether_addr(hdr->addr1) || !sta) { sel->rate = rate_lowest(local, sband, sta); - if (sta) - sta_info_put(sta); + rcu_read_unlock(); return; } @@ -344,7 +347,7 @@ static void rate_control_pid_get_rate(void *priv, struct net_device *dev, sta->last_txrate_idx = rateidx; - sta_info_put(sta); + rcu_read_unlock(); sel->rate = &sband->bitrates[rateidx]; diff --git a/net/mac80211/rc80211_simple.c b/net/mac80211/rc80211_simple.c index bcc541d4b95c..4f72fdca7f12 100644 --- a/net/mac80211/rc80211_simple.c +++ b/net/mac80211/rc80211_simple.c @@ -40,7 +40,7 @@ static void rate_control_rate_inc(struct ieee80211_local *local, int i = sta->txrate_idx; int maxrate; - sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev); + sdata = sta->sdata; if (sdata->bss && sdata->bss->force_unicast_rateidx > -1) { /* forced unicast rate - do not change STA rate */ return; @@ -70,7 +70,7 @@ static void rate_control_rate_dec(struct ieee80211_local *local, struct ieee80211_supported_band *sband; int i = sta->txrate_idx; - sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev); + sdata = sta->sdata; if (sdata->bss && sdata->bss->force_unicast_rateidx > -1) { /* forced unicast rate - do not change STA rate */ return; @@ -118,10 +118,12 @@ static void rate_control_simple_tx_status(void *priv, struct net_device *dev, struct sta_info *sta; struct sta_rate_control *srctrl; + rcu_read_lock(); + sta = sta_info_get(local, hdr->addr1); if (!sta) - return; + goto unlock; srctrl = sta->rate_ctrl_priv; srctrl->tx_num_xmit++; @@ -191,7 +193,8 @@ static void rate_control_simple_tx_status(void *priv, struct net_device *dev, } } - sta_info_put(sta); + unlock: + rcu_read_unlock(); } @@ -208,6 +211,8 @@ rate_control_simple_get_rate(void *priv, struct net_device *dev, int rateidx; u16 fc; + rcu_read_lock(); + sta = sta_info_get(local, hdr->addr1); /* Send management frames and broadcast/multicast data using lowest @@ -216,8 +221,7 @@ rate_control_simple_get_rate(void *priv, struct net_device *dev, if ((fc & IEEE80211_FCTL_FTYPE) != IEEE80211_FTYPE_DATA || is_multicast_ether_addr(hdr->addr1) || !sta) { sel->rate = rate_lowest(local, sband, sta); - if (sta) - sta_info_put(sta); + rcu_read_unlock(); return; } @@ -233,7 +237,7 @@ rate_control_simple_get_rate(void *priv, struct net_device *dev, sta->last_txrate_idx = rateidx; - sta_info_put(sta); + rcu_read_unlock(); sel->rate = &sband->bitrates[rateidx]; } diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index 2e65ca1cd1aa..8e1e2859bfd5 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -631,7 +631,7 @@ static void ap_sta_ps_start(struct net_device *dev, struct sta_info *sta) struct ieee80211_sub_if_data *sdata; DECLARE_MAC_BUF(mac); - sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev); + sdata = sta->sdata; if (sdata->bss) atomic_inc(&sdata->bss->num_sta_ps); @@ -652,7 +652,7 @@ static int ap_sta_ps_end(struct net_device *dev, struct sta_info *sta) struct ieee80211_tx_packet_data *pkt_data; DECLARE_MAC_BUF(mac); - sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev); + sdata = sta->sdata; if (sdata->bss) atomic_dec(&sdata->bss->num_sta_ps); @@ -1287,7 +1287,7 @@ ieee80211_deliver_skb(struct ieee80211_rx_data *rx) "multicast frame\n", dev->name); } else { dsta = sta_info_get(local, skb->data); - if (dsta && dsta->dev == dev) { + if (dsta && dsta->sdata->dev == dev) { /* * The destination station is associated to * this AP (in this VLAN), so send the frame @@ -1297,8 +1297,6 @@ ieee80211_deliver_skb(struct ieee80211_rx_data *rx) xmit_skb = skb; skb = NULL; } - if (dsta) - sta_info_put(dsta); } } @@ -1905,13 +1903,13 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw, rx.sta = sta_info_get(local, hdr->addr2); if (rx.sta) { - rx.dev = rx.sta->dev; - rx.sdata = IEEE80211_DEV_TO_SUB_IF(rx.dev); + rx.sdata = rx.sta->sdata; + rx.dev = rx.sta->sdata->dev; } if ((status->flag & RX_FLAG_MMIC_ERROR)) { ieee80211_rx_michael_mic_report(local->mdev, hdr, &rx); - goto end; + return; } if (unlikely(local->sta_sw_scanning || local->sta_hw_scanning)) @@ -1970,10 +1968,6 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw, ieee80211_invoke_rx_handlers(prev, &rx, skb); } else dev_kfree_skb(skb); - - end: - if (rx.sta) - sta_info_put(rx.sta); } #define SEQ_MODULO 0x1000 @@ -2150,7 +2144,7 @@ static u8 ieee80211_rx_reorder_ampdu(struct ieee80211_local *local, /* if this mpdu is fragmented - terminate rx aggregation session */ sc = le16_to_cpu(hdr->seq_ctrl); if (sc & IEEE80211_SCTL_FRAG) { - ieee80211_sta_stop_rx_ba_session(sta->dev, sta->addr, + ieee80211_sta_stop_rx_ba_session(sta->sdata->dev, sta->addr, tid, 0, WLAN_REASON_QSTA_REQUIRE_SETUP); ret = 1; goto end_reorder; @@ -2160,9 +2154,7 @@ static u8 ieee80211_rx_reorder_ampdu(struct ieee80211_local *local, mpdu_seq_num = (sc & IEEE80211_SCTL_SEQ) >> 4; ret = ieee80211_sta_manage_reorder_buf(hw, tid_agg_rx, skb, mpdu_seq_num, 0); -end_reorder: - if (sta) - sta_info_put(sta); + end_reorder: return ret; } diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c index 81c4e3392f40..ee5b66abc0f1 100644 --- a/net/mac80211/sta_info.c +++ b/net/mac80211/sta_info.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "ieee80211_i.h" @@ -23,14 +24,43 @@ #include "debugfs_sta.h" #include "mesh.h" -/* Caller must hold local->sta_lock */ -static void sta_info_hash_add(struct ieee80211_local *local, - struct sta_info *sta) -{ - sta->hnext = local->sta_hash[STA_HASH(sta->addr)]; - local->sta_hash[STA_HASH(sta->addr)] = sta; -} - +/** + * DOC: STA information lifetime rules + * + * STA info structures (&struct sta_info) are managed in a hash table + * for faster lookup and a list for iteration. They are managed using + * RCU, i.e. access to the list and hash table is protected by RCU. + * + * STA info structures are always "alive" when they are added with + * @sta_info_add() [this may be changed in the future to allow allocating + * outside of a critical section!], they are then added to the hash + * table and list. Therefore, @sta_info_add() must also be RCU protected, + * also, the caller of @sta_info_add() cannot assume that it owns the + * structure. + * + * Because there are debugfs entries for each station, and adding those + * must be able to sleep, it is also possible to "pin" a station entry, + * that means it can be removed from the hash table but not be freed. + * See the comment in @__sta_info_unlink() for more information. + * + * In order to remove a STA info structure, the caller needs to first + * unlink it (@sta_info_unlink()) from the list and hash tables and + * then wait for an RCU synchronisation before it can be freed. Due to + * the pinning and the possibility of multiple callers trying to remove + * the same STA info at the same time, @sta_info_unlink() can clear the + * STA info pointer it is passed to indicate that the STA info is owned + * by somebody else now. + * + * If @sta_info_unlink() did not clear the pointer then the caller owns + * the STA info structure now and is responsible of destroying it with + * a call to @sta_info_destroy(), not before RCU synchronisation, of + * course. Note that sta_info_destroy() must be protected by the RTNL. + * + * In all other cases, there is no concept of ownership on a STA entry, + * each structure is owned by the global hash table/list until it is + * removed. All users of the structure need to be RCU protected so that + * the structure won't be freed before they are done using it. + */ /* Caller must hold local->sta_lock */ static int sta_info_hash_del(struct ieee80211_local *local, @@ -42,46 +72,39 @@ static int sta_info_hash_del(struct ieee80211_local *local, if (!s) return -ENOENT; if (s == sta) { - local->sta_hash[STA_HASH(sta->addr)] = s->hnext; + rcu_assign_pointer(local->sta_hash[STA_HASH(sta->addr)], + s->hnext); return 0; } while (s->hnext && s->hnext != sta) s = s->hnext; if (s->hnext) { - s->hnext = sta->hnext; + rcu_assign_pointer(s->hnext, sta->hnext); return 0; } return -ENOENT; } -/* must hold local->sta_lock */ +/* protected by RCU */ static struct sta_info *__sta_info_find(struct ieee80211_local *local, u8 *addr) { struct sta_info *sta; - sta = local->sta_hash[STA_HASH(addr)]; + sta = rcu_dereference(local->sta_hash[STA_HASH(addr)]); while (sta) { if (compare_ether_addr(sta->addr, addr) == 0) break; - sta = sta->hnext; + sta = rcu_dereference(sta->hnext); } return sta; } struct sta_info *sta_info_get(struct ieee80211_local *local, u8 *addr) { - struct sta_info *sta; - - read_lock_bh(&local->sta_lock); - sta = __sta_info_find(local, addr); - if (sta) - __sta_info_get(sta); - read_unlock_bh(&local->sta_lock); - - return sta; + return __sta_info_find(local, addr); } EXPORT_SYMBOL(sta_info_get); @@ -91,81 +114,101 @@ struct sta_info *sta_info_get_by_idx(struct ieee80211_local *local, int idx, struct sta_info *sta; int i = 0; - read_lock_bh(&local->sta_lock); - list_for_each_entry(sta, &local->sta_list, list) { + list_for_each_entry_rcu(sta, &local->sta_list, list) { if (i < idx) { ++i; continue; - } else if (!dev || dev == sta->dev) { - __sta_info_get(sta); - read_unlock_bh(&local->sta_lock); + } else if (!dev || dev == sta->sdata->dev) { return sta; } } - read_unlock_bh(&local->sta_lock); return NULL; } -static void sta_info_release(struct kref *kref) +void sta_info_destroy(struct sta_info *sta) { - struct sta_info *sta = container_of(kref, struct sta_info, kref); struct ieee80211_local *local = sta->local; struct sk_buff *skb; int i; - /* free sta structure; it has already been removed from - * hash table etc. external structures. Make sure that all - * buffered frames are release (one might have been added - * after sta_info_free() was called). */ + ASSERT_RTNL(); + might_sleep(); + + rate_control_remove_sta_debugfs(sta); + ieee80211_sta_debugfs_remove(sta); + +#ifdef CONFIG_MAC80211_MESH + if (ieee80211_vif_is_mesh(&sta->sdata->vif)) + mesh_plink_deactivate(sta); +#endif + + /* + * NOTE: This will call synchronize_rcu() internally to + * make sure no key references can be in use. We rely on + * that here for the mesh code! + */ + ieee80211_key_free(sta->key); + WARN_ON(sta->key); + +#ifdef CONFIG_MAC80211_MESH + if (ieee80211_vif_is_mesh(&sta->sdata->vif)) + del_timer_sync(&sta->plink_timer); +#endif + while ((skb = skb_dequeue(&sta->ps_tx_buf)) != NULL) { local->total_ps_buffered--; dev_kfree_skb_any(skb); } - while ((skb = skb_dequeue(&sta->tx_filtered)) != NULL) { + + while ((skb = skb_dequeue(&sta->tx_filtered)) != NULL) dev_kfree_skb_any(skb); - } + for (i = 0; i < STA_TID_NUM; i++) { del_timer_sync(&sta->ampdu_mlme.tid_rx[i].session_timer); del_timer_sync(&sta->ampdu_mlme.tid_tx[i].addba_resp_timer); } rate_control_free_sta(sta->rate_ctrl, sta->rate_ctrl_priv); rate_control_put(sta->rate_ctrl); + kfree(sta); } -void sta_info_put(struct sta_info *sta) +/* Caller must hold local->sta_lock */ +static void sta_info_hash_add(struct ieee80211_local *local, + struct sta_info *sta) { - kref_put(&sta->kref, sta_info_release); + sta->hnext = local->sta_hash[STA_HASH(sta->addr)]; + rcu_assign_pointer(local->sta_hash[STA_HASH(sta->addr)], sta); } -EXPORT_SYMBOL(sta_info_put); - -struct sta_info *sta_info_add(struct ieee80211_local *local, - struct net_device *dev, u8 *addr, gfp_t gfp) +struct sta_info *sta_info_add(struct ieee80211_sub_if_data *sdata, + u8 *addr) { + struct ieee80211_local *local = sdata->local; struct sta_info *sta; int i; DECLARE_MAC_BUF(mac); + unsigned long flags; - sta = kzalloc(sizeof(*sta), gfp); + sta = kzalloc(sizeof(*sta), GFP_ATOMIC); if (!sta) return ERR_PTR(-ENOMEM); - kref_init(&sta->kref); + memcpy(sta->addr, addr, ETH_ALEN); + sta->local = local; + sta->sdata = sdata; sta->rate_ctrl = rate_control_get(local->rate_ctrl); - sta->rate_ctrl_priv = rate_control_alloc_sta(sta->rate_ctrl, gfp); + sta->rate_ctrl_priv = rate_control_alloc_sta(sta->rate_ctrl, + GFP_ATOMIC); if (!sta->rate_ctrl_priv) { rate_control_put(sta->rate_ctrl); kfree(sta); return ERR_PTR(-ENOMEM); } - memcpy(sta->addr, addr, ETH_ALEN); - sta->local = local; - sta->dev = dev; spin_lock_init(&sta->ampdu_mlme.ampdu_rx); spin_lock_init(&sta->ampdu_mlme.ampdu_tx); for (i = 0; i < STA_TID_NUM; i++) { @@ -190,29 +233,26 @@ struct sta_info *sta_info_add(struct ieee80211_local *local, } skb_queue_head_init(&sta->ps_tx_buf); skb_queue_head_init(&sta->tx_filtered); - write_lock_bh(&local->sta_lock); - /* mark sta as used (by caller) */ - __sta_info_get(sta); + spin_lock_irqsave(&local->sta_lock, flags); /* check if STA exists already */ if (__sta_info_find(local, addr)) { - write_unlock_bh(&local->sta_lock); - sta_info_put(sta); + spin_unlock_irqrestore(&local->sta_lock, flags); return ERR_PTR(-EEXIST); } list_add(&sta->list, &local->sta_list); local->num_sta++; sta_info_hash_add(local, sta); - if (local->ops->sta_notify) { - struct ieee80211_sub_if_data *sdata; - sdata = IEEE80211_DEV_TO_SUB_IF(dev); + /* notify driver */ + if (local->ops->sta_notify) { if (sdata->vif.type == IEEE80211_IF_TYPE_VLAN) sdata = sdata->u.vlan.ap; local->ops->sta_notify(local_to_hw(local), &sdata->vif, STA_NOTIFY_ADD, addr); } - write_unlock_bh(&local->sta_lock); + + spin_unlock_irqrestore(&local->sta_lock, flags); #ifdef CONFIG_MAC80211_VERBOSE_DEBUG printk(KERN_DEBUG "%s: Added STA %s\n", @@ -252,19 +292,20 @@ static void __sta_info_set_tim_bit(struct ieee80211_if_ap *bss, { if (bss) __bss_tim_set(bss, sta->aid); - if (sta->local->ops->set_tim) + if (sta->local->ops->set_tim) { + sta->local->tim_in_locked_section = true; sta->local->ops->set_tim(local_to_hw(sta->local), sta->aid, 1); + sta->local->tim_in_locked_section = false; + } } void sta_info_set_tim_bit(struct sta_info *sta) { - struct ieee80211_sub_if_data *sdata; + unsigned long flags; - sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev); - - read_lock_bh(&sta->local->sta_lock); - __sta_info_set_tim_bit(sdata->bss, sta); - read_unlock_bh(&sta->local->sta_lock); + spin_lock_irqsave(&sta->local->sta_lock, flags); + __sta_info_set_tim_bit(sta->sdata->bss, sta); + spin_unlock_irqrestore(&sta->local->sta_lock, flags); } static void __sta_info_clear_tim_bit(struct ieee80211_if_ap *bss, @@ -272,93 +313,135 @@ static void __sta_info_clear_tim_bit(struct ieee80211_if_ap *bss, { if (bss) __bss_tim_clear(bss, sta->aid); - if (sta->local->ops->set_tim) + if (sta->local->ops->set_tim) { + sta->local->tim_in_locked_section = true; sta->local->ops->set_tim(local_to_hw(sta->local), sta->aid, 0); + sta->local->tim_in_locked_section = false; + } } void sta_info_clear_tim_bit(struct sta_info *sta) { - struct ieee80211_sub_if_data *sdata; + unsigned long flags; - sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev); - - read_lock_bh(&sta->local->sta_lock); - __sta_info_clear_tim_bit(sdata->bss, sta); - read_unlock_bh(&sta->local->sta_lock); + spin_lock_irqsave(&sta->local->sta_lock, flags); + __sta_info_clear_tim_bit(sta->sdata->bss, sta); + spin_unlock_irqrestore(&sta->local->sta_lock, flags); } -/* Caller must hold local->sta_lock */ -void sta_info_remove(struct sta_info *sta) +/* + * See comment in __sta_info_unlink, + * caller must hold local->sta_lock. + */ +static void __sta_info_pin(struct sta_info *sta) { - struct ieee80211_local *local = sta->local; - struct ieee80211_sub_if_data *sdata; + WARN_ON(sta->pin_status != STA_INFO_PIN_STAT_NORMAL); + sta->pin_status = STA_INFO_PIN_STAT_PINNED; +} - /* don't do anything if we've been removed already */ - if (sta_info_hash_del(local, sta)) +/* + * See comment in __sta_info_unlink, returns sta if it + * needs to be destroyed. + */ +static struct sta_info *__sta_info_unpin(struct sta_info *sta) +{ + struct sta_info *ret = NULL; + unsigned long flags; + + spin_lock_irqsave(&sta->local->sta_lock, flags); + WARN_ON(sta->pin_status != STA_INFO_PIN_STAT_DESTROY && + sta->pin_status != STA_INFO_PIN_STAT_PINNED); + if (sta->pin_status == STA_INFO_PIN_STAT_DESTROY) + ret = sta; + sta->pin_status = STA_INFO_PIN_STAT_NORMAL; + spin_unlock_irqrestore(&sta->local->sta_lock, flags); + + return ret; +} + +static void __sta_info_unlink(struct sta_info **sta) +{ + struct ieee80211_local *local = (*sta)->local; + struct ieee80211_sub_if_data *sdata = (*sta)->sdata; +#ifdef CONFIG_MAC80211_VERBOSE_DEBUG + DECLARE_MAC_BUF(mbuf); +#endif + /* + * pull caller's reference if we're already gone. + */ + if (sta_info_hash_del(local, *sta)) { + *sta = NULL; return; + } - list_del(&sta->list); - sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev); - if (sta->flags & WLAN_STA_PS) { - sta->flags &= ~WLAN_STA_PS; + /* + * Also pull caller's reference if the STA is pinned by the + * task that is adding the debugfs entries. In that case, we + * leave the STA "to be freed". + * + * The rules are not trivial, but not too complex either: + * (1) pin_status is only modified under the sta_lock + * (2) sta_info_debugfs_add_work() will set the status + * to PINNED when it found an item that needs a new + * debugfs directory created. In that case, that item + * must not be freed although all *RCU* users are done + * with it. Hence, we tell the caller of _unlink() + * that the item is already gone (as can happen when + * two tasks try to unlink/destroy at the same time) + * (3) We set the pin_status to DESTROY here when we + * find such an item. + * (4) sta_info_debugfs_add_work() will reset the pin_status + * from PINNED to NORMAL when it is done with the item, + * but will check for DESTROY before resetting it in + * which case it will free the item. + */ + if ((*sta)->pin_status == STA_INFO_PIN_STAT_PINNED) { + (*sta)->pin_status = STA_INFO_PIN_STAT_DESTROY; + *sta = NULL; + return; + } + + list_del(&(*sta)->list); + + if ((*sta)->flags & WLAN_STA_PS) { + (*sta)->flags &= ~WLAN_STA_PS; if (sdata->bss) atomic_dec(&sdata->bss->num_sta_ps); - __sta_info_clear_tim_bit(sdata->bss, sta); + __sta_info_clear_tim_bit(sdata->bss, *sta); } + local->num_sta--; - if (ieee80211_vif_is_mesh(&sdata->vif)) - mesh_accept_plinks_update(sdata->dev); -} - -void sta_info_free(struct sta_info *sta) -{ - struct sk_buff *skb; - struct ieee80211_local *local = sta->local; - struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev); - - DECLARE_MAC_BUF(mac); - - might_sleep(); - - write_lock_bh(&local->sta_lock); - sta_info_remove(sta); - write_unlock_bh(&local->sta_lock); - - if (ieee80211_vif_is_mesh(&sdata->vif)) - mesh_plink_deactivate(sta); - - while ((skb = skb_dequeue(&sta->ps_tx_buf)) != NULL) { - local->total_ps_buffered--; - dev_kfree_skb(skb); - } - while ((skb = skb_dequeue(&sta->tx_filtered)) != NULL) { - dev_kfree_skb(skb); - } - -#ifdef CONFIG_MAC80211_VERBOSE_DEBUG - printk(KERN_DEBUG "%s: Removed STA %s\n", - wiphy_name(local->hw.wiphy), print_mac(mac, sta->addr)); -#endif /* CONFIG_MAC80211_VERBOSE_DEBUG */ - - ieee80211_key_free(sta->key); - WARN_ON(sta->key); - if (local->ops->sta_notify) { - if (sdata->vif.type == IEEE80211_IF_TYPE_VLAN) sdata = sdata->u.vlan.ap; local->ops->sta_notify(local_to_hw(local), &sdata->vif, - STA_NOTIFY_REMOVE, sta->addr); + STA_NOTIFY_REMOVE, (*sta)->addr); } - rate_control_remove_sta_debugfs(sta); - ieee80211_sta_debugfs_remove(sta); + if (ieee80211_vif_is_mesh(&sdata->vif)) { + mesh_accept_plinks_update(sdata); +#ifdef CONFIG_MAC80211_MESH + del_timer(&(*sta)->plink_timer); +#endif + } - sta_info_put(sta); +#ifdef CONFIG_MAC80211_VERBOSE_DEBUG + printk(KERN_DEBUG "%s: Removed STA %s\n", + wiphy_name(local->hw.wiphy), print_mac(mbuf, (*sta)->addr)); +#endif /* CONFIG_MAC80211_VERBOSE_DEBUG */ } +void sta_info_unlink(struct sta_info **sta) +{ + struct ieee80211_local *local = (*sta)->local; + unsigned long flags; + + spin_lock_irqsave(&local->sta_lock, flags); + __sta_info_unlink(sta); + spin_unlock_irqrestore(&local->sta_lock, flags); +} static inline int sta_info_buffer_expired(struct ieee80211_local *local, struct sta_info *sta, @@ -404,7 +487,7 @@ static void sta_info_cleanup_expire_buffered(struct ieee80211_local *local, if (!skb) break; - sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev); + sdata = sta->sdata; local->total_ps_buffered--; printk(KERN_DEBUG "Buffered frame expired (STA " "%s)\n", print_mac(mac, sta->addr)); @@ -421,13 +504,10 @@ static void sta_info_cleanup(unsigned long data) struct ieee80211_local *local = (struct ieee80211_local *) data; struct sta_info *sta; - read_lock_bh(&local->sta_lock); - list_for_each_entry(sta, &local->sta_list, list) { - __sta_info_get(sta); + rcu_read_lock(); + list_for_each_entry_rcu(sta, &local->sta_list, list) sta_info_cleanup_expire_buffered(local, sta); - sta_info_put(sta); - } - read_unlock_bh(&local->sta_lock); + rcu_read_unlock(); local->sta_cleanup.expires = round_jiffies(jiffies + STA_INFO_CLEANUP_INTERVAL); @@ -435,37 +515,45 @@ static void sta_info_cleanup(unsigned long data) } #ifdef CONFIG_MAC80211_DEBUGFS -static void sta_info_debugfs_add_task(struct work_struct *work) +static void sta_info_debugfs_add_work(struct work_struct *work) { struct ieee80211_local *local = container_of(work, struct ieee80211_local, sta_debugfs_add); struct sta_info *sta, *tmp; + unsigned long flags; while (1) { sta = NULL; - read_lock_bh(&local->sta_lock); + + spin_lock_irqsave(&local->sta_lock, flags); list_for_each_entry(tmp, &local->sta_list, list) { if (!tmp->debugfs.dir) { sta = tmp; - __sta_info_get(sta); + __sta_info_pin(sta); break; } } - read_unlock_bh(&local->sta_lock); + spin_unlock_irqrestore(&local->sta_lock, flags); if (!sta) break; ieee80211_sta_debugfs_add(sta); rate_control_add_sta_debugfs(sta); - sta_info_put(sta); + + sta = __sta_info_unpin(sta); + + if (sta) { + synchronize_rcu(); + sta_info_destroy(sta); + } } } #endif void sta_info_init(struct ieee80211_local *local) { - rwlock_init(&local->sta_lock); + spin_lock_init(&local->sta_lock); INIT_LIST_HEAD(&local->sta_list); setup_timer(&local->sta_cleanup, sta_info_cleanup, @@ -474,7 +562,7 @@ void sta_info_init(struct ieee80211_local *local) round_jiffies(jiffies + STA_INFO_CLEANUP_INTERVAL); #ifdef CONFIG_MAC80211_DEBUGFS - INIT_WORK(&local->sta_debugfs_add, sta_info_debugfs_add_task); + INIT_WORK(&local->sta_debugfs_add, sta_info_debugfs_add_work); #endif } @@ -493,24 +581,29 @@ void sta_info_stop(struct ieee80211_local *local) /** * sta_info_flush - flush matching STA entries from the STA table * @local: local interface data - * @dev: matching rule for the net device (sta->dev) or %NULL to match all STAs + * @sdata: matching rule for the net device (sta->dev) or %NULL to match all STAs */ -void sta_info_flush(struct ieee80211_local *local, struct net_device *dev) +void sta_info_flush(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata) { struct sta_info *sta, *tmp; LIST_HEAD(tmp_list); + unsigned long flags; - write_lock_bh(&local->sta_lock); - list_for_each_entry_safe(sta, tmp, &local->sta_list, list) - if (!dev || dev == sta->dev) { - __sta_info_get(sta); - sta_info_remove(sta); - list_add_tail(&sta->list, &tmp_list); + might_sleep(); + + spin_lock_irqsave(&local->sta_lock, flags); + list_for_each_entry_safe(sta, tmp, &local->sta_list, list) { + if (!sdata || sdata == sta->sdata) { + __sta_info_unlink(&sta); + if (sta) + list_add_tail(&sta->list, &tmp_list); } - write_unlock_bh(&local->sta_lock); - - list_for_each_entry_safe(sta, tmp, &tmp_list, list) { - sta_info_free(sta); - sta_info_put(sta); } + spin_unlock_irqrestore(&local->sta_lock, flags); + + synchronize_rcu(); + + list_for_each_entry_safe(sta, tmp, &tmp_list, list) + sta_info_destroy(sta); } diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h index b9dfb6fa893a..787124c253af 100644 --- a/net/mac80211/sta_info.h +++ b/net/mac80211/sta_info.h @@ -12,7 +12,6 @@ #include #include #include -#include #include "ieee80211_key.h" /** @@ -134,8 +133,14 @@ struct sta_ampdu_mlme { u8 dialog_token_allocator; }; + +/* see __sta_info_unlink */ +#define STA_INFO_PIN_STAT_NORMAL 0 +#define STA_INFO_PIN_STAT_PINNED 1 +#define STA_INFO_PIN_STAT_DESTROY 2 + + struct sta_info { - struct kref kref; struct list_head list; struct sta_info *hnext; /* next entry in hash table list */ @@ -166,8 +171,8 @@ struct sta_info { /* last rates used to send a frame to this STA */ int last_txrate_idx, last_nonerp_txrate_idx; - struct net_device *dev; /* which net device is this station associated - * to */ + /* sub_if_data this sta belongs to */ + struct ieee80211_sub_if_data *sdata; struct ieee80211_key *key; @@ -199,6 +204,12 @@ struct sta_info { u16 listen_interval; + /* + * for use by the internal lifetime management, + * see __sta_info_unlink + */ + u8 pin_status; + struct ieee80211_ht_info ht_info; /* 802.11n HT capabilities of this STA */ struct sta_ampdu_mlme ampdu_mlme; @@ -262,25 +273,37 @@ static inline enum plink_state sta_plink_state(struct sta_info *sta) */ #define STA_INFO_CLEANUP_INTERVAL (10 * HZ) -static inline void __sta_info_get(struct sta_info *sta) -{ - kref_get(&sta->kref); -} - -struct sta_info * sta_info_get(struct ieee80211_local *local, u8 *addr); +/* + * Get a STA info, must have be under RCU read lock. + */ +struct sta_info *sta_info_get(struct ieee80211_local *local, u8 *addr); +/* + * Get STA info by index, BROKEN! + */ struct sta_info *sta_info_get_by_idx(struct ieee80211_local *local, int idx, struct net_device *dev); -void sta_info_put(struct sta_info *sta); -struct sta_info *sta_info_add(struct ieee80211_local *local, - struct net_device *dev, u8 *addr, gfp_t gfp); -void sta_info_remove(struct sta_info *sta); -void sta_info_free(struct sta_info *sta); -void sta_info_init(struct ieee80211_local *local); -int sta_info_start(struct ieee80211_local *local); -void sta_info_stop(struct ieee80211_local *local); -void sta_info_flush(struct ieee80211_local *local, struct net_device *dev); +/* + * Add a new STA info, must be under RCU read lock + * because otherwise the returned reference isn't + * necessarily valid long enough. + */ +struct sta_info *sta_info_add(struct ieee80211_sub_if_data *sdata, + u8 *addr); +/* + * Unlink a STA info from the hash table/list. + * This can NULL the STA pointer if somebody else + * has already unlinked it. + */ +void sta_info_unlink(struct sta_info **sta); +void sta_info_destroy(struct sta_info *sta); void sta_info_set_tim_bit(struct sta_info *sta); void sta_info_clear_tim_bit(struct sta_info *sta); +void sta_info_init(struct ieee80211_local *local); +int sta_info_start(struct ieee80211_local *local); +void sta_info_stop(struct ieee80211_local *local); +void sta_info_flush(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata); + #endif /* STA_INFO_H */ diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 33e314f3aab7..80f4343a3007 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -327,10 +327,8 @@ static void purge_old_ps_buffers(struct ieee80211_local *local) } total += skb_queue_len(&ap->ps_bc_buf); } - rcu_read_unlock(); - read_lock_bh(&local->sta_lock); - list_for_each_entry(sta, &local->sta_list, list) { + list_for_each_entry_rcu(sta, &local->sta_list, list) { skb = skb_dequeue(&sta->ps_tx_buf); if (skb) { purged++; @@ -338,7 +336,8 @@ static void purge_old_ps_buffers(struct ieee80211_local *local) } total += skb_queue_len(&sta->ps_tx_buf); } - read_unlock_bh(&local->sta_lock); + + rcu_read_unlock(); local->total_ps_buffered = total; printk(KERN_DEBUG "%s: PS buffers full - purged %d frames\n", @@ -1141,20 +1140,17 @@ static int ieee80211_tx(struct net_device *dev, struct sk_buff *skb, return 0; } + rcu_read_lock(); + /* initialises tx */ res_prepare = __ieee80211_tx_prepare(&tx, skb, dev, control); if (res_prepare == TX_DROP) { dev_kfree_skb(skb); + rcu_read_unlock(); return 0; } - /* - * key references are protected using RCU and this requires that - * we are in a read-site RCU section during receive processing - */ - rcu_read_lock(); - sta = tx.sta; tx.channel = local->hw.conf.channel; @@ -1167,9 +1163,6 @@ static int ieee80211_tx(struct net_device *dev, struct sk_buff *skb, skb = tx.skb; /* handlers are allowed to change skb */ - if (sta) - sta_info_put(sta); - if (unlikely(res == TX_DROP)) { I802_DEBUG_INC(local->tx_handlers_drop); goto drop; @@ -1489,11 +1482,11 @@ int ieee80211_subif_start_xmit(struct sk_buff *skb, * in AP mode) */ if (!is_multicast_ether_addr(hdr.addr1)) { + rcu_read_lock(); sta = sta_info_get(local, hdr.addr1); - if (sta) { + if (sta) sta_flags = sta->flags; - sta_info_put(sta); - } + rcu_read_unlock(); } /* receiver is QoS enabled, use a QoS type frame */ @@ -1722,7 +1715,6 @@ static void ieee80211_beacon_add_tim(struct ieee80211_local *local, /* Generate bitmap for TIM only if there are any STAs in power save * mode. */ - read_lock_bh(&local->sta_lock); if (atomic_read(&bss->num_sta_ps) > 0) /* in the hope that this is faster than * checking byte-for-byte */ @@ -1773,7 +1765,6 @@ static void ieee80211_beacon_add_tim(struct ieee80211_local *local, *pos++ = aid0; /* Bitmap control */ *pos++ = 0; /* Part Virt Bitmap */ } - read_unlock_bh(&local->sta_lock); } struct sk_buff *ieee80211_beacon_get(struct ieee80211_hw *hw, @@ -1821,7 +1812,22 @@ struct sk_buff *ieee80211_beacon_get(struct ieee80211_hw *hw, ieee80211_include_sequence(sdata, (struct ieee80211_hdr *)skb->data); - ieee80211_beacon_add_tim(local, ap, skb, beacon); + /* + * Not very nice, but we want to allow the driver to call + * ieee80211_beacon_get() as a response to the set_tim() + * callback. That, however, is already invoked under the + * sta_lock to guarantee consistent and race-free update + * of the tim bitmap in mac80211 and the driver. + */ + if (local->tim_in_locked_section) { + ieee80211_beacon_add_tim(local, ap, skb, beacon); + } else { + unsigned long flags; + + spin_lock_irqsave(&local->sta_lock, flags); + ieee80211_beacon_add_tim(local, ap, skb, beacon); + spin_unlock_irqrestore(&local->sta_lock, flags); + } if (beacon->tail) memcpy(skb_put(skb, beacon->tail_len), @@ -1965,7 +1971,6 @@ ieee80211_get_buffered_bc(struct ieee80211_hw *hw, rcu_read_unlock(); return NULL; } - rcu_read_unlock(); if (bss->dtim_count != 0) return NULL; /* send buffered bc/mc only after DTIM beacon */ @@ -2010,8 +2015,7 @@ ieee80211_get_buffered_bc(struct ieee80211_hw *hw, skb = NULL; } - if (sta) - sta_info_put(sta); + rcu_read_unlock(); return skb; } diff --git a/net/mac80211/wme.c b/net/mac80211/wme.c index 8cc036decc82..4e94e4026e78 100644 --- a/net/mac80211/wme.c +++ b/net/mac80211/wme.c @@ -153,6 +153,7 @@ static int wme_qdiscop_enqueue(struct sk_buff *skb, struct Qdisc* qd) if (pkt_data->flags & IEEE80211_TXPD_REQUEUE) { queue = pkt_data->queue; + rcu_read_lock(); sta = sta_info_get(local, hdr->addr1); tid = skb->priority & QOS_CONTROL_TAG1D_MASK; if (sta) { @@ -164,8 +165,8 @@ static int wme_qdiscop_enqueue(struct sk_buff *skb, struct Qdisc* qd) } else { pkt_data->flags &= ~IEEE80211_TXPD_AMPDU; } - sta_info_put(sta); } + rcu_read_unlock(); skb_queue_tail(&q->requeued[queue], skb); qd->q.qlen++; return 0; @@ -187,6 +188,8 @@ static int wme_qdiscop_enqueue(struct sk_buff *skb, struct Qdisc* qd) p++; *p = 0; + rcu_read_lock(); + sta = sta_info_get(local, hdr->addr1); if (sta) { int ampdu_queue = sta->tid_to_tx_q[tid]; @@ -197,8 +200,9 @@ static int wme_qdiscop_enqueue(struct sk_buff *skb, struct Qdisc* qd) } else { pkt_data->flags &= ~IEEE80211_TXPD_AMPDU; } - sta_info_put(sta); } + + rcu_read_unlock(); } if (unlikely(queue >= local->hw.queues)) {