blob: 591b0be944ca84bb054f1742af093c3b56151b37 [file] [log] [blame]
/*
* 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");