diff options
Diffstat (limited to 'drivers/usb/gadget/rawbulk.c')
-rwxr-xr-x | drivers/usb/gadget/rawbulk.c | 1272 |
1 files changed, 1272 insertions, 0 deletions
diff --git a/drivers/usb/gadget/rawbulk.c b/drivers/usb/gadget/rawbulk.c new file mode 100755 index 00000000..5c027da4 --- /dev/null +++ b/drivers/usb/gadget/rawbulk.c @@ -0,0 +1,1272 @@ +/* + * Rawbulk Gadget Function 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. + * + */ + +/* #define DEBUG */ +/* #define VERBOSE_DEBUG */ + +#define DRIVER_AUTHOR "Karfield Chen <kfchen@via-telecom.com>" +#define DRIVER_DESC "Rawbulk Gadget - transport data from CP to Gadget" +#define DRIVER_VERSION "1.0.1" +#define DRIVER_NAME "usb_rawbulk" + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/err.h> +#include <linux/wakelock.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/types.h> +#include <linux/device.h> + +#include <linux/tty.h> +#include <linux/tty_driver.h> +#include <linux/tty_flip.h> + +#include <linux/usb/composite.h> +#include <linux/usb/rawbulk.h> + +#ifdef DEBUG +#define ldbg(f, a...) printk(KERN_DEBUG "%s - " f "\n", __func__, ##a) +#else +#define ldbg(...) {} +#endif + +/* sysfs attr idx assignment */ +enum _attr_idx { + ATTR_ENABLE = 0, /** enable switch for Rawbulk */ +#ifdef SUPPORT_LEGACY_CONTROL + ATTR_ENABLE_C, /** enable switch too, but for legacy */ +#endif + ATTR_AUTORECONN, /** enable to rebind cp when it reconnect */ + ATTR_STATISTICS, /** Rawbulk summary/statistics for one pipe */ + ATTR_NUPS, /** upstream transfers count */ + ATTR_NDOWNS, /** downstram transfers count */ + ATTR_UPSIZE, /** upstream buffer for each transaction */ + ATTR_DOWNSIZE, /** downstram buffer for each transaction */ + ATTR_SPLIT, /** split option for downstream */ + ATTR_PUSHABLE, /** set to use push-way for upstream */ + ATTR_DTR, /** DTR control, only for Data-Call port */ +}; + +/* USB gadget framework is not strong enough, and not be compatiable with some + * controller, such as musb. + * in musb driver, the usb_request's member list is used internally, but in some + * applications it used in function driver too. to avoid this, here we + * re-wrap the usb_request */ +struct usb_request_wrapper { + struct list_head list; + struct usb_request *request; + int length; + struct rawbulk_function *fn; + char buffer[0]; +}; + +static struct usb_request_wrapper *get_wrapper(struct usb_request *req) { + if (!req->buf) + return NULL; + return container_of(req->buf, struct usb_request_wrapper, buffer); +} + +/* Internal TTY functions/data */ +static int rawbulk_tty_activate(struct tty_port *port, struct tty_struct + *tty); +static void rawbulk_tty_deactivate(struct tty_port *port); +static int rawbulk_tty_install(struct tty_driver *driver, struct tty_struct + *tty); +static void rawbulk_tty_cleanup(struct tty_struct *tty); +static int rawbulk_tty_open(struct tty_struct *tty, struct file *flip); +static void rawbulk_tty_hangup(struct tty_struct *tty); +static void rawbulk_tty_close(struct tty_struct *tty, struct file *flip); +static int rawbulk_tty_write(struct tty_struct *tty, const unsigned char *buf, + int count); +static int rawbulk_tty_write_room(struct tty_struct *tty); +static int rawbulk_tty_chars_in_buffer(struct tty_struct *tty); +static void rawbulk_tty_throttle(struct tty_struct *tty); +static void rawbulk_tty_unthrottle(struct tty_struct *tty); +static void rawbulk_tty_set_termios(struct tty_struct *tty, struct ktermios + *old); +static int rawbulk_tty_tiocmget(struct tty_struct *tty); +static int rawbulk_tty_tiocmset(struct tty_struct *tty, unsigned int set, + unsigned int clear); + +static struct tty_port_operations rawbulk_tty_port_ops = { + .activate = rawbulk_tty_activate, + .shutdown = rawbulk_tty_deactivate, +}; + +static struct tty_operations rawbulk_tty_ops = { + .open = rawbulk_tty_open, + .close = rawbulk_tty_close, + .write = rawbulk_tty_write, + .hangup = rawbulk_tty_hangup, + .write_room = rawbulk_tty_write_room, + .set_termios = rawbulk_tty_set_termios, + .throttle = rawbulk_tty_throttle, + .unthrottle = rawbulk_tty_unthrottle, + .chars_in_buffer = rawbulk_tty_chars_in_buffer, + .tiocmget = rawbulk_tty_tiocmget, + .tiocmset = rawbulk_tty_tiocmset, + .cleanup = rawbulk_tty_cleanup, + .install = rawbulk_tty_install, +}; + +struct tty_driver *rawbulk_tty_driver; + +struct rawbulk_function *prealloced_functions[_MAX_TID] = { NULL }; +struct rawbulk_function *rawbulk_lookup_function(int transfer_id) { + if (transfer_id >= 0 && transfer_id < _MAX_TID) + return prealloced_functions[transfer_id]; + return NULL; +} +EXPORT_SYMBOL_GPL(rawbulk_lookup_function); + +static struct rawbulk_function *lookup_by_tty_minor(int minor) { + int n; + struct rawbulk_function *fn; + for (n = 0; n < _MAX_TID; n ++) { + fn = prealloced_functions[n]; + if (fn->tty_minor == minor) + return fn; + } + return NULL; +} + +static inline int check_enable_state(struct rawbulk_function *fn) { + int enab; + unsigned long flags; + spin_lock_irqsave(&fn->lock, flags); + enab = fn->enable? 1: 0; + spin_unlock_irqrestore(&fn->lock, flags); + return enab; +} + +int rawbulk_check_enable(struct rawbulk_function *fn) { + return check_enable_state(fn); +} +EXPORT_SYMBOL_GPL(rawbulk_check_enable); + +static inline int check_tty_opened(struct rawbulk_function *fn) { + int opened; + unsigned long flags; + spin_lock_irqsave(&fn->lock, flags); + opened = fn->tty_opened? 1: 0; + spin_unlock_irqrestore(&fn->lock, flags); + return opened; +} + +static inline void set_enable_state(struct rawbulk_function *fn, int enab) { + unsigned long flags; + spin_lock_irqsave(&fn->lock, flags); + fn->enable = !!enab; + spin_unlock_irqrestore(&fn->lock, flags); +} + +void rawbulk_disable_function(struct rawbulk_function *fn) { + set_enable_state(fn, 0); +} +EXPORT_SYMBOL_GPL(rawbulk_disable_function); + +static inline void set_tty_opened(struct rawbulk_function *fn, int opened) { + unsigned long flags; + spin_lock_irqsave(&fn->lock, flags); + fn->tty_opened = !!opened; + spin_unlock_irqrestore(&fn->lock, flags); +} + +#define port_to_rawbulk(p) container_of(p, struct rawbulk_function, port) +#define function_to_rawbulk(f) container_of(f, struct rawbulk_function, function) + +static struct usb_request_wrapper *get_req(struct list_head *head, spinlock_t + *lock) { + unsigned long flags; + struct usb_request_wrapper *req = NULL; + spin_lock_irqsave(lock, flags); + if (!list_empty(head)) { + req = list_first_entry(head, struct usb_request_wrapper, list); + list_del(&req->list); + } + spin_unlock_irqrestore(lock, flags); + return req; +} + +static void put_req(struct usb_request_wrapper *req, struct list_head *head, + spinlock_t *lock) { + unsigned long flags; + spin_lock_irqsave(lock, flags); + list_add_tail(&req->list, head); + spin_unlock_irqrestore(lock, flags); +} + +static void move_req(struct usb_request_wrapper *req, struct list_head *head, + spinlock_t *lock) { + unsigned long flags; + spin_lock_irqsave(lock, flags); + list_move_tail(&req->list, head); + spin_unlock_irqrestore(lock, flags); +} + +static void insert_req(struct usb_request_wrapper *req, struct list_head *head, + spinlock_t *lock) { + unsigned long flags; + spin_lock_irqsave(lock, flags); + list_add(&req->list, head); + spin_unlock_irqrestore(lock, flags); +} + +static int control_dtr(int set) { + struct usb_ctrlrequest ctrl = { + .bRequestType = USB_DIR_OUT | USB_TYPE_VENDOR, //0x40 + .bRequest = 0x01, + .wLength = 0, + .wIndex = 0, + }; + + ctrl.wValue = cpu_to_le16(!!set); + return rawbulk_forward_ctrlrequest(&ctrl); +} + +static void init_endpoint_desc(struct usb_endpoint_descriptor *epdesc, int in, + int maxpacksize) { + struct usb_endpoint_descriptor template = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + }; + + *epdesc = template; + if (in) + epdesc->bEndpointAddress = USB_DIR_IN; + else + epdesc->bEndpointAddress = USB_DIR_OUT; + epdesc->wMaxPacketSize = cpu_to_le16(maxpacksize); +} + +static void init_interface_desc(struct usb_interface_descriptor *ifdesc) { + struct usb_interface_descriptor template = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = 0xff,//USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = 0xff, + .bInterfaceProtocol = 0xff, + .iInterface = 0, + }; + + *ifdesc = template; +} + +static ssize_t rawbulk_attr_show(struct device *dev, struct device_attribute + *attr, char *buf); +static ssize_t rawbulk_attr_store(struct device *dev, struct device_attribute + *attr, const char *buf, size_t count); + +static inline void add_device_attr(struct rawbulk_function *fn, int n, const char + *name, int mode) { + if (n < MAX_ATTRIBUTES) { + fn->attr[n].attr.name = name; + fn->attr[n].attr.mode = mode; + fn->attr[n].show = rawbulk_attr_show; + fn->attr[n].store = rawbulk_attr_store; + } +} + +static int which_attr(struct rawbulk_function *fn, struct device_attribute + *attr) { + int n; + for (n = 0; n < fn->max_attrs; n ++) { + if (attr == &fn->attr[n]) + return n; + } + return -1; +} + +static ssize_t rawbulk_attr_show(struct device *dev, struct device_attribute *attr, + char *buf) { + int n; + int idx; + int enab; + struct rawbulk_function *fn; + + for (n = 0; n < _MAX_TID; n ++) { + fn = rawbulk_lookup_function(n); + if (fn->dev == dev) { + idx = which_attr(fn, attr); + break; + } +#ifdef SUPPORT_LEGACY_CONTROL + if (!strcmp(attr->attr.name, fn->shortname)) { + idx = ATTR_ENABLE_C; + break; + } +#endif + } + + if (n == _MAX_TID) + return 0; + + enab = check_enable_state(fn); + switch (idx) { + case ATTR_ENABLE: + return sprintf(buf, "%d", enab); +#ifdef SUPPORT_LEGACY_CONTROL + case ATTR_ENABLE_C: + return sprintf(buf, "%s", enab? "gadget": "tty"); +#endif + case ATTR_AUTORECONN: + return sprintf(buf, "%d", fn->autoreconn); + case ATTR_STATISTICS: + return rawbulk_transfer_statistics(fn->transfer_id, buf); + case ATTR_NUPS: + return sprintf(buf, "%d", enab? fn->nups: -1); + case ATTR_NDOWNS: + return sprintf(buf, "%d", enab? fn->ndowns: -1); + case ATTR_UPSIZE: + return sprintf(buf, "%d", enab? fn->upsz: -1); + case ATTR_DOWNSIZE: + return sprintf(buf, "%d", enab? fn->downsz: -1); + case ATTR_SPLIT: + return sprintf(buf, "%d", enab? fn->splitsz: -1); + case ATTR_PUSHABLE: + return sprintf(buf, "%d", fn->pushable); + default: + break; + } + return 0; +} + +static ssize_t rawbulk_attr_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) { + int n; + int rc = 0; + int idx = -1; + int nups; + int ndowns; + int upsz; + int downsz; + int splitsz; + int pushable; + struct rawbulk_function *fn; + + for (n = 0; n < _MAX_TID; n ++) { + fn = rawbulk_lookup_function(n); + if (fn->dev == dev) { + idx = which_attr(fn, attr); + break; + } +#ifdef SUPPORT_LEGACY_CONTROL + if (!strcmp(attr->attr.name, fn->shortname)) { + idx = ATTR_ENABLE_C; + break; + } +#endif + } + + if (idx < 0) { + printk(KERN_ERR "sorry, I cannot find rawbulk fn '%s'\n", attr->attr.name); + goto exit; + } + + nups = fn->nups; + ndowns = fn->ndowns; + upsz = fn->upsz; + downsz = fn->downsz; + splitsz = fn->splitsz; + pushable = fn->pushable; + + /* find out which attr(file) we write */ + +#ifdef SUPPORT_LEGACY_CONTROL + if (idx <= ATTR_ENABLE_C) +#else + if (idx == ATTR_ENABLE) +#endif + { + int enable; + if (idx == ATTR_ENABLE) { + enable = simple_strtoul(buf, NULL, 10); +#ifdef SUPPORT_LEGACY_CONTROL + } else if (idx == ATTR_ENABLE_C) { + if (!strncmp(buf, "tty", 3)) + enable = 0; + else if (!strncmp(buf, "gadget", 6)) + enable = 1; + else { + printk(KERN_ERR "invalid option(%s) for bypass, try again...\n", buf); + goto exit; + } +#endif /* SUPPORT_LEGACY_CONTROL */ + } else + goto exit; + + if (check_enable_state(fn) != (!!enable)) { + set_enable_state(fn, enable); + if (!!enable && fn->activated) { + printk(KERN_DEBUG "enable rawbulk %s channel, been activated? %s\n", + fn->shortname, fn->activated? "yes": "no"); + if (fn->transfer_id == RAWBULK_TID_MODEM) { + /* clear DTR to endup last session between AP and CP */ + control_dtr(1); + msleep(10); + control_dtr(0); + } + + /* Stop TTY i/o */ + rawbulk_tty_stop_io(fn); + + /* Start rawbulk transfer */ + wake_lock(&fn->keep_awake); + rc = rawbulk_start_transactions(fn->transfer_id, nups, + ndowns, upsz, downsz, splitsz, pushable); + if (rc < 0) { + set_enable_state(fn, !enable); + rawbulk_tty_start_io(fn); + } + } else { + /* Stop rawbulk transfer */ + printk(KERN_DEBUG "disable rawbulk %s channel, been activated? %s\n", + fn->shortname, fn->activated? "yes": "no"); + rawbulk_stop_transactions(fn->transfer_id); + if (fn->transfer_id == RAWBULK_TID_MODEM) { + /* clear DTR automatically when disable modem rawbulk */ + control_dtr(1); + msleep(10); + control_dtr(0); + } + wake_unlock(&fn->keep_awake); + + /* Start TTY i/o */ + rawbulk_tty_start_io(fn); + } + } + } else if (idx == ATTR_DTR) { + if (fn->transfer_id == RAWBULK_TID_MODEM) { + if (check_enable_state(fn)) + control_dtr(simple_strtoul(buf, NULL, 10)); + } + } else if (idx == ATTR_AUTORECONN) { + fn->autoreconn = !!simple_strtoul(buf, NULL, 10); + } else { + int val = simple_strtoul(buf, NULL, 10); + switch (idx) { + case ATTR_NUPS: nups = val; break; + case ATTR_NDOWNS: ndowns = val; break; + case ATTR_UPSIZE: upsz = val; break; + case ATTR_DOWNSIZE: downsz = val; break; + case ATTR_SPLIT: splitsz = val; break; + case ATTR_PUSHABLE: + pushable = !!val; + fn->pushable = pushable; + break; + default: goto exit; + } + + if (!check_enable_state(fn)) + goto exit; + + if (!fn->activated) + goto exit; + + rawbulk_stop_transactions(fn->transfer_id); + rc = rawbulk_start_transactions(fn->transfer_id, nups, ndowns, upsz, + downsz, splitsz, pushable); + if (rc >= 0) { + fn->nups = nups; + fn->ndowns = ndowns; + fn->upsz = upsz; + fn->downsz = downsz; + fn->splitsz = splitsz; + fn->pushable = pushable; + } else { + rawbulk_stop_transactions(fn->transfer_id); + wake_unlock(&fn->keep_awake); + set_enable_state(fn, 0); + } + } + +exit: + return count; +} + +static int rawbulk_create_files(struct rawbulk_function *fn) { + int n, rc; + for (n = 0; n < fn->max_attrs; n ++){ +#ifdef SUPPORT_LEGACY_CONTROL + if (n == ATTR_ENABLE_C) + continue; +#endif + rc = device_create_file(fn->dev, &fn->attr[n]); + if (rc < 0) { + while (--n >= 0) { +#ifdef SUPPORT_LEGACY_CONTROL + if (n == ATTR_ENABLE_C) + continue; +#endif + device_remove_file(fn->dev, &fn->attr[n]); + } + return rc; + } + } + return 0; +} + +static void rawbulk_remove_files(struct rawbulk_function *fn) { + int n = fn->max_attrs; + while (--n >= 0) { +#ifdef SUPPORT_LEGACY_CONTROL + if (n == ATTR_ENABLE_C) + continue; +#endif + device_remove_file(fn->dev, &fn->attr[n]); + } +} + +#define RAWBULK_TTY_MINORS 255 +static int tty_minors[RAWBULK_TTY_MINORS] = { 0 }; +static int alloc_tty_minor(struct rawbulk_function *fn) { + int n; + if (check_tty_opened(fn)) + printk(KERN_WARNING "ttyRB%d has opened yet %s has been disconnected\n", + fn->tty_minor, fn->longname); + for (n = 0; n < RAWBULK_TTY_MINORS; n ++) { + if (tty_minors[n] == 0) { + tty_minors[n] = fn->transfer_id + 0x8000; + return n; + } + } + return -ENODEV; +} + +static void free_tty_minor(struct rawbulk_function *fn) { + int n; + struct tty_struct **ttys = rawbulk_tty_driver->ttys; + if (!check_tty_opened(fn)) + tty_minors[fn->tty_minor] = 0; + /* Clear the closed tty minors of this port */ + for (n = 0; n < RAWBULK_TTY_MINORS; n ++) { + if ((tty_minors[n] == fn->transfer_id + 0x8000) && ttys[n]) { + struct tty_struct *tty = ttys[n]; + if (tty->count > 0) + continue; /* Keep the minor while tty is being used */ + if (test_bit(TTY_CLOSING, &tty->flags)) { + /* FIXME: Cannot close the tty_struct! */ + printk(KERN_DEBUG "cannot recycle the minor %d because " \ + "the TTY_CLOSING flags is still on\n", n); + continue; + } + tty_minors[n] = 0; + } + } +} + +int rawbulk_register_tty(struct rawbulk_function *fn) { + struct device *dev; + + spin_lock_init(&fn->tx_lock); + spin_lock_init(&fn->rx_lock); + INIT_LIST_HEAD(&fn->tx_free); + INIT_LIST_HEAD(&fn->rx_free); + INIT_LIST_HEAD(&fn->tx_inproc); + INIT_LIST_HEAD(&fn->rx_inproc); + INIT_LIST_HEAD(&fn->rx_throttled); + + fn->tty_minor = alloc_tty_minor(fn); + if (fn->tty_minor < 0) + return -ENODEV; + fn->tty_opened = 0; + fn->last_pushed = 0; + tty_port_init(&fn->port); + fn->port.ops = &rawbulk_tty_port_ops; + + /* Bring up the tty device */ + dev = tty_register_device(rawbulk_tty_driver, fn->tty_minor, fn->dev); + if (IS_ERR(dev)) + printk(KERN_ERR "Failed to attach ttyRB%d to %s device.\n", + fn->tty_minor, fn->longname); + + return 0; +} +EXPORT_SYMBOL_GPL(rawbulk_register_tty); + +void rawbulk_unregister_tty(struct rawbulk_function *fn) { + tty_unregister_device(rawbulk_tty_driver, fn->tty_minor); + free_tty_minor(fn); +} +EXPORT_SYMBOL_GPL(rawbulk_unregister_tty); + +/******************************************************************************/ + +/* Call this after all request has detached! */ +void rawbulk_tty_free_request(struct rawbulk_function *fn) { + struct usb_request_wrapper *req; + + while ((req = get_req(&fn->rx_free, &fn->rx_lock))) { + usb_ep_free_request(fn->bulk_out, req->request); + kfree(req); + } + + while ((req = get_req(&fn->tx_free, &fn->tx_lock))) { + usb_ep_free_request(fn->bulk_in, req->request); + kfree(req); + } +} +EXPORT_SYMBOL_GPL(rawbulk_tty_free_request); + +static void rawbulk_tty_rx_complete(struct usb_ep *ep, struct usb_request *req); +static void rawbulk_tty_tx_complete(struct usb_ep *ep, struct usb_request *req); + +int rawbulk_tty_alloc_request(struct rawbulk_function *fn) { + int n; + struct usb_request_wrapper *req; + unsigned long flags; + + spin_lock_irqsave(&fn->lock, flags); + if (!fn->bulk_out || !fn->bulk_in) { + spin_unlock_irqrestore(&fn->lock, flags); + return -ENODEV; + } + spin_unlock_irqrestore(&fn->lock, flags); + + /* Allocate and init rx request */ + for (n = 0; n < MAX_TTY_RX; n ++) { + req = kmalloc(sizeof(struct usb_request_wrapper) + + MAX_TTY_RX_PACKAGE, GFP_KERNEL); + if (!req) + break; + + req->request = usb_ep_alloc_request(fn->bulk_out, GFP_KERNEL); + if (!req->request) { + kfree(req); + break; + } + + req->fn = fn; + INIT_LIST_HEAD(&req->list); + req->length = MAX_TTY_RX_PACKAGE; + req->request->buf = req->buffer; + req->request->length = MAX_TTY_RX_PACKAGE; + req->request->complete = rawbulk_tty_rx_complete; + put_req(req, &fn->rx_free, &fn->rx_lock); + } + + if (n < MAX_TTY_RX) { + /* free allocated request */ + rawbulk_tty_free_request(fn); + return -ENOMEM; + } + + /* Allocate and init tx request */ + for (n = 0; n < MAX_TTY_TX; n ++) { + req = kmalloc(sizeof(struct usb_request_wrapper) + + MAX_TTY_TX_PACKAGE, GFP_KERNEL); + if (!req) + break; + + req->request = usb_ep_alloc_request(fn->bulk_in, GFP_KERNEL); + if (!req->request) { + kfree(req); + break; + } + + req->fn = fn; + INIT_LIST_HEAD(&req->list); + req->length = MAX_TTY_TX_PACKAGE; + req->request->zero = 0; + req->request->buf = req->buffer; + req->request->complete = rawbulk_tty_tx_complete; + put_req(req, &fn->tx_free, &fn->tx_lock); + } + + if (n < MAX_TTY_TX) { + /* free allocated request */ + rawbulk_tty_free_request(fn); + return -ENOMEM; + } + + return 0; +} +EXPORT_SYMBOL_GPL(rawbulk_tty_alloc_request); + +int rawbulk_tty_stop_io(struct rawbulk_function *fn) { + struct usb_request_wrapper *req; + + if (!check_tty_opened(fn)) + return -ENODEV; + + while ((req = get_req(&fn->rx_inproc, &fn->rx_lock))) { + if (req->request->status == -EINPROGRESS) { + put_req(req, &fn->rx_inproc, &fn->rx_lock); + usb_ep_dequeue(fn->bulk_out, req->request); + } + } + + while ((req = get_req(&fn->tx_inproc, &fn->tx_lock))) { + if (req->request->status == -EINPROGRESS) { + put_req(req, &fn->tx_inproc, &fn->tx_lock); + usb_ep_dequeue(fn->bulk_in, req->request); + } + } + return 0; +} +EXPORT_SYMBOL_GPL(rawbulk_tty_stop_io); + +int rawbulk_tty_start_io(struct rawbulk_function *fn) { + int ret; + struct usb_request_wrapper *req; + + if (!check_tty_opened(fn)) + return -ENODEV; + + while ((req = get_req(&fn->rx_free, &fn->rx_lock))) { + put_req(req, &fn->rx_inproc, &fn->rx_lock); + ret = usb_ep_queue(fn->bulk_out, req->request, GFP_ATOMIC); + if (ret < 0) { + move_req(req, &fn->rx_free, &fn->rx_lock); + return ret; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(rawbulk_tty_start_io); + +/* Complete the data send stage */ +static void rawbulk_tty_tx_complete(struct usb_ep *ep, struct usb_request *req) { + struct usb_request_wrapper *r = get_wrapper(req); + struct rawbulk_function *fn = r->fn; + + move_req(r, &fn->tx_free, &fn->tx_lock); +} + +static void rawbulk_tty_push_data(struct rawbulk_function *fn) { + struct usb_request_wrapper *r; + struct tty_struct *tty; + + tty = tty_port_tty_get(&fn->port); + tty->minimum_to_wake = 1; /* wakeup to read even pushed only once */ + /* walk the throttled list, push all the data to TTY */ + while ((r = get_req(&fn->rx_throttled, &fn->rx_lock))) { + char *sbuf; + int push_count; + struct usb_request *req = r->request; + + if (req->status < 0) { + put_req(r, &fn->rx_free, &fn->rx_lock); + continue; + } + sbuf = req->buf + fn->last_pushed; + push_count = req->actual - fn->last_pushed; + if (push_count) { + int count = tty_insert_flip_string(tty, sbuf, push_count); + if (count < push_count) { + /* We met throttled again */ + fn->last_pushed += count; + if (count) + tty_flip_buffer_push(tty); + insert_req(r, &fn->rx_throttled, &fn->rx_lock); + break; + } + tty_flip_buffer_push(tty); + fn->last_pushed = 0; + } + put_req(r, &fn->rx_free, &fn->rx_lock); + } + tty_kref_put(tty); +} + +/* Recieve data from host */ +static void rawbulk_tty_rx_complete(struct usb_ep *ep, struct usb_request *req) { + int ret; + struct usb_request_wrapper *r = get_wrapper(req); + struct rawbulk_function *fn = r->fn; + struct tty_struct *tty = fn->tty; + + if (req->status < 0 || !check_tty_opened(fn)) { + move_req(r, &fn->rx_free, &fn->rx_lock); + return; + } + + if (test_bit(TTY_THROTTLED, &tty->flags)) { + /* Give up to resubmitted the request */ + move_req(r, &fn->rx_free, &fn->rx_lock); + return; + } else { + /* Move the request to the throttled queue */ + move_req(r, &fn->rx_throttled, &fn->rx_lock); + /* Start to push data */ + rawbulk_tty_push_data(fn); + } + + /* Re-queue the request again */ + while ((r = get_req(&fn->rx_free, &fn->rx_lock))) { + put_req(r, &fn->rx_inproc, &fn->rx_lock); + ret = usb_ep_queue(fn->bulk_out, r->request, GFP_ATOMIC); + if (ret < 0) { + move_req(r, &fn->rx_free, &fn->rx_lock); + break; + } + } +} + +/* Start to queue request on BULK OUT endpoint, when tty is try to open */ +static int rawbulk_tty_activate(struct tty_port *port, struct tty_struct + *tty) { + struct rawbulk_function *fn = port_to_rawbulk(port); + /* Low latency for tty */ + tty->low_latency = 1; + tty->termios->c_cc[VMIN] = 1; + return rawbulk_tty_start_io(fn); +} + +static void rawbulk_tty_deactivate(struct tty_port *port) { + struct usb_request_wrapper *req; + struct rawbulk_function *fn = port_to_rawbulk(port); + + /* This is a little different from stop_io */ + while ((req = get_req(&fn->rx_inproc, &fn->rx_lock))) { + put_req(req, &fn->rx_inproc, &fn->rx_lock); + usb_ep_dequeue(fn->bulk_out, req->request); + } + + while ((req = get_req(&fn->rx_throttled, &fn->rx_lock))) + put_req(req, &fn->rx_free, &fn->rx_lock); + + while ((req = get_req(&fn->tx_inproc, &fn->tx_lock))) { + put_req(req, &fn->tx_inproc, &fn->tx_lock); + usb_ep_dequeue(fn->bulk_in, req->request); + } +} + +static int rawbulk_tty_write(struct tty_struct *tty, const unsigned char *buf, + int count) { + int ret; + int submitted = 0; + struct usb_request_wrapper *req; + struct rawbulk_function *fn = tty->driver_data; + + if (check_enable_state(fn)) + return -EBUSY; + + /* Get new request(s) that freed, fill it, queue it to the endpoint */ + while ((req = get_req(&fn->tx_free, &fn->tx_lock))) { + int length = count - submitted; + if (length <= 0) { + put_req(req, &fn->tx_free, &fn->tx_lock); + break; + } + if (length > MAX_TTY_TX_PACKAGE) + length = MAX_TTY_TX_PACKAGE; + memcpy(req->buffer, buf + submitted, length); + req->request->length = length; + put_req(req, &fn->tx_inproc, &fn->tx_lock); + ret = usb_ep_queue(fn->bulk_in, req->request, GFP_ATOMIC); + if (ret < 0) { + move_req(req, &fn->tx_free, &fn->tx_lock); + return ret; + } + submitted += length; + } + + return submitted; +} + +static int rawbulk_tty_write_room(struct tty_struct *tty) { + int room = 0; + unsigned long flags; + struct usb_request_wrapper *req; + struct rawbulk_function *fn = tty->driver_data; + + if (check_enable_state(fn)) + return 0; + + spin_lock_irqsave(&fn->tx_lock, flags); + list_for_each_entry(req, &fn->tx_free, list) + room += req->length; + spin_unlock_irqrestore(&fn->tx_lock, flags); + + return room; +} + +static int rawbulk_tty_chars_in_buffer(struct tty_struct *tty) { + int chars = 0; + unsigned long flags; + struct usb_request_wrapper *req; + struct rawbulk_function *fn = tty->driver_data; + + if (check_enable_state(fn)) + return 0; + + spin_lock_irqsave(&fn->rx_lock, flags); + list_for_each_entry(req, &fn->rx_throttled, list) { + if (req->request->status < 0) + continue; + chars += req->request->actual; + } + chars -= fn->last_pushed; + spin_unlock_irqrestore(&fn->rx_lock, flags); + + return chars; +} + +static void rawbulk_tty_throttle(struct tty_struct *tty) { + struct usb_request_wrapper *req; + struct rawbulk_function *fn = tty->driver_data; + + /* Stop the processing requests */ + while ((req = get_req(&fn->rx_inproc, &fn->rx_lock))) { + if (req->request->status == -EINPROGRESS) { + put_req(req, &fn->rx_inproc, &fn->rx_lock); + usb_ep_dequeue(fn->bulk_out, req->request); + } + } +} + +static void rawbulk_tty_unthrottle(struct tty_struct *tty) { + int ret; + struct usb_request_wrapper *req; + struct rawbulk_function *fn = tty->driver_data; + + /* Try to push the throttled requests' data to TTY */ + rawbulk_tty_push_data(fn); + + /* Restart the free requests */ + while ((req = get_req(&fn->rx_free, &fn->rx_lock))) { + put_req(req, &fn->rx_inproc, &fn->rx_lock); + ret = usb_ep_queue(fn->bulk_out, req->request, GFP_ATOMIC); + if (ret < 0) { + move_req(req, &fn->rx_free, &fn->rx_lock); + break; + } + } +} + +static void rawbulk_tty_set_termios(struct tty_struct *tty, struct ktermios + *old) { + //struct rawbulk_function *fn = tty->driver_data; +} + +static int rawbulk_tty_tiocmget(struct tty_struct *tty) { + //struct rawbulk_function *fn = tty->driver_data; + return 0; +} + +static int rawbulk_tty_tiocmset(struct tty_struct *tty, unsigned int set, + unsigned int clear) { + //struct rawbulk_function *fn = tty->driver_data; + return 0; +} + +static int rawbulk_tty_install(struct tty_driver *driver, struct tty_struct + *tty) { + int ret = 0; + struct rawbulk_function *fn = lookup_by_tty_minor(tty->index); + + if (!fn) + return -ENODEV; + + ret = tty_init_termios(tty); + if (ret < 0) + return ret; + + tty->driver_data = fn; + fn->tty = tty; + + /* Final install (we use the default method) */ + tty_driver_kref_get(driver); + tty->count++; + driver->ttys[tty->index] = tty; + return ret; +} + +static void rawbulk_tty_cleanup(struct tty_struct *tty) { + struct rawbulk_function *fn = lookup_by_tty_minor(tty->index); + tty->driver_data = NULL; + if (fn) + fn->tty = NULL; +} + +static int rawbulk_tty_open(struct tty_struct *tty, struct file *flip) { + int ret; + struct rawbulk_function *fn = lookup_by_tty_minor(tty->index); + + if (!fn) + return -ENODEV; + + tty->driver_data = fn; + fn->tty = tty; + + if (check_enable_state(fn)) + return -EBUSY; + + if (check_tty_opened(fn)) + return -EBUSY; + + set_tty_opened(fn, 1); + + ret = tty_port_open(&fn->port, tty, flip); + if (ret < 0) + return ret; + return 0; +} + +static void rawbulk_tty_hangup(struct tty_struct *tty) { + struct rawbulk_function *fn = tty->driver_data; + if (!fn) + return; + tty_port_hangup(&fn->port); +} + +static void rawbulk_tty_close(struct tty_struct *tty, struct file *flip) { + struct rawbulk_function *fn = tty->driver_data; + if (!fn) + return; + set_tty_opened(fn, 0); + tty_port_close(&fn->port, tty, flip); +} + +static __init int rawbulk_tty_init(void) { + int ret; + struct tty_driver *drv = alloc_tty_driver(_MAX_TID); + if (!drv) + return -ENOMEM; + + drv->owner = THIS_MODULE; + drv->driver_name = "rawbulkport"; + drv->name = "ttyRB";//prefix + drv->type = TTY_DRIVER_TYPE_SERIAL; + drv->subtype = SERIAL_TYPE_NORMAL; + drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; + drv->init_termios = tty_std_termios; + drv->init_termios.c_cflag = + B9600 | CS8 | CREAD | HUPCL | CLOCAL; + drv->init_termios.c_lflag = 0; + drv->init_termios.c_ispeed = 9600; + drv->init_termios.c_ospeed = 9600; + + tty_set_operations(drv, &rawbulk_tty_ops); + + ret = tty_register_driver(drv); + if (ret < 0) { + put_tty_driver(drv); + } else + rawbulk_tty_driver = drv; + return ret; +} + +/******************************************************************************/ +static struct class *rawbulk_class; + +static struct _function_init_stuff { + const char *longname; + const char *shortname; + const char *iString; + unsigned int ep_maxpkg; + unsigned int nups; + unsigned int ndowns; + unsigned int upsz; + unsigned int downsz; + unsigned int splitsz; + bool autoreconn; + bool pushable; +} _init_params[] = { + {"rawbulk-modem", "data", "Modem Port", 64, 16, 16, PAGE_SIZE, PAGE_SIZE, PAGE_SIZE, true, false }, + {"rawbulk-ets", "ets", "ETS Port", 512, 4, 1, PAGE_SIZE, PAGE_SIZE, PAGE_SIZE, true, false }, + {"rawbulk-at", "atc", "AT Channel", 64, 1, 1, PAGE_SIZE, PAGE_SIZE, PAGE_SIZE, true, false }, + {"rawbulk-pcv", "pcv", "PCM Voice", 64, 1, 1, PAGE_SIZE, PAGE_SIZE, PAGE_SIZE, true, false }, + {"rawbulk-gps", "gps", "LBS GPS Port", 64, 1, 1, PAGE_SIZE, PAGE_SIZE, PAGE_SIZE, true, false }, + { }, /* End of configurations */ +}; + +static __init struct rawbulk_function *rawbulk_alloc_function(int transfer_id) { + int rc; + struct rawbulk_function *fn; + struct _function_init_stuff *_init = &_init_params[transfer_id]; + + if (transfer_id == _MAX_TID) + return NULL; + + fn = kzalloc(sizeof *fn, GFP_KERNEL); + if (IS_ERR(fn)) + return NULL; + + /* init default features of rawbulk functions */ + fn->longname = _init->longname; + fn->shortname = _init->shortname; + fn->string_defs[0].s = _init->iString; + fn->nups = _init->nups; + fn->ndowns = _init->ndowns; + fn->upsz = _init->upsz; + fn->downsz = _init->downsz; + fn->splitsz = _init->splitsz; + fn->autoreconn = _init->autoreconn; + fn->pushable = _init->pushable; + + fn->tty_minor = -1; + /* init descriptors */ + init_interface_desc(&fn->interface); + init_endpoint_desc(&fn->fs_bulkin_endpoint, 1, _init->ep_maxpkg); + init_endpoint_desc(&fn->hs_bulkin_endpoint, 1, _init->ep_maxpkg); + init_endpoint_desc(&fn->fs_bulkout_endpoint, 0, _init->ep_maxpkg); + init_endpoint_desc(&fn->hs_bulkout_endpoint, 0, _init->ep_maxpkg); + + fn->fs_descs[INTF_DESC] = (struct usb_descriptor_header *) &fn->interface; + fn->fs_descs[BULKIN_DESC] = (struct usb_descriptor_header *) &fn->fs_bulkin_endpoint; + fn->fs_descs[BULKOUT_DESC] = (struct usb_descriptor_header *) &fn->fs_bulkout_endpoint; + + fn->hs_descs[INTF_DESC] = (struct usb_descriptor_header *) &fn->interface; + fn->hs_descs[BULKIN_DESC] = (struct usb_descriptor_header *) &fn->hs_bulkin_endpoint; + fn->hs_descs[BULKOUT_DESC] = (struct usb_descriptor_header *) &fn->hs_bulkout_endpoint; + + fn->string_table.language = 0x0409; + fn->string_table.strings = fn->string_defs; + fn->strings[0] = &fn->string_table; + fn->strings[1] = NULL; + + fn->transfer_id = transfer_id; + + /* init function callbacks */ + fn->function.strings = fn->strings; + fn->function.descriptors = fn->fs_descs; + fn->function.hs_descriptors = fn->hs_descs; + + /* init device attributes */ + add_device_attr(fn, ATTR_ENABLE, "enable", 0755); +#ifdef SUPPORT_LEGACY_CONTROL + add_device_attr(fn, ATTR_ENABLE_C, fn->shortname, 0755); +#endif + add_device_attr(fn, ATTR_AUTORECONN, "autoreconn", 0755); + add_device_attr(fn, ATTR_STATISTICS, "statistics", 0644); + add_device_attr(fn, ATTR_NUPS, "nups", 0755); + add_device_attr(fn, ATTR_NDOWNS, "ndowns", 0755); + add_device_attr(fn, ATTR_UPSIZE, "ups_size", 0755); + add_device_attr(fn, ATTR_DOWNSIZE, "downs_size", 0755); + add_device_attr(fn, ATTR_SPLIT, "split_size", 0755); + add_device_attr(fn, ATTR_PUSHABLE, "pushable", 0755); + + fn->max_attrs = ATTR_PUSHABLE + 1; + + if (transfer_id == RAWBULK_TID_MODEM) { + // printk(" kevin chagnge dtr for 200 for cts, disable usb est bypass \n"); + add_device_attr(fn, ATTR_DTR, "dtr", 0200); + fn->max_attrs ++; + } + + fn->dev = device_create(rawbulk_class, NULL, MKDEV(0, + fn->transfer_id), NULL, fn->shortname); + if (IS_ERR(fn->dev)) { + kfree(fn); + return NULL; + } + + rc = rawbulk_create_files(fn); + if (rc < 0) { + device_destroy(rawbulk_class, fn->dev->devt); + kfree(fn); + return NULL; + } + + spin_lock_init(&fn->lock); + wake_lock_init(&fn->keep_awake, WAKE_LOCK_SUSPEND, fn->longname); + return fn; +} + +static void rawbulk_destory_function(struct rawbulk_function *fn) { + if (!fn) + return; + wake_lock_destroy(&fn->keep_awake); + rawbulk_remove_files(fn); + device_destroy(rawbulk_class, fn->dev->devt); + kfree(fn); +} + +#ifdef SUPPORT_LEGACY_CONTROL +static struct attribute *legacy_sysfs[_MAX_TID + 1] = { NULL }; +static struct attribute_group legacy_sysfs_group = { + .attrs = legacy_sysfs, +}; +struct kobject *legacy_sysfs_stuff; +#endif /* SUPPORT_LEGACY_CONTROL */ + +static int __init init(void) { + int n; + int rc = 0; + + printk(KERN_INFO "rawbulk functions init.\n"); + + rawbulk_class = class_create(THIS_MODULE, "usb_rawbulk"); + if (IS_ERR(rawbulk_class)) + return PTR_ERR(rawbulk_class); + + for (n = 0; n < _MAX_TID; n ++) { + struct rawbulk_function *fn = rawbulk_alloc_function(n); + if (IS_ERR(fn)) { + while (n --) + rawbulk_destory_function(prealloced_functions[n]); + rc = PTR_ERR(fn); + break; + } + prealloced_functions[n] = fn; +#ifdef SUPPORT_LEGACY_CONTROL + legacy_sysfs[n] = &fn->attr[ATTR_ENABLE_C].attr; +#endif + } + + if (rc < 0) { + class_destroy(rawbulk_class); + return rc; + } + +#ifdef SUPPORT_LEGACY_CONTROL + /* make compatiable with old bypass sysfs access */ + legacy_sysfs_stuff = kobject_create_and_add("usb_bypass", NULL); + if (legacy_sysfs_stuff) { + rc = sysfs_create_group(legacy_sysfs_stuff, &legacy_sysfs_group); + if (rc < 0) + printk(KERN_ERR "failed to create legacy bypass sys-stuff, but continue...\n"); + } +#endif /* SUPPORT_LEGACY_CONTROL */ + + rc = rawbulk_tty_init(); + if (rc < 0) { + printk(KERN_ERR "failed to init rawbulk tty driver.\n"); + return rc; + } + + for (n = 0; n < _MAX_TID; n ++) { + struct rawbulk_function *fn = prealloced_functions[n]; + rc = rawbulk_register_tty(fn); + if (rc < 0) { + printk(KERN_ERR "fatal error: we cannot create ttyRB%d for %s\n", + n, fn->longname); + return rc; + } + } + + return 0; +} + +module_init(init); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL"); + |