Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751135AbbD3Hs0 (ORCPT ); Thu, 30 Apr 2015 03:48:26 -0400 Received: from mail-wi0-f176.google.com ([209.85.212.176]:34849 "EHLO mail-wi0-f176.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1750766AbbD3HsX (ORCPT ); Thu, 30 Apr 2015 03:48:23 -0400 From: Eyal Reizer X-Google-Original-From: Eyal Reizer To: linux-kernel@vger.kernel.org Cc: Eyal Reizer , Pavan Savoy , Vishal Mahaveer Subject: [PATCH] Bluetooth: Add tty HCI driver Date: Thu, 30 Apr 2015 10:48:09 +0300 Message-Id: <1430380089-2395-1-git-send-email-eyalr@ti.com> X-Mailer: git-send-email 1.7.9.5 Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 16717 Lines: 598 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. Signed-off-by: Pavan Savoy [Fixed checkpatch warnings] Signed-off-by: Vishal Mahaveer [Fixed checkpatch --strict warnings] Signed-off-by: Eyal Reizer --- drivers/misc/ti-st/Kconfig | 8 + drivers/misc/ti-st/Makefile | 1 + drivers/misc/ti-st/tty_hci.c | 538 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 547 insertions(+) create mode 100644 drivers/misc/ti-st/tty_hci.c diff --git a/drivers/misc/ti-st/Kconfig b/drivers/misc/ti-st/Kconfig index f34dcc5..f2df2c7 100644 --- a/drivers/misc/ti-st/Kconfig +++ b/drivers/misc/ti-st/Kconfig @@ -14,4 +14,12 @@ config TI_ST 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 diff --git a/drivers/misc/ti-st/Makefile b/drivers/misc/ti-st/Makefile index 78d7ebb..4546219 100644 --- a/drivers/misc/ti-st/Makefile +++ b/drivers/misc/ti-st/Makefile @@ -4,3 +4,4 @@ # 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 diff --git a/drivers/misc/ti-st/tty_hci.c b/drivers/misc/ti-st/tty_hci.c new file mode 100644 index 0000000..7a6669f --- /dev/null +++ b/drivers/misc/ti-st/tty_hci.c @@ -0,0 +1,538 @@ +/* + * TTY emulation for user-space Bluetooth stacks over HCI-H4 + * Copyright (C) 2011-2012 Texas Instruments + * Author: Pavan Savoy + * + * 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. + */ + +/** define one of the following for debugging +#define DEBUG +#define VERBOSE +*/ + +#define pr_fmt(fmt) "(hci_tty): " fmt +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +/* 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); + goto error; + } + + /* 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); + err = -ETIMEDOUT; + goto error; + } + + /* 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); + err = -EAGAIN; + goto error; + } + +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; + +error: + kfree(hst); + return err; +} + +/** 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; + } + /* returning skb */ + rskb = alloc_skb(size, GFP_KERNEL); + if (!rskb) + 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) + 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, "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) { + *(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 parameters 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 parameters 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 "); +MODULE_LICENSE("GPL"); -- 1.7.9.5 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/