--- linux/drivers/usb/acm.c-orig 2007-04-13 18:32:16.352672105 +0200 +++ linux/drivers/usb/acm.c 2007-04-13 18:33:21.063526545 +0200 @@ -124,6 +124,9 @@ #define ACM_CTRL_PARITY 0x20 #define ACM_CTRL_OVERRUN 0x40 +// some devices don't have one comm and one data interface, but only one interface with endpoints for comm and data +#define SINGLE_IF_ACM 0x01 + /* * Line speed and caracter encoding. */ @@ -139,6 +142,8 @@ * Internal driver structures. */ +#define TD_SIZE 16384 + struct acm { struct usb_device *dev; /* the coresponding usb device */ struct usb_interface *iface; /* the interfaces - +0 control +1 data */ @@ -153,12 +158,23 @@ unsigned int minor; /* acm minor number */ unsigned char throttle; /* throttled by tty layer */ unsigned char clocal; /* termios CLOCAL */ + unsigned long throttle_start; + unsigned char resubmit_to_unthrottle; /* Leftover data from last operation */ + unsigned char *throttle_data; + int td_len; + int td_busy; + unsigned char used_interfaces; + struct semaphore mutex; }; +#define mutex_lock(x) down(x) +#define mutex_unlock(x) up(x) + /* global params controlling max sizes for read, write, control */ static int maxszr = 0; static int maxszw = 0; static int maxszc = 0; +static int nonlegacy = 0; static struct usb_driver acm_driver; static struct tty_driver acm_tty_driver; @@ -167,6 +183,95 @@ #define ACM_READY(acm) (acm && acm->dev && acm->used) /* + * Helper functions to optimize throttleing + */ +static int +acm_fill_tty(struct urb *urb, struct tty_struct *tty, unsigned char *data, int length) +{ + struct acm *acm = urb->context; + int n = 0; + /*printk("acm_fill_tty: %d bytes\n", length);*/ + if (!urb->status && !acm->throttle) { + for (n = 0; n < length && !acm->throttle; n++) { + /* if we insert more than TTY_FLIPBUF_SIZE characters, + * we drop them. */ + if (tty->flip.count >= TTY_FLIPBUF_SIZE) { + tty_flip_buffer_push(tty); + } + tty_insert_flip_char(tty, data[n], 0); + } + tty_flip_buffer_push(tty); + } + /*printk("copied %d bytes.\n", n);*/ + return n; +} + +static int +acm_shift_if_throttle(unsigned char *data, int *length, int shift_by) +{ + if (shift_by < *length) { + dbg("need to shift uncopied %d bytes to front.", *length - shift_by); + memmove(data, data + shift_by, *length - shift_by); + *length -= shift_by; + return 1; + } + return 0; +} + +static int +acm_buffer_if_thottle(struct acm *acm, unsigned char *data, int start, int *length) +{ + int copied = *length; + if (start < *length) { + int space = TD_SIZE - acm->td_len; + int needed = *length - start; + copied = (space < needed)? space: needed; + dbg("need to push %d to throttle buffer, can copy %d.", + needed, copied); + memcpy(acm->throttle_data + acm->td_len, data, copied); + acm->td_len += copied; + *length -= copied; + } + return copied; +} + +static int +acm_empty_throttle(struct urb *urb, struct tty_struct *tty) +{ + unsigned long flags; + struct acm *acm = urb->context; + + save_flags(flags); + cli(); + + if (acm->td_busy) { + restore_flags(flags); + return 0; + } + acm->td_busy = 1; + restore_flags(flags); + + if (acm->td_len > 0) { + + dbg("acm_empty_throttle: trying to empty throttle buffer: %d bytes.", + acm->td_len); + + /* if there has been something left from previous operations + * we try to complete this before looking at the urb */ + int copied = acm_fill_tty(urb, tty, acm->throttle_data, acm->td_len); + if (acm_shift_if_throttle(acm->throttle_data, &acm->td_len, copied)) { + /* we were unable to empty the throttle data, so we can't + * copy anything more now */ + acm->td_busy = 0; + return 0; + } + acm->td_len = 0; + } + acm->td_busy = 0; + return 1; +} + +/* * Functions for ACM control messages. */ @@ -174,7 +279,10 @@ { int retval = usb_control_msg(acm->dev, usb_sndctrlpipe(acm->dev, 0), request, USB_RT_ACM, value, acm->iface[0].altsetting[0].bInterfaceNumber, buf, len, HZ * 5); - dbg("acm_control_msg: rq: 0x%02x val: %#x len: %#x result: %d", request, value, len, retval); + if (retval < 0) + err("acm_control_msg: rq: 0x%02x val: %#x len: %#x result: %d", request, value, len, retval); + else + dbg("acm_control_msg: rq: 0x%02x val: %#x len: %#x result: %d", request, value, len, retval); return retval < 0 ? retval : 0; } @@ -191,10 +299,12 @@ struct acm *acm = urb->context; struct usb_ctrlrequest *dr = urb->transfer_buffer; unsigned char *data = (unsigned char *)(dr + 1); - int newctrl; + int newctrl, s1, s2; if (!ACM_READY(acm)) return; + //err("acm_ctrl_irq %p %i", urb, dr->bRequestType); + if (urb->status < 0) { dbg("nonzero ctrl irq status received: %d", urb->status); return; @@ -226,8 +336,15 @@ return; + case 0x2a: + s1 = le32_to_cpup((__u32 *) data); + s2 = le32_to_cpup((__u32 *) (data+4)); + + dbg("acm.c: ctrl 0x2a: idx %i len %i speed %i %i", dr->wIndex, dr->wLength, s1, s2); + return; + default: - dbg("unknown control event received: request %d index %d len %d data0 %d data1 %d", + err("unknown control event received: request %d index %d len %d data0 %d data1 %d", dr->bRequest, dr->wIndex, dr->wLength, data[0], data[1]); return; } @@ -238,36 +355,39 @@ struct acm *acm = urb->context; struct tty_struct *tty = acm->tty; unsigned char *data = urb->transfer_buffer; - int i = 0; + int copied = 0; + int buffered = 0; if (!ACM_READY(acm)) return; - if (urb->status) - dbg("nonzero read bulk status received: %d", urb->status); + if (urb->status) { + err("nonzero read bulk status received: %d", urb->status); + } - if (!urb->status && !acm->throttle) { - for (i = 0; i < urb->actual_length && !acm->throttle; i++) { - /* if we insert more than TTY_FLIPBUF_SIZE characters, - * we drop them. */ - if (tty->flip.count >= TTY_FLIPBUF_SIZE) { - tty_flip_buffer_push(tty); - } - tty_insert_flip_char(tty, data[i], 0); - } - tty_flip_buffer_push(tty); + if (!acm_empty_throttle(urb, tty)) { + dbg("could not empty throttle buffer, entering throttle state, acm->td_busy: %d.", acm->td_busy); } + /* got here, either there was nothing in the throttle data or it could + * all be copied without throttleing again */ + copied = acm_fill_tty(urb, tty, data, urb->actual_length); if (acm->throttle) { - memmove(data, data + i, urb->actual_length - i); - urb->actual_length -= i; - return; + int length = urb->actual_length; + buffered = acm_buffer_if_thottle(acm, data, copied, &urb->actual_length); + if (buffered < length - copied + && acm_shift_if_throttle(data, &urb->actual_length, copied + buffered)) { + dbg("need to resubmit to unthrottle\n"); + acm->resubmit_to_unthrottle = 1; + return; + } } urb->actual_length = 0; urb->dev = acm->dev; - if (usb_submit_urb(urb)) + if (usb_submit_urb(urb)) { dbg("failed resubmitting read urb"); + } } static void acm_write_bulk(struct urb *urb) @@ -283,6 +403,9 @@ mark_bh(IMMEDIATE_BH); } +static int unlinking_in_progress=0; +static int closing=0; + static void acm_softint(void *private) { struct acm *acm = private; @@ -306,34 +429,57 @@ if (!acm || !acm->dev) return -EINVAL; + mutex_lock (&acm->mutex); + tty->driver_data = acm; acm->tty = tty; MOD_INC_USE_COUNT; - lock_kernel(); + if ( closing ) + err("acm_tty_open: potential possibility of race condition detected"); + + if ( unlinking_in_progress ) { + err("acm_tty_open: cannot open because unlinking_in_progress %i", acm->used); + mutex_unlock (&acm->mutex); + return -1; + } - if (acm->used++) { - unlock_kernel(); - return 0; - } + if (acm->used) { + acm->used++; + mutex_unlock (&acm->mutex); + return 0; + } - unlock_kernel(); + unlinking_in_progress=1; + err("acm_tty_open: %i %p !!", acm->used, tty); + + acm->resubmit_to_unthrottle = 0; + acm->td_len = 0; + acm->td_busy = 0; acm->ctrlurb.dev = acm->dev; if (usb_submit_urb(&acm->ctrlurb)) - dbg("usb_submit_urb(ctrl irq) failed"); + dbg("acm open: usb_submit_urb(ctrl irq) failed"); + else + dbg("acm open: ctrlurb %p submitted", &acm->ctrlurb); + + acm->used++; + acm_set_control(acm, acm->ctrlout = ACM_CTRL_DTR | ACM_CTRL_RTS); acm->readurb.dev = acm->dev; if (usb_submit_urb(&acm->readurb)) - dbg("usb_submit_urb(read bulk) failed"); - - acm_set_control(acm, acm->ctrlout = ACM_CTRL_DTR | ACM_CTRL_RTS); + dbg("acm open: usb_submit_urb(read bulk) failed"); + else + dbg("acm open: readurb %p submitted", &acm->readurb); - /* force low_latency on so that our tty_push actually forces the data through, + /* force low_latency on so that our tty_push actually forces the data through, otherwise it is scheduled, and with high data rates data can get lost. */ tty->low_latency = 1; + unlinking_in_progress=0; + mutex_unlock (&acm->mutex); + return 0; } @@ -343,19 +489,35 @@ if (!acm || !acm->used) return; - if (!--acm->used) { - if (acm->dev) { - acm_set_control(acm, acm->ctrlout = 0); - usb_unlink_urb(&acm->ctrlurb); - usb_unlink_urb(&acm->writeurb); - usb_unlink_urb(&acm->readurb); - } else { - tty_unregister_devfs(&acm_tty_driver, acm->minor); - acm_table[acm->minor] = NULL; - kfree(acm); - } + mutex_lock (&acm->mutex); + + closing = 1; + if (--acm->used) { + closing=0; + MOD_DEC_USE_COUNT; + mutex_unlock (&acm->mutex); + return; + } + unlinking_in_progress = 1; + + err("acm_tty_close: %i %p", acm->used, tty); + + if (acm->dev) { + acm_set_control(acm, acm->ctrlout = 0); + usb_unlink_urb(&acm->ctrlurb); + usb_unlink_urb(&acm->writeurb); + usb_unlink_urb(&acm->readurb); + } else { + tty_unregister_devfs(&acm_tty_driver, acm->minor); + acm_table[acm->minor] = NULL; + kfree(acm->throttle_data); + kfree(acm); } + + closing=0; + unlinking_in_progress = 0; MOD_DEC_USE_COUNT; + mutex_unlock (&acm->mutex); } static int acm_tty_write(struct tty_struct *tty, int from_user, const unsigned char *buf, int count) @@ -363,8 +525,16 @@ struct acm *acm = tty->driver_data; if (!ACM_READY(acm)) return -EINVAL; - if (acm->writeurb.status == -EINPROGRESS) return 0; - if (!count) return 0; + + if (acm->writeurb.status == -EINPROGRESS) { + dbg("tty_write in progress"); + return 0; + } + + if (!count) { + dbg("tty_write: nothing to write"); + return 0; + } count = (count > acm->writesize) ? acm->writesize : count; @@ -401,22 +571,44 @@ { struct acm *acm = tty->driver_data; if (!ACM_READY(acm)) return; + dbg("acm_tty_throttle ON %ld ---> %ld", jiffies-acm->throttle_start, jiffies); acm->throttle = 1; + acm->throttle_start = jiffies; } static void acm_tty_unthrottle(struct tty_struct *tty) { struct acm *acm = tty->driver_data; if (!ACM_READY(acm)) return; + dbg("acm_tty_throttle OFF %ld ---> %ld", jiffies, jiffies-acm->throttle_start); acm->throttle = 0; - if (acm->readurb.status != -EINPROGRESS) + + if (!acm_empty_throttle(&acm->readurb, tty)) { + if (acm->td_busy) { + printk("***** pending acm_empty_throttle!\n"); + } else { + dbg("throttle not emptied.\n"); + } + } + + if (acm->resubmit_to_unthrottle != 0) { + dbg("resubmit_to_unthrottle: acm_read_bulk"); + acm->resubmit_to_unthrottle = 0; acm_read_bulk(&acm->readurb); + } } static void acm_tty_break_ctl(struct tty_struct *tty, int state) { struct acm *acm = tty->driver_data; + if (!ACM_READY(acm)) return; + + if (nonlegacy) { + err("non-legacy port, skipping acm_tty_break_ctl"); + return; + } + if (acm_send_break(acm, state ? 0xffff : 0)) dbg("send break failed"); } @@ -455,7 +647,19 @@ case TIOCMBIC: newctrl &= ~mask; break; } - if (acm->ctrlout == newctrl) return 0; + if (acm->ctrlout == newctrl) { + dbg("acm_tty_ioctl: set old state %x", newctrl); + return 0; + } + + err("acm_tty_ioctl: %s%s%s -> dtr%s rts%s (%lx)", + cmd==TIOCMBIC?"Clear":(cmd==TIOCMBIS?"Set":"SET"), + mask & ACM_CTRL_DTR ? " DTR":"", + mask & ACM_CTRL_RTS ? " RTS":"", + newctrl & ACM_CTRL_DTR ? "+":"-", + newctrl & ACM_CTRL_RTS ? "+":"-", + arg); + return acm_set_control(acm, acm->ctrlout = newctrl); } @@ -483,6 +687,12 @@ if (!ACM_READY(acm)) return; + if (nonlegacy) { + acm->clocal = ((termios->c_cflag & CLOCAL) != 0); + dbg("non-legacy port, skipping acm_tty_set_termios"); + return; + } + newline.speed = cpu_to_le32p(acm_tty_speed + (termios->c_cflag & CBAUD & ~CBAUDEX) + (termios->c_cflag & CBAUDEX ? 15 : 0)); newline.stopbits = termios->c_cflag & CSTOPB ? 2 : 0; @@ -518,34 +727,64 @@ struct usb_config_descriptor *cfacm; struct usb_interface_descriptor *ifcom, *ifdata; struct usb_endpoint_descriptor *epctrl, *epread, *epwrite; - int readsize, ctrlsize, minor, i, j; + int readsize, ctrlsize, minor, i; unsigned char *buf; + unsigned char used_interfaces=2; for (i = 0; i < dev->descriptor.bNumConfigurations; i++) { cfacm = dev->config + i; - dbg("probing config %d", cfacm->bConfigurationValue); + ifcom = cfacm->interface[ifnum].altsetting + 0; + + if (id->driver_info == SINGLE_IF_ACM) { + printk("using single_if_acm\n"); + struct usb_endpoint_descriptor *ep=ifcom->endpoint; + int k; + + if (ifcom->bNumEndpoints != 3) { + continue; + } + + epctrl = epread = epwrite = NULL; + for (k=0; k<3; ++k, ++ep) { + if ((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT && + (ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) { + epctrl = ep; + } else if ((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_BULK && + (ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) { + epread = ep; + } else if ( (ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_BULK) { + epwrite = ep; + } + } - for (j = 0; j < cfacm->bNumInterfaces - 1; j++) { + if ( !epctrl || !epread || !epwrite ) { + dbg("SINGLE_IF_ACM acm_probe inv eps epctrl %s epread %s epwrite %s", epctrl?"ok":"missing", + epread?"ok":"missing", epwrite?"ok":"missing"); + dbg("SINGLE_IF_ACM Invalid enpoint configuration"); + continue; + } - if (usb_interface_claimed(cfacm->interface + j) || - usb_interface_claimed(cfacm->interface + j + 1)) + used_interfaces = 1; + } else { + if ((ifnum+1)>=cfacm->bNumInterfaces || usb_interface_claimed(cfacm->interface + ifnum + 1)) { + // no data interface available continue; + } - ifcom = cfacm->interface[j].altsetting + 0; - ifdata = cfacm->interface[j + 1].altsetting + 0; + ifdata = cfacm->interface[ifnum + 1].altsetting + 0; if (ifdata->bInterfaceClass != 10 || ifdata->bNumEndpoints < 2) { - ifcom = cfacm->interface[j + 1].altsetting + 0; - ifdata = cfacm->interface[j].altsetting + 0; + ifcom = cfacm->interface[ifnum + 1].altsetting + 0; + ifdata = cfacm->interface[ifnum].altsetting + 0; if (ifdata->bInterfaceClass != 10 || ifdata->bNumEndpoints < 2) continue; } if (ifcom->bInterfaceClass != 2 || ifcom->bInterfaceSubClass != 2 || - ifcom->bInterfaceProtocol < 1 || ifcom->bInterfaceProtocol > 6 || - ifcom->bNumEndpoints < 1) + ifcom->bInterfaceProtocol < 1 || ifcom->bInterfaceProtocol > 6 || + ifcom->bNumEndpoints < 1) continue; epctrl = ifcom->endpoint + 0; @@ -553,76 +792,86 @@ epwrite = ifdata->endpoint + 1; if ((epctrl->bEndpointAddress & 0x80) != 0x80 || (epctrl->bmAttributes & 3) != 3 || - (epread->bmAttributes & 3) != 2 || (epwrite->bmAttributes & 3) != 2 || - ((epread->bEndpointAddress & 0x80) ^ (epwrite->bEndpointAddress & 0x80)) != 0x80) + (epread->bmAttributes & 3) != 2 || (epwrite->bmAttributes & 3) != 2 || + ((epread->bEndpointAddress & 0x80) ^ (epwrite->bEndpointAddress & 0x80)) != 0x80) continue; - dbg("using interface %d\n", j); - if ((epread->bEndpointAddress & 0x80) != 0x80) { epread = ifdata->endpoint + 1; epwrite = ifdata->endpoint + 0; } + } - usb_set_configuration(dev, cfacm->bConfigurationValue); + usb_set_configuration(dev, cfacm->bConfigurationValue); - for (minor = 0; minor < ACM_TTY_MINORS && acm_table[minor]; minor++); - if (acm_table[minor]) { - err("no more free acm devices"); - return NULL; - } + for (minor = 0; minor < ACM_TTY_MINORS && acm_table[minor]; minor++); + if (acm_table[minor]) { + err("no more free acm devices"); + return NULL; + } - if (!(acm = kmalloc(sizeof(struct acm), GFP_KERNEL))) { - err("out of memory"); - return NULL; - } - memset(acm, 0, sizeof(struct acm)); + if (!(acm = kmalloc(sizeof(struct acm), GFP_KERNEL))) { + err("out of memory"); + return NULL; + } + memset(acm, 0, sizeof(struct acm)); - ctrlsize = (epctrl->wMaxPacketSize > maxszc)? - epctrl->wMaxPacketSize: maxszc; - readsize = (epread->wMaxPacketSize > maxszr)? - epread->wMaxPacketSize: maxszr; - acm->writesize = (epwrite->wMaxPacketSize > maxszw)? - epwrite->wMaxPacketSize: maxszw; - - acm->iface = cfacm->interface + j; - acm->minor = minor; - acm->dev = dev; - - acm->tqueue.routine = acm_softint; - acm->tqueue.data = acm; - - if (!(buf = kmalloc(ctrlsize + readsize + acm->writesize, GFP_KERNEL))) { - err("out of memory"); - kfree(acm); - return NULL; - } + ctrlsize = (epctrl->wMaxPacketSize > maxszc)? + epctrl->wMaxPacketSize: maxszc; + readsize = (epread->wMaxPacketSize > maxszr)? + epread->wMaxPacketSize: maxszr; + acm->writesize = (epwrite->wMaxPacketSize > maxszw)? + epwrite->wMaxPacketSize: maxszw; + + init_MUTEX (&acm->mutex); + if (!(acm->throttle_data = kmalloc(TD_SIZE * sizeof (*acm->throttle_data), GFP_KERNEL))) { + err("out of memory (throttle_data)"); + kfree(acm); + return NULL; + } + acm->iface = cfacm->interface + ifnum; + acm->minor = minor; + acm->dev = dev; - FILL_INT_URB(&acm->ctrlurb, dev, usb_rcvintpipe(dev, epctrl->bEndpointAddress), - buf, ctrlsize, acm_ctrl_irq, acm, epctrl->bInterval); + acm->used_interfaces = used_interfaces; - FILL_BULK_URB(&acm->readurb, dev, usb_rcvbulkpipe(dev, epread->bEndpointAddress), - buf += ctrlsize, readsize, acm_read_bulk, acm); - acm->readurb.transfer_flags |= USB_NO_FSBR; + acm->tqueue.routine = acm_softint; + acm->tqueue.data = acm; - FILL_BULK_URB(&acm->writeurb, dev, usb_sndbulkpipe(dev, epwrite->bEndpointAddress), - buf += readsize, acm->writesize, acm_write_bulk, acm); - acm->writeurb.transfer_flags |= USB_NO_FSBR; + if (!(buf = kmalloc(ctrlsize + readsize + acm->writesize, GFP_KERNEL))) { + err("out of memory (urb buf)"); + kfree(acm); + return NULL; + } - printk(KERN_INFO "ttyACM%d: USB ACM device\n", minor); + FILL_INT_URB(&acm->ctrlurb, dev, usb_rcvintpipe(dev, epctrl->bEndpointAddress), + buf, ctrlsize, acm_ctrl_irq, acm, epctrl->bInterval); - acm_set_control(acm, acm->ctrlout); + FILL_BULK_URB(&acm->readurb, dev, usb_rcvbulkpipe(dev, epread->bEndpointAddress), + buf += ctrlsize, readsize, acm_read_bulk, acm); + acm->readurb.transfer_flags |= USB_NO_FSBR; - acm->line.speed = cpu_to_le32(9600); - acm->line.databits = 8; - acm_set_line(acm, &acm->line); + FILL_BULK_URB(&acm->writeurb, dev, usb_sndbulkpipe(dev, epwrite->bEndpointAddress), + buf += readsize, acm->writesize, acm_write_bulk, acm); + acm->writeurb.transfer_flags |= USB_NO_FSBR; - usb_driver_claim_interface(&acm_driver, acm->iface + 0, acm); - usb_driver_claim_interface(&acm_driver, acm->iface + 1, acm); + printk(KERN_INFO "ttyACM%d: USB ACM device C %p W %p R %p %x\n", minor, &acm->ctrlurb, + &acm->writeurb, &acm->readurb, acm->ctrlout); + + acm_set_control(acm, acm->ctrlout); - tty_register_devfs(&acm_tty_driver, 0, minor); - return acm_table[minor] = acm; + acm->line.speed = cpu_to_le32(9600); + acm->line.databits = 8; + acm_set_line(acm, &acm->line); + + if ( acm->used_interfaces == 2 ) { + // only just checked interface is claimed automatically, so claim data interface too + usb_driver_claim_interface(&acm_driver, acm->iface + 1, acm); } + + tty_register_devfs(&acm_tty_driver, 0, minor); + + return acm_table[minor] = acm; } return NULL; @@ -646,7 +895,9 @@ kfree(acm->ctrlurb.transfer_buffer); usb_driver_release_interface(&acm_driver, acm->iface + 0); - usb_driver_release_interface(&acm_driver, acm->iface + 1); + if ( acm->used_interfaces == 2 ) { + usb_driver_release_interface(&acm_driver, acm->iface + 1); + } if (!acm->used) { tty_unregister_devfs(&acm_tty_driver, acm->minor); @@ -665,6 +916,9 @@ static struct usb_device_id acm_ids[] = { { USB_DEVICE(0x22B8, 0x1005) }, /* Motorola TimePort 280 */ + { USB_DEVICE(0x05C6, 0x7001), driver_info: SINGLE_IF_ACM }, /* Siemens HC15/HC25 */ + { USB_DEVICE(0x0681, 0x003e), driver_info: SINGLE_IF_ACM }, /* Siemens HC15/HC25 */ + { USB_DEVICE(0x22B8, 0x1006) }, { USB_DEVICE_INFO(USB_CLASS_COMM, 0, 0) }, { USB_DEVICE_INFO(USB_CLASS_COMM, 2, 0) }, { } @@ -735,7 +989,7 @@ return -1; } - info(DRIVER_VERSION ":" DRIVER_DESC); + info(DRIVER_VERSION ":" DRIVER_DESC "(non-legacy %d)", nonlegacy); return 0; } @@ -759,7 +1013,9 @@ MODULE_PARM(maxszc, "i"); MODULE_PARM_DESC(maxszc, "User specified USB endpoint control size"); +MODULE_PARM(nonlegacy, "i"); +MODULE_PARM_DESC(nonlegacy, "Set this to 1 to for use with non-legacy device"); + MODULE_AUTHOR( DRIVER_AUTHOR ); MODULE_DESCRIPTION( DRIVER_DESC ); MODULE_LICENSE("GPL"); -