Search Linux Wireless

[RFC/RFT] p54: do automatic device restart

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Previously, when the device stalled because the
firmware crashed [which in turn could be caused
by the driver] the user had to reload the driver
or replug the device manually.

With this patch, the driver will now try to re-
start the device on its own, when the outgoing
queue has reached its threshold of 32 queued
frames or firmware commands.
---

Andrew,

so far I was able to get a stable connection
with p54pci even if I let it crash at 20Hz.
I didn't had much luck with isl3887 though
[although that's not surprising given that
we can't/don't know how to stop it when it
crashed].

I'm hoping I can finish the crash-on-purpose
control knob tomorrow afternoon. Do you have
debugfs [CONFIG_MAC80211_DEBUGFS] enabled in
your kernel/compat-wireless config, or do
need some assistance with that?

Regards,
	Christian
---
 drivers/net/wireless/p54/main.c   |   67 +++++++++++++++++++++++++++++++------
 drivers/net/wireless/p54/p54.h    |    5 +++
 drivers/net/wireless/p54/p54pci.c |   23 ++++++++++++-
 drivers/net/wireless/p54/p54pci.h |    1 +
 drivers/net/wireless/p54/p54spi.c |    6 ++++
 drivers/net/wireless/p54/p54usb.c |   19 ++++++-----
 drivers/net/wireless/p54/txrx.c   |   20 ++++++-----
 7 files changed, 112 insertions(+), 29 deletions(-)

diff --git a/drivers/net/wireless/p54/main.c b/drivers/net/wireless/p54/main.c
index aadda99..fa6df63 100644
--- a/drivers/net/wireless/p54/main.c
+++ b/drivers/net/wireless/p54/main.c
@@ -201,17 +201,16 @@ out:
 	return err;
 }
 
-static void p54_stop(struct ieee80211_hw *dev)
+static void __p54_stop(struct p54_common *priv)
 {
-	struct p54_common *priv = dev->priv;
 	int i;
 
-	priv->mode = NL80211_IFTYPE_UNSPECIFIED;
+	ieee80211_stop_queues(priv->hw);
 	priv->softled_state = 0;
 	cancel_delayed_work_sync(&priv->work);
 	mutex_lock(&priv->conf_mutex);
 	p54_set_leds(priv);
-	priv->stop(dev);
+	priv->stop(priv->hw);
 	skb_queue_purge(&priv->tx_pending);
 	skb_queue_purge(&priv->tx_queue);
 	for (i = 0; i < P54_QUEUE_NUM; i++) {
@@ -224,6 +223,14 @@ static void p54_stop(struct ieee80211_hw *dev)
 	mutex_unlock(&priv->conf_mutex);
 }
 
+static void p54_stop(struct ieee80211_hw *dev)
+{
+	struct p54_common *priv = dev->priv;
+
+	priv->mode = NL80211_IFTYPE_UNSPECIFIED;
+	__p54_stop(priv);
+}
+
 static int p54_add_interface(struct ieee80211_hw *dev,
 			     struct ieee80211_vif *vif)
 {
@@ -435,10 +442,9 @@ static void p54_work(struct work_struct *work)
 	if (unlikely(priv->mode == NL80211_IFTYPE_UNSPECIFIED))
 		return ;
 
-	/*
-	 * TODO: walk through tx_queue and do the following tasks
+	/* TODO: walk through tx_queue and do the following tasks
 	 * 	1. initiate bursts.
-	 *      2. cancel stuck frames / reset the device if necessary.
+	 *	2. cancel stuck frames
 	 */
 
 	mutex_lock(&priv->conf_mutex);
@@ -675,11 +681,10 @@ static void p54_flush(struct ieee80211_hw *dev, bool drop)
 	struct p54_common *priv = dev->priv;
 	unsigned int total, i;
 
-	/*
-	 * Currently, it wouldn't really matter if we wait for one second
+	/* Currently, it wouldn't really matter if we wait for one second
 	 * or 15 minutes. But once someone gets around and completes the
-	 * TODOs [ancel stuck frames / reset device] in p54_work, it will
-	 * suddenly make sense to wait that long.
+	 * TODOs [cancel stuck frames] in p54_work, it will suddenly make
+	 * sense to wait that long.
 	 */
 	i = P54_STATISTICS_UPDATE * 2 / 20;
 
@@ -729,6 +734,45 @@ static const struct ieee80211_ops p54_ops = {
 	.set_coverage_class	= p54_set_coverage_class,
 };
 
+void p54_do_reset(struct ieee80211_hw *hw)
+{
+	struct p54_common *priv = hw->priv;
+
+	if (atomic_inc_return(&priv->reset_pending) > 1) {
+		wiphy_debug(priv->hw->wiphy, "device reset is already scheduled.");
+		return;
+	}
+
+	wiphy_warn(priv->hw->wiphy, "restarting device.");
+	schedule_work(&priv->reset_work);
+}
+
+void p54_reset_done_callback(struct ieee80211_hw *hw)
+{
+	struct p54_common *priv = hw->priv;
+
+	atomic_set(&priv->reset_pending, 0);
+
+	if (priv->mode != NL80211_IFTYPE_UNSPECIFIED)
+		ieee80211_restart_hw(hw);
+}
+EXPORT_SYMBOL_GPL(p54_reset_done_callback);
+
+static void p54_reset_work(struct work_struct *work)
+{
+	struct p54_common *priv = container_of(work, struct p54_common,
+					       reset_work);
+	int err;
+
+	__p54_stop(priv);
+	err = priv->reset(priv->hw);
+	if (err) {
+		priv->mode = NL80211_IFTYPE_UNSPECIFIED;
+		wiphy_err(priv->hw->wiphy,
+			  "failed to restart device (%d)", err);
+	}
+}
+
 struct ieee80211_hw *p54_init_common(size_t priv_data_len)
 {
 	struct ieee80211_hw *dev;
@@ -791,6 +835,7 @@ struct ieee80211_hw *p54_init_common(size_t priv_data_len)
 	init_completion(&priv->eeprom_comp);
 	init_completion(&priv->beacon_comp);
 	INIT_DELAYED_WORK(&priv->work, p54_work);
+	INIT_WORK(&priv->reset_work, p54_reset_work);
 
 	memset(&priv->mc_maclist[0], ~0, ETH_ALEN);
 	priv->curchan = NULL;
diff --git a/drivers/net/wireless/p54/p54.h b/drivers/net/wireless/p54/p54.h
index 40b401e..e3e23f1 100644
--- a/drivers/net/wireless/p54/p54.h
+++ b/drivers/net/wireless/p54/p54.h
@@ -170,9 +170,12 @@ struct p54_common {
 	void (*tx)(struct ieee80211_hw *dev, struct sk_buff *skb);
 	int (*open)(struct ieee80211_hw *dev);
 	void (*stop)(struct ieee80211_hw *dev);
+	int (*reset)(struct ieee80211_hw *dev);
 	struct sk_buff_head tx_pending;
 	struct sk_buff_head tx_queue;
 	struct mutex conf_mutex;
+	struct work_struct reset_work;
+	atomic_t reset_pending;
 	bool registered;
 
 	/* memory management (as seen by the firmware) */
@@ -275,6 +278,8 @@ int p54_read_eeprom(struct ieee80211_hw *dev);
 struct ieee80211_hw *p54_init_common(size_t priv_data_len);
 int p54_register_common(struct ieee80211_hw *dev, struct device *pdev);
 void p54_free_common(struct ieee80211_hw *dev);
+void p54_do_reset(struct ieee80211_hw *hw);
+void p54_reset_done_callback(struct ieee80211_hw *hw);
 
 void p54_unregister_common(struct ieee80211_hw *dev);
 
diff --git a/drivers/net/wireless/p54/p54pci.c b/drivers/net/wireless/p54/p54pci.c
index 57e3af8..0fc027f 100644
--- a/drivers/net/wireless/p54/p54pci.c
+++ b/drivers/net/wireless/p54/p54pci.c
@@ -373,7 +373,10 @@ static void p54p_stop(struct ieee80211_hw *dev)
 	P54P_READ(int_enable);
 	udelay(10);
 
-	free_irq(priv->pdev->irq, dev);
+	if (priv->registered) {
+		free_irq(priv->pdev->irq, dev);
+		priv->registered = false;
+	}
 
 	tasklet_kill(&priv->tasklet);
 
@@ -440,6 +443,7 @@ static int p54p_open(struct ieee80211_hw *dev)
 		dev_err(&priv->pdev->dev, "failed to register IRQ handler\n");
 		return err;
 	}
+	priv->registered = true;
 
 	memset(priv->ring_control, 0, sizeof(*priv->ring_control));
 	err = p54p_upload_firmware(dev);
@@ -540,6 +544,22 @@ out:
 	pci_dev_put(pdev);
 }
 
+static int p54p_reset(struct ieee80211_hw *dev)
+{
+	int err;
+
+	p54p_stop(dev);
+	err = p54p_open(dev);
+	if (err)
+		goto err_out;
+
+	p54_reset_done_callback(dev);
+
+err_out:
+	p54p_stop(dev);
+	return err;
+}
+
 static int p54p_probe(struct pci_dev *pdev,
 				const struct pci_device_id *id)
 {
@@ -614,6 +634,7 @@ static int p54p_probe(struct pci_dev *pdev,
 	priv->common.open = p54p_open;
 	priv->common.stop = p54p_stop;
 	priv->common.tx = p54p_tx;
+	priv->common.reset = p54p_reset;
 
 	spin_lock_init(&priv->lock);
 	tasklet_init(&priv->tasklet, p54p_tasklet, (unsigned long)dev);
diff --git a/drivers/net/wireless/p54/p54pci.h b/drivers/net/wireless/p54/p54pci.h
index 68405c1..5ba6b3d 100644
--- a/drivers/net/wireless/p54/p54pci.h
+++ b/drivers/net/wireless/p54/p54pci.h
@@ -96,6 +96,7 @@ struct p54p_priv {
 	struct tasklet_struct tasklet;
 	const struct firmware *firmware;
 	spinlock_t lock;
+	bool registered;
 	struct p54p_ring_control *ring_control;
 	dma_addr_t ring_control_dma;
 	u32 rx_idx_data, tx_idx_data;
diff --git a/drivers/net/wireless/p54/p54spi.c b/drivers/net/wireless/p54/p54spi.c
index 4fd49a0..1b2d447 100644
--- a/drivers/net/wireless/p54/p54spi.c
+++ b/drivers/net/wireless/p54/p54spi.c
@@ -595,6 +595,11 @@ static void p54spi_op_stop(struct ieee80211_hw *dev)
 	cancel_work_sync(&priv->work);
 }
 
+static int p54spi_op_reset(struct ieee80211_hw *dev)
+{
+	return -EOPNOTSUPP;
+}
+
 static int p54spi_probe(struct spi_device *spi)
 {
 	struct p54s_priv *priv = NULL;
@@ -657,6 +662,7 @@ static int p54spi_probe(struct spi_device *spi)
 	priv->common.open = p54spi_op_start;
 	priv->common.stop = p54spi_op_stop;
 	priv->common.tx = p54spi_op_tx;
+	priv->common.reset = p54spi_op_reset;
 
 	ret = p54spi_request_firmware(hw);
 	if (ret < 0)
diff --git a/drivers/net/wireless/p54/p54usb.c b/drivers/net/wireless/p54/p54usb.c
index 1f78585..c623a09 100644
--- a/drivers/net/wireless/p54/p54usb.c
+++ b/drivers/net/wireless/p54/p54usb.c
@@ -483,13 +483,11 @@ static int p54u_firmware_reset_3887(struct ieee80211_hw *dev)
 	buf = kmemdup(p54u_romboot_3887, 4, GFP_KERNEL);
 	if (!buf)
 		return -ENOMEM;
-	ret = p54u_bulk_msg(priv, P54U_PIPE_DATA,
-			    buf, 4);
+	ret = p54u_bulk_msg(priv, P54U_PIPE_DATA, buf, 4);
 	kfree(buf);
 	if (ret)
 		dev_err(&priv->udev->dev, "(p54usb) unable to jump to "
 			"boot ROM (%d)!\n", ret);
-
 	return ret;
 }
 
@@ -990,6 +988,13 @@ static int p54u_load_firmware(struct ieee80211_hw *dev,
 	return err;
 }
 
+static int p54u_reset(struct ieee80211_hw *dev)
+{
+	struct p54u_priv *priv = dev->priv;
+	usb_queue_reset_device(priv->intf);
+	return 0;
+}
+
 static int p54u_probe(struct usb_interface *intf,
 				const struct usb_device_id *id)
 {
@@ -1038,6 +1043,7 @@ static int p54u_probe(struct usb_interface *intf,
 	}
 	priv->common.open = p54u_open;
 	priv->common.stop = p54u_stop;
+	priv->common.reset = p54u_reset;
 	if (recognized_pipes < P54U_PIPE_NUMBER) {
 #ifdef CONFIG_PM
 		/* ISL3887 needs a full reset on resume */
@@ -1081,7 +1087,6 @@ static void p54u_disconnect(struct usb_interface *intf)
 static int p54u_pre_reset(struct usb_interface *intf)
 {
 	struct ieee80211_hw *dev = usb_get_intfdata(intf);
-
 	if (!dev)
 		return -ENODEV;
 
@@ -1107,7 +1112,6 @@ static int p54u_resume(struct usb_interface *intf)
 static int p54u_post_reset(struct usb_interface *intf)
 {
 	struct ieee80211_hw *dev = usb_get_intfdata(intf);
-	struct p54u_priv *priv;
 	int err;
 
 	err = p54u_resume(intf);
@@ -1115,10 +1119,7 @@ static int p54u_post_reset(struct usb_interface *intf)
 		return err;
 
 	/* reinitialize old device state */
-	priv = dev->priv;
-	if (priv->common.mode != NL80211_IFTYPE_UNSPECIFIED)
-		ieee80211_restart_hw(dev);
-
+	p54_reset_done_callback(dev);
 	return 0;
 }
 
diff --git a/drivers/net/wireless/p54/txrx.c b/drivers/net/wireless/p54/txrx.c
index 12f0a34..7bfbf1f 100644
--- a/drivers/net/wireless/p54/txrx.c
+++ b/drivers/net/wireless/p54/txrx.c
@@ -97,12 +97,11 @@ static int p54_assign_address(struct p54_common *priv, struct sk_buff *skb)
 
 	spin_lock_irqsave(&priv->tx_queue.lock, flags);
 	if (unlikely(skb_queue_len(&priv->tx_queue) == 32)) {
-		/*
-		 * The tx_queue is now really full.
-		 *
-		 * TODO: check if the device has crashed and reset it.
+		/* The tx_queue is full and the device doesn't
+		 * seem to care... So reset it!
 		 */
 		spin_unlock_irqrestore(&priv->tx_queue.lock, flags);
+		p54_do_reset(priv->hw);
 		return -EBUSY;
 	}
 
@@ -791,13 +790,14 @@ void p54_tx_80211(struct ieee80211_hw *dev,
 	u8 nrates = 0, nremaining = 8;
 	bool burst_allowed = false;
 
+	if (atomic_read(&priv->reset_pending))
+		goto drop;
+
 	p54_tx_80211_header(priv, skb, info, control->sta, &queue, &extra_len,
 			    &hdr_flags, &aid, &burst_allowed);
 
-	if (p54_tx_qos_accounting_alloc(priv, skb, queue)) {
-		ieee80211_free_txskb(dev, skb);
-		return;
-	}
+	if (p54_tx_qos_accounting_alloc(priv, skb, queue))
+		goto drop;
 
 	padding = (unsigned long)(skb->data - (sizeof(*hdr) + sizeof(*txhdr))) & 3;
 	len = skb->len;
@@ -938,4 +938,8 @@ void p54_tx_80211(struct ieee80211_hw *dev,
 	p54info->extra_len = extra_len;
 
 	p54_tx(priv, skb);
+	return;
+
+drop:
+	ieee80211_free_txskb(dev, skb);
 }
-- 
1.7.10.4

--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Host AP]     [ATH6KL]     [Linux Wireless Personal Area Network]     [Linux Bluetooth]     [Linux Netdev]     [Kernel Newbies]     [Linux Kernel]     [IDE]     [Git]     [Netfilter]     [Bugtraq]     [Yosemite Hiking]     [MIPS Linux]     [ARM Linux]     [Linux RAID]

  Powered by Linux