p54: fix beaconing related firmware crash

This patch fixes a firmware crash which can be provoked by changing
operation mode.

Acked-by: Larry Finger <Larry.Finger@lwfinger.net>
Signed-off-by: Christian Lamparter <chunkeey@web.de>
Tested-by: Larry Finger <Larry.Finger@lwfinger.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
diff --git a/drivers/net/wireless/p54/main.c b/drivers/net/wireless/p54/main.c
index f19add2..955f6d7 100644
--- a/drivers/net/wireless/p54/main.c
+++ b/drivers/net/wireless/p54/main.c
@@ -130,7 +130,6 @@
 			struct ieee80211_vif *vif)
 {
 	struct sk_buff *beacon;
-	__le32 old_beacon_req_id;
 	int ret;
 
 	beacon = ieee80211_beacon_get(priv->hw, vif);
@@ -140,15 +139,16 @@
 	if (ret)
 		return ret;
 
-	old_beacon_req_id = priv->beacon_req_id;
-	priv->beacon_req_id = GET_REQ_ID(beacon);
-
-	ret = p54_tx_80211(priv->hw, beacon);
-	if (ret) {
-		priv->beacon_req_id = old_beacon_req_id;
-		return -ENOSPC;
-	}
-
+	/*
+	 * During operation, the firmware takes care of beaconing.
+	 * The driver only needs to upload a new beacon template, once
+	 * the template was changed by the stack or userspace.
+	 *
+	 * LMAC API 3.2.2 also specifies that the driver does not need
+	 * to cancel the old beacon template by hand, instead the firmware
+	 * will release the previous one through the feedback mechanism.
+	 */
+	WARN_ON(p54_tx_80211(priv->hw, beacon));
 	priv->tsf_high32 = 0;
 	priv->tsf_low32 = 0;
 
@@ -253,9 +253,14 @@
 
 	mutex_lock(&priv->conf_mutex);
 	priv->vif = NULL;
-	if (priv->beacon_req_id) {
+
+	/*
+	 * LMAC API 3.2.2 states that any active beacon template must be
+	 * canceled by the driver before attempting a mode transition.
+	 */
+	if (le32_to_cpu(priv->beacon_req_id) != 0) {
 		p54_tx_cancel(priv, priv->beacon_req_id);
-		priv->beacon_req_id = cpu_to_le32(0);
+		wait_for_completion_interruptible_timeout(&priv->beacon_comp, HZ);
 	}
 	priv->mode = NL80211_IFTYPE_MONITOR;
 	memset(priv->mac_addr, 0, ETH_ALEN);
@@ -544,6 +549,7 @@
 				      BIT(NL80211_IFTYPE_MESH_POINT);
 
 	dev->channel_change_time = 1000;	/* TODO: find actual value */
+	priv->beacon_req_id = cpu_to_le32(0);
 	priv->tx_stats[P54_QUEUE_BEACON].limit = 1;
 	priv->tx_stats[P54_QUEUE_FWSCAN].limit = 1;
 	priv->tx_stats[P54_QUEUE_MGMT].limit = 3;
@@ -567,6 +573,7 @@
 	mutex_init(&priv->conf_mutex);
 	mutex_init(&priv->eeprom_mutex);
 	init_completion(&priv->eeprom_comp);
+	init_completion(&priv->beacon_comp);
 	INIT_DELAYED_WORK(&priv->work, p54_work);
 
 	return dev;