diff --git a/include/net/nfc/nci_core.h b/include/net/nfc/nci_core.h
index 664d5058e..2f542f5c9 100644
--- a/include/net/nfc/nci_core.h
+++ b/include/net/nfc/nci_core.h
@@ -454,6 +454,7 @@ struct nci_uart {
spinlock_t rx_lock;
struct work_struct write_work;
struct tty_struct *tty;
+ unsigned long flags;
unsigned long tx_state;
struct sk_buff_head tx_q;
struct sk_buff *tx_skb;diff --git a/net/nfc/nci/uart.c b/net/nfc/nci/uart.c
index aa20e8603..529c8cf88 100644
--- a/net/nfc/nci/uart.c
+++ b/net/nfc/nci/uart.c
@@ -13,6 +13,7 @@
#include <linux/kernel.h>
#include <linux/init.h>
+#include <linux/mutex.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/interrupt.h>
@@ -34,7 +35,12 @@
#define NCI_UART_SENDING 1
#define NCI_UART_TX_WAKEUP 2
+/* UART flags */
+#define NCI_UART_DRIVER_SET 0
+#define NCI_UART_READY 1
+
static struct nci_uart *nci_uart_drivers[NCI_UART_DRIVER_MAX];
+static DEFINE_MUTEX(nci_uart_drivers_lock);
static inline struct sk_buff *nci_uart_dequeue(struct nci_uart *nu)
{@@ -102,43 +108,82 @@ static void nci_uart_write_work(struct work_struct *work)
clear_bit(NCI_UART_SENDING, &nu->tx_state);
}
-static int nci_uart_set_driver(struct tty_struct *tty, unsigned int driver)
+static int nci_uart_set_driver(struct nci_uart *nu, unsigned int driver)
{
- struct nci_uart *nu = NULL;
+ struct nci_uart *template;
int ret;
if (driver >= NCI_UART_DRIVER_MAX)
return -EINVAL;
- if (!nci_uart_drivers[driver])
- return -ENOENT;
+ if (test_and_set_bit_lock(NCI_UART_DRIVER_SET, &nu->flags))
+ return -EBUSY;
+
+ mutex_lock(&nci_uart_drivers_lock);
+ template = nci_uart_drivers[driver];
+ if (!template || !try_module_get(template->owner)) {
+ ret = -ENOENT;
+ goto err_unlock;
+ }
+
+ nu->owner = template->owner;
+ nu->ops = template->ops;
+ nu->name = template->name;
+ nu->driver = template->driver;
+ mutex_unlock(&nci_uart_drivers_lock);
+
+ ret = nu->ops.open(nu);
+ if (ret)
+ goto err_module;
+
+ /* Publish transport fields before callbacks observe READY. */
+ smp_mb__before_atomic();
+ set_bit(NCI_UART_READY, &nu->flags);
+
+ return 0;
+
+err_module:
+ cancel_work_sync(&nu->write_work);
+ 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);
+ module_put(nu->owner);
+ nu->owner = NULL;
+ goto err_clear;
+err_unlock:
+ mutex_unlock(&nci_uart_drivers_lock);
+err_clear:
+ clear_bit_unlock(NCI_UART_DRIVER_SET, &nu->flags);
+ return ret;
+}
+
+/* ------ LDISC part ------ */
+
+static int nci_uart_tty_alloc(struct tty_struct *tty)
+{
+ struct nci_uart *nu = kzalloc_obj(*nu);
- nu = kzalloc_obj(*nu);
if (!nu)
return -ENOMEM;
- memcpy(nu, nci_uart_drivers[driver], sizeof(struct nci_uart));
nu->tty = tty;
skb_queue_head_init(&nu->tx_q);
INIT_WORK(&nu->write_work, nci_uart_write_work);
spin_lock_init(&nu->rx_lock);
- ret = nu->ops.open(nu);
- if (ret) {
- kfree(nu);
- return ret;
- } else if (!try_module_get(nu->owner)) {
- nu->ops.close(nu);
- kfree(nu);
- return -ENOENT;
- }
tty->disc_data = nu;
+ tty->receive_room = 65536;
+
+ /* Flush any pending characters in the driver */
+ tty_driver_flush_buffer(tty);
return 0;
}
-/* ------ LDISC part ------ */
-
/* nci_uart_tty_open
*
* Called when line discipline changed to NCI_UART.@@ -156,13 +201,7 @@ static int nci_uart_tty_open(struct tty_struct *tty)
if (!tty->ops->write)
return -EOPNOTSUPP;
- tty->disc_data = NULL;
- tty->receive_room = 65536;
-
- /* Flush any pending characters in the driver */
- tty_driver_flush_buffer(tty);
-
- return 0;
+ return nci_uart_tty_alloc(tty);
}
/* nci_uart_tty_close()
@@ -180,14 +219,17 @@ static void nci_uart_tty_close(struct tty_struct *tty)
if (!nu)
return;
- kfree_skb(nu->tx_skb);
- kfree_skb(nu->rx_skb);
+ if (test_bit(NCI_UART_DRIVER_SET, &nu->flags)) {
+ kfree_skb(nu->tx_skb);
+ kfree_skb(nu->rx_skb);
- skb_queue_purge(&nu->tx_q);
+ skb_queue_purge(&nu->tx_q);
+
+ nu->ops.close(nu);
+ module_put(nu->owner);
+ }
- nu->ops.close(nu);
nu->tty = NULL;
- module_put(nu->owner);
cancel_work_sync(&nu->write_work);
@@ -211,7 +253,8 @@ static void nci_uart_tty_wakeup(struct tty_struct *tty)
clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
- if (tty != nu->tty)
+ if (tty != nu->tty ||
+ !test_bit_acquire(NCI_UART_READY, &nu->flags))
return;
nci_uart_tx_wakeup(nu);
@@ -300,7 +343,8 @@ static void nci_uart_tty_receive(struct tty_struct *tty, const u8 *data,
{
struct nci_uart *nu = tty->disc_data;
- if (!nu || tty != nu->tty)
+ if (!nu || tty != nu->tty ||
+ !test_bit_acquire(NCI_UART_READY, &nu->flags))
return;
spin_lock(&nu->rx_lock);@@ -328,13 +372,12 @@ static int nci_uart_tty_ioctl(struct tty_struct *tty, unsigned int cmd,
struct nci_uart *nu = tty->disc_data;
int err = 0;
+ if (!nu)
+ return -EBADF;
+
switch (cmd) {
case NCIUARTSETDRIVER:
- if (!nu)
- return nci_uart_set_driver(tty, (unsigned int)arg);
- else
- return -EBUSY;
- break;
+ return nci_uart_set_driver(nu, (unsigned int)arg);
default:
err = n_tty_ioctl_helper(tty, cmd, arg);
break;@@ -370,33 +413,46 @@ static int nci_uart_send(struct nci_uart *nu, struct sk_buff *skb)
int nci_uart_register(struct nci_uart *nu)
{
+ int ret = 0;
+
if (!nu || !nu->ops.open ||
- !nu->ops.recv || !nu->ops.close)
+ !nu->ops.recv || !nu->ops.close ||
+ (unsigned int)nu->driver >= NCI_UART_DRIVER_MAX)
return -EINVAL;
- /* Set the send callback */
- nu->ops.send = nci_uart_send;
-
/* Add this driver in the driver list */
+ mutex_lock(&nci_uart_drivers_lock);
if (nci_uart_drivers[nu->driver]) {
pr_err("driver %d is already registered\n", nu->driver);
- return -EBUSY;
+ ret = -EBUSY;
+ goto unlock;
}
+
+ /* Set the send callback */
+ nu->ops.send = nci_uart_send;
nci_uart_drivers[nu->driver] = nu;
pr_info("NCI uart driver '%s [%d]' registered\n", nu->name, nu->driver);
- return 0;
+unlock:
+ mutex_unlock(&nci_uart_drivers_lock);
+ return ret;
}
EXPORT_SYMBOL_GPL(nci_uart_register);
void nci_uart_unregister(struct nci_uart *nu)
{
+ mutex_lock(&nci_uart_drivers_lock);
+
pr_info("NCI uart driver '%s [%d]' unregistered\n", nu->name,
nu->driver);
/* Remove this driver from the driver list */
- nci_uart_drivers[nu->driver] = NULL;
+ if ((unsigned int)nu->driver < NCI_UART_DRIVER_MAX &&
+ nci_uart_drivers[nu->driver] == nu)
+ nci_uart_drivers[nu->driver] = NULL;
+
+ mutex_unlock(&nci_uart_drivers_lock);
}
EXPORT_SYMBOL_GPL(nci_uart_unregister);
--
2.54.0