Search Linux Wireless

[WIP] p54pci: fix resume p54 cardbus

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

 



This patch tries to fix the resume freeze, which is described in
https://bugzilla.redhat.com/show_bug.cgi?id=583623

Unlike (normal) PCI cards, cardbus cards are easily removable.
Therefore, the user might replace/remove the card while
the system is suspended. The pcmcia subsystem takes special
precautions to deal with such cases and un- and rebinds
all devices on the pci-bridge during the resume process.

But here's the catch: p54pci uses request_firmware
which blocks until the filesystem is available.
This deadlocks, because the filesystem won't
be initialized until all pci devices are ready again.
---
highly experimental and possibly unstable
---
diff --git a/drivers/net/wireless/p54/main.c b/drivers/net/wireless/p54/main.c
index 7bbd9d3..810197c 100644
--- a/drivers/net/wireless/p54/main.c
+++ b/drivers/net/wireless/p54/main.c
@@ -603,6 +603,8 @@ int p54_register_common(struct ieee80211_hw *dev, struct device *pdev)
 		return err;
 	}
 
+	priv->registered = true;
+
 #ifdef CONFIG_P54_LEDS
 	err = p54_init_leds(priv);
 	if (err)
@@ -638,6 +640,9 @@ void p54_unregister_common(struct ieee80211_hw *dev)
 {
 	struct p54_common *priv = dev->priv;
 
+	if (!priv->registered)
+		return;
+
 #ifdef CONFIG_P54_LEDS
 	p54_unregister_leds(priv);
 #endif /* CONFIG_P54_LEDS */
diff --git a/drivers/net/wireless/p54/p54.h b/drivers/net/wireless/p54/p54.h
index 43a3b2e..850e6bb 100644
--- a/drivers/net/wireless/p54/p54.h
+++ b/drivers/net/wireless/p54/p54.h
@@ -172,6 +172,7 @@ struct p54_common {
 	struct sk_buff_head tx_pending;
 	struct sk_buff_head tx_queue;
 	struct mutex conf_mutex;
+	bool registered;
 
 	/* memory management (as seen by the firmware) */
 	u32 rx_start;
diff --git a/drivers/net/wireless/p54/p54pci.c b/drivers/net/wireless/p54/p54pci.c
index c916e46..353085d 100644
--- a/drivers/net/wireless/p54/p54pci.c
+++ b/drivers/net/wireless/p54/p54pci.c
@@ -50,7 +50,6 @@ static int p54p_upload_firmware(struct ieee80211_hw *dev)
 {
 	struct p54p_priv *priv = dev->priv;
 	__le32 reg;
-	int err;
 	__le32 *data;
 	u32 remains, left, device_addr;
 
@@ -75,11 +74,7 @@ static int p54p_upload_firmware(struct ieee80211_hw *dev)
 	wmb();
 
 	/* wait for the firmware to reset properly */
-	mdelay(10);
-
-	err = p54_parse_firmware(dev, priv->firmware);
-	if (err)
-		return err;
+	mdelay(50);
 
 	if (priv->common.fw_interface != FW_LM86) {
 		dev_err(&priv->pdev->dev, "wrong firmware, "
@@ -360,8 +355,12 @@ static void p54p_stop(struct ieee80211_hw *dev)
 {
 	struct p54p_priv *priv = dev->priv;
 	struct p54p_ring_control *ring_control = priv->ring_control;
-	unsigned int i;
 	struct p54p_desc *desc;
+	unsigned int i;
+
+	mutex_lock(&priv->mutex);
+	if (!priv->started)
+		goto out_unlock;
 
 	P54P_WRITE(int_enable, cpu_to_le32(0));
 	P54P_READ(int_enable);
@@ -420,6 +419,10 @@ static void p54p_stop(struct ieee80211_hw *dev)
 	}
 
 	memset(ring_control, 0, sizeof(*ring_control));
+	priv->started = false;
+
+out_unlock:
+	mutex_unlock(&priv->mutex);
 }
 
 static int p54p_open(struct ieee80211_hw *dev)
@@ -427,19 +430,25 @@ static int p54p_open(struct ieee80211_hw *dev)
 	struct p54p_priv *priv = dev->priv;
 	int err;
 
+	mutex_lock(&priv->mutex);
+	if (WARN_ON(priv->started)) {
+		err = -EBUSY;
+		goto out_unlock;
+	}
+
 	init_completion(&priv->boot_comp);
 	err = request_irq(priv->pdev->irq, p54p_interrupt,
 			  IRQF_SHARED, "p54pci", dev);
 	if (err) {
 		dev_err(&priv->pdev->dev, "failed to register IRQ handler\n");
-		return err;
+		goto out_unlock;
 	}
 
 	memset(priv->ring_control, 0, sizeof(*priv->ring_control));
 	err = p54p_upload_firmware(dev);
 	if (err) {
 		free_irq(priv->pdev->irq, dev);
-		return err;
+		goto out_unlock;
 	}
 	priv->rx_idx_data = priv->tx_idx_data = 0;
 	priv->rx_idx_mgmt = priv->tx_idx_mgmt = 0;
@@ -464,10 +473,10 @@ static int p54p_open(struct ieee80211_hw *dev)
 	P54P_READ(dev_int);
 
 	if (!wait_for_completion_interruptible_timeout(&priv->boot_comp, HZ)) {
-		printk(KERN_ERR "%s: Cannot boot firmware!\n",
-		       wiphy_name(dev->wiphy));
+		dev_err(&priv->pdev->dev, "Cannot boot firmware!");
 		p54p_stop(dev);
-		return -ETIMEDOUT;
+		err = -ETIMEDOUT;
+		goto out_unlock;
 	}
 
 	P54P_WRITE(int_enable, cpu_to_le32(ISL38XX_INT_IDENT_UPDATE));
@@ -480,7 +489,78 @@ static int p54p_open(struct ieee80211_hw *dev)
 	wmb();
 	udelay(10);
 
-	return 0;
+	priv->started = true;
+
+out_unlock:
+	mutex_unlock(&priv->mutex);
+	return err;
+}
+
+const static char *p54p_fws[] = { "isl3886pci", "isl3886" };
+
+static void p54p_fw_callback(const struct firmware *fw, void *context);
+static int p54p_request_fw(struct p54p_priv *priv)
+{
+
+	if (ARRAY_SIZE(p54p_fws) <= priv->fw_idx)
+		return -ENOENT;
+
+	return request_firmware_nowait(THIS_MODULE, 1, p54p_fws[priv->fw_idx],
+				       &priv->pdev->dev, GFP_KERNEL, priv,
+				       p54p_fw_callback);
+}
+
+static void p54p_fw_callback(const struct firmware *fw, void *context)
+{
+	struct p54p_priv *priv = context;
+	struct ieee80211_hw *dev = pci_get_drvdata(priv->pdev);
+	int err;
+
+	if (!fw) {
+		dev_err(&priv->pdev->dev, "Cannot find firmware (\"%s\")",
+			p54p_fws[priv->fw_idx]);
+		priv->fw_idx++;
+
+		err = p54p_request_fw(priv);
+		if (err)
+			goto err_out;
+
+		return;
+	}
+
+	priv->firmware = fw;
+
+	err = p54_parse_firmware(dev, priv->firmware);
+	if (err) {
+		dev_err(&priv->pdev->dev, "Failed to parse firmware");
+		goto err_out;
+	}
+
+	err = p54p_open(dev);
+	if (err)
+		goto err_out;
+	err = p54_read_eeprom(dev);
+	p54p_stop(dev);
+	if (err)
+		goto err_out;
+
+	err = p54_register_common(dev, &priv->pdev->dev);
+	if (err)
+		goto err_out;
+
+	return;
+
+err_out:
+	device_release_driver(&priv->pdev->dev);
+}
+
+static void p54p_init_dev(struct pci_dev *pdev)
+{
+	pci_set_master(pdev);
+	pci_try_set_mwi(pdev);
+
+	pci_write_config_byte(pdev, 0x40, 0);
+	pci_write_config_byte(pdev, 0x41, 0);
 }
 
 static int __devinit p54p_probe(struct pci_dev *pdev,
@@ -518,11 +598,7 @@ static int __devinit p54p_probe(struct pci_dev *pdev,
 		goto err_free_reg;
 	}
 
-	pci_set_master(pdev);
-	pci_try_set_mwi(pdev);
-
-	pci_write_config_byte(pdev, 0x40, 0);
-	pci_write_config_byte(pdev, 0x41, 0);
+	p54p_init_dev(pdev);
 
 	dev = p54_init_common(sizeof(*priv));
 	if (!dev) {
@@ -556,34 +632,16 @@ static int __devinit p54p_probe(struct pci_dev *pdev,
 	priv->common.tx = p54p_tx;
 
 	spin_lock_init(&priv->lock);
+	mutex_init(&priv->mutex);
 	tasklet_init(&priv->tasklet, p54p_tasklet, (unsigned long)dev);
 
-	err = request_firmware(&priv->firmware, "isl3886pci",
-			       &priv->pdev->dev);
-	if (err) {
-		dev_err(&pdev->dev, "Cannot find firmware (isl3886pci)\n");
-		err = request_firmware(&priv->firmware, "isl3886",
-				       &priv->pdev->dev);
-		if (err)
-			goto err_free_common;
-	}
-
-	err = p54p_open(dev);
-	if (err)
-		goto err_free_common;
-	err = p54_read_eeprom(dev);
-	p54p_stop(dev);
-	if (err)
-		goto err_free_common;
-
-	err = p54_register_common(dev, &pdev->dev);
+	err = p54p_request_fw(priv);
 	if (err)
 		goto err_free_common;
 
 	return 0;
 
  err_free_common:
-	release_firmware(priv->firmware);
 	pci_free_consistent(pdev, sizeof(*priv->ring_control),
 			    priv->ring_control, priv->ring_control_dma);
 
@@ -611,6 +669,8 @@ static void __devexit p54p_remove(struct pci_dev *pdev)
 
 	p54_unregister_common(dev);
 	priv = dev->priv;
+	p54p_stop(dev);
+	mutex_destroy(&priv->mutex);
 	release_firmware(priv->firmware);
 	pci_free_consistent(pdev, sizeof(*priv->ring_control),
 			    priv->ring_control, priv->ring_control_dma);
@@ -624,32 +684,48 @@ static void __devexit p54p_remove(struct pci_dev *pdev)
 static int p54p_suspend(struct pci_dev *pdev, pm_message_t state)
 {
 	struct ieee80211_hw *dev = pci_get_drvdata(pdev);
-	struct p54p_priv *priv = dev->priv;
-
-	if (priv->common.mode != NL80211_IFTYPE_UNSPECIFIED) {
-		ieee80211_stop_queues(dev);
-		p54p_stop(dev);
-	}
 
+	p54p_stop(dev);
 	pci_save_state(pdev);
-	pci_set_power_state(pdev, pci_choose_state(pdev, state));
-	return 0;
+	pci_disable_device(pdev);
+	return pci_set_power_state(pdev, pci_choose_state(pdev, state));
 }
 
 static int p54p_resume(struct pci_dev *pdev)
 {
 	struct ieee80211_hw *dev = pci_get_drvdata(pdev);
 	struct p54p_priv *priv = dev->priv;
+	int err;
+
+	err = pci_set_power_state(pdev, PCI_D0);
+	if (err) {
+		dev_err(&pdev->dev, "failed to power-up device");
+		return err;
+	}
 
-	pci_set_power_state(pdev, PCI_D0);
-	pci_restore_state(pdev);
+	err = pci_enable_device(pdev);
+	if (err) {
+		dev_err(&pdev->dev, "failed to reenable device");
+		return err;
+	}
+
+	err = pci_restore_state(pdev);
+	if (err) {
+		dev_err(&pdev->dev, "failed to restore device state");
+		return err;
+	}
+
+	p54p_init_dev(pdev);
 
 	if (priv->common.mode != NL80211_IFTYPE_UNSPECIFIED) {
-		p54p_open(dev);
-		ieee80211_wake_queues(dev);
+		err = p54p_open(dev);
+		if (err) {
+			dev_err(&pdev->dev, "failed to bring up link");
+			return err;
+		}
 	}
 
-	return 0;
+	return err;
 }
 #endif /* CONFIG_PM */
 
diff --git a/drivers/net/wireless/p54/p54pci.h b/drivers/net/wireless/p54/p54pci.h
index 9fd822f..d4a9bdc 100644
--- a/drivers/net/wireless/p54/p54pci.h
+++ b/drivers/net/wireless/p54/p54pci.h
@@ -95,7 +95,10 @@ struct p54p_priv {
 	struct pci_dev *pdev;
 	struct p54p_csr __iomem *map;
 	struct tasklet_struct tasklet;
+	unsigned int fw_idx;
 	const struct firmware *firmware;
+	bool started;
+	struct mutex mutex;
 	spinlock_t lock;
 	struct p54p_ring_control *ring_control;
 	dma_addr_t ring_control_dma;
--
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 Bluetooth]     [Linux Netdev]     [Kernel Newbies]     [Linux Kernel]     [IDE]     [Security]     [Git]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux ATA RAID]     [Samba]     [Device Mapper]
  Powered by Linux