From a2bfb4a346d2c2e25f84b35c6044ff53296be1ee Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Sat, 16 May 2009 21:13:19 +0200 Subject: USB: support for cdc-acm of single interface devices This implement support in cdc-acm for acm devices another popular OS can handle - adds support for autodetection of devices that use one interface - autodetection of endpoints - add a quirk for surpressing a setting that OS doesn't use - autoassume that quirk for single interface devices Signed-off-by: Oliver Neukum Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-acm.c | 68 +++++++++++++++++++++++++++++++++++++-------- drivers/usb/class/cdc-acm.h | 2 ++ 2 files changed, 58 insertions(+), 12 deletions(-) (limited to 'drivers/usb/class') diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index ddeb691..778e023 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -937,9 +937,9 @@ static int acm_probe(struct usb_interface *intf, int buflen = intf->altsetting->extralen; struct usb_interface *control_interface; struct usb_interface *data_interface; - struct usb_endpoint_descriptor *epctrl; - struct usb_endpoint_descriptor *epread; - struct usb_endpoint_descriptor *epwrite; + struct usb_endpoint_descriptor *epctrl = NULL; + struct usb_endpoint_descriptor *epread = NULL; + struct usb_endpoint_descriptor *epwrite = NULL; struct usb_device *usb_dev = interface_to_usbdev(intf); struct acm *acm; int minor; @@ -952,6 +952,7 @@ static int acm_probe(struct usb_interface *intf, unsigned long quirks; int num_rx_buf; int i; + int combined_interfaces = 0; /* normal quirks */ quirks = (unsigned long)id->driver_info; @@ -1033,9 +1034,15 @@ next_desc: data_interface = usb_ifnum_to_if(usb_dev, (data_interface_num = call_interface_num)); control_interface = intf; } else { - dev_dbg(&intf->dev, - "No union descriptor, giving up\n"); - return -ENODEV; + if (intf->cur_altsetting->desc.bNumEndpoints != 3) { + dev_dbg(&intf->dev,"No union descriptor, giving up\n"); + return -ENODEV; + } else { + dev_warn(&intf->dev,"No union descriptor, testing for castrated device\n"); + combined_interfaces = 1; + control_interface = data_interface = intf; + goto look_for_collapsed_interface; + } } } else { control_interface = usb_ifnum_to_if(usb_dev, union_header->bMasterInterface0); @@ -1049,6 +1056,36 @@ next_desc: if (data_interface_num != call_interface_num) dev_dbg(&intf->dev, "Separate call control interface. That is not fully supported.\n"); + if (control_interface == data_interface) { + /* some broken devices designed for windows work this way */ + dev_warn(&intf->dev,"Control and data interfaces are not separated!\n"); + combined_interfaces = 1; + /* a popular other OS doesn't use it */ + quirks |= NO_CAP_LINE; + if (data_interface->cur_altsetting->desc.bNumEndpoints != 3) { + dev_err(&intf->dev, "This needs exactly 3 endpoints\n"); + return -EINVAL; + } +look_for_collapsed_interface: + for (i = 0; i < 3; i++) { + struct usb_endpoint_descriptor *ep; + ep = &data_interface->cur_altsetting->endpoint[i].desc; + + if (usb_endpoint_is_int_in(ep)) + epctrl = ep; + else if (usb_endpoint_is_bulk_out(ep)) + epwrite = ep; + else if (usb_endpoint_is_bulk_in(ep)) + epread = ep; + else + return -EINVAL; + } + if (!epctrl || !epread || !epwrite) + return -ENODEV; + else + goto made_compressed_probe; + } + skip_normal_probe: /*workaround for switched interfaces */ @@ -1068,10 +1105,11 @@ skip_normal_probe: } /* Accept probe requests only for the control interface */ - if (intf != control_interface) + if (!combined_interfaces && intf != control_interface) return -ENODEV; - if (usb_interface_claimed(data_interface)) { /* valid in this context */ + if (!combined_interfaces && usb_interface_claimed(data_interface)) { + /* valid in this context */ dev_dbg(&intf->dev, "The data interface isn't available\n"); return -EBUSY; } @@ -1095,6 +1133,7 @@ skip_normal_probe: epread = epwrite; epwrite = t; } +made_compressed_probe: dbg("interfaces are valid"); for (minor = 0; minor < ACM_TTY_MINORS && acm_table[minor]; minor++); @@ -1112,12 +1151,15 @@ skip_normal_probe: ctrlsize = le16_to_cpu(epctrl->wMaxPacketSize); readsize = le16_to_cpu(epread->wMaxPacketSize) * (quirks == SINGLE_RX_URB ? 1 : 2); + acm->combined_interfaces = combined_interfaces; acm->writesize = le16_to_cpu(epwrite->wMaxPacketSize) * 20; acm->control = control_interface; acm->data = data_interface; acm->minor = minor; acm->dev = usb_dev; acm->ctrl_caps = ac_management_function; + if (quirks & NO_CAP_LINE) + acm->ctrl_caps &= ~USB_CDC_CAP_LINE; acm->ctrlsize = ctrlsize; acm->readsize = readsize; acm->rx_buflimit = num_rx_buf; @@ -1223,9 +1265,10 @@ skip_normal_probe: skip_countries: usb_fill_int_urb(acm->ctrlurb, usb_dev, - usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress), - acm->ctrl_buffer, ctrlsize, acm_ctrl_irq, acm, - epctrl->bInterval); + usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress), + acm->ctrl_buffer, ctrlsize, acm_ctrl_irq, acm, + /* works around buggy devices */ + epctrl->bInterval ? epctrl->bInterval : 0xff); acm->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; acm->ctrlurb->transfer_dma = acm->ctrl_dma; @@ -1312,7 +1355,8 @@ static void acm_disconnect(struct usb_interface *intf) acm->ctrl_dma); acm_read_buffers_free(acm); - usb_driver_release_interface(&acm_driver, intf == acm->control ? + if (!acm->combined_interfaces) + usb_driver_release_interface(&acm_driver, intf == acm->control ? acm->data : acm->control); if (acm->port.count == 0) { diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h index 4c38564..1602324 100644 --- a/drivers/usb/class/cdc-acm.h +++ b/drivers/usb/class/cdc-acm.h @@ -125,6 +125,7 @@ struct acm { unsigned char clocal; /* termios CLOCAL */ unsigned int ctrl_caps; /* control capabilities from the class specific header */ unsigned int susp_count; /* number of suspended interfaces */ + int combined_interfaces:1; /* control and data collapsed */ struct acm_wb *delayed_wb; /* write queued for a device about to be woken */ }; @@ -133,3 +134,4 @@ struct acm { /* constants describing various quirks and errors */ #define NO_UNION_NORMAL 1 #define SINGLE_RX_URB 2 +#define NO_CAP_LINE 4 -- cgit v1.1 From 1f17c5026ce27d0449903d34f9fca461a45fe1cb Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Thu, 28 May 2009 20:33:58 +0400 Subject: USB: cdc-acm: quirk for Alcatel OT-I650 This mobile phone fails to work as a modem, failing with: cdc_acm: Zero length descriptor references cdc_acm: probe of 1-6.1.3:1.1 failed with error -22 Tested to work fine with this patch. Signed-off-by: Kir Kolyshkin Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-acm.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers/usb/class') diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 778e023..38bfdb0 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -1495,6 +1495,9 @@ static struct usb_device_id acm_ids[] = { Maybe we should define a new quirk for this. */ }, + { USB_DEVICE(0x1bbb, 0x0003), /* Alcatel OT-I650 */ + .driver_info = NO_UNION_NORMAL, /* reports zero length descriptor */ + }, /* control interfaces with various AT-command sets */ { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, -- cgit v1.1 From a92b63e7e4c185b4dd9e87762e2cb716e54482d0 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Mon, 15 Jun 2009 13:13:05 -0700 Subject: USB: usbtmc: fix switch statment Steve Holland pointed out that we forgot to call break; in the switch statment. This probably resolves a lot of the bug reports I've gotten for the driver lately. Stupid me... Reported-by: Steve Holland Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/usbtmc.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers/usb/class') diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index c40a9b2..3703789 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -927,21 +927,27 @@ static long usbtmc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) switch (cmd) { case USBTMC_IOCTL_CLEAR_OUT_HALT: retval = usbtmc_ioctl_clear_out_halt(data); + break; case USBTMC_IOCTL_CLEAR_IN_HALT: retval = usbtmc_ioctl_clear_in_halt(data); + break; case USBTMC_IOCTL_INDICATOR_PULSE: retval = usbtmc_ioctl_indicator_pulse(data); + break; case USBTMC_IOCTL_CLEAR: retval = usbtmc_ioctl_clear(data); + break; case USBTMC_IOCTL_ABORT_BULK_OUT: retval = usbtmc_ioctl_abort_bulk_out(data); + break; case USBTMC_IOCTL_ABORT_BULK_IN: retval = usbtmc_ioctl_abort_bulk_in(data); + break; } mutex_unlock(&data->io_mutex); -- cgit v1.1