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