Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4d36ec58 authored by Johannes Berg's avatar Johannes Berg Committed by John W. Linville
Browse files

mac80211: split hardware scan by band



There's currently a very odd bug in mac80211 -- a
hardware scan that is done while the hardware is
really operating on 2.4 GHz will include CCK rates
in the probe request frame, even on 5 GHz (if the
driver uses the mac80211 IEs). Vice versa, if the
hardware is operating on 5 GHz the 2.4 GHz probe
requests will not include CCK rates even though
they should.

Fix this by splitting up cfg80211 scan requests by
band -- recalculating the IEs every time -- and
requesting only per-band scans from the driver.

Apparently this bug hasn't been a problem yet, but
it is imaginable that some older access points get
confused if confronted with such behaviour.

Signed-off-by: default avatarJohannes Berg <johannes@sipsolutions.net>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent b59f04cb
Loading
Loading
Loading
Loading
+4 −4
Original line number Original line Diff line number Diff line
@@ -667,10 +667,9 @@ struct ieee80211_local {
	unsigned long scanning;
	unsigned long scanning;
	struct cfg80211_ssid scan_ssid;
	struct cfg80211_ssid scan_ssid;
	struct cfg80211_scan_request *int_scan_req;
	struct cfg80211_scan_request *int_scan_req;
	struct cfg80211_scan_request *scan_req;
	struct cfg80211_scan_request *scan_req, *hw_scan_req;
	struct ieee80211_channel *scan_channel;
	struct ieee80211_channel *scan_channel;
	const u8 *orig_ies;
	enum ieee80211_band hw_scan_band;
	int orig_ies_len;
	int scan_channel_idx;
	int scan_channel_idx;
	int scan_ies_len;
	int scan_ies_len;


@@ -1050,7 +1049,8 @@ void ieee80211_send_auth(struct ieee80211_sub_if_data *sdata,
			 u8 *extra, size_t extra_len, const u8 *bssid,
			 u8 *extra, size_t extra_len, const u8 *bssid,
			 const u8 *key, u8 key_len, u8 key_idx);
			 const u8 *key, u8 key_len, u8 key_idx);
int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer,
int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer,
			     const u8 *ie, size_t ie_len);
			     const u8 *ie, size_t ie_len,
			     enum ieee80211_band band);
void ieee80211_send_probe_req(struct ieee80211_sub_if_data *sdata, u8 *dst,
void ieee80211_send_probe_req(struct ieee80211_sub_if_data *sdata, u8 *dst,
			      const u8 *ssid, size_t ssid_len,
			      const u8 *ssid, size_t ssid_len,
			      const u8 *ie, size_t ie_len);
			      const u8 *ie, size_t ie_len);
+71 −25
Original line number Original line Diff line number Diff line
@@ -187,6 +187,39 @@ ieee80211_scan_rx(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb)
	return RX_QUEUED;
	return RX_QUEUED;
}
}


/* return false if no more work */
static bool ieee80211_prep_hw_scan(struct ieee80211_local *local)
{
	struct cfg80211_scan_request *req = local->scan_req;
	enum ieee80211_band band;
	int i, ielen, n_chans;

	do {
		if (local->hw_scan_band == IEEE80211_NUM_BANDS)
			return false;

		band = local->hw_scan_band;
		n_chans = 0;
		for (i = 0; i < req->n_channels; i++) {
			if (req->channels[i]->band == band) {
				local->hw_scan_req->channels[n_chans] =
							req->channels[i];
				n_chans++;
			}
		}

		local->hw_scan_band++;
	} while (!n_chans);

	local->hw_scan_req->n_channels = n_chans;

	ielen = ieee80211_build_preq_ies(local, (u8 *)local->hw_scan_req->ie,
					 req->ie, req->ie_len, band);
	local->hw_scan_req->ie_len = ielen;

	return true;
}

/*
/*
 * inform AP that we will go to sleep so that it will buffer the frames
 * inform AP that we will go to sleep so that it will buffer the frames
 * while we scan
 * while we scan
@@ -247,13 +280,6 @@ static void ieee80211_scan_ps_disable(struct ieee80211_sub_if_data *sdata)
	}
	}
}
}


static void ieee80211_restore_scan_ies(struct ieee80211_local *local)
{
	kfree(local->scan_req->ie);
	local->scan_req->ie = local->orig_ies;
	local->scan_req->ie_len = local->orig_ies_len;
}

void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted)
void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted)
{
{
	struct ieee80211_local *local = hw_to_local(hw);
	struct ieee80211_local *local = hw_to_local(hw);
@@ -272,15 +298,22 @@ void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted)
		return;
		return;
	}
	}


	if (test_bit(SCAN_HW_SCANNING, &local->scanning))
	was_hw_scan = test_bit(SCAN_HW_SCANNING, &local->scanning);
		ieee80211_restore_scan_ies(local);
	if (was_hw_scan && !aborted && ieee80211_prep_hw_scan(local)) {
		ieee80211_queue_delayed_work(&local->hw,
					     &local->scan_work, 0);
		mutex_unlock(&local->scan_mtx);
		return;
	}

	kfree(local->hw_scan_req);
	local->hw_scan_req = NULL;


	if (local->scan_req != local->int_scan_req)
	if (local->scan_req != local->int_scan_req)
		cfg80211_scan_done(local->scan_req, aborted);
		cfg80211_scan_done(local->scan_req, aborted);
	local->scan_req = NULL;
	local->scan_req = NULL;
	local->scan_sdata = NULL;
	local->scan_sdata = NULL;


	was_hw_scan = test_bit(SCAN_HW_SCANNING, &local->scanning);
	local->scanning = 0;
	local->scanning = 0;
	local->scan_channel = NULL;
	local->scan_channel = NULL;


@@ -392,19 +425,23 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata,


	if (local->ops->hw_scan) {
	if (local->ops->hw_scan) {
		u8 *ies;
		u8 *ies;
		int ielen;


		ies = kmalloc(2 + IEEE80211_MAX_SSID_LEN +
		local->hw_scan_req = kmalloc(
			      local->scan_ies_len + req->ie_len, GFP_KERNEL);
				sizeof(*local->hw_scan_req) +
		if (!ies)
				req->n_channels * sizeof(req->channels[0]) +
				2 + IEEE80211_MAX_SSID_LEN + local->scan_ies_len +
				req->ie_len, GFP_KERNEL);
		if (!local->hw_scan_req)
			return -ENOMEM;
			return -ENOMEM;


		ielen = ieee80211_build_preq_ies(local, ies,
		local->hw_scan_req->ssids = req->ssids;
						 req->ie, req->ie_len);
		local->hw_scan_req->n_ssids = req->n_ssids;
		local->orig_ies = req->ie;
		ies = (u8 *)local->hw_scan_req +
		local->orig_ies_len = req->ie_len;
			sizeof(*local->hw_scan_req) +
		req->ie = ies;
			req->n_channels * sizeof(req->channels[0]);
		req->ie_len = ielen;
		local->hw_scan_req->ie = ies;

		local->hw_scan_band = 0;
	}
	}


	local->scan_req = req;
	local->scan_req = req;
@@ -436,16 +473,17 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata,
	ieee80211_recalc_idle(local);
	ieee80211_recalc_idle(local);
	mutex_unlock(&local->scan_mtx);
	mutex_unlock(&local->scan_mtx);


	if (local->ops->hw_scan)
	if (local->ops->hw_scan) {
		rc = drv_hw_scan(local, local->scan_req);
		WARN_ON(!ieee80211_prep_hw_scan(local));
	else
		rc = drv_hw_scan(local, local->hw_scan_req);
	} else
		rc = ieee80211_start_sw_scan(local);
		rc = ieee80211_start_sw_scan(local);


	mutex_lock(&local->scan_mtx);
	mutex_lock(&local->scan_mtx);


	if (rc) {
	if (rc) {
		if (local->ops->hw_scan)
		kfree(local->hw_scan_req);
			ieee80211_restore_scan_ies(local);
		local->hw_scan_req = NULL;
		local->scanning = 0;
		local->scanning = 0;


		ieee80211_recalc_idle(local);
		ieee80211_recalc_idle(local);
@@ -654,6 +692,14 @@ void ieee80211_scan_work(struct work_struct *work)
		return;
		return;
	}
	}


	if (local->hw_scan_req) {
		int rc = drv_hw_scan(local, local->hw_scan_req);
		mutex_unlock(&local->scan_mtx);
		if (rc)
			ieee80211_scan_completed(&local->hw, true);
		return;
	}

	if (local->scan_req && !local->scanning) {
	if (local->scan_req && !local->scanning) {
		struct cfg80211_scan_request *req = local->scan_req;
		struct cfg80211_scan_request *req = local->scan_req;
		int rc;
		int rc;
+5 −3
Original line number Original line Diff line number Diff line
@@ -872,13 +872,14 @@ void ieee80211_send_auth(struct ieee80211_sub_if_data *sdata,
}
}


int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer,
int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer,
			     const u8 *ie, size_t ie_len)
			     const u8 *ie, size_t ie_len,
			     enum ieee80211_band band)
{
{
	struct ieee80211_supported_band *sband;
	struct ieee80211_supported_band *sband;
	u8 *pos, *supp_rates_len, *esupp_rates_len = NULL;
	u8 *pos, *supp_rates_len, *esupp_rates_len = NULL;
	int i;
	int i;


	sband = local->hw.wiphy->bands[local->hw.conf.channel->band];
	sband = local->hw.wiphy->bands[band];


	pos = buffer;
	pos = buffer;


@@ -966,7 +967,8 @@ void ieee80211_send_probe_req(struct ieee80211_sub_if_data *sdata, u8 *dst,
	memcpy(pos, ssid, ssid_len);
	memcpy(pos, ssid, ssid_len);
	pos += ssid_len;
	pos += ssid_len;


	skb_put(skb, ieee80211_build_preq_ies(local, pos, ie, ie_len));
	skb_put(skb, ieee80211_build_preq_ies(local, pos, ie, ie_len,
					      local->hw.conf.channel->band));


	ieee80211_tx_skb(sdata, skb, 0);
	ieee80211_tx_skb(sdata, skb, 0);
}
}