diff --git a/drivers/nfc/nfcmrvl/main.c b/drivers/nfc/nfcmrvl/main.c
index 6efa83219..81c760534 100644
--- a/drivers/nfc/nfcmrvl/main.c
+++ b/drivers/nfc/nfcmrvl/main.c
@@ -154,19 +154,24 @@ struct nfcmrvl_private *nfcmrvl_nci_register_dev(enum nfcmrvl_phy phy,
nci_set_drvdata(priv->ndev, priv);
+ /* Ensure that the controller is powered off before exposing it. */
+ nfcmrvl_chip_halt(priv);
+
+ if (priv->if_ops->nci_ready)
+ priv->if_ops->nci_ready(priv);
+
rc = nci_register_device(priv->ndev);
if (rc) {
nfc_err(dev, "nci_register_device failed %d\n", rc);
goto error_fw_dnld_deinit;
}
- /* Ensure that controller is powered off */
- nfcmrvl_chip_halt(priv);
-
nfc_info(dev, "registered with nci successfully\n");
return priv;
error_fw_dnld_deinit:
+ if (priv->if_ops->nci_quiesce)
+ priv->if_ops->nci_quiesce(priv);
nfcmrvl_fw_dnld_deinit(priv);
error_free_dev:
nci_free_device(priv->ndev);@@ -181,6 +186,9 @@ void nfcmrvl_nci_unregister_dev(struct nfcmrvl_private *priv)
struct nci_dev *ndev = priv->ndev;
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);
@@ -241,6 +249,7 @@ void nfcmrvl_chip_halt(struct nfcmrvl_private *priv)
if (priv->config.reset_gpio)
gpiod_set_value(priv->config.reset_gpio, 1);
}
+EXPORT_SYMBOL_GPL(nfcmrvl_chip_halt);
int nfcmrvl_parse_dt(struct device_node *node,
struct nfcmrvl_platform_data *pdata)
diff --git a/drivers/nfc/nfcmrvl/nfcmrvl.h b/drivers/nfc/nfcmrvl/nfcmrvl.h
index 62fa77d58..5c2a5382c 100644
--- a/drivers/nfc/nfcmrvl/nfcmrvl.h
+++ b/drivers/nfc/nfcmrvl/nfcmrvl.h
@@ -112,6 +112,8 @@ struct nfcmrvl_if_ops {
int (*nci_open) (struct nfcmrvl_private *priv);
int (*nci_close) (struct nfcmrvl_private *priv);
int (*nci_send) (struct nfcmrvl_private *priv, struct sk_buff *skb);
+ void (*nci_ready)(struct nfcmrvl_private *priv);
+ void (*nci_quiesce)(struct nfcmrvl_private *priv);
void (*nci_update_config)(struct nfcmrvl_private *priv,
const void *param);
};diff --git a/drivers/nfc/nfcmrvl/uart.c b/drivers/nfc/nfcmrvl/uart.c
index 9aedd1687..b7f99cdb4 100644
--- a/drivers/nfc/nfcmrvl/uart.c
+++ b/drivers/nfc/nfcmrvl/uart.c
@@ -44,6 +44,27 @@ static int nfcmrvl_uart_nci_send(struct nfcmrvl_private *priv,
return nu->ops.send(nu, skb);
}
+static void nfcmrvl_uart_nci_ready(struct nfcmrvl_private *priv)
+{
+ struct nci_uart *nu = priv->drv_data;
+
+ priv->support_fw_dnld = true;
+ nu->drv_data = priv;
+ nu->ndev = priv->ndev;
+ nci_uart_set_ready(nu);
+}
+
+static void nfcmrvl_uart_nci_quiesce(struct nfcmrvl_private *priv)
+{
+ struct nci_uart *nu = priv->drv_data;
+
+ nci_uart_rx_stop(nu);
+ nci_uart_tx_stop(nu);
+ nfcmrvl_chip_halt(priv);
+ nu->drv_data = NULL;
+ nu->ndev = NULL;
+}
+
static void nfcmrvl_uart_nci_update_config(struct nfcmrvl_private *priv,
const void *param)
{@@ -58,6 +79,8 @@ static const struct nfcmrvl_if_ops uart_ops = {
.nci_open = nfcmrvl_uart_nci_open,
.nci_close = nfcmrvl_uart_nci_close,
.nci_send = nfcmrvl_uart_nci_send,
+ .nci_ready = nfcmrvl_uart_nci_ready,
+ .nci_quiesce = nfcmrvl_uart_nci_quiesce,
.nci_update_config = nfcmrvl_uart_nci_update_config
};
@@ -136,11 +159,6 @@ static int nfcmrvl_nci_uart_open(struct nci_uart *nu)
if (IS_ERR(priv))
return PTR_ERR(priv);
- priv->support_fw_dnld = true;
-
- nu->drv_data = priv;
- nu->ndev = priv->ndev;
-
return 0;
}
diff --git a/include/net/nfc/nci_core.h b/include/net/nfc/nci_core.h
index 2f542f5c9..6b938c2b5 100644
--- a/include/net/nfc/nci_core.h
+++ b/include/net/nfc/nci_core.h
@@ -436,6 +436,7 @@ struct nci_uart;
struct nci_uart_ops {
int (*open)(struct nci_uart *nci_uart);
+ /* Quiesce callbacks before releasing transport-private state. */
void (*close)(struct nci_uart *nci_uart);
int (*recv)(struct nci_uart *nci_uart, struct sk_buff *skb);
int (*send)(struct nci_uart *nci_uart, struct sk_buff *skb);@@ -452,6 +453,7 @@ struct nci_uart {
/* Dynamic data */
struct nci_dev *ndev;
spinlock_t rx_lock;
+ spinlock_t tx_lock; /* protects tx_state */
struct work_struct write_work;
struct tty_struct *tty;
unsigned long flags;@@ -466,5 +468,8 @@ struct nci_uart {
int nci_uart_register(struct nci_uart *nu);
void nci_uart_unregister(struct nci_uart *nu);
void nci_uart_set_config(struct nci_uart *nu, int baudrate, int flow_ctrl);
+void nci_uart_set_ready(struct nci_uart *nu);
+void nci_uart_rx_stop(struct nci_uart *nu);
+void nci_uart_tx_stop(struct nci_uart *nu);
#endif /* __NCI_CORE_H */diff --git a/net/nfc/nci/uart.c b/net/nfc/nci/uart.c
index 529c8cf88..63671ea38 100644
--- a/net/nfc/nci/uart.c
+++ b/net/nfc/nci/uart.c
@@ -34,6 +34,7 @@
/* TX states */
#define NCI_UART_SENDING 1
#define NCI_UART_TX_WAKEUP 2
+#define NCI_UART_CLOSING 3
/* UART flags */
#define NCI_UART_DRIVER_SET 0
@@ -64,24 +65,61 @@ static inline int nci_uart_queue_empty(struct nci_uart *nu)
static int nci_uart_tx_wakeup(struct nci_uart *nu)
{
+ unsigned long flags;
+
+ spin_lock_irqsave(&nu->tx_lock, flags);
+ if (test_bit(NCI_UART_CLOSING, &nu->tx_state) ||
+ !test_bit_acquire(NCI_UART_READY, &nu->flags))
+ goto unlock;
+
if (test_and_set_bit(NCI_UART_SENDING, &nu->tx_state)) {
set_bit(NCI_UART_TX_WAKEUP, &nu->tx_state);
- return 0;
+ goto unlock;
}
schedule_work(&nu->write_work);
+unlock:
+ spin_unlock_irqrestore(&nu->tx_lock, flags);
+
return 0;
}
+void nci_uart_tx_stop(struct nci_uart *nu)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&nu->tx_lock, flags);
+ set_bit(NCI_UART_CLOSING, &nu->tx_state);
+ spin_unlock_irqrestore(&nu->tx_lock, flags);
+
+ cancel_work_sync(&nu->write_work);
+ clear_bit(TTY_DO_WRITE_WAKEUP, &nu->tty->flags);
+
+ spin_lock_irqsave(&nu->tx_lock, flags);
+ clear_bit(NCI_UART_SENDING, &nu->tx_state);
+ clear_bit(NCI_UART_TX_WAKEUP, &nu->tx_state);
+ spin_unlock_irqrestore(&nu->tx_lock, flags);
+}
+EXPORT_SYMBOL_GPL(nci_uart_tx_stop);
+
static void nci_uart_write_work(struct work_struct *work)
{
struct nci_uart *nu = container_of(work, struct nci_uart, write_work);
struct tty_struct *tty = nu->tty;
struct sk_buff *skb;
+ unsigned long flags;
restart:
+ spin_lock_irqsave(&nu->tx_lock, flags);
+ if (test_bit(NCI_UART_CLOSING, &nu->tx_state)) {
+ clear_bit(NCI_UART_SENDING, &nu->tx_state);
+ clear_bit(NCI_UART_TX_WAKEUP, &nu->tx_state);
+ spin_unlock_irqrestore(&nu->tx_lock, flags);
+ return;
+ }
clear_bit(NCI_UART_TX_WAKEUP, &nu->tx_state);
+ spin_unlock_irqrestore(&nu->tx_lock, flags);
if (nu->ops.tx_start)
nu->ops.tx_start(nu);@@ -99,14 +137,55 @@ static void nci_uart_write_work(struct work_struct *work)
kfree_skb(skb);
}
- if (test_bit(NCI_UART_TX_WAKEUP, &nu->tx_state))
+ spin_lock_irqsave(&nu->tx_lock, flags);
+ if (test_bit(NCI_UART_CLOSING, &nu->tx_state)) {
+ clear_bit(NCI_UART_SENDING, &nu->tx_state);
+ clear_bit(NCI_UART_TX_WAKEUP, &nu->tx_state);
+ spin_unlock_irqrestore(&nu->tx_lock, flags);
+ if (nu->ops.tx_done)
+ nu->ops.tx_done(nu);
+ return;
+ }
+ if (test_and_clear_bit(NCI_UART_TX_WAKEUP, &nu->tx_state)) {
+ spin_unlock_irqrestore(&nu->tx_lock, flags);
goto restart;
+ }
+ spin_unlock_irqrestore(&nu->tx_lock, flags);
if (nu->ops.tx_done && nci_uart_queue_empty(nu))
nu->ops.tx_done(nu);
+ spin_lock_irqsave(&nu->tx_lock, flags);
+ if (!test_bit(NCI_UART_CLOSING, &nu->tx_state) &&
+ test_and_clear_bit(NCI_UART_TX_WAKEUP, &nu->tx_state)) {
+ spin_unlock_irqrestore(&nu->tx_lock, flags);
+ goto restart;
+ }
clear_bit(NCI_UART_SENDING, &nu->tx_state);
+ clear_bit(NCI_UART_TX_WAKEUP, &nu->tx_state);
+ spin_unlock_irqrestore(&nu->tx_lock, flags);
+}
+
+void nci_uart_set_ready(struct nci_uart *nu)
+{
+ /* Publish transport fields before callbacks observe READY. */
+ smp_mb__before_atomic();
+ set_bit(NCI_UART_READY, &nu->flags);
+
+ if (!nci_uart_queue_empty(nu))
+ nci_uart_tx_wakeup(nu);
}
+EXPORT_SYMBOL_GPL(nci_uart_set_ready);
+
+void nci_uart_rx_stop(struct nci_uart *nu)
+{
+ spin_lock(&nu->rx_lock);
+ clear_bit(NCI_UART_READY, &nu->flags);
+ kfree_skb(nu->rx_skb);
+ nu->rx_skb = NULL;
+ spin_unlock(&nu->rx_lock);
+}
+EXPORT_SYMBOL_GPL(nci_uart_rx_stop);
static int nci_uart_set_driver(struct nci_uart *nu, unsigned int driver)
{@@ -136,21 +215,17 @@ static int nci_uart_set_driver(struct nci_uart *nu, unsigned int driver)
if (ret)
goto err_module;
- /* Publish transport fields before callbacks observe READY. */
- smp_mb__before_atomic();
- set_bit(NCI_UART_READY, &nu->flags);
+ nci_uart_set_ready(nu);
return 0;
err_module:
- cancel_work_sync(&nu->write_work);
+ nci_uart_rx_stop(nu);
+ nci_uart_tx_stop(nu);
kfree_skb(nu->tx_skb);
nu->tx_skb = NULL;
- kfree_skb(nu->rx_skb);
- nu->rx_skb = NULL;
skb_queue_purge(&nu->tx_q);
- clear_bit(NCI_UART_SENDING, &nu->tx_state);
- clear_bit(NCI_UART_TX_WAKEUP, &nu->tx_state);
+ clear_bit(NCI_UART_CLOSING, &nu->tx_state);
module_put(nu->owner);
nu->owner = NULL;
goto err_clear;
@@ -173,6 +248,7 @@ static int nci_uart_tty_alloc(struct tty_struct *tty)
nu->tty = tty;
skb_queue_head_init(&nu->tx_q);
INIT_WORK(&nu->write_work, nci_uart_write_work);
+ spin_lock_init(&nu->tx_lock);
spin_lock_init(&nu->rx_lock);
tty->disc_data = nu;
@@ -213,26 +289,25 @@ static void nci_uart_tty_close(struct tty_struct *tty)
{
struct nci_uart *nu = tty->disc_data;
- /* Detach from the tty */
- tty->disc_data = NULL;
-
if (!nu)
return;
+ tty->disc_data = NULL;
+
if (test_bit(NCI_UART_DRIVER_SET, &nu->flags)) {
- kfree_skb(nu->tx_skb);
- kfree_skb(nu->rx_skb);
+ nu->ops.close(nu);
+ nci_uart_rx_stop(nu);
+ nci_uart_tx_stop(nu);
+
+ kfree_skb(nu->tx_skb);
skb_queue_purge(&nu->tx_q);
- nu->ops.close(nu);
module_put(nu->owner);
}
nu->tty = NULL;
- cancel_work_sync(&nu->write_work);
-
kfree(nu);
}
@@ -348,6 +423,10 @@ static void nci_uart_tty_receive(struct tty_struct *tty, const u8 *data,
return;
spin_lock(&nu->rx_lock);
+ if (!test_bit(NCI_UART_READY, &nu->flags)) {
+ spin_unlock(&nu->rx_lock);
+ return;
+ }
nci_uart_default_recv_buf(nu, data, count);
spin_unlock(&nu->rx_lock);
--
2.54.0