diff options
author | Srikant Patnaik | 2015-01-11 12:28:04 +0530 |
---|---|---|
committer | Srikant Patnaik | 2015-01-11 12:28:04 +0530 |
commit | 871480933a1c28f8a9fed4c4d34d06c439a7a422 (patch) | |
tree | 8718f573808810c2a1e8cb8fb6ac469093ca2784 /drivers/usb/gadget/rawbulk_transfer.c | |
parent | 9d40ac5867b9aefe0722bc1f110b965ff294d30d (diff) | |
download | FOSSEE-netbook-kernel-source-871480933a1c28f8a9fed4c4d34d06c439a7a422.tar.gz FOSSEE-netbook-kernel-source-871480933a1c28f8a9fed4c4d34d06c439a7a422.tar.bz2 FOSSEE-netbook-kernel-source-871480933a1c28f8a9fed4c4d34d06c439a7a422.zip |
Moved, renamed, and deleted files
The original directory structure was scattered and unorganized.
Changes are basically to make it look like kernel structure.
Diffstat (limited to 'drivers/usb/gadget/rawbulk_transfer.c')
-rwxr-xr-x | drivers/usb/gadget/rawbulk_transfer.c | 1372 |
1 files changed, 1372 insertions, 0 deletions
diff --git a/drivers/usb/gadget/rawbulk_transfer.c b/drivers/usb/gadget/rawbulk_transfer.c new file mode 100755 index 00000000..70ec5be1 --- /dev/null +++ b/drivers/usb/gadget/rawbulk_transfer.c @@ -0,0 +1,1372 @@ +/* + * Rawbulk Driver from VIA Telecom + * + * Copyright (C) 2011 VIA Telecom, Inc. + * Author: Karfield Chen (kfchen@via-telecom.com) + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ +/* + * Rawbulk is transfer performer between CBP host driver and Gadget driver + * + * 1 rawbulk transfer can be consist by serval upstream and/or downstream + * transactions. + * + * upstream: CBP Driver ---> Gadget IN + * downstream: Gadget OUT ---> CBP Driver + * + * upstream flowchart: + * + * -x-> usb_submit_urb -> complete -> usb_ep_queue -> complete -, + * | | + * `----------------------(control)----------------------------' + * + * downstream flowchart: + * + * -x-> usb_ep_queue -> complete -> usb_submit_urb -> complete -, + * | | + * `----------------------(control)----------------------------' + * + */ + +/* #define DEBUG */ +/* #define VERBOSE_DEBUG */ + +#define DRIVER_AUTHOR "Karfield Chen <kfchen@via-telecom.com>" +#define DRIVER_DESC "Rawbulk Driver - perform bypass for Kunlun" +#define DRIVER_VERSION "1.0.3" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/list.h> +#include <linux/spinlock.h> +#include <linux/workqueue.h> +#include <linux/usb/rawbulk.h> +#include <linux/moduleparam.h> + +#ifdef VERBOSE_DEBUG +#define ldbg(fmt, args...) \ + printk(KERN_DEBUG "%s: " fmt "\n", __func__, ##args) +#define tdbg(t, fmt, args...) \ + printk(KERN_DEBUG "Rawbulk %s: " fmt "\n", t->name, ##args) +#else +#define ldbg(args...) +#define tdbg(args...) +#endif + +#define lerr(fmt, args...) \ + printk(KERN_ERR "%s: " fmt "\n", __func__, ##args) +#define terr(t, fmt, args...) \ + printk(KERN_ERR "Rawbulk [%s]:" fmt "\n", t->name, ##args) + +#define FREE_STALLED 1 +#define FREE_IDLED 2 +#define FREE_RETRIVING 4 +#define FREE_ALL (FREE_STALLED | FREE_IDLED | FREE_RETRIVING) + +#define STOP_UPSTREAM 0x1 +#define STOP_DOWNSTREAM 0x2 + +struct rawbulk_transfer { + enum transfer_id id; + spinlock_t lock; + int control; + struct usb_anchor submitted; + struct usb_function *function; + struct usb_interface *interface; + struct mutex mutex; /* protection for API calling */ + rawbulk_autoreconn_callback_t autoreconn; + rawbulk_intercept_t inceptor; + spinlock_t suspend_lock; + int suspended; + struct { + int ntrans; + struct list_head transactions; + struct usb_ep *ep; + struct usb_host_endpoint *host_ep; + } upstream, downstream; +}; + +static inline int get_epnum(struct usb_host_endpoint *ep) { + return (int)(ep->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); +} + +static inline int get_maxpacksize(struct usb_host_endpoint *ep) { + return (int)(le16_to_cpu(ep->desc.wMaxPacketSize)); +} + + +#define MAX_RESPONSE 32 +struct rawbulk_transfer_model { + struct usb_device *udev; + struct usb_composite_dev *cdev; + char ctrl_response[MAX_RESPONSE]; + struct usb_ctrlrequest forward_dr; + struct urb *forwarding_urb; + struct rawbulk_transfer transfer[_MAX_TID]; +}; +static struct rawbulk_transfer_model *rawbulk; + +static struct rawbulk_transfer *id_to_transfer(int transfer_id) { + if (transfer_id < 0 || transfer_id >= _MAX_TID) + return NULL; + return &rawbulk->transfer[transfer_id]; +} + +/* + * upstream + */ + +#define UPSTREAM_STAT_FREE 0 +#define UPSTREAM_STAT_RETRIEVING 1 +#define UPSTREAM_STAT_UPLOADING 2 + +struct upstream_transaction { + int state; + int stalled; + char name[32]; + struct list_head tlist; + struct delayed_work delayed; + struct rawbulk_transfer *transfer; + struct usb_request *req; + struct urb *urb; + int buffer_length; + unsigned char *buffer; +}; + +static void upstream_delayed_work(struct work_struct *work); +static struct upstream_transaction * +alloc_upstream_transaction(struct rawbulk_transfer *transfer, int bufsz, int pushable) +{ + struct upstream_transaction *t; + + if (bufsz < get_maxpacksize(transfer->upstream.host_ep)) + return NULL; + if (bufsz < PAGE_SIZE || bufsz % PAGE_SIZE != 0) + return NULL; + + t = kzalloc(sizeof *t, GFP_KERNEL); + if (!t) + return NULL; + + t->buffer = (unsigned char *)__get_free_pages(GFP_KERNEL, get_order(bufsz)); + if (!t->buffer) { + kfree(t); + return NULL; + } + + t->buffer_length = bufsz; + + t->req = usb_ep_alloc_request(transfer->upstream.ep, GFP_KERNEL); + if (!t->req) + goto failto_alloc_usb_request; + + if (!pushable) { + t->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!t->urb) + goto failto_alloc_urb; + } + + t->name[0] = 0; + sprintf(t->name, "U%d (H:ep%din, G:%s)", transfer->upstream.ntrans, + get_epnum(transfer->upstream.host_ep), + transfer->upstream.ep->name); + + INIT_LIST_HEAD(&t->tlist); + list_add_tail(&t->tlist, &transfer->upstream.transactions); + transfer->upstream.ntrans ++; + t->transfer = transfer; + + INIT_DELAYED_WORK(&t->delayed, upstream_delayed_work); + t->state = UPSTREAM_STAT_FREE; + return t; + +failto_alloc_urb: + usb_ep_free_request(transfer->upstream.ep, t->req); +failto_alloc_usb_request: + free_page((unsigned long)t->buffer); + kfree(t); + return NULL; +} + +static void free_upstream_transaction(struct rawbulk_transfer *transfer, int option) { + struct list_head *p, *n; + list_for_each_safe(p, n, &transfer->upstream.transactions) { + struct upstream_transaction *t = list_entry(p, struct + upstream_transaction, tlist); + + if ((option & FREE_RETRIVING) && + (t->state == UPSTREAM_STAT_RETRIEVING)) { + if (t->urb) usb_unlink_urb(t->urb); + goto free; + } + + if ((option & FREE_STALLED) && t->stalled) + goto free; + + if ((option & FREE_IDLED) && (t->state == UPSTREAM_STAT_FREE)) + goto free; + + continue; +free: + list_del(p); + if (t->urb) + usb_free_urb(t->urb); + usb_ep_free_request(transfer->upstream.ep, t->req); + free_page((unsigned long)t->buffer); + kfree(t); + + transfer->upstream.ntrans --; + } +} + +static void upstream_second_stage(struct urb *urb); +static void upstream_final_stage(struct usb_ep *ep, struct usb_request + *req); + +static int start_upstream(struct upstream_transaction *t, gfp_t mem_flags) { + int rc; + unsigned long flags; + struct rawbulk_transfer *transfer = t->transfer; + + spin_lock_irqsave(&transfer->lock, flags); + if (transfer->control & STOP_UPSTREAM) { + spin_unlock_irqrestore(&transfer->lock, flags); + cancel_delayed_work(&t->delayed); + t->state = UPSTREAM_STAT_FREE; + t->stalled = 1; + return -EPIPE; + } + spin_unlock_irqrestore(&transfer->lock, flags); + + t->stalled = 0; + t->state = UPSTREAM_STAT_FREE; + + usb_fill_bulk_urb(t->urb, rawbulk->udev, + usb_rcvbulkpipe(rawbulk->udev, + get_epnum(transfer->upstream.host_ep)), + t->buffer, t->buffer_length, + upstream_second_stage, t); + usb_anchor_urb(t->urb, &transfer->submitted); + + rc = usb_submit_urb(t->urb, mem_flags); + usb_mark_last_busy(rawbulk->udev); + if (rc < 0) { + terr(t, "fail to submit urb %d", rc); + /* FIXME: if we met the host is suspending yet, we may add re-submit + * this urb in a ashman thread. so what is a ashman? this can deal any + * error that occur in this driver, and maintain the driver's health, I + * will complete this code in future, here we just stall the transaction + */ + t->stalled = 1; + usb_unanchor_urb(t->urb); + } + + t->state = UPSTREAM_STAT_RETRIEVING; + return rc; +} + +static void upstream_delayed_work(struct work_struct *work) { + int rc; + int stop; + unsigned long flags; + struct delayed_work *dw = container_of(work, struct delayed_work, work); + struct upstream_transaction *t = container_of (work, struct + upstream_transaction, delayed.work); + struct rawbulk_transfer *transfer = t->transfer; + + spin_lock_irqsave(&transfer->lock, flags); + stop = !!(transfer->control & STOP_UPSTREAM); + spin_unlock_irqrestore(&transfer->lock, flags); + + rc = start_upstream(t, GFP_ATOMIC); + if (rc < 0 && !stop) + schedule_delayed_work(dw, HZ); +} + +static unsigned int dump_mask = 0; +module_param(dump_mask, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(dump_mask, "Set data dump mask for each transfers"); + +static inline void dump_data(struct rawbulk_transfer *trans, + const char *str, const unsigned char *data, int size) { + int i; + int no_chars = 0; + + if (!(dump_mask & (1 << trans->id))) + return; + + printk(KERN_DEBUG "DUMP tid = %d, %s: len = %d, chars = \"", + trans->id, str, size); + for (i = 0; i < size; ++i) { + char c = data[i]; + if (c > 0x20 && c < 0x7e) { + printk("%c", c); + } else { + printk("."); + no_chars ++; + } + } + printk("\", data = "); + for (i = 0; i < size; ++i) { + printk("%.2x ", data[i]); + if (i > 7) + break; + } + if (size < 8) { + printk("\n"); + return; + } else if (i < size - 8) { + printk("... "); + i = size - 8; + } + for (; i < size; ++i) + printk("%.2x ", data[i]); + printk("\n"); +} + +static void upstream_second_stage(struct urb *urb) { + int rc; + struct upstream_transaction *t = urb->context; + struct rawbulk_transfer *transfer = t->transfer; + struct usb_request *req = t->req; + + if (urb->status < 0) { + if (urb->status == -ECONNRESET) { + terr(t, "usb-device connection reset!"); + return; + } + printk("WTF?! %s failed %d\n", __func__, urb->status); + /* start again atomatically */ + rc = start_upstream(t, GFP_ATOMIC); +#if 0 + if (rc < 0) + schedule_delayed_work(&t->delayed, HZ); +#endif + return; + } + + dump_data(transfer, "upstream", t->buffer, urb->actual_length); + /* allow zero-length package to pass + if (!urb->actual_length) { + terr(t, "urb actual_length 0"); + start_upstream(t, GFP_ATOMIC); + return; + } + */ + + req->status = 0; + req->context = t; + req->buf = urb->transfer_buffer; + req->length = urb->actual_length; + req->complete = upstream_final_stage; + if (urb->actual_length == 0) + req->zero = 1; + else + req->zero = 0; + + t->state = UPSTREAM_STAT_UPLOADING; + rc = usb_ep_queue(transfer->upstream.ep, req, GFP_ATOMIC); + if (rc < 0) { + terr(t, "fail to queue request, %d", rc); + t->stalled = 1; + t->state = UPSTREAM_STAT_FREE; + } +} + +static void upstream_final_stage(struct usb_ep *ep, + struct usb_request *req) { + struct upstream_transaction *t = req->context; + + t->state = UPSTREAM_STAT_FREE; + + if (req->status < 0) { + if (req->status != -ECONNRESET) + terr(t, "req status %d", req->status); + if (req->status == -ESHUTDOWN) { + t->stalled = 1; + return; + } + } + + if (!req->actual) + terr(t, "req actual 0"); + + if (t->urb) + start_upstream(t, GFP_ATOMIC); +} + +static void stop_upstream(struct upstream_transaction *t) { + struct rawbulk_transfer *transfer = t->transfer; + if (t->state == UPSTREAM_STAT_RETRIEVING) { + cancel_delayed_work(&t->delayed); + if (t->urb) + usb_unlink_urb(t->urb); + } else if (t->state == UPSTREAM_STAT_UPLOADING) { + usb_ep_dequeue(transfer->upstream.ep, t->req); + } + t->stalled = 1; + t->state = UPSTREAM_STAT_FREE; +} + +int rawbulk_push_upstream_buffer(int transfer_id, const void *buffer, + unsigned int length) { + int ret = -ENOENT; + struct upstream_transaction *t; + struct rawbulk_transfer *transfer; + struct usb_request *req; + + transfer = id_to_transfer(transfer_id); + if (!transfer) + return -ENODEV; + + /* search for free transfer */ + mutex_lock(&transfer->mutex); + list_for_each_entry(t, &transfer->upstream.transactions, tlist) { + if (t && t->state != UPSTREAM_STAT_UPLOADING) { + ret = 0; + break; + } + } + mutex_unlock(&transfer->mutex); + if (ret < 0) { + terr(t, "no entry for transfer %d while pushing!\n", transfer_id); + return ret; + } + req = t->req; + if (!req) + return -EINVAL; + + /* copy the buffer */ + memcpy(t->buffer, buffer, length); + dump_data(transfer, "pushing up", t->buffer, length); + + req->status = 0; + req->length = length; + req->buf = t->buffer; + req->complete = upstream_final_stage; + req->zero = (length > 0)? 0: 1; + + t->state = UPSTREAM_STAT_UPLOADING; + ret = usb_ep_queue(transfer->upstream.ep, req, GFP_ATOMIC); + if (ret < 0) { + terr(t, "fail to queue request, %d", ret); + t->stalled = 1; + t->state = UPSTREAM_STAT_FREE; + return ret; + } + return 0; +} +EXPORT_SYMBOL_GPL(rawbulk_push_upstream_buffer); + +/* + * downstream + */ + +#define MAX_SPLIT_LIMITED 64 + +#define DOWNSTREAM_STAT_FREE 0 +#define DOWNSTREAM_STAT_RETRIEVING 1 +#define DOWNSTREAM_STAT_DOWNLOADING 2 + +struct downstream_transaction { + int state; + int stalled; + char name[32]; + struct list_head tlist; + struct rawbulk_transfer *transfer; + struct usb_request *req; + int nurb; + struct urb *urb[MAX_SPLIT_LIMITED]; + int urb_length; + int buffer_length; + unsigned char buffer[0]; +}; + +static void downstream_second_stage(struct usb_ep *ep, + struct usb_request *req); +static void downstream_final_stage(struct urb *urb); + +static struct downstream_transaction *alloc_downstream_transaction( + struct rawbulk_transfer *transfer, int bufsz, int urbsz) { + int n = 0; + struct downstream_transaction *t; + int maxurbs = bufsz / urbsz; + + if (urbsz < get_maxpacksize(transfer->downstream.host_ep)) + return NULL; + + if (maxurbs > MAX_SPLIT_LIMITED) + return NULL; + + t = kzalloc(sizeof *t + bufsz * sizeof(unsigned char), GFP_KERNEL); + if (!t) + return NULL; + + t->buffer_length = bufsz; + t->urb_length = urbsz; + + t->req = usb_ep_alloc_request(transfer->downstream.ep, GFP_KERNEL); + if (!t->req) + goto failto_alloc_usb_request; + + for (n = 0; n < maxurbs; n ++) { + t->urb[n] = usb_alloc_urb(0, GFP_KERNEL); + if (!t->urb[n]) + goto failto_alloc_urb; + } + t->nurb = 0; + + t->name[0] = 0; + sprintf(t->name, "D%d (G:%s, H:ep%dout)", transfer->downstream.ntrans, + transfer->downstream.ep->name, + get_epnum(transfer->downstream.host_ep)); + + INIT_LIST_HEAD(&t->tlist); + list_add_tail(&t->tlist, &transfer->downstream.transactions); + transfer->downstream.ntrans ++; + t->transfer = transfer; + + t->state = DOWNSTREAM_STAT_FREE; + return t; + +failto_alloc_urb: + while (n --) + usb_free_urb(t->urb[n]); + usb_ep_free_request(transfer->downstream.ep, t->req); +failto_alloc_usb_request: + kfree(t); + return NULL; +} + +static void free_downstream_transaction(struct rawbulk_transfer *transfer, int option) { + int i; + int maxurbs; + struct list_head *p, *n; + list_for_each_safe(p, n, &transfer->downstream.transactions) { + struct downstream_transaction *t = list_entry(p, struct + downstream_transaction, tlist); + + if ((option & FREE_STALLED) && t->stalled) + goto free; + + if ((option & FREE_IDLED) && (t->state == DOWNSTREAM_STAT_FREE)) + goto free; + + if ((option & FREE_RETRIVING) && + (t->state == DOWNSTREAM_STAT_RETRIEVING)) { + usb_ep_dequeue(transfer->downstream.ep, t->req); + goto free; + } + + continue; +free: + list_del(p); + maxurbs = t->buffer_length / t->urb_length; + for (i = 0; i < maxurbs; i ++) + usb_free_urb(t->urb[i]); + usb_ep_free_request(transfer->downstream.ep, t->req); + kfree(t); + + transfer->downstream.ntrans --; + } +} + +static int start_downstream(struct downstream_transaction *t) { + int rc; + unsigned long flags; + struct rawbulk_transfer *transfer = t->transfer; + struct usb_request *req = t->req; + + spin_lock_irqsave(&transfer->lock, flags); + if (transfer->control & STOP_DOWNSTREAM) { + spin_unlock_irqrestore(&transfer->lock, flags); + t->state = DOWNSTREAM_STAT_FREE; + t->stalled = 1; + return -EPIPE; + } + spin_unlock_irqrestore(&transfer->lock, flags); + + t->stalled = 0; + t->state = DOWNSTREAM_STAT_FREE; + + req->buf = t->buffer; + req->length = t->buffer_length; + req->complete = downstream_second_stage; + + rc = usb_ep_queue(transfer->downstream.ep, req, GFP_ATOMIC); + if (rc < 0) { + terr(t, "fail to queue request, %d", rc); + t->stalled = 1; + return rc; + } + + t->state = DOWNSTREAM_STAT_RETRIEVING; + return 0; +} + +static void downstream_second_stage(struct usb_ep *ep, + struct usb_request *req) { + int n = 0; + void *next_buf = req->buf; + int data_length = req->actual; + struct downstream_transaction *t = container_of(req->buf, + struct downstream_transaction, buffer); + struct rawbulk_transfer *transfer = t->transfer; + + if (req->status) { + if (req->status != -ECONNRESET) + terr(t, "req status %d", req->status); + if (req->status == -ESHUTDOWN ||req->status == -ECONNRESET ) + t->stalled = 1; + else + start_downstream(t); + return; + } + + dump_data(transfer, "downstream", t->buffer, req->actual); + /* + if (!data_length) { + terr(t, "req actual 0"); + start_downstream(t); + return; + } + */ + + /* split recieved data into servral urb size less than maxpacket of cbp + * endpoint */ + do { + int rc; + struct urb *urb = t->urb[n]; + int tsize = (data_length > t->urb_length)? t->urb_length: + data_length; + + urb->status = 0; + usb_fill_bulk_urb(urb, rawbulk->udev, + usb_sndbulkpipe(rawbulk->udev, + get_epnum(transfer->downstream.host_ep)), + next_buf, tsize, downstream_final_stage, t); + usb_anchor_urb(urb, &transfer->submitted); + + rc = usb_submit_urb(urb, GFP_ATOMIC); + usb_mark_last_busy(rawbulk->udev); + if (rc < 0) { + terr(t, "fail to submit urb %d, n %d", rc, n); + usb_unanchor_urb(urb); + break; + } + + data_length -= tsize; + next_buf += tsize; + n ++; + } while (data_length > 0); + + t->nurb = n; + t->state = DOWNSTREAM_STAT_DOWNLOADING; +} + +static void downstream_final_stage(struct urb *urb) { + int n; + int actual_total = 0; + struct downstream_transaction *t = urb->context; + + if (urb->status < 0) { + printk("WTF?! - %s failed %d\n", __func__, urb->status); + } + + /* check the condition, re-queue usb_request on gadget ep again if possible */ + for (n = 0; n < t->nurb; n ++) { + int status = t->urb[n]->status; + + if (status == -EINPROGRESS) + return; + + if (!status) + actual_total += t->urb[n]->actual_length; + } + + if (actual_total < t->req->actual) { + terr(t, "requested %d actual %d", t->req->actual, actual_total); + t->stalled = 1; + return; + } + + /* completely transfered over, we can start next transfer now */ + start_downstream(t); +} + +static void stop_downstream(struct downstream_transaction *t) { + int n; + struct rawbulk_transfer *transfer = t->transfer; + int maxurbs = t->buffer_length / t->urb_length; + + if (t->state == DOWNSTREAM_STAT_RETRIEVING) { + usb_ep_dequeue(transfer->downstream.ep, t->req); + } else if (t->state == DOWNSTREAM_STAT_DOWNLOADING) { + for (n = 0; n < maxurbs; n ++) + usb_unlink_urb(t->urb[n]); + } + + t->stalled = 1; +} + +/* + * Ctrlrequest forwarding + */ + +static void response_complete(struct usb_ep *ep, struct usb_request *req) { + if (req->status < 0) + ldbg("feedback error %d\n", req->status); +} + +static void forward_ctrl_complete(struct urb *urb) { + struct usb_composite_dev *cdev = urb->context; + if (!cdev) + return; + + if (urb->status >= 0) { + struct usb_request *req = cdev->req; + if (urb->pipe & USB_DIR_IN) { + memcpy(req->buf, urb->transfer_buffer, urb->actual_length); + req->zero = 0; + req->length = urb->actual_length; + req->complete = response_complete; + ldbg("cp echo: len %d data[0] %02x", req->length, *((char *)req->buf)); + usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + } else /* Finish the status stage */ + usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + } +} + +int rawbulk_forward_ctrlrequest(const struct usb_ctrlrequest *ctrl) { + struct usb_device *dev; + struct urb *urb; + + ldbg("req_type %02x req %02x value %04x index %04x len %d", + ctrl->bRequestType, ctrl->bRequest, ctrl->wValue, ctrl->wIndex, + ctrl->wLength); + + dev = rawbulk->udev; + if (!dev) { + return -ENODEV; + } + + rawbulk->forward_dr.bRequestType = ctrl->bRequestType; + rawbulk->forward_dr.bRequest = ctrl->bRequest; + rawbulk->forward_dr.wValue = ctrl->wValue; + //rawbulk->forward_dr.wIndex = ctrl->wIndex; + rawbulk->forward_dr.wIndex = 0;//MODEM DATA PORT + rawbulk->forward_dr.wLength = ctrl->wLength; + + if (!rawbulk->forwarding_urb) { + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + return -ENOMEM; + rawbulk->forwarding_urb = urb; + } else + urb = rawbulk->forwarding_urb; + + if (ctrl->bRequestType & USB_DIR_IN) + usb_fill_control_urb(urb, dev, + usb_rcvctrlpipe(dev, 0), + (unsigned char *)&rawbulk->forward_dr, + rawbulk->ctrl_response, + __le16_to_cpu(ctrl->wLength), + forward_ctrl_complete, + rawbulk->cdev); + else + usb_fill_control_urb(urb, dev, + usb_sndctrlpipe(dev, 0), + (unsigned char *)&rawbulk->forward_dr, + NULL, 0, + forward_ctrl_complete, + rawbulk->cdev); + + return usb_submit_urb(urb, GFP_ATOMIC); +} + +EXPORT_SYMBOL_GPL(rawbulk_forward_ctrlrequest); + +int rawbulk_start_transactions(int transfer_id, int nups, int ndowns, int upsz, + int downsz, int splitsz, int pushable) { + int n; + int rc; + unsigned long flags = 0; + int suspended; + struct rawbulk_transfer *transfer; + struct upstream_transaction *upstream; + struct downstream_transaction *downstream; + + transfer = id_to_transfer(transfer_id); + if (!transfer) + return -ENODEV; + + if (!rawbulk->cdev || !rawbulk->udev) + return -ENODEV; + + if (!transfer->function || !transfer->interface) + return -ENODEV; + + ldbg("start transactions on id %d, nups %d ndowns %d upsz %d downsz %d split %d pushable %d\n", + transfer_id, nups, ndowns, upsz, downsz, splitsz, pushable); + + mutex_lock(&transfer->mutex); + + /* stop host transfer 1stly */ + if (transfer->inceptor) + transfer->inceptor(transfer->interface, pushable? + (RAWBULK_INCEPT_FLAG_ENABLE | RAWBULK_INCEPT_FLAG_PUSH_WAY): + RAWBULK_INCEPT_FLAG_ENABLE); + + /* Make upstream buffer larger than the pusher */ + if (pushable && (upsz < 4096)) + upsz = 4096; + + for (n = 0; n < nups; n ++) { + upstream = alloc_upstream_transaction(transfer, upsz, pushable); + if (!upstream) { + rc = -ENOMEM; + ldbg("fail to allocate upstream transaction n %d", n); + goto failto_alloc_upstream; + } + } + + for (n = 0; n < ndowns; n ++) { + downstream = alloc_downstream_transaction(transfer, downsz, splitsz); + if (!downstream) { + rc = -ENOMEM; + ldbg("fail to allocate downstream transaction n %d", n); + goto failto_alloc_downstream; + } + } + + spin_lock_irqsave(&transfer->suspend_lock, flags); + suspended = transfer->suspended; + spin_unlock_irqrestore(&transfer->suspend_lock, flags); + + if (suspended) { + ldbg("interface %d is sleeping", transfer_id); + mutex_unlock(&transfer->mutex); + return 0; + } + + transfer->control &= ~STOP_UPSTREAM; + if (!pushable) { + list_for_each_entry(upstream, &transfer->upstream.transactions, tlist) { + if (upstream->state == UPSTREAM_STAT_FREE && !upstream->stalled) { + rc = start_upstream(upstream, GFP_KERNEL); + if (rc < 0) { + ldbg("fail to start upstream %s rc %d\n", upstream->name, rc); + goto failto_start_upstream; + } + } + } + } + + transfer->control &= ~STOP_DOWNSTREAM; + list_for_each_entry(downstream, &transfer->downstream.transactions, tlist) { + if (downstream->state == DOWNSTREAM_STAT_FREE && !downstream->stalled) { + rc = start_downstream(downstream); + if (rc < 0) { + ldbg("fail to start downstream %s rc %d\n", downstream->name, rc); + goto failto_start_downstream; + } + } + } + + mutex_unlock(&transfer->mutex); + return 0; + +failto_start_downstream: + list_for_each_entry(downstream, &transfer->downstream.transactions, tlist) + stop_downstream(downstream); +failto_start_upstream: + list_for_each_entry(upstream, &transfer->upstream.transactions, tlist) + stop_upstream(upstream); +failto_alloc_downstream: + free_downstream_transaction(transfer, FREE_ALL); +failto_alloc_upstream: + free_upstream_transaction(transfer, FREE_ALL); + /* recover host transfer */ + if (transfer->inceptor) + transfer->inceptor(transfer->interface, 0); + mutex_unlock(&transfer->mutex); + return rc; +} + +EXPORT_SYMBOL_GPL(rawbulk_start_transactions); + +void rawbulk_stop_transactions(int transfer_id) { + unsigned long flags; + struct rawbulk_transfer *transfer; + struct upstream_transaction *upstream; + struct downstream_transaction *downstream; + + transfer = id_to_transfer(transfer_id); + if (!transfer) + return; + + mutex_lock(&transfer->mutex); + spin_lock_irqsave(&transfer->lock, flags); + transfer->control |= (STOP_UPSTREAM | STOP_DOWNSTREAM); + spin_unlock_irqrestore(&transfer->lock, flags); + + list_for_each_entry(upstream, &transfer->upstream.transactions, tlist) + stop_upstream(upstream); + free_upstream_transaction(transfer, FREE_ALL); + list_for_each_entry(downstream, &transfer->downstream.transactions, tlist) + stop_downstream(downstream); + free_downstream_transaction(transfer, FREE_ALL); + if (transfer->inceptor) + transfer->inceptor(transfer->interface, 0); + mutex_unlock(&transfer->mutex); +} + +EXPORT_SYMBOL_GPL(rawbulk_stop_transactions); + +int rawbulk_halt_transactions(int transfer_id) { + unsigned long flags; + struct rawbulk_transfer *transfer; + struct upstream_transaction *upstream; + struct downstream_transaction *downstream; + + transfer = id_to_transfer(transfer_id); + if (!transfer) + return -ENODEV; + + spin_lock_irqsave(&transfer->lock, flags); + transfer->control |= (STOP_UPSTREAM | STOP_DOWNSTREAM); + spin_unlock_irqrestore(&transfer->lock, flags); + + list_for_each_entry(upstream, &transfer->upstream.transactions, tlist) + stop_upstream(upstream); + list_for_each_entry(downstream, &transfer->downstream.transactions, tlist) + stop_downstream(downstream); + return 0; +} + +EXPORT_SYMBOL_GPL(rawbulk_halt_transactions); + +int rawbulk_resume_transactions(int transfer_id) { + int rc; + struct rawbulk_transfer *transfer; + struct upstream_transaction *upstream; + struct downstream_transaction *downstream; + + transfer = id_to_transfer(transfer_id); + if (!transfer) + return -ENODEV; + + if (!rawbulk->cdev || !rawbulk->udev) + return -ENODEV; + + if (!transfer->function || !transfer->interface) + return -ENODEV; + + transfer->control &= ~STOP_UPSTREAM; + list_for_each_entry(upstream, &transfer->upstream.transactions, tlist) { + if (upstream->state == UPSTREAM_STAT_FREE && !upstream->stalled) { + rc = start_upstream(upstream, GFP_KERNEL); + if (rc < 0) { + ldbg("fail to start upstream %s rc %d", upstream->name, rc); + goto failto_start_upstream; + } + } + } + + transfer->control &= ~STOP_DOWNSTREAM; + list_for_each_entry(downstream, &transfer->downstream.transactions, tlist) { + if (downstream->state == DOWNSTREAM_STAT_FREE && !downstream->stalled) { + rc = start_downstream(downstream); + if (rc < 0) { + ldbg("fail to start downstream %s rc %d", downstream->name, rc); + goto failto_start_downstream; + } + } + } + return 0; + +failto_start_downstream: + list_for_each_entry(downstream, &transfer->downstream.transactions, tlist) + stop_downstream(downstream); +failto_start_upstream: + list_for_each_entry(upstream, &transfer->upstream.transactions, tlist) + stop_upstream(upstream); + return rc; +} + +EXPORT_SYMBOL_GPL(rawbulk_resume_transactions); + +int rawbulk_transfer_state(int transfer_id) { + int stalled = 1; + int count = 0; + struct rawbulk_transfer *transfer; + struct upstream_transaction *upstream; + struct downstream_transaction *downstream; + + transfer = id_to_transfer(transfer_id); + if (!transfer) + return -EINVAL; + + if (!rawbulk->udev || !rawbulk->cdev) + return -ENODEV; + + if (!transfer->interface || !transfer->function) + return -EIO; + + list_for_each_entry(upstream, &transfer->upstream.transactions, tlist) { + if (!upstream->stalled) + stalled = 0; + count ++; + } + + if (stalled || count == 0) + return -EACCES; + + list_for_each_entry(downstream, &transfer->downstream.transactions, tlist) { + if (!downstream->stalled) + stalled = 0; + count ++; + } + if (stalled || count == 0) + return -EACCES; + + return 0; +} + +EXPORT_SYMBOL_GPL(rawbulk_transfer_state); + +static char *state2string(int state, int upstream) { + if (upstream) { + switch (state) { + case UPSTREAM_STAT_FREE: + return "FREE"; + case UPSTREAM_STAT_RETRIEVING: + return "RETRIEVING"; + case UPSTREAM_STAT_UPLOADING: + return "UPLOADING"; + default: + return "UNKNOW"; + } + } else { + switch (state) { + case DOWNSTREAM_STAT_FREE: + return "FREE"; + case DOWNSTREAM_STAT_RETRIEVING: + return "RETRIEVING"; + case DOWNSTREAM_STAT_DOWNLOADING: + return "DOWNLOADING"; + default: + return "UNKNOW"; + } + } +} + +int rawbulk_transfer_statistics(int transfer_id, char *buf) { + char *pbuf = buf; + struct rawbulk_transfer *transfer; + struct upstream_transaction *upstream; + struct downstream_transaction *downstream; + + transfer = id_to_transfer(transfer_id); + if (!transfer) + return sprintf(pbuf, "-ENODEV, id %d\n", transfer_id); + + pbuf += sprintf(pbuf, "rawbulk statistics:\n"); + if (rawbulk->udev) + pbuf += sprintf(pbuf, " host device: %d-%d\n", rawbulk->udev->bus->busnum, + rawbulk->udev->devnum); + else + pbuf += sprintf(pbuf, " host device: -ENODEV\n"); + if (rawbulk->cdev && rawbulk->cdev->config) + pbuf += sprintf(pbuf, " gadget device: %s\n", + rawbulk->cdev->config->label); + else + pbuf += sprintf(pbuf, " gadget device: -ENODEV\n"); + pbuf += sprintf(pbuf, " upstreams (total %d transactions)\n", + transfer->upstream.ntrans); + list_for_each_entry(upstream, &transfer->upstream.transactions, tlist) { + pbuf += sprintf(pbuf, " %s state: %s", upstream->name, + state2string(upstream->state, 1)); + pbuf += sprintf(pbuf, ", maxbuf: %d bytes", upstream->buffer_length); + if (upstream->stalled) + pbuf += sprintf(pbuf, " (stalled!)"); + pbuf += sprintf(pbuf, "\n"); + } + pbuf += sprintf(pbuf, " downstreams (total %d transactions)\n", + transfer->downstream.ntrans); + list_for_each_entry(downstream, &transfer->downstream.transactions, tlist) { + pbuf += sprintf(pbuf, " %s state: %s", downstream->name, + state2string(downstream->state, 0)); + pbuf += sprintf(pbuf, ", maxbuf: %d bytes", downstream->buffer_length); + if (downstream->state == DOWNSTREAM_STAT_DOWNLOADING) + pbuf += sprintf(pbuf, ", spliting: %d urbs(%d bytes)", downstream->nurb, + downstream->urb_length); + if (downstream->stalled) + pbuf += sprintf(pbuf, " (stalled!)"); + pbuf += sprintf(pbuf, "\n"); + } + pbuf += sprintf(pbuf, "\n"); + return (int)(pbuf - buf); +} + +EXPORT_SYMBOL_GPL(rawbulk_transfer_statistics); + +int rawbulk_bind_function(int transfer_id, struct usb_function *function, struct + usb_ep *bulk_out, struct usb_ep *bulk_in, + rawbulk_autoreconn_callback_t autoreconn_callback) { + struct rawbulk_transfer *transfer; + + if (!function || !bulk_out || !bulk_in) + return -EINVAL; + + transfer = id_to_transfer(transfer_id); + if (!transfer) + return -ENODEV; + + transfer->downstream.ep = bulk_out; + transfer->upstream.ep = bulk_in; + transfer->function = function; + rawbulk->cdev = function->config->cdev; + + transfer->autoreconn = autoreconn_callback; + return 0; +} + +EXPORT_SYMBOL_GPL(rawbulk_bind_function); + +void rawbulk_unbind_function(int transfer_id) { + int n; + int no_functions = 1; + struct rawbulk_transfer *transfer; + + transfer = id_to_transfer(transfer_id); + if (!transfer) + return; + + rawbulk_stop_transactions(transfer_id); + transfer->downstream.ep = NULL; + transfer->upstream.ep = NULL; + transfer->function = NULL; + + for (n = 0; n < _MAX_TID; n ++) { + if (!!rawbulk->transfer[n].function) + no_functions = 0; + } + + if (no_functions) + rawbulk->cdev = NULL; +} + +EXPORT_SYMBOL_GPL(rawbulk_unbind_function); + +int rawbulk_bind_host_interface(struct usb_interface *interface, + rawbulk_intercept_t inceptor) { + int n; + int transfer_id; + struct rawbulk_transfer *transfer; + struct usb_device *udev; + + if (!interface || !inceptor) + return -EINVAL; + + transfer_id = interface->cur_altsetting->desc.bInterfaceNumber; + transfer = id_to_transfer(transfer_id); + if (!transfer) + return -ENODEV; + + udev = interface_to_usbdev(interface); + if (!udev) + return -ENODEV; + + if (!rawbulk->udev) { + rawbulk->udev = udev; + } + + /* search host bulk ep for up/downstreams */ + for (n = 0; n < interface->cur_altsetting->desc.bNumEndpoints; n++) { + struct usb_host_endpoint *endpoint = + &interface->cur_altsetting->endpoint[n]; + if (usb_endpoint_is_bulk_out(&endpoint->desc)) + transfer->upstream.host_ep = endpoint; + if (usb_endpoint_is_bulk_in(&endpoint->desc)) + transfer->downstream.host_ep = endpoint; + } + + if (!transfer->upstream.host_ep || !transfer->downstream.host_ep) { + lerr("endpoints do not match bulk pair that needed\n"); + return -EINVAL; + } + + transfer->interface = interface; + transfer->inceptor = inceptor; + + if (transfer->autoreconn) + transfer->autoreconn(transfer->id); + + return 0; +} + +EXPORT_SYMBOL_GPL(rawbulk_bind_host_interface); + +void rawbulk_unbind_host_interface(struct usb_interface *interface) { + int n; + int no_interfaces = 1; + struct rawbulk_transfer *transfer; + int transfer_id = interface->cur_altsetting->desc.bInterfaceNumber; + + transfer = id_to_transfer(transfer_id); + if (!transfer) + return; + + rawbulk_stop_transactions(transfer_id); + transfer->upstream.host_ep = NULL; + transfer->downstream.host_ep = NULL; + transfer->interface = NULL; + transfer->inceptor = NULL; + + for (n = 0; n < _MAX_TID; n ++) { + if(!!rawbulk->transfer[n].interface) + no_interfaces = 0; + } + + if (no_interfaces) { + usb_kill_urb(rawbulk->forwarding_urb); + usb_free_urb(rawbulk->forwarding_urb); + rawbulk->forwarding_urb = NULL; + rawbulk->udev = NULL; + } +} + +EXPORT_SYMBOL_GPL(rawbulk_unbind_host_interface); + +int rawbulk_suspend_host_interface(int transfer_id, pm_message_t message) { + unsigned long flags = 0; + struct rawbulk_transfer *transfer; + + transfer = id_to_transfer(transfer_id); + if (!transfer) + return -EINVAL; + + spin_lock_irqsave(&transfer->suspend_lock, flags); + transfer->suspended = 1; + spin_unlock_irqrestore(&transfer->suspend_lock, flags); + + return 0; +} + +EXPORT_SYMBOL_GPL(rawbulk_suspend_host_interface); + +int rawbulk_resume_host_interface(int transfer_id) { + int rc; + unsigned long flags = 0; + struct rawbulk_transfer *transfer; + struct upstream_transaction *upstream; + struct downstream_transaction *downstream; + + transfer = id_to_transfer(transfer_id); + if (!transfer) + return -EINVAL; + + spin_lock_irqsave(&transfer->suspend_lock, flags); + transfer->suspended = 0; + spin_unlock_irqrestore(&transfer->suspend_lock, flags); + + list_for_each_entry(upstream, &transfer->upstream.transactions, tlist) { + if (upstream->stalled) { + transfer->control &= ~STOP_UPSTREAM; + /* restart transaction again */ + ldbg("restart upstream: %s", upstream->name); + stop_upstream(upstream); + rc = start_upstream(upstream, GFP_KERNEL); + if (rc < 0) { + ldbg("fail to start upstream %s rc %d", upstream->name, rc); + goto failto_start_upstream; + } + } + } + + list_for_each_entry(downstream, &transfer->downstream.transactions, tlist) { + if (downstream->stalled) { + transfer->control &= ~STOP_DOWNSTREAM; + ldbg("restart downstream: %s", downstream->name); + stop_downstream(downstream); + rc = start_downstream(downstream); + if (rc < 0) { + ldbg("fail to start downstream %s rc %d", downstream->name, rc); + goto failto_start_downstream; + } + } + } + + return 0; +failto_start_downstream: + list_for_each_entry(downstream, &transfer->downstream.transactions, tlist) + stop_downstream(downstream); +failto_start_upstream: + list_for_each_entry(upstream, &transfer->upstream.transactions, tlist) + stop_upstream(upstream); + return rc; +} + +EXPORT_SYMBOL_GPL(rawbulk_resume_host_interface); + +static __init int rawbulk_init(void) { + int n; + + rawbulk = kzalloc(sizeof *rawbulk, GFP_KERNEL); + if (!rawbulk) + return -ENOMEM; + + for (n = 0; n < _MAX_TID; n ++) { + struct rawbulk_transfer *t = &rawbulk->transfer[n]; + + t->id = n; + INIT_LIST_HEAD(&t->upstream.transactions); + INIT_LIST_HEAD(&t->downstream.transactions); + + mutex_init(&t->mutex); + spin_lock_init(&t->lock); + spin_lock_init(&t->suspend_lock); + t->suspended = 0; + t->control = STOP_UPSTREAM | STOP_DOWNSTREAM; + + init_usb_anchor(&t->submitted); + } + + return 0; +} + +fs_initcall (rawbulk_init); + +static __exit void rawbulk_exit(void) { + int n; + for (n = 0; n < _MAX_TID; n ++) + rawbulk_stop_transactions(n); + + if (rawbulk->forwarding_urb) { + usb_kill_urb(rawbulk->forwarding_urb); + usb_free_urb(rawbulk->forwarding_urb); + rawbulk->forwarding_urb = NULL; + } + + kfree(rawbulk); +} + +module_exit (rawbulk_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL"); + |