libertas: disable functionality when interface is down

Modify the driver so that it does not function when the interface is
down, in preparation for runtime power management.

No commands can be run while the interface is down, so the ndo_dev_stop
routine now directly does all necessary work (including asking the device
to disconnect from the network and disabling multicast functionality)
directly.

power_save and power_restore hooks are added meaning that card drivers
can take steps to turn the device off when the interface is down.

The MAC address can now only be changed when all interfaces are down;
the new address will be programmed when an interface gets brought up.
This matches mac80211 behaviour.

Also, some small cleanups/simplifications were made in the surrounding
device handling logic.

Signed-off-by: Daniel Drake <dsd@laptop.org>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
diff --git a/drivers/net/wireless/libertas/main.c b/drivers/net/wireless/libertas/main.c
index ee28ae5..d62d1fb 100644
--- a/drivers/net/wireless/libertas/main.c
+++ b/drivers/net/wireless/libertas/main.c
@@ -99,6 +99,37 @@
 	return 0;
 }
 
+int lbs_start_iface(struct lbs_private *priv)
+{
+	struct cmd_ds_802_11_mac_address cmd;
+	int ret;
+
+	if (priv->power_restore) {
+		ret = priv->power_restore(priv);
+		if (ret)
+			return ret;
+	}
+
+	cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+	cmd.action = cpu_to_le16(CMD_ACT_SET);
+	memcpy(cmd.macadd, priv->current_addr, ETH_ALEN);
+
+	ret = lbs_cmd_with_response(priv, CMD_802_11_MAC_ADDRESS, &cmd);
+	if (ret) {
+		lbs_deb_net("set MAC address failed\n");
+		goto err;
+	}
+
+	lbs_update_channel(priv);
+
+	priv->iface_running = true;
+	return 0;
+
+err:
+	if (priv->power_save)
+		priv->power_save(priv);
+	return ret;
+}
 
 /**
  *  lbs_dev_open - open the ethX interface
@@ -112,23 +143,64 @@
 	int ret = 0;
 
 	lbs_deb_enter(LBS_DEB_NET);
+	if (!priv->iface_running) {
+		ret = lbs_start_iface(priv);
+		if (ret)
+			goto out;
+	}
 
 	spin_lock_irq(&priv->driver_lock);
-	priv->stopping = false;
 
-	if (priv->connect_status == LBS_CONNECTED)
-		netif_carrier_on(dev);
-	else
-		netif_carrier_off(dev);
+	netif_carrier_off(dev);
 
 	if (!priv->tx_pending_len)
 		netif_wake_queue(dev);
 
 	spin_unlock_irq(&priv->driver_lock);
+
+out:
 	lbs_deb_leave_args(LBS_DEB_NET, "ret %d", ret);
 	return ret;
 }
 
+static bool lbs_command_queue_empty(struct lbs_private *priv)
+{
+	unsigned long flags;
+	bool ret;
+	spin_lock_irqsave(&priv->driver_lock, flags);
+	ret = priv->cur_cmd == NULL && list_empty(&priv->cmdpendingq);
+	spin_unlock_irqrestore(&priv->driver_lock, flags);
+	return ret;
+}
+
+int lbs_stop_iface(struct lbs_private *priv)
+{
+	unsigned long flags;
+	int ret = 0;
+
+	lbs_deb_enter(LBS_DEB_MAIN);
+
+	spin_lock_irqsave(&priv->driver_lock, flags);
+	priv->iface_running = false;
+	kfree_skb(priv->currenttxskb);
+	priv->currenttxskb = NULL;
+	priv->tx_pending_len = 0;
+	spin_unlock_irqrestore(&priv->driver_lock, flags);
+
+	cancel_work_sync(&priv->mcast_work);
+
+	/* Disable command processing, and wait for all commands to complete */
+	lbs_deb_main("waiting for commands to complete\n");
+	wait_event(priv->waitq, lbs_command_queue_empty(priv));
+	lbs_deb_main("all commands completed\n");
+
+	if (priv->power_save)
+		ret = priv->power_save(priv);
+
+	lbs_deb_leave(LBS_DEB_MAIN);
+	return ret;
+}
+
 /**
  *  lbs_eth_stop - close the ethX interface
  *
@@ -141,18 +213,25 @@
 
 	lbs_deb_enter(LBS_DEB_NET);
 
+	if (priv->connect_status == LBS_CONNECTED)
+		lbs_disconnect(priv, WLAN_REASON_DEAUTH_LEAVING);
+
 	spin_lock_irq(&priv->driver_lock);
-	priv->stopping = true;
 	netif_stop_queue(dev);
 	spin_unlock_irq(&priv->driver_lock);
 
-	schedule_work(&priv->mcast_work);
+	lbs_update_mcast(priv);
 	cancel_delayed_work_sync(&priv->scan_work);
 	if (priv->scan_req) {
 		cfg80211_scan_done(priv->scan_req, false);
 		priv->scan_req = NULL;
 	}
 
+	netif_carrier_off(priv->dev);
+
+	if (!lbs_iface_active(priv))
+		lbs_stop_iface(priv);
+
 	lbs_deb_leave(LBS_DEB_NET);
 	return 0;
 }
@@ -170,7 +249,7 @@
 	/* Wake main thread if commands are pending */
 	if (!priv->cur_cmd || priv->tx_pending_len > 0) {
 		if (!priv->wakeup_dev_required)
-			wake_up_interruptible(&priv->waitq);
+			wake_up(&priv->waitq);
 	}
 
 	spin_unlock_irqrestore(&priv->driver_lock, flags);
@@ -183,29 +262,24 @@
 	int ret = 0;
 	struct lbs_private *priv = dev->ml_priv;
 	struct sockaddr *phwaddr = addr;
-	struct cmd_ds_802_11_mac_address cmd;
 
 	lbs_deb_enter(LBS_DEB_NET);
 
+	/*
+	 * Can only set MAC address when all interfaces are down, to be written
+	 * to the hardware when one of them is brought up.
+	 */
+	if (lbs_iface_active(priv))
+		return -EBUSY;
+
 	/* In case it was called from the mesh device */
 	dev = priv->dev;
 
-	cmd.hdr.size = cpu_to_le16(sizeof(cmd));
-	cmd.action = cpu_to_le16(CMD_ACT_SET);
-	memcpy(cmd.macadd, phwaddr->sa_data, ETH_ALEN);
-
-	ret = lbs_cmd_with_response(priv, CMD_802_11_MAC_ADDRESS, &cmd);
-	if (ret) {
-		lbs_deb_net("set MAC address failed\n");
-		goto done;
-	}
-
 	memcpy(priv->current_addr, phwaddr->sa_data, ETH_ALEN);
 	memcpy(dev->dev_addr, phwaddr->sa_data, ETH_ALEN);
 	if (priv->mesh_dev)
 		memcpy(priv->mesh_dev->dev_addr, phwaddr->sa_data, ETH_ALEN);
 
-done:
 	lbs_deb_leave_args(LBS_DEB_NET, "ret %d", ret);
 	return ret;
 }
@@ -259,18 +333,18 @@
 	return i;
 }
 
-static void lbs_set_mcast_worker(struct work_struct *work)
+void lbs_update_mcast(struct lbs_private *priv)
 {
-	struct lbs_private *priv = container_of(work, struct lbs_private, mcast_work);
 	struct cmd_ds_mac_multicast_adr mcast_cmd;
-	int dev_flags;
+	int dev_flags = 0;
 	int nr_addrs;
 	int old_mac_control = priv->mac_control;
 
 	lbs_deb_enter(LBS_DEB_NET);
 
-	dev_flags = priv->dev->flags;
-	if (priv->mesh_dev)
+	if (netif_running(priv->dev))
+		dev_flags |= priv->dev->flags;
+	if (priv->mesh_dev && netif_running(priv->mesh_dev))
 		dev_flags |= priv->mesh_dev->flags;
 
 	if (dev_flags & IFF_PROMISC) {
@@ -316,6 +390,12 @@
 	lbs_deb_leave(LBS_DEB_NET);
 }
 
+static void lbs_set_mcast_worker(struct work_struct *work)
+{
+	struct lbs_private *priv = container_of(work, struct lbs_private, mcast_work);
+	lbs_update_mcast(priv);
+}
+
 void lbs_set_multicast_list(struct net_device *dev)
 {
 	struct lbs_private *priv = dev->ml_priv;
@@ -648,7 +728,7 @@
 	if (priv->dnld_sent == DNLD_CMD_SENT)
 		priv->dnld_sent = DNLD_RES_RECEIVED;
 
-	wake_up_interruptible(&priv->waitq);
+	wake_up(&priv->waitq);
 out:
 	spin_unlock_irqrestore(&priv->driver_lock, flags);
 	lbs_deb_leave(LBS_DEB_CMD);
@@ -890,10 +970,6 @@
 	lbs_remove_mesh(priv);
 	lbs_scan_deinit(priv);
 
-	dev = priv->dev;
-
-	cancel_work_sync(&priv->mcast_work);
-
 	/* worker thread destruction blocks on the in-flight command which
 	 * should have been cleared already in lbs_stop_card().
 	 */
@@ -964,8 +1040,6 @@
 	if (lbs_mesh_activated(priv))
 		lbs_start_mesh(priv);
 
-	lbs_update_channel(priv);
-
 	lbs_debugfs_init_one(priv, dev);
 
 	netdev_info(dev, "Marvell WLAN 802.11 adapter\n");
@@ -982,8 +1056,6 @@
 void lbs_stop_card(struct lbs_private *priv)
 {
 	struct net_device *dev;
-	struct cmd_ctrl_node *cmdnode;
-	unsigned long flags;
 
 	lbs_deb_enter(LBS_DEB_MAIN);
 
@@ -996,30 +1068,6 @@
 
 	lbs_debugfs_remove_one(priv);
 	lbs_deinit_mesh(priv);
-
-	/* Delete the timeout of the currently processing command */
-	del_timer_sync(&priv->command_timer);
-	del_timer_sync(&priv->auto_deepsleep_timer);
-
-	/* Flush pending command nodes */
-	spin_lock_irqsave(&priv->driver_lock, flags);
-	lbs_deb_main("clearing pending commands\n");
-	list_for_each_entry(cmdnode, &priv->cmdpendingq, list) {
-		cmdnode->result = -ENOENT;
-		cmdnode->cmdwaitqwoken = 1;
-		wake_up(&cmdnode->cmdwait_q);
-	}
-
-	/* Flush the command the card is currently processing */
-	if (priv->cur_cmd) {
-		lbs_deb_main("clearing current command\n");
-		priv->cur_cmd->result = -ENOENT;
-		priv->cur_cmd->cmdwaitqwoken = 1;
-		wake_up(&priv->cur_cmd->cmdwait_q);
-	}
-	lbs_deb_main("done clearing commands\n");
-	spin_unlock_irqrestore(&priv->driver_lock, flags);
-
 	unregister_netdev(dev);
 
 out:
@@ -1040,7 +1088,7 @@
 
 	kfifo_in(&priv->event_fifo, (unsigned char *) &event, sizeof(u32));
 
-	wake_up_interruptible(&priv->waitq);
+	wake_up(&priv->waitq);
 
 	spin_unlock_irqrestore(&priv->driver_lock, flags);
 	lbs_deb_leave(LBS_DEB_THREAD);
@@ -1058,7 +1106,7 @@
 	BUG_ON(resp_idx > 1);
 	priv->resp_idx = resp_idx;
 
-	wake_up_interruptible(&priv->waitq);
+	wake_up(&priv->waitq);
 
 	lbs_deb_leave(LBS_DEB_THREAD);
 }