cfg80211: let SME control reassociation vs. association

Since we don't really know that well in the kernel,
let's let the SME control whether it wants to use
reassociation or not, by allowing it to give the
previous BSSID in the associate() parameters.

Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 0f29cd0..e6d8860 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -1256,6 +1256,12 @@
 		sdata->u.mgd.flags &= ~IEEE80211_STA_MFP_ENABLED;
 	}
 
+	if (req->prev_bssid) {
+		sdata->u.mgd.flags |= IEEE80211_STA_PREV_BSSID_SET;
+		memcpy(sdata->u.mgd.prev_bssid, req->prev_bssid, ETH_ALEN);
+	} else
+		sdata->u.mgd.flags &= ~IEEE80211_STA_PREV_BSSID_SET;
+
 	if (req->crypto.control_port)
 		sdata->u.mgd.flags |= IEEE80211_STA_CONTROL_PORT;
 	else
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index aa1829a..2448645 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -879,9 +879,6 @@
 		ieee80211_rx_bss_put(local, bss);
 	}
 
-	ifmgd->flags |= IEEE80211_STA_PREV_BSSID_SET;
-	memcpy(ifmgd->prev_bssid, sdata->u.mgd.bssid, ETH_ALEN);
-
 	ifmgd->last_probe = jiffies;
 	ieee80211_led_assoc(local, 1);
 
@@ -1470,10 +1467,6 @@
 	if (status_code != WLAN_STATUS_SUCCESS) {
 		printk(KERN_DEBUG "%s: AP denied association (code=%d)\n",
 		       sdata->dev->name, status_code);
-		/* if this was a reassociation, ensure we try a "full"
-		 * association next time. This works around some broken APs
-		 * which do not correctly reject reassociation requests. */
-		ifmgd->flags &= ~IEEE80211_STA_PREV_BSSID_SET;
 		cfg80211_send_rx_assoc(sdata->dev, (u8 *) mgmt, len,
 				       GFP_KERNEL);
 		/* Wait for SME to decide what to do next */
diff --git a/net/wireless/core.h b/net/wireless/core.h
index 82918f5..4554453 100644
--- a/net/wireless/core.h
+++ b/net/wireless/core.h
@@ -202,7 +202,8 @@
 		       const u8 *ie, int ie_len);
 int cfg80211_mlme_assoc(struct cfg80211_registered_device *rdev,
 			struct net_device *dev, struct ieee80211_channel *chan,
-			const u8 *bssid, const u8 *ssid, int ssid_len,
+			const u8 *bssid, const u8 *prev_bssid,
+			const u8 *ssid, int ssid_len,
 			const u8 *ie, int ie_len, bool use_mfp,
 			struct cfg80211_crypto_settings *crypt);
 int cfg80211_mlme_deauth(struct cfg80211_registered_device *rdev,
diff --git a/net/wireless/mlme.c b/net/wireless/mlme.c
index 020f33b..087d337 100644
--- a/net/wireless/mlme.c
+++ b/net/wireless/mlme.c
@@ -335,7 +335,8 @@
 
 int cfg80211_mlme_assoc(struct cfg80211_registered_device *rdev,
 			struct net_device *dev, struct ieee80211_channel *chan,
-			const u8 *bssid, const u8 *ssid, int ssid_len,
+			const u8 *bssid, const u8 *prev_bssid,
+			const u8 *ssid, int ssid_len,
 			const u8 *ie, int ie_len, bool use_mfp,
 			struct cfg80211_crypto_settings *crypt)
 {
@@ -353,6 +354,7 @@
 	req.ie_len = ie_len;
 	memcpy(&req.crypto, crypt, sizeof(req.crypto));
 	req.use_mfp = use_mfp;
+	req.prev_bssid = prev_bssid;
 	req.bss = cfg80211_get_bss(&rdev->wiphy, chan, bssid, ssid, ssid_len,
 				   WLAN_CAPABILITY_ESS, WLAN_CAPABILITY_ESS);
 	if (!req.bss)
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 723512b..44c520c 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -71,6 +71,7 @@
 	[NL80211_ATTR_IFNAME] = { .type = NLA_NUL_STRING, .len = IFNAMSIZ-1 },
 
 	[NL80211_ATTR_MAC] = { .type = NLA_BINARY, .len = ETH_ALEN },
+	[NL80211_ATTR_PREV_BSSID] = { .type = NLA_BINARY, .len = ETH_ALEN },
 
 	[NL80211_ATTR_KEY_DATA] = { .type = NLA_BINARY,
 				    .len = WLAN_MAX_KEY_LEN },
@@ -3187,7 +3188,7 @@
 	struct net_device *dev;
 	struct cfg80211_crypto_settings crypto;
 	struct ieee80211_channel *chan;
-	const u8 *bssid, *ssid, *ie = NULL;
+	const u8 *bssid, *ssid, *ie = NULL, *prev_bssid = NULL;
 	int err, ssid_len, ie_len = 0;
 	bool use_mfp = false;
 
@@ -3248,10 +3249,13 @@
 		}
 	}
 
+	if (info->attrs[NL80211_ATTR_PREV_BSSID])
+		prev_bssid = nla_data(info->attrs[NL80211_ATTR_PREV_BSSID]);
+
 	err = nl80211_crypto_settings(info, &crypto, 1);
 	if (!err)
-		err = cfg80211_mlme_assoc(rdev, dev, chan, bssid, ssid,
-					  ssid_len, ie, ie_len, use_mfp,
+		err = cfg80211_mlme_assoc(rdev, dev, chan, bssid, prev_bssid,
+					  ssid, ssid_len, ie, ie_len, use_mfp,
 					  &crypto);
 
 out:
diff --git a/net/wireless/sme.c b/net/wireless/sme.c
index 412161f..066a19e 100644
--- a/net/wireless/sme.c
+++ b/net/wireless/sme.c
@@ -125,8 +125,14 @@
 	case CFG80211_CONN_ASSOCIATE_NEXT:
 		BUG_ON(!drv->ops->assoc);
 		wdev->conn->state = CFG80211_CONN_ASSOCIATING;
+		/*
+		 * We could, later, implement roaming here and then actually
+		 * set prev_bssid to non-NULL. But then we need to be aware
+		 * that some APs don't like that -- so we'd need to retry
+		 * the association.
+		 */
 		err = cfg80211_mlme_assoc(drv, wdev->netdev,
-					  params->channel, params->bssid,
+					  params->channel, params->bssid, NULL,
 					  params->ssid, params->ssid_len,
 					  params->ie, params->ie_len,
 					  false, &params->crypto);