diff options
Diffstat (limited to 'net/mac80211/iface.c')
| -rw-r--r-- | net/mac80211/iface.c | 190 | 
1 files changed, 151 insertions, 39 deletions
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 50deb017fd6..ebbe264e2b0 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -249,6 +249,8 @@ static int ieee80211_open(struct net_device *dev)  			local->fif_other_bss++;  		ieee80211_configure_filter(local); + +		netif_carrier_on(dev);  		break;  	default:  		res = drv_add_interface(local, &sdata->vif); @@ -268,7 +270,6 @@ static int ieee80211_open(struct net_device *dev)  		changed |= ieee80211_reset_erp_info(sdata);  		ieee80211_bss_info_change_notify(sdata, changed); -		ieee80211_enable_keys(sdata);  		if (sdata->vif.type == NL80211_IFTYPE_STATION)  			netif_carrier_off(dev); @@ -321,15 +322,6 @@ static int ieee80211_open(struct net_device *dev)  	ieee80211_recalc_ps(local, -1); -	/* -	 * ieee80211_sta_work is disabled while network interface -	 * is down. Therefore, some configuration changes may not -	 * yet be effective. Trigger execution of ieee80211_sta_work -	 * to fix this. -	 */ -	if (sdata->vif.type == NL80211_IFTYPE_STATION) -		ieee80211_queue_work(&local->hw, &sdata->u.mgd.work); -  	netif_tx_start_all_queues(dev);  	return 0; @@ -349,7 +341,6 @@ static int ieee80211_stop(struct net_device *dev)  {  	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);  	struct ieee80211_local *local = sdata->local; -	struct sta_info *sta;  	unsigned long flags;  	struct sk_buff *skb, *tmp;  	u32 hw_reconf_flags = 0; @@ -366,18 +357,6 @@ static int ieee80211_stop(struct net_device *dev)  	ieee80211_work_purge(sdata);  	/* -	 * Now delete all active aggregation sessions. -	 */ -	rcu_read_lock(); - -	list_for_each_entry_rcu(sta, &local->sta_list, list) { -		if (sta->sdata == sdata) -			ieee80211_sta_tear_down_BA_sessions(sta); -	} - -	rcu_read_unlock(); - -	/*  	 * Remove all stations associated with this interface.  	 *  	 * This must be done before calling ops->remove_interface() @@ -483,27 +462,14 @@ static int ieee80211_stop(struct net_device *dev)  		 * whether the interface is running, which, at this point,  		 * it no longer is.  		 */ -		cancel_work_sync(&sdata->u.mgd.work);  		cancel_work_sync(&sdata->u.mgd.chswitch_work);  		cancel_work_sync(&sdata->u.mgd.monitor_work);  		cancel_work_sync(&sdata->u.mgd.beacon_connection_loss_work); -		/* -		 * When we get here, the interface is marked down. -		 * Call synchronize_rcu() to wait for the RX path -		 * should it be using the interface and enqueuing -		 * frames at this very time on another CPU. -		 */ -		synchronize_rcu(); -		skb_queue_purge(&sdata->u.mgd.skb_queue);  		/* fall through */  	case NL80211_IFTYPE_ADHOC: -		if (sdata->vif.type == NL80211_IFTYPE_ADHOC) { +		if (sdata->vif.type == NL80211_IFTYPE_ADHOC)  			del_timer_sync(&sdata->u.ibss.timer); -			cancel_work_sync(&sdata->u.ibss.work); -			synchronize_rcu(); -			skb_queue_purge(&sdata->u.ibss.skb_queue); -		}  		/* fall through */  	case NL80211_IFTYPE_MESH_POINT:  		if (ieee80211_vif_is_mesh(&sdata->vif)) { @@ -518,6 +484,16 @@ static int ieee80211_stop(struct net_device *dev)  		}  		/* fall through */  	default: +		flush_work(&sdata->work); +		/* +		 * When we get here, the interface is marked down. +		 * Call synchronize_rcu() to wait for the RX path +		 * should it be using the interface and enqueuing +		 * frames at this very time on another CPU. +		 */ +		synchronize_rcu(); +		skb_queue_purge(&sdata->skb_queue); +  		if (local->scan_sdata == sdata)  			ieee80211_scan_cancel(local); @@ -531,8 +507,8 @@ static int ieee80211_stop(struct net_device *dev)  				BSS_CHANGED_BEACON_ENABLED);  		} -		/* disable all keys for as long as this netdev is down */ -		ieee80211_disable_keys(sdata); +		/* free all remaining keys, there shouldn't be any */ +		ieee80211_free_keys(sdata);  		drv_remove_interface(local, &sdata->vif);  	} @@ -727,6 +703,136 @@ static void ieee80211_if_setup(struct net_device *dev)  	dev->destructor = free_netdev;  } +static void ieee80211_iface_work(struct work_struct *work) +{ +	struct ieee80211_sub_if_data *sdata = +		container_of(work, struct ieee80211_sub_if_data, work); +	struct ieee80211_local *local = sdata->local; +	struct sk_buff *skb; +	struct sta_info *sta; +	struct ieee80211_ra_tid *ra_tid; + +	if (!ieee80211_sdata_running(sdata)) +		return; + +	if (local->scanning) +		return; + +	/* +	 * ieee80211_queue_work() should have picked up most cases, +	 * here we'll pick the rest. +	 */ +	if (WARN(local->suspended, +		 "interface work scheduled while going to suspend\n")) +		return; + +	/* first process frames */ +	while ((skb = skb_dequeue(&sdata->skb_queue))) { +		struct ieee80211_mgmt *mgmt = (void *)skb->data; + +		if (skb->pkt_type == IEEE80211_SDATA_QUEUE_AGG_START) { +			ra_tid = (void *)&skb->cb; +			ieee80211_start_tx_ba_cb(&sdata->vif, ra_tid->ra, +						 ra_tid->tid); +		} else if (skb->pkt_type == IEEE80211_SDATA_QUEUE_AGG_STOP) { +			ra_tid = (void *)&skb->cb; +			ieee80211_stop_tx_ba_cb(&sdata->vif, ra_tid->ra, +						ra_tid->tid); +		} else if (ieee80211_is_action(mgmt->frame_control) && +			   mgmt->u.action.category == WLAN_CATEGORY_BACK) { +			int len = skb->len; + +			mutex_lock(&local->sta_mtx); +			sta = sta_info_get_bss(sdata, mgmt->sa); +			if (sta) { +				switch (mgmt->u.action.u.addba_req.action_code) { +				case WLAN_ACTION_ADDBA_REQ: +					ieee80211_process_addba_request( +							local, sta, mgmt, len); +					break; +				case WLAN_ACTION_ADDBA_RESP: +					ieee80211_process_addba_resp(local, sta, +								     mgmt, len); +					break; +				case WLAN_ACTION_DELBA: +					ieee80211_process_delba(sdata, sta, +								mgmt, len); +					break; +				default: +					WARN_ON(1); +					break; +				} +			} +			mutex_unlock(&local->sta_mtx); +		} else if (ieee80211_is_data_qos(mgmt->frame_control)) { +			struct ieee80211_hdr *hdr = (void *)mgmt; +			/* +			 * So the frame isn't mgmt, but frame_control +			 * is at the right place anyway, of course, so +			 * the if statement is correct. +			 * +			 * Warn if we have other data frame types here, +			 * they must not get here. +			 */ +			WARN_ON(hdr->frame_control & +					cpu_to_le16(IEEE80211_STYPE_NULLFUNC)); +			WARN_ON(!(hdr->seq_ctrl & +					cpu_to_le16(IEEE80211_SCTL_FRAG))); +			/* +			 * This was a fragment of a frame, received while +			 * a block-ack session was active. That cannot be +			 * right, so terminate the session. +			 */ +			mutex_lock(&local->sta_mtx); +			sta = sta_info_get_bss(sdata, mgmt->sa); +			if (sta) { +				u16 tid = *ieee80211_get_qos_ctl(hdr) & +						IEEE80211_QOS_CTL_TID_MASK; + +				__ieee80211_stop_rx_ba_session( +					sta, tid, WLAN_BACK_RECIPIENT, +					WLAN_REASON_QSTA_REQUIRE_SETUP); +			} +			mutex_unlock(&local->sta_mtx); +		} else switch (sdata->vif.type) { +		case NL80211_IFTYPE_STATION: +			ieee80211_sta_rx_queued_mgmt(sdata, skb); +			break; +		case NL80211_IFTYPE_ADHOC: +			ieee80211_ibss_rx_queued_mgmt(sdata, skb); +			break; +		case NL80211_IFTYPE_MESH_POINT: +			if (!ieee80211_vif_is_mesh(&sdata->vif)) +				break; +			ieee80211_mesh_rx_queued_mgmt(sdata, skb); +			break; +		default: +			WARN(1, "frame for unexpected interface type"); +			break; +		} + +		kfree_skb(skb); +	} + +	/* then other type-dependent work */ +	switch (sdata->vif.type) { +	case NL80211_IFTYPE_STATION: +		ieee80211_sta_work(sdata); +		break; +	case NL80211_IFTYPE_ADHOC: +		ieee80211_ibss_work(sdata); +		break; +	case NL80211_IFTYPE_MESH_POINT: +		if (!ieee80211_vif_is_mesh(&sdata->vif)) +			break; +		ieee80211_mesh_work(sdata); +		break; +	default: +		break; +	} +} + +  /*   * Helper function to initialise an interface to a specific type.   */ @@ -744,6 +850,9 @@ static void ieee80211_setup_sdata(struct ieee80211_sub_if_data *sdata,  	/* only monitor differs */  	sdata->dev->type = ARPHRD_ETHER; +	skb_queue_head_init(&sdata->skb_queue); +	INIT_WORK(&sdata->work, ieee80211_iface_work); +  	switch (type) {  	case NL80211_IFTYPE_AP:  		skb_queue_head_init(&sdata->u.ap.ps_bc_buf); @@ -969,6 +1078,9 @@ int ieee80211_if_add(struct ieee80211_local *local, const char *name,  	sdata->wdev.wiphy = local->hw.wiphy;  	sdata->local = local;  	sdata->dev = ndev; +#ifdef CONFIG_INET +	sdata->arp_filter_state = true; +#endif  	for (i = 0; i < IEEE80211_FRAGMENT_MAX; i++)  		skb_queue_head_init(&sdata->fragments[i].skb_list);  |