aboutsummaryrefslogtreecommitdiffstats
path: root/packages/linux/linux-mtx-2-2.4.27/45-acm-tty-and-sb2.patch
diff options
context:
space:
mode:
Diffstat (limited to 'packages/linux/linux-mtx-2-2.4.27/45-acm-tty-and-sb2.patch')
-rw-r--r--packages/linux/linux-mtx-2-2.4.27/45-acm-tty-and-sb2.patch721
1 files changed, 721 insertions, 0 deletions
diff --git a/packages/linux/linux-mtx-2-2.4.27/45-acm-tty-and-sb2.patch b/packages/linux/linux-mtx-2-2.4.27/45-acm-tty-and-sb2.patch
new file mode 100644
index 0000000000..4561facb73
--- /dev/null
+++ b/packages/linux/linux-mtx-2-2.4.27/45-acm-tty-and-sb2.patch
@@ -0,0 +1,721 @@
+--- 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");
+-