Commits

Anonymous committed 582ab23

Bluetooth: Add tty HCI driver

tty_hci driver exposes a /dev/hci_tty character device node, that intends to
emulate a generic /dev/ttyX device that would be used by the user-space
Bluetooth stacks to send/receive data to/from the WL combo-connectivity
chipsets.

The device driver has no internal logic of its own to intrepret data & all
such logic is handled by the user-space stack.

Change-Id: Ifa3860bbc7e252af210fde710bce14143239b552
Signed-off-by: Pavan Savoy <pavan_savoy@ti.com>

Comments (0)

Files changed (3)

drivers/misc/ti-st/Kconfig

 	  are returned to relevant protocol drivers based on their
 	  packet types.
 
+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

drivers/misc/ti-st/Makefile

 #
 obj-$(CONFIG_TI_ST) 		+= st_drv.o
 st_drv-objs			:= st_core.o st_kim.o st_ll.o
+obj-$(CONFIG_ST_HCI)		+= tty_hci.o

drivers/misc/ti-st/tty_hci.c

+/*
+ *  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");