mirror of
https://github.com/followmsi/android_kernel_google_msm.git
synced 2024-11-06 23:17:41 +00:00
b8d8bfa400
The current code allocates a new Rx buffer in Rx completion handler. Rx buffers are freed after user space consumes them. This results in allocating larger buffers in atomic context which may fail some times. Don't queue the Rx buffer immediately. Defer it till user space consumes it and reuse the same buffer. The test results indicate no throughput degradation. The Rx buffers will be freed when interface is suspended or disconnected. CRs-Fixed: 430595 Change-Id: I8e69e27df2e11b38497e0384a8edd87ecedcb24b Signed-off-by: Pavankumar Kondeti <pkondeti@codeaurora.org> (cherry picked from commit 5c915c1949b79bc59d2b222bfcce02cd429619b2)
804 lines
18 KiB
C
804 lines
18 KiB
C
/*
|
|
* Copyright (c) 2012, Code Aurora Forum. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only 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.
|
|
*/
|
|
|
|
/* add additional information to our printk's */
|
|
#define pr_fmt(fmt) "%s: " fmt "\n", __func__
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kref.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/ratelimit.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/usb.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/list.h>
|
|
#include <linux/wait.h>
|
|
|
|
#define DRIVER_DESC "USB host ks bridge driver"
|
|
#define DRIVER_VERSION "1.0"
|
|
|
|
struct data_pkt {
|
|
int n_read;
|
|
char *buf;
|
|
size_t len;
|
|
struct list_head list;
|
|
void *ctxt;
|
|
};
|
|
|
|
#define FILE_OPENED BIT(0)
|
|
#define USB_DEV_CONNECTED BIT(1)
|
|
#define NO_RX_REQS 10
|
|
#define NO_BRIDGE_INSTANCES 2
|
|
#define BOOT_BRIDGE_INDEX 0
|
|
#define EFS_BRIDGE_INDEX 1
|
|
#define MAX_DATA_PKT_SIZE 16384
|
|
|
|
struct ks_bridge {
|
|
char *name;
|
|
spinlock_t lock;
|
|
struct workqueue_struct *wq;
|
|
struct work_struct to_mdm_work;
|
|
struct work_struct start_rx_work;
|
|
struct list_head to_mdm_list;
|
|
struct list_head to_ks_list;
|
|
wait_queue_head_t ks_wait_q;
|
|
|
|
/* usb specific */
|
|
struct usb_device *udev;
|
|
struct usb_interface *ifc;
|
|
__u8 in_epAddr;
|
|
__u8 out_epAddr;
|
|
unsigned int in_pipe;
|
|
unsigned int out_pipe;
|
|
struct usb_anchor submitted;
|
|
|
|
unsigned long flags;
|
|
|
|
#define DBG_MSG_LEN 40
|
|
#define DBG_MAX_MSG 500
|
|
unsigned int dbg_idx;
|
|
rwlock_t dbg_lock;
|
|
char (dbgbuf[DBG_MAX_MSG])[DBG_MSG_LEN]; /* buffer */
|
|
};
|
|
struct ks_bridge *__ksb[NO_BRIDGE_INSTANCES];
|
|
|
|
/* by default debugging is enabled */
|
|
static unsigned int enable_dbg = 1;
|
|
module_param(enable_dbg, uint, S_IRUGO | S_IWUSR);
|
|
|
|
static void
|
|
dbg_log_event(struct ks_bridge *ksb, char *event, int d1, int d2)
|
|
{
|
|
unsigned long flags;
|
|
unsigned long long t;
|
|
unsigned long nanosec;
|
|
|
|
if (!enable_dbg)
|
|
return;
|
|
|
|
write_lock_irqsave(&ksb->dbg_lock, flags);
|
|
t = cpu_clock(smp_processor_id());
|
|
nanosec = do_div(t, 1000000000)/1000;
|
|
scnprintf(ksb->dbgbuf[ksb->dbg_idx], DBG_MSG_LEN, "%5lu.%06lu:%s:%x:%x",
|
|
(unsigned long)t, nanosec, event, d1, d2);
|
|
|
|
ksb->dbg_idx++;
|
|
ksb->dbg_idx = ksb->dbg_idx % DBG_MAX_MSG;
|
|
write_unlock_irqrestore(&ksb->dbg_lock, flags);
|
|
}
|
|
|
|
static
|
|
struct data_pkt *ksb_alloc_data_pkt(size_t count, gfp_t flags, void *ctxt)
|
|
{
|
|
struct data_pkt *pkt;
|
|
|
|
pkt = kzalloc(sizeof(struct data_pkt), flags);
|
|
if (!pkt) {
|
|
pr_err("failed to allocate data packet\n");
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
pkt->buf = kmalloc(count, flags);
|
|
if (!pkt->buf) {
|
|
pr_err("failed to allocate data buffer\n");
|
|
kfree(pkt);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
pkt->len = count;
|
|
INIT_LIST_HEAD(&pkt->list);
|
|
pkt->ctxt = ctxt;
|
|
|
|
return pkt;
|
|
}
|
|
|
|
static void ksb_free_data_pkt(struct data_pkt *pkt)
|
|
{
|
|
kfree(pkt->buf);
|
|
kfree(pkt);
|
|
}
|
|
|
|
|
|
static void
|
|
submit_one_urb(struct ks_bridge *ksb, gfp_t flags, struct data_pkt *pkt);
|
|
static ssize_t ksb_fs_read(struct file *fp, char __user *buf,
|
|
size_t count, loff_t *pos)
|
|
{
|
|
int ret;
|
|
unsigned long flags;
|
|
struct ks_bridge *ksb = fp->private_data;
|
|
struct data_pkt *pkt;
|
|
size_t space, copied;
|
|
|
|
read_start:
|
|
if (!test_bit(USB_DEV_CONNECTED, &ksb->flags))
|
|
return -ENODEV;
|
|
|
|
spin_lock_irqsave(&ksb->lock, flags);
|
|
if (list_empty(&ksb->to_ks_list)) {
|
|
spin_unlock_irqrestore(&ksb->lock, flags);
|
|
ret = wait_event_interruptible(ksb->ks_wait_q,
|
|
!list_empty(&ksb->to_ks_list) ||
|
|
!test_bit(USB_DEV_CONNECTED, &ksb->flags));
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
goto read_start;
|
|
}
|
|
|
|
space = count;
|
|
copied = 0;
|
|
while (!list_empty(&ksb->to_ks_list) && space) {
|
|
size_t len;
|
|
|
|
pkt = list_first_entry(&ksb->to_ks_list, struct data_pkt, list);
|
|
len = min_t(size_t, space, pkt->len - pkt->n_read);
|
|
spin_unlock_irqrestore(&ksb->lock, flags);
|
|
|
|
ret = copy_to_user(buf + copied, pkt->buf + pkt->n_read, len);
|
|
if (ret) {
|
|
pr_err("copy_to_user failed err:%d\n", ret);
|
|
ksb_free_data_pkt(pkt);
|
|
return ret;
|
|
}
|
|
|
|
pkt->n_read += len;
|
|
space -= len;
|
|
copied += len;
|
|
|
|
spin_lock_irqsave(&ksb->lock, flags);
|
|
if (pkt->n_read == pkt->len) {
|
|
/*
|
|
* re-init the packet and queue it
|
|
* for more data.
|
|
*/
|
|
list_del_init(&pkt->list);
|
|
pkt->n_read = 0;
|
|
pkt->len = MAX_DATA_PKT_SIZE;
|
|
spin_unlock_irqrestore(&ksb->lock, flags);
|
|
submit_one_urb(ksb, GFP_KERNEL, pkt);
|
|
spin_lock_irqsave(&ksb->lock, flags);
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&ksb->lock, flags);
|
|
|
|
dbg_log_event(ksb, "KS_READ", copied, 0);
|
|
|
|
pr_debug("count:%d space:%d copied:%d", count, space, copied);
|
|
|
|
return copied;
|
|
}
|
|
|
|
static void ksb_tx_cb(struct urb *urb)
|
|
{
|
|
struct data_pkt *pkt = urb->context;
|
|
struct ks_bridge *ksb = pkt->ctxt;
|
|
|
|
dbg_log_event(ksb, "C TX_URB", urb->status, 0);
|
|
pr_debug("status:%d", urb->status);
|
|
|
|
if (ksb->ifc)
|
|
usb_autopm_put_interface_async(ksb->ifc);
|
|
|
|
if (urb->status < 0)
|
|
pr_err_ratelimited("urb failed with err:%d", urb->status);
|
|
|
|
ksb_free_data_pkt(pkt);
|
|
}
|
|
|
|
static void ksb_tomdm_work(struct work_struct *w)
|
|
{
|
|
struct ks_bridge *ksb = container_of(w, struct ks_bridge, to_mdm_work);
|
|
struct data_pkt *pkt;
|
|
unsigned long flags;
|
|
struct urb *urb;
|
|
int ret;
|
|
|
|
spin_lock_irqsave(&ksb->lock, flags);
|
|
while (!list_empty(&ksb->to_mdm_list)
|
|
&& test_bit(USB_DEV_CONNECTED, &ksb->flags)) {
|
|
pkt = list_first_entry(&ksb->to_mdm_list,
|
|
struct data_pkt, list);
|
|
list_del_init(&pkt->list);
|
|
spin_unlock_irqrestore(&ksb->lock, flags);
|
|
|
|
urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
if (!urb) {
|
|
pr_err_ratelimited("unable to allocate urb");
|
|
ksb_free_data_pkt(pkt);
|
|
return;
|
|
}
|
|
|
|
ret = usb_autopm_get_interface(ksb->ifc);
|
|
if (ret < 0 && ret != -EAGAIN && ret != -EACCES) {
|
|
pr_err_ratelimited("autopm_get failed:%d", ret);
|
|
usb_free_urb(urb);
|
|
ksb_free_data_pkt(pkt);
|
|
return;
|
|
}
|
|
usb_fill_bulk_urb(urb, ksb->udev, ksb->out_pipe,
|
|
pkt->buf, pkt->len, ksb_tx_cb, pkt);
|
|
usb_anchor_urb(urb, &ksb->submitted);
|
|
|
|
dbg_log_event(ksb, "S TX_URB", pkt->len, 0);
|
|
|
|
ret = usb_submit_urb(urb, GFP_KERNEL);
|
|
if (ret) {
|
|
pr_err("out urb submission failed");
|
|
usb_unanchor_urb(urb);
|
|
usb_free_urb(urb);
|
|
ksb_free_data_pkt(pkt);
|
|
usb_autopm_put_interface(ksb->ifc);
|
|
return;
|
|
}
|
|
|
|
usb_free_urb(urb);
|
|
|
|
spin_lock_irqsave(&ksb->lock, flags);
|
|
}
|
|
spin_unlock_irqrestore(&ksb->lock, flags);
|
|
}
|
|
|
|
static ssize_t ksb_fs_write(struct file *fp, const char __user *buf,
|
|
size_t count, loff_t *pos)
|
|
{
|
|
int ret;
|
|
struct data_pkt *pkt;
|
|
unsigned long flags;
|
|
struct ks_bridge *ksb = fp->private_data;
|
|
|
|
pkt = ksb_alloc_data_pkt(count, GFP_KERNEL, ksb);
|
|
if (IS_ERR(pkt)) {
|
|
pr_err("unable to allocate data packet");
|
|
return PTR_ERR(pkt);
|
|
}
|
|
|
|
ret = copy_from_user(pkt->buf, buf, count);
|
|
if (ret) {
|
|
pr_err("copy_from_user failed: err:%d", ret);
|
|
ksb_free_data_pkt(pkt);
|
|
return ret;
|
|
}
|
|
|
|
spin_lock_irqsave(&ksb->lock, flags);
|
|
list_add_tail(&pkt->list, &ksb->to_mdm_list);
|
|
spin_unlock_irqrestore(&ksb->lock, flags);
|
|
|
|
queue_work(ksb->wq, &ksb->to_mdm_work);
|
|
|
|
return count;
|
|
}
|
|
|
|
static int efs_fs_open(struct inode *ip, struct file *fp)
|
|
{
|
|
struct ks_bridge *ksb = __ksb[EFS_BRIDGE_INDEX];
|
|
|
|
pr_debug(":%s", ksb->name);
|
|
dbg_log_event(ksb, "EFS-FS-OPEN", 0, 0);
|
|
|
|
if (!ksb) {
|
|
pr_err("ksb is being removed");
|
|
return -ENODEV;
|
|
}
|
|
|
|
fp->private_data = ksb;
|
|
set_bit(FILE_OPENED, &ksb->flags);
|
|
|
|
if (test_bit(USB_DEV_CONNECTED, &ksb->flags))
|
|
queue_work(ksb->wq, &ksb->start_rx_work);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ksb_fs_open(struct inode *ip, struct file *fp)
|
|
{
|
|
struct ks_bridge *ksb = __ksb[BOOT_BRIDGE_INDEX];
|
|
|
|
pr_debug(":%s", ksb->name);
|
|
dbg_log_event(ksb, "KS-FS-OPEN", 0, 0);
|
|
|
|
if (!ksb) {
|
|
pr_err("ksb is being removed");
|
|
return -ENODEV;
|
|
}
|
|
|
|
fp->private_data = ksb;
|
|
set_bit(FILE_OPENED, &ksb->flags);
|
|
|
|
if (test_bit(USB_DEV_CONNECTED, &ksb->flags))
|
|
queue_work(ksb->wq, &ksb->start_rx_work);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ksb_fs_release(struct inode *ip, struct file *fp)
|
|
{
|
|
struct ks_bridge *ksb = fp->private_data;
|
|
|
|
pr_debug(":%s", ksb->name);
|
|
dbg_log_event(ksb, "FS-RELEASE", 0, 0);
|
|
|
|
clear_bit(FILE_OPENED, &ksb->flags);
|
|
fp->private_data = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct file_operations ksb_fops = {
|
|
.owner = THIS_MODULE,
|
|
.read = ksb_fs_read,
|
|
.write = ksb_fs_write,
|
|
.open = ksb_fs_open,
|
|
.release = ksb_fs_release,
|
|
};
|
|
|
|
static struct miscdevice ksb_fboot_dev = {
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = "ks_bridge",
|
|
.fops = &ksb_fops,
|
|
};
|
|
|
|
static const struct file_operations efs_fops = {
|
|
.owner = THIS_MODULE,
|
|
.read = ksb_fs_read,
|
|
.write = ksb_fs_write,
|
|
.open = efs_fs_open,
|
|
.release = ksb_fs_release,
|
|
};
|
|
|
|
static struct miscdevice ksb_efs_dev = {
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = "efs_bridge",
|
|
.fops = &efs_fops,
|
|
};
|
|
|
|
static const struct usb_device_id ksb_usb_ids[] = {
|
|
{ USB_DEVICE(0x5c6, 0x9008),
|
|
.driver_info = (unsigned long)&ksb_fboot_dev, },
|
|
{ USB_DEVICE(0x5c6, 0x9048),
|
|
.driver_info = (unsigned long)&ksb_efs_dev, },
|
|
{ USB_DEVICE(0x5c6, 0x904C),
|
|
.driver_info = (unsigned long)&ksb_efs_dev, },
|
|
|
|
{} /* terminating entry */
|
|
};
|
|
MODULE_DEVICE_TABLE(usb, ksb_usb_ids);
|
|
|
|
static void ksb_rx_cb(struct urb *urb);
|
|
static void
|
|
submit_one_urb(struct ks_bridge *ksb, gfp_t flags, struct data_pkt *pkt)
|
|
{
|
|
struct urb *urb;
|
|
int ret;
|
|
|
|
urb = usb_alloc_urb(0, flags);
|
|
if (!urb) {
|
|
pr_err("unable to allocate urb");
|
|
ksb_free_data_pkt(pkt);
|
|
return;
|
|
}
|
|
|
|
usb_fill_bulk_urb(urb, ksb->udev, ksb->in_pipe,
|
|
pkt->buf, pkt->len,
|
|
ksb_rx_cb, pkt);
|
|
usb_anchor_urb(urb, &ksb->submitted);
|
|
|
|
dbg_log_event(ksb, "S RX_URB", pkt->len, 0);
|
|
|
|
ret = usb_submit_urb(urb, flags);
|
|
if (ret) {
|
|
pr_err("in urb submission failed");
|
|
usb_unanchor_urb(urb);
|
|
usb_free_urb(urb);
|
|
ksb_free_data_pkt(pkt);
|
|
return;
|
|
}
|
|
|
|
usb_free_urb(urb);
|
|
}
|
|
static void ksb_rx_cb(struct urb *urb)
|
|
{
|
|
struct data_pkt *pkt = urb->context;
|
|
struct ks_bridge *ksb = pkt->ctxt;
|
|
|
|
dbg_log_event(ksb, "C RX_URB", urb->status, urb->actual_length);
|
|
|
|
pr_debug("status:%d actual:%d", urb->status, urb->actual_length);
|
|
|
|
/*non zero len of data received while unlinking urb*/
|
|
if (urb->status == -ENOENT && urb->actual_length > 0)
|
|
goto add_to_list;
|
|
|
|
if (urb->status < 0) {
|
|
if (urb->status != -ESHUTDOWN && urb->status != -ENOENT
|
|
&& urb->status != -EPROTO)
|
|
pr_err_ratelimited("urb failed with err:%d",
|
|
urb->status);
|
|
ksb_free_data_pkt(pkt);
|
|
return;
|
|
}
|
|
|
|
if (urb->actual_length == 0) {
|
|
submit_one_urb(ksb, GFP_ATOMIC, pkt);
|
|
return;
|
|
}
|
|
|
|
add_to_list:
|
|
spin_lock(&ksb->lock);
|
|
pkt->len = urb->actual_length;
|
|
list_add_tail(&pkt->list, &ksb->to_ks_list);
|
|
spin_unlock(&ksb->lock);
|
|
|
|
/* wake up read thread */
|
|
wake_up(&ksb->ks_wait_q);
|
|
}
|
|
|
|
static void ksb_start_rx_work(struct work_struct *w)
|
|
{
|
|
struct ks_bridge *ksb =
|
|
container_of(w, struct ks_bridge, start_rx_work);
|
|
struct data_pkt *pkt;
|
|
struct urb *urb;
|
|
int i = 0;
|
|
int ret;
|
|
|
|
for (i = 0; i < NO_RX_REQS; i++) {
|
|
pkt = ksb_alloc_data_pkt(MAX_DATA_PKT_SIZE, GFP_KERNEL, ksb);
|
|
if (IS_ERR(pkt)) {
|
|
pr_err("unable to allocate data pkt");
|
|
return;
|
|
}
|
|
|
|
urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
if (!urb) {
|
|
pr_err("unable to allocate urb");
|
|
ksb_free_data_pkt(pkt);
|
|
return;
|
|
}
|
|
|
|
ret = usb_autopm_get_interface(ksb->ifc);
|
|
if (ret < 0 && ret != -EAGAIN && ret != -EACCES) {
|
|
pr_err_ratelimited("autopm_get failed:%d", ret);
|
|
usb_free_urb(urb);
|
|
ksb_free_data_pkt(pkt);
|
|
return;
|
|
}
|
|
|
|
usb_fill_bulk_urb(urb, ksb->udev, ksb->in_pipe,
|
|
pkt->buf, pkt->len,
|
|
ksb_rx_cb, pkt);
|
|
usb_anchor_urb(urb, &ksb->submitted);
|
|
|
|
dbg_log_event(ksb, "S RX_URB", pkt->len, 0);
|
|
|
|
ret = usb_submit_urb(urb, GFP_KERNEL);
|
|
if (ret) {
|
|
pr_err("in urb submission failed");
|
|
usb_unanchor_urb(urb);
|
|
usb_free_urb(urb);
|
|
ksb_free_data_pkt(pkt);
|
|
usb_autopm_put_interface(ksb->ifc);
|
|
return;
|
|
}
|
|
|
|
usb_autopm_put_interface_async(ksb->ifc);
|
|
usb_free_urb(urb);
|
|
}
|
|
}
|
|
|
|
static int
|
|
ksb_usb_probe(struct usb_interface *ifc, const struct usb_device_id *id)
|
|
{
|
|
__u8 ifc_num;
|
|
struct usb_host_interface *ifc_desc;
|
|
struct usb_endpoint_descriptor *ep_desc;
|
|
int i;
|
|
struct ks_bridge *ksb;
|
|
struct miscdevice *fs_dev;
|
|
|
|
ifc_num = ifc->cur_altsetting->desc.bInterfaceNumber;
|
|
|
|
switch (id->idProduct) {
|
|
case 0x9008:
|
|
if (ifc_num != 0)
|
|
return -ENODEV;
|
|
ksb = __ksb[BOOT_BRIDGE_INDEX];
|
|
break;
|
|
case 0x9048:
|
|
case 0x904C:
|
|
if (ifc_num != 2)
|
|
return -ENODEV;
|
|
ksb = __ksb[EFS_BRIDGE_INDEX];
|
|
break;
|
|
default:
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (!ksb) {
|
|
pr_err("ksb is not initialized");
|
|
return -ENODEV;
|
|
}
|
|
|
|
ksb->udev = usb_get_dev(interface_to_usbdev(ifc));
|
|
ksb->ifc = ifc;
|
|
ifc_desc = ifc->cur_altsetting;
|
|
|
|
for (i = 0; i < ifc_desc->desc.bNumEndpoints; i++) {
|
|
ep_desc = &ifc_desc->endpoint[i].desc;
|
|
|
|
if (!ksb->in_epAddr && usb_endpoint_is_bulk_in(ep_desc))
|
|
ksb->in_epAddr = ep_desc->bEndpointAddress;
|
|
|
|
if (!ksb->out_epAddr && usb_endpoint_is_bulk_out(ep_desc))
|
|
ksb->out_epAddr = ep_desc->bEndpointAddress;
|
|
}
|
|
|
|
if (!(ksb->in_epAddr && ksb->out_epAddr)) {
|
|
pr_err("could not find bulk in and bulk out endpoints");
|
|
usb_put_dev(ksb->udev);
|
|
ksb->ifc = NULL;
|
|
return -ENODEV;
|
|
}
|
|
|
|
ksb->in_pipe = usb_rcvbulkpipe(ksb->udev, ksb->in_epAddr);
|
|
ksb->out_pipe = usb_sndbulkpipe(ksb->udev, ksb->out_epAddr);
|
|
|
|
usb_set_intfdata(ifc, ksb);
|
|
set_bit(USB_DEV_CONNECTED, &ksb->flags);
|
|
|
|
dbg_log_event(ksb, "PID-ATT", id->idProduct, 0);
|
|
|
|
fs_dev = (struct miscdevice *)id->driver_info;
|
|
misc_register(fs_dev);
|
|
|
|
ifc->needs_remote_wakeup = 1;
|
|
usb_enable_autosuspend(ksb->udev);
|
|
|
|
pr_debug("usb dev connected");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ksb_usb_suspend(struct usb_interface *ifc, pm_message_t message)
|
|
{
|
|
struct ks_bridge *ksb = usb_get_intfdata(ifc);
|
|
|
|
dbg_log_event(ksb, "SUSPEND", 0, 0);
|
|
|
|
usb_kill_anchored_urbs(&ksb->submitted);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ksb_usb_resume(struct usb_interface *ifc)
|
|
{
|
|
struct ks_bridge *ksb = usb_get_intfdata(ifc);
|
|
|
|
dbg_log_event(ksb, "RESUME", 0, 0);
|
|
|
|
if (test_bit(FILE_OPENED, &ksb->flags))
|
|
queue_work(ksb->wq, &ksb->start_rx_work);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ksb_usb_disconnect(struct usb_interface *ifc)
|
|
{
|
|
struct ks_bridge *ksb = usb_get_intfdata(ifc);
|
|
unsigned long flags;
|
|
struct data_pkt *pkt;
|
|
|
|
dbg_log_event(ksb, "PID-DETACH", 0, 0);
|
|
|
|
clear_bit(USB_DEV_CONNECTED, &ksb->flags);
|
|
wake_up(&ksb->ks_wait_q);
|
|
cancel_work_sync(&ksb->to_mdm_work);
|
|
|
|
usb_kill_anchored_urbs(&ksb->submitted);
|
|
|
|
spin_lock_irqsave(&ksb->lock, flags);
|
|
while (!list_empty(&ksb->to_ks_list)) {
|
|
pkt = list_first_entry(&ksb->to_ks_list,
|
|
struct data_pkt, list);
|
|
list_del_init(&pkt->list);
|
|
ksb_free_data_pkt(pkt);
|
|
}
|
|
while (!list_empty(&ksb->to_mdm_list)) {
|
|
pkt = list_first_entry(&ksb->to_mdm_list,
|
|
struct data_pkt, list);
|
|
list_del_init(&pkt->list);
|
|
ksb_free_data_pkt(pkt);
|
|
}
|
|
spin_unlock_irqrestore(&ksb->lock, flags);
|
|
|
|
ifc->needs_remote_wakeup = 0;
|
|
usb_put_dev(ksb->udev);
|
|
ksb->ifc = NULL;
|
|
usb_set_intfdata(ifc, NULL);
|
|
|
|
return;
|
|
}
|
|
|
|
static struct usb_driver ksb_usb_driver = {
|
|
.name = "ks_bridge",
|
|
.probe = ksb_usb_probe,
|
|
.disconnect = ksb_usb_disconnect,
|
|
.suspend = ksb_usb_suspend,
|
|
.resume = ksb_usb_resume,
|
|
.id_table = ksb_usb_ids,
|
|
.supports_autosuspend = 1,
|
|
};
|
|
|
|
static ssize_t ksb_debug_show(struct seq_file *s, void *unused)
|
|
{
|
|
unsigned long flags;
|
|
struct ks_bridge *ksb = s->private;
|
|
int i;
|
|
|
|
read_lock_irqsave(&ksb->dbg_lock, flags);
|
|
for (i = 0; i < DBG_MAX_MSG; i++) {
|
|
if (i == (ksb->dbg_idx - 1))
|
|
seq_printf(s, "-->%s\n", ksb->dbgbuf[i]);
|
|
else
|
|
seq_printf(s, "%s\n", ksb->dbgbuf[i]);
|
|
}
|
|
read_unlock_irqrestore(&ksb->dbg_lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ksb_debug_open(struct inode *ip, struct file *fp)
|
|
{
|
|
return single_open(fp, ksb_debug_show, ip->i_private);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct file_operations dbg_fops = {
|
|
.open = ksb_debug_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
static struct dentry *dbg_dir;
|
|
static int __init ksb_init(void)
|
|
{
|
|
struct ks_bridge *ksb;
|
|
int num_instances = 0;
|
|
int ret = 0;
|
|
int i;
|
|
|
|
dbg_dir = debugfs_create_dir("ks_bridge", NULL);
|
|
if (IS_ERR(dbg_dir))
|
|
pr_err("unable to create debug dir");
|
|
|
|
for (i = 0; i < NO_BRIDGE_INSTANCES; i++) {
|
|
ksb = kzalloc(sizeof(struct ks_bridge), GFP_KERNEL);
|
|
if (!ksb) {
|
|
pr_err("unable to allocat mem for ks_bridge");
|
|
return -ENOMEM;
|
|
}
|
|
__ksb[i] = ksb;
|
|
|
|
ksb->name = kasprintf(GFP_KERNEL, "ks_bridge:%i", i + 1);
|
|
if (!ksb->name) {
|
|
pr_info("unable to allocate name");
|
|
kfree(ksb);
|
|
ret = -ENOMEM;
|
|
goto dev_free;
|
|
}
|
|
|
|
spin_lock_init(&ksb->lock);
|
|
INIT_LIST_HEAD(&ksb->to_mdm_list);
|
|
INIT_LIST_HEAD(&ksb->to_ks_list);
|
|
init_waitqueue_head(&ksb->ks_wait_q);
|
|
ksb->wq = create_singlethread_workqueue(ksb->name);
|
|
if (!ksb->wq) {
|
|
pr_err("unable to allocate workqueue");
|
|
kfree(ksb->name);
|
|
kfree(ksb);
|
|
ret = -ENOMEM;
|
|
goto dev_free;
|
|
}
|
|
|
|
INIT_WORK(&ksb->to_mdm_work, ksb_tomdm_work);
|
|
INIT_WORK(&ksb->start_rx_work, ksb_start_rx_work);
|
|
init_usb_anchor(&ksb->submitted);
|
|
|
|
ksb->dbg_idx = 0;
|
|
ksb->dbg_lock = __RW_LOCK_UNLOCKED(lck);
|
|
|
|
if (!IS_ERR(dbg_dir))
|
|
debugfs_create_file(ksb->name, S_IRUGO, dbg_dir,
|
|
ksb, &dbg_fops);
|
|
|
|
num_instances++;
|
|
}
|
|
|
|
ret = usb_register(&ksb_usb_driver);
|
|
if (ret) {
|
|
pr_err("unable to register ks bridge driver");
|
|
goto dev_free;
|
|
}
|
|
|
|
pr_info("init done");
|
|
|
|
return 0;
|
|
|
|
dev_free:
|
|
if (!IS_ERR(dbg_dir))
|
|
debugfs_remove_recursive(dbg_dir);
|
|
|
|
for (i = 0; i < num_instances; i++) {
|
|
ksb = __ksb[i];
|
|
|
|
destroy_workqueue(ksb->wq);
|
|
kfree(ksb->name);
|
|
kfree(ksb);
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
static void __exit ksb_exit(void)
|
|
{
|
|
struct ks_bridge *ksb;
|
|
int i;
|
|
|
|
if (!IS_ERR(dbg_dir))
|
|
debugfs_remove_recursive(dbg_dir);
|
|
|
|
usb_deregister(&ksb_usb_driver);
|
|
|
|
for (i = 0; i < NO_BRIDGE_INSTANCES; i++) {
|
|
ksb = __ksb[i];
|
|
|
|
destroy_workqueue(ksb->wq);
|
|
kfree(ksb->name);
|
|
kfree(ksb);
|
|
}
|
|
}
|
|
|
|
module_init(ksb_init);
|
|
module_exit(ksb_exit);
|
|
|
|
MODULE_DESCRIPTION(DRIVER_DESC);
|
|
MODULE_VERSION(DRIVER_VERSION);
|
|
MODULE_LICENSE("GPL v2");
|