123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488 |
- /*
- * Copyright (C) 2015, Marvell International Ltd.
- *
- * This software file (the "File") is distributed by Marvell International
- * Ltd. under the terms of the GNU General Public License Version 2, June 1991
- * (the "License"). You may use, redistribute and/or modify this File in
- * accordance with the terms and conditions of the License, a copy of which
- * is available on the worldwide web at
- * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
- *
- * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
- * ARE EXPRESSLY DISCLAIMED. The License provides additional details about
- * this warranty disclaimer.
- */
- /* Inspired (hugely) by HCI LDISC implementation in Bluetooth.
- *
- * Copyright (C) 2000-2001 Qualcomm Incorporated
- * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com>
- * Copyright (C) 2004-2005 Marcel Holtmann <marcel@holtmann.org>
- */
- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/init.h>
- #include <linux/types.h>
- #include <linux/fcntl.h>
- #include <linux/interrupt.h>
- #include <linux/ptrace.h>
- #include <linux/poll.h>
- #include <linux/slab.h>
- #include <linux/tty.h>
- #include <linux/errno.h>
- #include <linux/string.h>
- #include <linux/signal.h>
- #include <linux/ioctl.h>
- #include <linux/skbuff.h>
- #include <net/nfc/nci.h>
- #include <net/nfc/nci_core.h>
- /* TX states */
- #define NCI_UART_SENDING 1
- #define NCI_UART_TX_WAKEUP 2
- static struct nci_uart *nci_uart_drivers[NCI_UART_DRIVER_MAX];
- static inline struct sk_buff *nci_uart_dequeue(struct nci_uart *nu)
- {
- struct sk_buff *skb = nu->tx_skb;
- if (!skb)
- skb = skb_dequeue(&nu->tx_q);
- else
- nu->tx_skb = NULL;
- return skb;
- }
- static inline int nci_uart_queue_empty(struct nci_uart *nu)
- {
- if (nu->tx_skb)
- return 0;
- return skb_queue_empty(&nu->tx_q);
- }
- static int nci_uart_tx_wakeup(struct nci_uart *nu)
- {
- if (test_and_set_bit(NCI_UART_SENDING, &nu->tx_state)) {
- set_bit(NCI_UART_TX_WAKEUP, &nu->tx_state);
- return 0;
- }
- schedule_work(&nu->write_work);
- return 0;
- }
- 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;
- restart:
- clear_bit(NCI_UART_TX_WAKEUP, &nu->tx_state);
- if (nu->ops.tx_start)
- nu->ops.tx_start(nu);
- while ((skb = nci_uart_dequeue(nu))) {
- int len;
- set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
- len = tty->ops->write(tty, skb->data, skb->len);
- skb_pull(skb, len);
- if (skb->len) {
- nu->tx_skb = skb;
- break;
- }
- kfree_skb(skb);
- }
- if (test_bit(NCI_UART_TX_WAKEUP, &nu->tx_state))
- goto restart;
- if (nu->ops.tx_done && nci_uart_queue_empty(nu))
- nu->ops.tx_done(nu);
- clear_bit(NCI_UART_SENDING, &nu->tx_state);
- }
- static int nci_uart_set_driver(struct tty_struct *tty, unsigned int driver)
- {
- struct nci_uart *nu = NULL;
- int ret;
- if (driver >= NCI_UART_DRIVER_MAX)
- return -EINVAL;
- if (!nci_uart_drivers[driver])
- return -ENOENT;
- nu = kzalloc(sizeof(*nu), GFP_KERNEL);
- if (!nu)
- return -ENOMEM;
- memcpy(nu, nci_uart_drivers[driver], sizeof(struct nci_uart));
- nu->tty = tty;
- tty->disc_data = nu;
- 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) {
- tty->disc_data = NULL;
- kfree(nu);
- } else if (!try_module_get(nu->owner)) {
- nu->ops.close(nu);
- tty->disc_data = NULL;
- kfree(nu);
- return -ENOENT;
- }
- return ret;
- }
- /* ------ LDISC part ------ */
- /* nci_uart_tty_open
- *
- * Called when line discipline changed to NCI_UART.
- *
- * Arguments:
- * tty pointer to tty info structure
- * Return Value:
- * 0 if success, otherwise error code
- */
- static int nci_uart_tty_open(struct tty_struct *tty)
- {
- /* Error if the tty has no write op instead of leaving an exploitable
- * hole
- */
- 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;
- }
- /* nci_uart_tty_close()
- *
- * Called when the line discipline is changed to something
- * else, the tty is closed, or the tty detects a hangup.
- */
- static void nci_uart_tty_close(struct tty_struct *tty)
- {
- struct nci_uart *nu = (void *)tty->disc_data;
- /* Detach from the tty */
- tty->disc_data = NULL;
- if (!nu)
- return;
- if (nu->tx_skb)
- kfree_skb(nu->tx_skb);
- if (nu->rx_skb)
- kfree_skb(nu->rx_skb);
- skb_queue_purge(&nu->tx_q);
- nu->ops.close(nu);
- nu->tty = NULL;
- module_put(nu->owner);
- cancel_work_sync(&nu->write_work);
- kfree(nu);
- }
- /* nci_uart_tty_wakeup()
- *
- * Callback for transmit wakeup. Called when low level
- * device driver can accept more send data.
- *
- * Arguments: tty pointer to associated tty instance data
- * Return Value: None
- */
- static void nci_uart_tty_wakeup(struct tty_struct *tty)
- {
- struct nci_uart *nu = (void *)tty->disc_data;
- if (!nu)
- return;
- clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
- if (tty != nu->tty)
- return;
- nci_uart_tx_wakeup(nu);
- }
- /* nci_uart_tty_receive()
- *
- * Called by tty low level driver when receive data is
- * available.
- *
- * Arguments: tty pointer to tty isntance data
- * data pointer to received data
- * flags pointer to flags for data
- * count count of received data in bytes
- *
- * Return Value: None
- */
- static void nci_uart_tty_receive(struct tty_struct *tty, const u8 *data,
- char *flags, int count)
- {
- struct nci_uart *nu = (void *)tty->disc_data;
- if (!nu || tty != nu->tty)
- return;
- spin_lock(&nu->rx_lock);
- nu->ops.recv_buf(nu, (void *)data, flags, count);
- spin_unlock(&nu->rx_lock);
- tty_unthrottle(tty);
- }
- /* nci_uart_tty_ioctl()
- *
- * Process IOCTL system call for the tty device.
- *
- * Arguments:
- *
- * tty pointer to tty instance data
- * file pointer to open file object for device
- * cmd IOCTL command code
- * arg argument for IOCTL call (cmd dependent)
- *
- * Return Value: Command dependent
- */
- static int nci_uart_tty_ioctl(struct tty_struct *tty, struct file *file,
- unsigned int cmd, unsigned long arg)
- {
- struct nci_uart *nu = (void *)tty->disc_data;
- int err = 0;
- switch (cmd) {
- case NCIUARTSETDRIVER:
- if (!nu)
- return nci_uart_set_driver(tty, (unsigned int)arg);
- else
- return -EBUSY;
- break;
- default:
- err = n_tty_ioctl_helper(tty, file, cmd, arg);
- break;
- }
- return err;
- }
- /* We don't provide read/write/poll interface for user space. */
- static ssize_t nci_uart_tty_read(struct tty_struct *tty, struct file *file,
- unsigned char __user *buf, size_t nr)
- {
- return 0;
- }
- static ssize_t nci_uart_tty_write(struct tty_struct *tty, struct file *file,
- const unsigned char *data, size_t count)
- {
- return 0;
- }
- static unsigned int nci_uart_tty_poll(struct tty_struct *tty,
- struct file *filp, poll_table *wait)
- {
- return 0;
- }
- static int nci_uart_send(struct nci_uart *nu, struct sk_buff *skb)
- {
- /* Queue TX packet */
- skb_queue_tail(&nu->tx_q, skb);
- /* Try to start TX (if possible) */
- nci_uart_tx_wakeup(nu);
- return 0;
- }
- /* -- Default recv_buf handler --
- *
- * This handler supposes that NCI frames are sent over UART link without any
- * framing. It reads NCI header, retrieve the packet size and once all packet
- * bytes are received it passes it to nci_uart driver for processing.
- */
- static int nci_uart_default_recv_buf(struct nci_uart *nu, const u8 *data,
- char *flags, int count)
- {
- int chunk_len;
- if (!nu->ndev) {
- nfc_err(nu->tty->dev,
- "receive data from tty but no NCI dev is attached yet, drop buffer\n");
- return 0;
- }
- /* Decode all incoming data in packets
- * and enqueue then for processing.
- */
- while (count > 0) {
- /* If this is the first data of a packet, allocate a buffer */
- if (!nu->rx_skb) {
- nu->rx_packet_len = -1;
- nu->rx_skb = nci_skb_alloc(nu->ndev,
- NCI_MAX_PACKET_SIZE,
- GFP_KERNEL);
- if (!nu->rx_skb)
- return -ENOMEM;
- }
- /* Eat byte after byte till full packet header is received */
- if (nu->rx_skb->len < NCI_CTRL_HDR_SIZE) {
- *skb_put(nu->rx_skb, 1) = *data++;
- --count;
- continue;
- }
- /* Header was received but packet len was not read */
- if (nu->rx_packet_len < 0)
- nu->rx_packet_len = NCI_CTRL_HDR_SIZE +
- nci_plen(nu->rx_skb->data);
- /* Compute how many bytes are missing and how many bytes can
- * be consumed.
- */
- chunk_len = nu->rx_packet_len - nu->rx_skb->len;
- if (count < chunk_len)
- chunk_len = count;
- memcpy(skb_put(nu->rx_skb, chunk_len), data, chunk_len);
- data += chunk_len;
- count -= chunk_len;
- /* Chcek if packet is fully received */
- if (nu->rx_packet_len == nu->rx_skb->len) {
- /* Pass RX packet to driver */
- if (nu->ops.recv(nu, nu->rx_skb) != 0)
- nfc_err(nu->tty->dev, "corrupted RX packet\n");
- /* Next packet will be a new one */
- nu->rx_skb = NULL;
- }
- }
- return 0;
- }
- /* -- Default recv handler -- */
- static int nci_uart_default_recv(struct nci_uart *nu, struct sk_buff *skb)
- {
- return nci_recv_frame(nu->ndev, skb);
- }
- int nci_uart_register(struct nci_uart *nu)
- {
- if (!nu || !nu->ops.open ||
- !nu->ops.recv || !nu->ops.close)
- return -EINVAL;
- /* Set the send callback */
- nu->ops.send = nci_uart_send;
- /* Install default handlers if not overridden */
- if (!nu->ops.recv_buf)
- nu->ops.recv_buf = nci_uart_default_recv_buf;
- if (!nu->ops.recv)
- nu->ops.recv = nci_uart_default_recv;
- /* Add this driver in the driver list */
- if (nci_uart_drivers[nu->driver]) {
- pr_err("driver %d is already registered\n", nu->driver);
- return -EBUSY;
- }
- nci_uart_drivers[nu->driver] = nu;
- pr_info("NCI uart driver '%s [%d]' registered\n", nu->name, nu->driver);
- return 0;
- }
- EXPORT_SYMBOL_GPL(nci_uart_register);
- void nci_uart_unregister(struct nci_uart *nu)
- {
- 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;
- }
- EXPORT_SYMBOL_GPL(nci_uart_unregister);
- void nci_uart_set_config(struct nci_uart *nu, int baudrate, int flow_ctrl)
- {
- struct ktermios new_termios;
- if (!nu->tty)
- return;
- down_read(&nu->tty->termios_rwsem);
- new_termios = nu->tty->termios;
- up_read(&nu->tty->termios_rwsem);
- tty_termios_encode_baud_rate(&new_termios, baudrate, baudrate);
- if (flow_ctrl)
- new_termios.c_cflag |= CRTSCTS;
- else
- new_termios.c_cflag &= ~CRTSCTS;
- tty_set_termios(nu->tty, &new_termios);
- }
- EXPORT_SYMBOL_GPL(nci_uart_set_config);
- static struct tty_ldisc_ops nci_uart_ldisc = {
- .magic = TTY_LDISC_MAGIC,
- .owner = THIS_MODULE,
- .name = "n_nci",
- .open = nci_uart_tty_open,
- .close = nci_uart_tty_close,
- .read = nci_uart_tty_read,
- .write = nci_uart_tty_write,
- .poll = nci_uart_tty_poll,
- .receive_buf = nci_uart_tty_receive,
- .write_wakeup = nci_uart_tty_wakeup,
- .ioctl = nci_uart_tty_ioctl,
- };
- static int __init nci_uart_init(void)
- {
- memset(nci_uart_drivers, 0, sizeof(nci_uart_drivers));
- return tty_register_ldisc(N_NCI, &nci_uart_ldisc);
- }
- static void __exit nci_uart_exit(void)
- {
- tty_unregister_ldisc(N_NCI);
- }
- module_init(nci_uart_init);
- module_exit(nci_uart_exit);
- MODULE_AUTHOR("Marvell International Ltd.");
- MODULE_DESCRIPTION("NFC NCI UART driver");
- MODULE_LICENSE("GPL");
- MODULE_ALIAS_LDISC(N_NCI);
|