/* * 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. * * 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 #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\n", __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); if (!hst) return -ENOMEM; file->private_data = hst; skb_queue_head_init(&hst->rx_list); init_waitqueue_head(&hst->data_q); 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) { hst->st_write = ti_st_proto[i].write; if (!hst->st_write) { pr_err("undefined ST write function\n"); err = -EIO; goto unreg; } continue; } if (err != -EINPROGRESS) { pr_err("st_register failed %d\n", err); /* this channel is not registered - don't unregister */ --i; goto unreg; } /* ST is busy with either protocol * registration or firmware download. */ pr_debug("waiting for registration " "completion signal from ST\n"); 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\n", BT_REGISTER_TIMEOUT / 1000); err = -ETIMEDOUT; goto unreg; } /* Is ST registration callback * called with ERROR status? */ if (hst->reg_status != 0) { pr_err("ST registration completed with invalid " "status %d\n", hst->reg_status); err = -EAGAIN; goto unreg; } } return 0; unreg: while (i-- >= 0) /* Undo registration with ST */ if (st_unregister(&ti_st_proto[i])) pr_err("st_unregister() failed\n"); 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\n", 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\n", __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"); skb_queue_head(&hst->rx_list, skb); 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 ?\n"); return -EINVAL; } skb = alloc_skb(size, GFP_KERNEL); /* Validate Created SKB */ if (NULL == skb) { pr_err("Error aaloacting SKB\n"); 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\n"); kfree_skb(skb); return -EIO; } #ifdef VERBOSE pr_debug("start data..\n"); print_hex_dump(KERN_INFO, "data, size, 0); pr_debug("\n..end data\n"); #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; int retVal; pr_debug("inside %s (%p, %u, %lx)\n", __func__, file, cmd, arg); /* Validate input parameters */ if ((NULL == file) || (0 == cmd)) { pr_err("invalid input parameters passed to %s\n", __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) { retVal = skb->len + 1; /* Re-Store the SKB for furtur Read operations */ skb_queue_head(&hst->rx_list, skb); } else { retVal = 0; } pr_debug("returning %d\n", retVal); if (copy_to_user((void*)arg, &retVal, sizeof(retVal))) retCode = -EFAULT; break; default: pr_debug("Un-Identified IOCTL %d\n", 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\n"); 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\n"); 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\n"); 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 "); MODULE_LICENSE("GPL");