diff --git a/drivers/nfc/nfcmrvl/fw_dnld.c b/drivers/nfc/nfcmrvl/fw_dnld.c
index 8b9d52573..6e8608655 100644
--- a/drivers/nfc/nfcmrvl/fw_dnld.c
+++ b/drivers/nfc/nfcmrvl/fw_dnld.c
@@ -90,22 +90,50 @@ static struct sk_buff *alloc_lc_skb(struct nfcmrvl_private *priv, uint8_t plen)
return skb;
}
-static void fw_dnld_over(struct nfcmrvl_private *priv, u32 error)
+static void fw_dnld_stop_cmds(struct nci_dev *ndev)
{
- if (priv->fw_dnld.fw) {
- release_firmware(priv->fw_dnld.fw);
- priv->fw_dnld.fw = NULL;
- priv->fw_dnld.header = NULL;
- priv->fw_dnld.binary_config = NULL;
- }
-
- atomic_set(&priv->ndev->cmd_cnt, 0);
-
- if (timer_pending(&priv->ndev->cmd_timer))
- timer_delete_sync(&priv->ndev->cmd_timer);
+ /*
+ * The timer callback can queue cmd_work after a workqueue flush, while
+ * cmd_work can rearm the timer. Drain both directions before purging
+ * commands which belong to the firmware state machine.
+ */
+ timer_delete_sync(&ndev->cmd_timer);
+ atomic_set(&ndev->cmd_cnt, 0);
+ cancel_work_sync(&ndev->cmd_work);
+ timer_delete_sync(&ndev->cmd_timer);
+ skb_queue_purge(&ndev->cmd_q);
+}
- if (timer_pending(&priv->fw_dnld.timer))
- timer_delete_sync(&priv->fw_dnld.timer);
+static void fw_dnld_over(struct nfcmrvl_private *priv, int error)
+{
+ struct nfcmrvl_fw_dnld *fw_dnld = &priv->fw_dnld;
+ char name[NFC_FIRMWARE_NAME_MAXSIZE + 1];
+ u32 result = error < 0 ? -error : error;
+ unsigned long flags;
+
+ spin_lock_irqsave(&fw_dnld->rx_lock, flags);
+ if (!fw_dnld->active) {
+ spin_unlock_irqrestore(&fw_dnld->rx_lock, flags);
+ return;
+ }
+ fw_dnld->active = false;
+ fw_dnld->completing = true;
+ spin_unlock_irqrestore(&fw_dnld->rx_lock, flags);
+
+ strscpy(name, fw_dnld->name);
+ wait_event(fw_dnld->rx_wait, !READ_ONCE(fw_dnld->rx_active));
+ timer_delete_sync(&fw_dnld->timer);
+ /* Timeout and receive work share a single-thread workqueue. */
+ cancel_work(&fw_dnld->timeout_work);
+ fw_dnld_stop_cmds(priv->ndev);
+ skb_queue_purge(&fw_dnld->rx_q);
+
+ if (fw_dnld->fw) {
+ release_firmware(fw_dnld->fw);
+ fw_dnld->fw = NULL;
+ fw_dnld->header = NULL;
+ fw_dnld->binary_config = NULL;
+ }
nfc_info(priv->dev, "FW loading over (%d)]\n", error);
@@ -114,7 +142,11 @@ static void fw_dnld_over(struct nfcmrvl_private *priv, u32 error)
nfcmrvl_chip_halt(priv);
}
- nfc_fw_download_done(priv->ndev->nfc_dev, priv->fw_dnld.name, error);
+ nfc_fw_download_done(priv->ndev->nfc_dev, name, result);
+
+ spin_lock_irqsave(&fw_dnld->rx_lock, flags);
+ fw_dnld->completing = false;
+ spin_unlock_irqrestore(&fw_dnld->rx_lock, flags);
}
static void fw_dnld_timeout(struct timer_list *t)
@@ -122,6 +154,18 @@ static void fw_dnld_timeout(struct timer_list *t)
struct nfcmrvl_private *priv = timer_container_of(priv, t,
fw_dnld.timer);
+ queue_work(priv->fw_dnld.rx_wq, &priv->fw_dnld.timeout_work);
+}
+
+static void fw_dnld_timeout_work(struct work_struct *work)
+{
+ struct nfcmrvl_fw_dnld *fw_dnld = container_of(work,
+ struct nfcmrvl_fw_dnld,
+ timeout_work);
+ struct nfcmrvl_private *priv = container_of(fw_dnld,
+ struct nfcmrvl_private,
+ fw_dnld);
+
nfc_err(priv->dev, "FW loading timeout");
priv->fw_dnld.state = STATE_RESET;
fw_dnld_over(priv, -ETIMEDOUT);@@ -395,62 +439,74 @@ static int process_state_boot(struct nfcmrvl_private *priv,
static void fw_dnld_rx_work(struct work_struct *work)
{
- int ret;
- struct sk_buff *skb;
struct nfcmrvl_fw_dnld *fw_dnld = container_of(work,
struct nfcmrvl_fw_dnld,
rx_work);
struct nfcmrvl_private *priv = container_of(fw_dnld,
struct nfcmrvl_private,
fw_dnld);
+ struct sk_buff *skb;
+ unsigned long flags;
+ int ret;
- while ((skb = skb_dequeue(&fw_dnld->rx_q))) {
- nfc_send_to_raw_sock(priv->ndev->nfc_dev, skb,
- RAW_PAYLOAD_NCI, NFC_DIRECTION_RX);
- switch (fw_dnld->state) {
- case STATE_RESET:
- ret = process_state_reset(priv, skb);
- break;
- case STATE_INIT:
- ret = process_state_init(priv, skb);
- break;
- case STATE_SET_REF_CLOCK:
- ret = process_state_set_ref_clock(priv, skb);
- break;
- case STATE_SET_HI_CONFIG:
- ret = process_state_set_hi_config(priv, skb);
- break;
- case STATE_OPEN_LC:
- ret = process_state_open_lc(priv, skb);
- break;
- case STATE_FW_DNLD:
- ret = process_state_fw_dnld(priv, skb);
- break;
- case STATE_CLOSE_LC:
- ret = process_state_close_lc(priv, skb);
- break;
- case STATE_BOOT:
- ret = process_state_boot(priv, skb);
- break;
- default:
- ret = -EFAULT;
- }
+ skb = skb_dequeue(&fw_dnld->rx_q);
+ if (!skb)
+ return;
- kfree_skb(skb);
+ nfc_send_to_raw_sock(priv->ndev->nfc_dev, skb,
+ RAW_PAYLOAD_NCI, NFC_DIRECTION_RX);
+ switch (fw_dnld->state) {
+ case STATE_RESET:
+ ret = process_state_reset(priv, skb);
+ break;
+ case STATE_INIT:
+ ret = process_state_init(priv, skb);
+ break;
+ case STATE_SET_REF_CLOCK:
+ ret = process_state_set_ref_clock(priv, skb);
+ break;
+ case STATE_SET_HI_CONFIG:
+ ret = process_state_set_hi_config(priv, skb);
+ break;
+ case STATE_OPEN_LC:
+ ret = process_state_open_lc(priv, skb);
+ break;
+ case STATE_FW_DNLD:
+ ret = process_state_fw_dnld(priv, skb);
+ break;
+ case STATE_CLOSE_LC:
+ ret = process_state_close_lc(priv, skb);
+ break;
+ case STATE_BOOT:
+ ret = process_state_boot(priv, skb);
+ break;
+ default:
+ ret = -EFAULT;
+ }
- if (ret != 0) {
- nfc_err(priv->dev, "FW loading error");
- fw_dnld_over(priv, ret);
- break;
- }
+ kfree_skb(skb);
+
+ if (ret) {
+ nfc_err(priv->dev, "FW loading error");
+ fw_dnld_over(priv, ret);
}
+
+ spin_lock_irqsave(&fw_dnld->rx_lock, flags);
+ if (!fw_dnld->stopping && !fw_dnld->starting && fw_dnld->active &&
+ !skb_queue_empty(&fw_dnld->rx_q))
+ queue_work(fw_dnld->rx_wq, &fw_dnld->rx_work);
+ spin_unlock_irqrestore(&fw_dnld->rx_lock, flags);
}
int nfcmrvl_fw_dnld_init(struct nfcmrvl_private *priv)
{
char name[32];
+ spin_lock_init(&priv->fw_dnld.rx_lock);
+ init_waitqueue_head(&priv->fw_dnld.rx_wait);
INIT_WORK(&priv->fw_dnld.rx_work, fw_dnld_rx_work);
+ INIT_WORK(&priv->fw_dnld.timeout_work, fw_dnld_timeout_work);
+ timer_setup(&priv->fw_dnld.timer, fw_dnld_timeout, 0);
snprintf(name, sizeof(name), "%s_nfcmrvl_fw_dnld_rx_wq",
dev_name(&priv->ndev->nfc_dev->dev));
priv->fw_dnld.rx_wq = create_singlethread_workqueue(name);@@ -462,26 +518,64 @@ int nfcmrvl_fw_dnld_init(struct nfcmrvl_private *priv)
void nfcmrvl_fw_dnld_deinit(struct nfcmrvl_private *priv)
{
+ nfcmrvl_fw_dnld_abort(priv);
destroy_workqueue(priv->fw_dnld.rx_wq);
}
void nfcmrvl_fw_dnld_recv_frame(struct nfcmrvl_private *priv,
struct sk_buff *skb)
{
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->fw_dnld.rx_lock, flags);
+ if (priv->fw_dnld.stopping || !priv->fw_dnld.active) {
+ spin_unlock_irqrestore(&priv->fw_dnld.rx_lock, flags);
+ kfree_skb(skb);
+ return;
+ }
+ priv->fw_dnld.rx_active++;
+ spin_unlock_irqrestore(&priv->fw_dnld.rx_lock, flags);
+
/* Discard command timer */
- if (timer_pending(&priv->ndev->cmd_timer))
- timer_delete_sync(&priv->ndev->cmd_timer);
+ timer_delete_sync(&priv->ndev->cmd_timer);
/* Allow next command */
atomic_set(&priv->ndev->cmd_cnt, 1);
- /* Queue and trigger rx work */
- skb_queue_tail(&priv->fw_dnld.rx_q, skb);
- queue_work(priv->fw_dnld.rx_wq, &priv->fw_dnld.rx_work);
+ spin_lock_irqsave(&priv->fw_dnld.rx_lock, flags);
+ if (!priv->fw_dnld.stopping && priv->fw_dnld.active) {
+ /* Queue the frame, but do not process it during the reset pulse. */
+ skb_queue_tail(&priv->fw_dnld.rx_q, skb);
+ if (!priv->fw_dnld.starting)
+ queue_work(priv->fw_dnld.rx_wq,
+ &priv->fw_dnld.rx_work);
+ skb = NULL;
+ }
+ priv->fw_dnld.rx_active--;
+ if (!priv->fw_dnld.rx_active &&
+ (priv->fw_dnld.stopping || !priv->fw_dnld.active))
+ wake_up_all(&priv->fw_dnld.rx_wait);
+ spin_unlock_irqrestore(&priv->fw_dnld.rx_lock, flags);
+ kfree_skb(skb);
}
void nfcmrvl_fw_dnld_abort(struct nfcmrvl_private *priv)
{
+ struct nfcmrvl_fw_dnld *fw_dnld = &priv->fw_dnld;
+ unsigned long flags;
+
+ device_lock(&priv->ndev->nfc_dev->dev);
+ spin_lock_irqsave(&fw_dnld->rx_lock, flags);
+ fw_dnld->stopping = true;
+ spin_unlock_irqrestore(&fw_dnld->rx_lock, flags);
+ device_unlock(&priv->ndev->nfc_dev->dev);
+
+ wait_event(fw_dnld->rx_wait, !READ_ONCE(fw_dnld->rx_active));
+ timer_shutdown_sync(&fw_dnld->timer);
+ cancel_work_sync(&fw_dnld->rx_work);
+ cancel_work_sync(&fw_dnld->timeout_work);
+ skb_queue_purge(&fw_dnld->rx_q);
+
fw_dnld_over(priv, -EHOSTDOWN);
}
@@ -489,6 +583,7 @@ int nfcmrvl_fw_dnld_start(struct nci_dev *ndev, const char *firmware_name)
{
struct nfcmrvl_private *priv = nci_get_drvdata(ndev);
struct nfcmrvl_fw_dnld *fw_dnld = &priv->fw_dnld;
+ unsigned long flags;
int res;
if (!priv->support_fw_dnld)@@ -497,6 +592,14 @@ int nfcmrvl_fw_dnld_start(struct nci_dev *ndev, const char *firmware_name)
if (!firmware_name || !firmware_name[0])
return -EINVAL;
+ spin_lock_irqsave(&fw_dnld->rx_lock, flags);
+ if (fw_dnld->stopping || fw_dnld->active || fw_dnld->completing) {
+ res = fw_dnld->stopping ? -ENODEV : -EBUSY;
+ spin_unlock_irqrestore(&fw_dnld->rx_lock, flags);
+ return res;
+ }
+ spin_unlock_irqrestore(&fw_dnld->rx_lock, flags);
+
strscpy(fw_dnld->name, firmware_name);
/*@@ -520,6 +623,7 @@ int nfcmrvl_fw_dnld_start(struct nci_dev *ndev, const char *firmware_name)
firmware_name, fw_dnld->header->magic,
fw_dnld->header->phy);
release_firmware(fw_dnld->fw);
+ fw_dnld->fw = NULL;
fw_dnld->header = NULL;
return -EINVAL;
}
@@ -532,11 +636,6 @@ int nfcmrvl_fw_dnld_start(struct nci_dev *ndev, const char *firmware_name)
fw_dnld->binary_config = &fw_dnld->header->firmware;
}
- /* Configure a timer for timeout */
- timer_setup(&priv->fw_dnld.timer, fw_dnld_timeout, 0);
- mod_timer(&priv->fw_dnld.timer,
- jiffies + msecs_to_jiffies(FW_DNLD_TIMEOUT));
-
/* Ronfigure HI to be sure that it is the bootrom values */
priv->if_ops->nci_update_config(priv,
&fw_dnld->header->bootrom.config);
@@ -546,8 +645,24 @@ int nfcmrvl_fw_dnld_start(struct nci_dev *ndev, const char *firmware_name)
/* First, reset the chip */
priv->fw_dnld.state = STATE_RESET;
+
+ spin_lock_irqsave(&fw_dnld->rx_lock, flags);
+ fw_dnld->active = true;
+ fw_dnld->starting = true;
+ spin_unlock_irqrestore(&fw_dnld->rx_lock, flags);
+
nfcmrvl_chip_reset(priv);
+ spin_lock_irqsave(&fw_dnld->rx_lock, flags);
+ fw_dnld->starting = false;
+ if (fw_dnld->active) {
+ mod_timer(&fw_dnld->timer,
+ jiffies + msecs_to_jiffies(FW_DNLD_TIMEOUT));
+ if (!skb_queue_empty(&fw_dnld->rx_q))
+ queue_work(fw_dnld->rx_wq, &fw_dnld->rx_work);
+ }
+ spin_unlock_irqrestore(&fw_dnld->rx_lock, flags);
+
/* Now wait for CORE_RESET_NTF or timeout */
return 0;diff --git a/drivers/nfc/nfcmrvl/fw_dnld.h b/drivers/nfc/nfcmrvl/fw_dnld.h
index 7c4d91b01..a8ed7bfcc 100644
--- a/drivers/nfc/nfcmrvl/fw_dnld.h
+++ b/drivers/nfc/nfcmrvl/fw_dnld.h
@@ -8,6 +8,8 @@
#ifndef __NFCMRVL_FW_DNLD_H__
#define __NFCMRVL_FW_DNLD_H__
+#include <linux/spinlock.h>
+#include <linux/wait.h>
#include <linux/workqueue.h>
#define NFCMRVL_FW_MAGIC 0x88888888
@@ -72,7 +74,16 @@ struct nfcmrvl_fw_dnld {
struct workqueue_struct *rx_wq;
struct work_struct rx_work;
+ struct work_struct timeout_work;
struct sk_buff_head rx_q;
+ /* Protects stopping, starting, active, completing, and rx_active. */
+ spinlock_t rx_lock;
+ wait_queue_head_t rx_wait;
+ unsigned int rx_active;
+ bool stopping;
+ bool starting;
+ bool active;
+ bool completing;
struct timer_list timer;
};diff --git a/drivers/nfc/nfcmrvl/main.c b/drivers/nfc/nfcmrvl/main.c
index 81c760534..02ea3356b 100644
--- a/drivers/nfc/nfcmrvl/main.c
+++ b/drivers/nfc/nfcmrvl/main.c
@@ -185,13 +185,12 @@ void nfcmrvl_nci_unregister_dev(struct nfcmrvl_private *priv)
{
struct nci_dev *ndev = priv->ndev;
+ nfcmrvl_fw_dnld_abort(priv);
nci_unregister_device(ndev);
+
if (priv->if_ops->nci_quiesce)
priv->if_ops->nci_quiesce(priv);
- if (priv->ndev->nfc_dev->fw_download_in_progress)
- nfcmrvl_fw_dnld_abort(priv);
-
nfcmrvl_fw_dnld_deinit(priv);
nci_free_device(ndev);--
2.54.0