TI Bluetooth: Adding TTY HCI driver
Raghavendra Shenoy Mathav [Tue, 22 Jan 2013 11:45:37 +0000 (16:45 +0530)]
Signed-off-by: Raghavendra Shenoy Mathav <raghavendra.shenoy@ti.com>

Bug 1179655

Change-Id: If34ce056025d52d213e70cd55d6e056bed8b3486
Signed-off-by: Nagarjuna Kristam <nkristam@nvidia.com>
Reviewed-on: http://git-master/r/196969
Reviewed-by: Rakesh Kumar <krakesh@nvidia.com>
Reviewed-by: Rakesh Goyal <rgoyal@nvidia.com>
Tested-by: Rakesh Goyal <rgoyal@nvidia.com>
Reviewed-by: Bharat Nihalani <bnihalani@nvidia.com>

drivers/misc/ti-st/Kconfig
drivers/misc/ti-st/Makefile
drivers/misc/ti-st/tty_hci.c [new file with mode: 0644]

index 8f20dda..a4da78d 100644 (file)
@@ -21,4 +21,13 @@ config ST_GPS
          This enables the GPS driver for TI WL128x BT/FM/GPS combo devices.
          It will provide a character device for the TI GPS host software to
          access the GPS core on the WL128x.
+
+config ST_HCI
+       tristate "HCI TTY emulation driver for Bluetooth"
+       depends on TI_ST
+       help
+         This enables the TTY device like emulation for HCI used by
+         user-space Bluetooth stacks.
+         It will provide a character device for user space Bluetooth stack to
+         send/receive data over shared transport.
 endmenu
index 3f6d990..2449ead 100644 (file)
@@ -5,3 +5,4 @@
 obj-$(CONFIG_TI_ST)            += st_drv.o
 st_drv-objs                    := st_core.o st_kim.o st_ll.o
 obj-$(CONFIG_ST_GPS)            += gps_drv.o
+obj-$(CONFIG_ST_HCI)           += tty_hci.o
diff --git a/drivers/misc/ti-st/tty_hci.c b/drivers/misc/ti-st/tty_hci.c
new file mode 100644 (file)
index 0000000..591b0be
--- /dev/null
@@ -0,0 +1,543 @@
+/*
+ *  TTY emulation for user-space Bluetooth stacks over HCI-H4
+ *  Copyright (C) 2011-2012 Texas Instruments
+ *  Author: Pavan Savoy <pavan_savoy@ti.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+/** define one of the following for debugging
+#define DEBUG
+#define VERBOSE
+*/
+
+#define pr_fmt(fmt) "(hci_tty): " fmt
+#include <linux/module.h>
+#include <linux/cdev.h>
+#include <linux/fs.h>
+#include <linux/device.h>
+
+#include <linux/uaccess.h>
+#include <linux/tty.h>
+#include <linux/sched.h>
+
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/skbuff.h>
+#include <linux/interrupt.h>
+
+#include <linux/ti_wilink_st.h>
+
+/* Number of seconds to wait for registration completion
+ * when ST returns PENDING status.
+ */
+#define BT_REGISTER_TIMEOUT   6000     /* 6 sec */
+
+/**
+ * struct ti_st - driver operation structure
+ * @hdev: hci device pointer which binds to bt driver
+ * @reg_status: ST registration callback status
+ * @st_write: write function provided by the ST driver
+ *     to be used by the driver during send_frame.
+ * @wait_reg_completion - completion sync between ti_st_open
+ *     and st_reg_completion_cb.
+ */
+struct ti_st {
+       struct hci_dev *hdev;
+       char reg_status;
+       long (*st_write) (struct sk_buff *);
+       struct completion wait_reg_completion;
+       wait_queue_head_t data_q;
+       struct sk_buff_head rx_list;
+};
+
+#define DEVICE_NAME     "hci_tty"
+
+/***********Functions called from ST driver**********************************/
+/* Called by Shared Transport layer when receive data is
+ * available */
+static long st_receive(void *priv_data, struct sk_buff *skb)
+{
+       struct ti_st    *hst = (void *) priv_data;
+
+       pr_debug("@ %s", __func__);
+#ifdef VERBOSE
+       print_hex_dump(KERN_INFO, ">rx>", DUMP_PREFIX_NONE,
+                       16, 1, skb->data, skb->len, 0);
+#endif
+       skb_queue_tail(&hst->rx_list, skb);
+       wake_up_interruptible(&hst->data_q);
+       return 0;
+}
+
+/* Called by ST layer to indicate protocol registration completion
+ * status.ti_st_open() function will wait for signal from this
+ * API when st_register() function returns ST_PENDING.
+ */
+static void st_reg_completion_cb(void *priv_data, char data)
+{
+       struct ti_st    *lhst = (void *) priv_data;
+
+       pr_info("@ %s\n", __func__);
+       /* Save registration status for use in ti_st_open() */
+       lhst->reg_status = data;
+       /* complete the wait in ti_st_open() */
+       complete(&lhst->wait_reg_completion);
+}
+
+/* protocol structure registered with shared transport */
+#define MAX_BT_CHNL_IDS 3
+static struct st_proto_s ti_st_proto[MAX_BT_CHNL_IDS] = {
+       {
+               .chnl_id = 0x04, /* HCI Events */
+               .hdr_len = 2,
+               .offset_len_in_hdr = 1,
+               .len_size = 1, /* sizeof(plen) in struct hci_event_hdr */
+               .reserve = 8,
+       },
+       {
+               .chnl_id = 0x02, /* ACL */
+               .hdr_len = 4,
+               .offset_len_in_hdr = 2,
+               .len_size = 2,  /* sizeof(dlen) in struct hci_acl_hdr */
+               .reserve = 8,
+       },
+       {
+               .chnl_id = 0x03, /* SCO */
+               .hdr_len = 3,
+               .offset_len_in_hdr = 2,
+               .len_size = 1, /* sizeof(dlen) in struct hci_sco_hdr */
+               .reserve = 8,
+       },
+};
+/** hci_tty_open Function
+ *  This function will perform an register on ST driver.
+ *
+ *  Parameters :
+ *  @file  : File pointer for BT char driver
+ *  @inod  :
+ *  Returns  0 -  on success
+ *           else suitable error code
+ */
+int hci_tty_open(struct inode *inod, struct file *file)
+{
+       int i = 0, err = 0;
+       unsigned long timeleft;
+       struct ti_st *hst;
+
+       pr_info("inside %s (%p, %p)\n", __func__, inod, file);
+
+       hst = kzalloc(sizeof(*hst), GFP_KERNEL);
+       file->private_data = hst;
+       hst = file->private_data;
+
+       for (i = 0; i < MAX_BT_CHNL_IDS; i++) {
+               ti_st_proto[i].priv_data = hst;
+               ti_st_proto[i].max_frame_size = 1026;
+               ti_st_proto[i].recv = st_receive;
+               ti_st_proto[i].reg_complete_cb = st_reg_completion_cb;
+
+               /* Prepare wait-for-completion handler */
+               init_completion(&hst->wait_reg_completion);
+               /* Reset ST registration callback status flag,
+                * this value will be updated in
+                * st_reg_completion_cb()
+                * function whenever it called from ST driver.
+                */
+               hst->reg_status = -EINPROGRESS;
+
+               err = st_register(&ti_st_proto[i]);
+               if (!err)
+                       goto done;
+
+               if (err != -EINPROGRESS) {
+                       pr_err("st_register failed %d", err);
+                       return err;
+               }
+
+               /* ST is busy with either protocol
+                * registration or firmware download.
+                */
+               pr_debug("waiting for registration "
+                               "completion signal from ST");
+               timeleft = wait_for_completion_timeout
+                       (&hst->wait_reg_completion,
+                        msecs_to_jiffies(BT_REGISTER_TIMEOUT));
+               if (!timeleft) {
+                       pr_err("Timeout(%d sec),didn't get reg "
+                                       "completion signal from ST",
+                                       BT_REGISTER_TIMEOUT / 1000);
+                       return -ETIMEDOUT;
+               }
+
+               /* Is ST registration callback
+                * called with ERROR status? */
+               if (hst->reg_status != 0) {
+                       pr_err("ST registration completed with invalid "
+                                       "status %d", hst->reg_status);
+                       return -EAGAIN;
+               }
+
+done:
+               hst->st_write = ti_st_proto[i].write;
+               if (!hst->st_write) {
+                       pr_err("undefined ST write function");
+                       for (i = 0; i < MAX_BT_CHNL_IDS; i++) {
+                               /* Undo registration with ST */
+                               err = st_unregister(&ti_st_proto[i]);
+                               if (err)
+                                       pr_err("st_unregister() failed with "
+                                                       "error %d", err);
+                               hst->st_write = NULL;
+                       }
+                       return -EIO;
+               }
+       }
+
+       skb_queue_head_init(&hst->rx_list);
+       init_waitqueue_head(&hst->data_q);
+
+       return 0;
+}
+
+/** hci_tty_release Function
+ *  This function will un-registers from the ST driver.
+ *
+ *  Parameters :
+ *  @file  : File pointer for BT char driver
+ *  @inod  :
+ *  Returns  0 -  on success
+ *           else suitable error code
+ */
+int hci_tty_release(struct inode *inod, struct file *file)
+{
+       int err, i;
+       struct ti_st *hst = file->private_data;
+
+       pr_info("inside %s (%p, %p)\n", __func__, inod, file);
+
+       for (i = 0; i < MAX_BT_CHNL_IDS; i++) {
+               err = st_unregister(&ti_st_proto[i]);
+               if (err)
+                       pr_err("st_unregister(%d) failed with error %d",
+                                       ti_st_proto[i].chnl_id, err);
+       }
+
+       hst->st_write = NULL;
+       skb_queue_purge(&hst->rx_list);
+       kfree(hst);
+       return err;
+}
+
+/** hci_tty_read Function
+ *
+ *  Parameters :
+ *  @file  : File pointer for BT char driver
+ *  @data  : Data which needs to be passed to APP
+ *  @size  : Length of the data passesd
+ *  offset :
+ *  Returns  Size of packet received -  on success
+ *           else suitable error code
+ */
+ssize_t hci_tty_read(struct file *file, char __user *data, size_t size,
+               loff_t *offset)
+{
+       int len = 0, tout;
+       struct sk_buff *skb = NULL, *rskb = NULL;
+       struct ti_st    *hst;
+
+       pr_debug("inside %s (%p, %p, %u, %p)\n",
+                       __func__, file, data, size, offset);
+
+       /* Validate input parameters */
+       if ((NULL == file) || (((NULL == data) || (0 == size)))) {
+               pr_err("Invalid input params passed to %s", __func__);
+               return -EINVAL;
+       }
+
+       hst = file->private_data;
+
+       /* cannot come here if poll-ed before reading
+        * if not poll-ed wait on the same wait_q
+        */
+       tout = wait_event_interruptible_timeout(hst->data_q,
+                       !skb_queue_empty(&hst->rx_list),
+                               msecs_to_jiffies(1000));
+       /* Check for timed out condition */
+       if (0 == tout) {
+               pr_err("Device Read timed out\n");
+               return -ETIMEDOUT;
+       }
+
+       /* hst->rx_list not empty skb already present */
+       skb = skb_dequeue(&hst->rx_list);
+       if (!skb) {
+               pr_err("dequed skb is null?\n");
+               return -EIO;
+       }
+
+#ifdef VERBOSE
+       print_hex_dump(KERN_INFO, ">in>", DUMP_PREFIX_NONE,
+                       16, 1, skb->data, skb->len, 0);
+#endif
+
+       /* Forward the data to the user */
+       if (skb->len >= size) {
+               pr_err("FIONREAD not done before read\n");
+               return -ENOMEM;
+       } else {
+               /* returning skb */
+               rskb = alloc_skb(size, GFP_KERNEL);
+               if (!rskb) {
+                       pr_err("alloc_skb error\n");
+                       return -ENOMEM;
+               }
+
+               /* cb[0] has the pkt_type 0x04 or 0x02 or 0x03 */
+               memcpy(skb_put(rskb, 1), &skb->cb[0], 1);
+               memcpy(skb_put(rskb, skb->len), skb->data, skb->len);
+
+               if (copy_to_user(data, rskb->data, rskb->len)) {
+                       pr_err("unable to copy to user space\n");
+                       /* Queue the skb back to head */
+                       skb_queue_head(&hst->rx_list, skb);
+                       kfree_skb(rskb);
+                       return -EIO;
+               }
+       }
+
+       len = rskb->len;        /* len of returning skb */
+       kfree_skb(skb);
+       kfree_skb(rskb);
+       pr_debug("total size read= %d\n", len);
+       return len;
+}
+
+/* hci_tty_write Function
+ *
+ *  Parameters :
+ *  @file   : File pointer for BT char driver
+ *  @data   : packet data from BT application
+ *  @size   : Size of the packet data
+ *  @offset :
+ *  Returns  Size of packet on success
+ *           else suitable error code
+ */
+ssize_t hci_tty_write(struct file *file, const char __user *data,
+               size_t size, loff_t *offset)
+{
+       struct ti_st *hst = file->private_data;
+       struct  sk_buff *skb;
+
+       pr_debug("inside %s (%p, %p, %u, %p)\n",
+                       __func__, file, data, size, offset);
+
+       if (!hst->st_write) {
+               pr_err(" Can't write to ST, hhci_tty->st_write null ?");
+               return -EINVAL;
+       }
+
+       skb = alloc_skb(size, GFP_KERNEL);
+       /* Validate Created SKB */
+       if (NULL == skb) {
+               pr_err("Error aaloacting SKB");
+               return -ENOMEM;
+       }
+
+       /* Forward the data from the user space to ST core */
+       if (copy_from_user(skb_put(skb, size), data, size)) {
+               pr_err(" Unable to copy from user space");
+               kfree_skb(skb);
+               return -EIO;
+       }
+
+#ifdef VERBOSE
+       pr_debug("start data..");
+       print_hex_dump(KERN_INFO, "<out<", DUMP_PREFIX_NONE,
+                       16, 1, skb->data, size, 0);
+       pr_debug("\n..end data");
+#endif
+
+       hst->st_write(skb);
+       return size;
+}
+
+/** hci_tty_ioctl Function
+ *  This will peform the functions as directed by the command and command
+ *  argument.
+ *
+ *  Parameters :
+ *  @file  : File pointer for BT char driver
+ *  @cmd   : IOCTL Command
+ *  @arg   : Command argument for IOCTL command
+ *  Returns  0 on success
+ *           else suitable error code
+ */
+static long hci_tty_ioctl(struct file *file,
+               unsigned int cmd, unsigned long arg)
+{
+       struct sk_buff *skb = NULL;
+       int             retCode = 0;
+       struct ti_st    *hst;
+
+       pr_debug("inside %s (%p, %u, %lx)", __func__, file, cmd, arg);
+
+       /* Validate input parameters */
+       if ((NULL == file) || (0 == cmd)) {
+               pr_err("invalid input parameters passed to %s", __func__);
+               return -EINVAL;
+       }
+
+       hst = file->private_data;
+
+       switch (cmd) {
+       case FIONREAD:
+               /* Deque the SKB from the head if rx_list is not empty
+                * update the argument with skb->len to provide amount of data
+                * available in the available SKB +1 for the PKT_TYPE
+                * field not provided in data by TI-ST.
+                */
+               skb = skb_dequeue(&hst->rx_list);
+               if (skb != NULL) {
+                       *(unsigned int *)arg = skb->len + 1;
+                       /* Re-Store the SKB for furtur Read operations */
+                       skb_queue_head(&hst->rx_list, skb);
+               } else {
+                       *(unsigned int *)arg = 0;
+               }
+               pr_debug("returning %d\n", *(unsigned int *)arg);
+               break;
+       default:
+               pr_debug("Un-Identified IOCTL %d", cmd);
+               retCode = 0;
+               break;
+       }
+
+       return retCode;
+}
+
+/** hci_tty_poll Function
+ *  This function will wait till some data is received to the hci_tty driver from ST
+ *
+ *  Parameters :
+ *  @file  : File pointer for BT char driver
+ *  @wait  : POLL wait information
+ *  Returns  status of POLL on success
+ *           else suitable error code
+ */
+static unsigned int hci_tty_poll(struct file *file, poll_table *wait)
+{
+       struct ti_st    *hst = file->private_data;
+       unsigned long mask = 0;
+
+       pr_debug("@ %s\n", __func__);
+
+       /* wait to be completed by st_receive */
+       poll_wait(file, &hst->data_q, wait);
+       pr_debug("poll broke\n");
+
+       if (!skb_queue_empty(&hst->rx_list)) {
+               pr_debug("rx list que !empty\n");
+               mask |= POLLIN; /* TODO: check app for mask */
+       }
+
+       return mask;
+}
+
+/* BT Char driver function pointers
+ * These functions are called from USER space by pefroming File Operations
+ * on /dev/hci_tty node exposed by this driver during init
+ */
+const struct file_operations hci_tty_chrdev_ops = {
+       .owner = THIS_MODULE,
+       .open = hci_tty_open,
+       .read = hci_tty_read,
+       .write = hci_tty_write,
+       .unlocked_ioctl = hci_tty_ioctl,
+       .poll = hci_tty_poll,
+       .release = hci_tty_release,
+};
+
+/*********Functions called during insmod and delmod****************************/
+
+static int hci_tty_major;              /* major number */
+static struct class *hci_tty_class;    /* class during class_create */
+static struct device *hci_tty_dev;     /* dev during device_create */
+/** hci_tty_init Function
+ *  This function Initializes the hci_tty driver parametes and exposes
+ *  /dev/hci_tty node to user space
+ *
+ *  Parameters : NULL
+ *  Returns  0 on success
+ *           else suitable error code
+ */
+static int __init hci_tty_init(void)
+{
+       pr_info("inside %s\n", __func__);
+
+       /* Expose the device DEVICE_NAME to user space
+        * And obtain the major number for the device
+        */
+       hci_tty_major = register_chrdev(0, DEVICE_NAME, \
+                       &hci_tty_chrdev_ops);
+       if (0 > hci_tty_major) {
+               pr_err("Error when registering to char dev");
+               return hci_tty_major;
+       }
+
+       /*  udev */
+       hci_tty_class = class_create(THIS_MODULE, DEVICE_NAME);
+       if (IS_ERR(hci_tty_class)) {
+               pr_err("Something went wrong in class_create");
+               unregister_chrdev(hci_tty_major, DEVICE_NAME);
+               return -1;
+       }
+
+       hci_tty_dev =
+               device_create(hci_tty_class, NULL, MKDEV(hci_tty_major, 0),
+                               NULL, DEVICE_NAME);
+       if (IS_ERR(hci_tty_dev)) {
+               pr_err("Error in device create");
+               unregister_chrdev(hci_tty_major, DEVICE_NAME);
+               class_destroy(hci_tty_class);
+               return -1;
+       }
+       pr_info("allocated %d, %d\n", hci_tty_major, 0);
+       return 0;
+}
+
+/** hci_tty_exit Function
+ *  This function Destroys the hci_tty driver parametes and /dev/hci_tty node
+ *
+ *  Parameters : NULL
+ *  Returns   NULL
+ */
+static void __exit hci_tty_exit(void)
+{
+       pr_info("inside %s\n", __func__);
+       pr_info("bye.. freeing up %d\n", hci_tty_major);
+
+       device_destroy(hci_tty_class, MKDEV(hci_tty_major, 0));
+       class_destroy(hci_tty_class);
+       unregister_chrdev(hci_tty_major, DEVICE_NAME);
+}
+
+module_init(hci_tty_init);
+module_exit(hci_tty_exit);
+
+MODULE_AUTHOR("Pavan Savoy <pavan_savoy@ti.com>");
+MODULE_LICENSE("GPL");