/* * 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 " #define DRIVER_DESC "Rawbulk Gadget - transport data from CP to Gadget" #define DRIVER_VERSION "1.0.1" #define DRIVER_NAME "usb_rawbulk" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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");