544 lines
14 KiB
C
544 lines
14 KiB
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, unreg_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) {
|
|
pr_err("hci_tty_open alloc error\n");
|
|
return -ENOMEM;
|
|
}
|
|
file->private_data = hst;
|
|
|
|
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 open_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 open_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 open_error;
|
|
}
|
|
|
|
done:
|
|
hst->st_write = ti_st_proto[i].write;
|
|
if (!hst->st_write) {
|
|
pr_err("undefined ST write function");
|
|
err = -EIO;
|
|
goto open_error;
|
|
}
|
|
}
|
|
|
|
skb_queue_head_init(&hst->rx_list);
|
|
init_waitqueue_head(&hst->data_q);
|
|
|
|
return 0;
|
|
|
|
open_error:
|
|
for (i = 0; i < MAX_BT_CHNL_IDS; i++) {
|
|
/* Undo registration with ST */
|
|
unreg_err = st_unregister(&ti_st_proto[i]);
|
|
if (unreg_err)
|
|
pr_err("st_unregister() failed with error %d"
|
|
, unreg_err);
|
|
hst->st_write = NULL;
|
|
}
|
|
file->private_data = NULL;
|
|
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, ret;
|
|
struct sk_buff *skb = NULL;
|
|
struct ti_st *hst;
|
|
char __user *d = data;
|
|
|
|
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 {
|
|
/* cb[0] has the pkt_type 0x04 or 0x02 or 0x03 */
|
|
ret = copy_to_user(d++, &skb->cb[0], 1);
|
|
ret |= copy_to_user(d, skb->data, skb->len);
|
|
if (ret) {
|
|
pr_err("unable to copy to user space\n");
|
|
/* Queue the skb back to head */
|
|
skb_queue_head(&hst->rx_list, skb);
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
len = skb->len + 1; /* len of returning skb */
|
|
kfree_skb(skb);
|
|
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");
|