diff options
Diffstat (limited to 'drivers/usb/class')
| -rw-r--r-- | drivers/usb/class/cdc-acm.c | 79 | ||||
| -rw-r--r-- | drivers/usb/class/cdc-acm.h | 19 | ||||
| -rw-r--r-- | drivers/usb/class/cdc-wdm.c | 72 | 
3 files changed, 105 insertions, 65 deletions
| diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 7f6f3ab5b8a6..30ef946a8e1a 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -173,7 +173,7 @@ static int acm_wb_alloc(struct acm *acm)  	for (;;) {  		wb = &acm->wb[wbn];  		if (!wb->use) { -			wb->use = 1; +			wb->use = true;  			wb->len = 0;  			return wbn;  		} @@ -191,7 +191,8 @@ static int acm_wb_is_avail(struct acm *acm)  	n = ACM_NW;  	spin_lock_irqsave(&acm->write_lock, flags);  	for (i = 0; i < ACM_NW; i++) -		n -= acm->wb[i].use; +		if(acm->wb[i].use) +			n--;  	spin_unlock_irqrestore(&acm->write_lock, flags);  	return n;  } @@ -201,7 +202,7 @@ static int acm_wb_is_avail(struct acm *acm)   */  static void acm_write_done(struct acm *acm, struct acm_wb *wb)  { -	wb->use = 0; +	wb->use = false;  	acm->transmitting--;  	usb_autopm_put_interface_async(acm->control);  } @@ -741,7 +742,7 @@ static void acm_port_shutdown(struct tty_port *port)  		if (!urb)  			break;  		wb = urb->context; -		wb->use = 0; +		wb->use = false;  		usb_autopm_put_interface_async(acm->control);  	} @@ -792,7 +793,7 @@ static int acm_tty_write(struct tty_struct *tty,  	wb = &acm->wb[wbn];  	if (!acm->dev) { -		wb->use = 0; +		wb->use = false;  		spin_unlock_irqrestore(&acm->write_lock, flags);  		return -ENODEV;  	} @@ -804,7 +805,7 @@ static int acm_tty_write(struct tty_struct *tty,  	stat = usb_autopm_get_interface_async(acm->control);  	if (stat) { -		wb->use = 0; +		wb->use = false;  		spin_unlock_irqrestore(&acm->write_lock, flags);  		return stat;  	} @@ -1196,9 +1197,6 @@ static int acm_probe(struct usb_interface *intf,  		return -EINVAL;  	} -	if (!intf->cur_altsetting) -		return -EINVAL; -  	if (!buflen) {  		if (intf->cur_altsetting->endpoint &&  				intf->cur_altsetting->endpoint->extralen && @@ -1221,39 +1219,42 @@ static int acm_probe(struct usb_interface *intf,  		call_intf_num = cmgmd->bDataInterface;  	if (!union_header) { -		if (call_intf_num > 0) { +		if (intf->cur_altsetting->desc.bNumEndpoints == 3) { +			dev_dbg(&intf->dev, "No union descriptor, assuming single interface\n"); +			combined_interfaces = 1; +			control_interface = data_interface = intf; +			goto look_for_collapsed_interface; +		} else if (call_intf_num > 0) {  			dev_dbg(&intf->dev, "No union descriptor, using call management descriptor\n"); -			/* quirks for Droids MuIn LCD */ -			if (quirks & NO_DATA_INTERFACE) { -				data_interface = usb_ifnum_to_if(usb_dev, 0); -			} else { -				data_intf_num = call_intf_num; -				data_interface = usb_ifnum_to_if(usb_dev, data_intf_num); -			} +			data_intf_num = call_intf_num; +			data_interface = usb_ifnum_to_if(usb_dev, data_intf_num);  			control_interface = intf;  		} else { -			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; -			} +			dev_dbg(&intf->dev, "No union descriptor, giving up\n"); +			return -ENODEV;  		}  	} else { +		int class = -1; +  		data_intf_num = union_header->bSlaveInterface0;  		control_interface = usb_ifnum_to_if(usb_dev, union_header->bMasterInterface0);  		data_interface = usb_ifnum_to_if(usb_dev, data_intf_num); + +		if (control_interface) +			class = control_interface->cur_altsetting->desc.bInterfaceClass; + +		if (class != USB_CLASS_COMM && class != USB_CLASS_CDC_DATA) { +			dev_dbg(&intf->dev, "Broken union descriptor, assuming single interface\n"); +			combined_interfaces = 1; +			control_interface = data_interface = intf; +			goto look_for_collapsed_interface; +		}  	}  	if (!control_interface || !data_interface) {  		dev_dbg(&intf->dev, "no interfaces\n");  		return -ENODEV;  	} -	if (!data_interface->cur_altsetting || !control_interface->cur_altsetting) -		return -ENODEV;  	if (data_intf_num != call_intf_num)  		dev_dbg(&intf->dev, "Separate call control interface. That is not fully supported.\n"); @@ -1280,10 +1281,8 @@ look_for_collapsed_interface:  skip_normal_probe:  	/*workaround for switched interfaces */ -	if (data_interface->cur_altsetting->desc.bInterfaceClass -						!= CDC_DATA_INTERFACE_TYPE) { -		if (control_interface->cur_altsetting->desc.bInterfaceClass -						== CDC_DATA_INTERFACE_TYPE) { +	if (data_interface->cur_altsetting->desc.bInterfaceClass != USB_CLASS_CDC_DATA) { +		if (control_interface->cur_altsetting->desc.bInterfaceClass == USB_CLASS_CDC_DATA) {  			dev_dbg(&intf->dev,  				"Your device has switched interfaces.\n");  			swap(control_interface, data_interface); @@ -1876,11 +1875,6 @@ static const struct usb_device_id acm_ids[] = {  	/* NOTE: non-Nokia COMM/ACM/0xff is likely MSFT RNDIS... NOT a modem! */ -	/* Support for Droids MuIn LCD */ -	{ USB_DEVICE(0x04d8, 0x000b), -	.driver_info = NO_DATA_INTERFACE, -	}, -  #if IS_ENABLED(CONFIG_INPUT_IMS_PCU)  	{ USB_DEVICE(0x04d8, 0x0082),	/* Application mode */  	.driver_info = IGNORE_DEVICE, @@ -1906,6 +1900,17 @@ static const struct usb_device_id acm_ids[] = {  	.driver_info = IGNORE_DEVICE,  	}, +	/* Exclude ETAS ES58x */ +	{ USB_DEVICE(0x108c, 0x0159), /* ES581.4 */ +	.driver_info = IGNORE_DEVICE, +	}, +	{ USB_DEVICE(0x108c, 0x0168), /* ES582.1 */ +	.driver_info = IGNORE_DEVICE, +	}, +	{ USB_DEVICE(0x108c, 0x0169), /* ES584.1 */ +	.driver_info = IGNORE_DEVICE, +	}, +  	{ USB_DEVICE(0x1bc7, 0x0021), /* Telit 3G ACM only composition */  	.driver_info = SEND_ZERO_PACKET,  	}, diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h index cd5e9d8ab237..9dce179d031b 100644 --- a/drivers/usb/class/cdc-acm.h +++ b/drivers/usb/class/cdc-acm.h @@ -64,12 +64,12 @@  #define ACM_NR  16  struct acm_wb { -	unsigned char *buf; +	u8 *buf;  	dma_addr_t dmah; -	int len; -	int use; +	unsigned int len;  	struct urb		*urb;  	struct acm		*instance; +	bool use;  };  struct acm_rb { @@ -131,15 +131,12 @@ struct acm {  	unsigned long quirks;  }; -#define CDC_DATA_INTERFACE_TYPE	0x0a -  /* constants describing various quirks and errors */  #define NO_UNION_NORMAL			BIT(0)  #define SINGLE_RX_URB			BIT(1)  #define NO_CAP_LINE			BIT(2) -#define NO_DATA_INTERFACE		BIT(4) -#define IGNORE_DEVICE			BIT(5) -#define QUIRK_CONTROL_LINE_STATE	BIT(6) -#define CLEAR_HALT_CONDITIONS		BIT(7) -#define SEND_ZERO_PACKET		BIT(8) -#define DISABLE_ECHO			BIT(9) +#define IGNORE_DEVICE			BIT(3) +#define QUIRK_CONTROL_LINE_STATE	BIT(4) +#define CLEAR_HALT_CONDITIONS		BIT(5) +#define SEND_ZERO_PACKET		BIT(6) +#define DISABLE_ECHO			BIT(7) diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index 7f5de956a2fc..02d0cfd23bb2 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c @@ -58,6 +58,9 @@ MODULE_DEVICE_TABLE (usb, wdm_ids);  #define WDM_MAX			16 +/* we cannot wait forever at flush() */ +#define WDM_FLUSH_TIMEOUT	(30 * HZ) +  /* CDC-WMC r1.1 requires wMaxCommand to be "at least 256 decimal (0x100)" */  #define WDM_DEFAULT_BUFSIZE	256 @@ -151,7 +154,7 @@ static void wdm_out_callback(struct urb *urb)  	kfree(desc->outbuf);  	desc->outbuf = NULL;  	clear_bit(WDM_IN_USE, &desc->flags); -	wake_up(&desc->wait); +	wake_up_all(&desc->wait);  }  static void wdm_in_callback(struct urb *urb) @@ -393,6 +396,9 @@ static ssize_t wdm_write  	if (test_bit(WDM_RESETTING, &desc->flags))  		r = -EIO; +	if (test_bit(WDM_DISCONNECTING, &desc->flags)) +		r = -ENODEV; +  	if (r < 0) {  		rv = r;  		goto out_free_mem_pm; @@ -424,6 +430,7 @@ static ssize_t wdm_write  	if (rv < 0) {  		desc->outbuf = NULL;  		clear_bit(WDM_IN_USE, &desc->flags); +		wake_up_all(&desc->wait); /* for wdm_wait_for_response() */  		dev_err(&desc->intf->dev, "Tx URB error: %d\n", rv);  		rv = usb_translate_errors(rv);  		goto out_free_mem_pm; @@ -583,28 +590,58 @@ err:  	return rv;  } -static int wdm_flush(struct file *file, fl_owner_t id) +static int wdm_wait_for_response(struct file *file, long timeout)  {  	struct wdm_device *desc = file->private_data; +	long rv; /* Use long here because (int) MAX_SCHEDULE_TIMEOUT < 0. */ + +	/* +	 * Needs both flags. We cannot do with one because resetting it would +	 * cause a race with write() yet we need to signal a disconnect. +	 */ +	rv = wait_event_interruptible_timeout(desc->wait, +			      !test_bit(WDM_IN_USE, &desc->flags) || +			      test_bit(WDM_DISCONNECTING, &desc->flags), +			      timeout); -	wait_event(desc->wait, -			/* -			 * needs both flags. We cannot do with one -			 * because resetting it would cause a race -			 * with write() yet we need to signal -			 * a disconnect -			 */ -			!test_bit(WDM_IN_USE, &desc->flags) || -			test_bit(WDM_DISCONNECTING, &desc->flags)); - -	/* cannot dereference desc->intf if WDM_DISCONNECTING */ +	/* +	 * To report the correct error. This is best effort. +	 * We are inevitably racing with the hardware. +	 */  	if (test_bit(WDM_DISCONNECTING, &desc->flags))  		return -ENODEV; -	if (desc->werr < 0) -		dev_err(&desc->intf->dev, "Error in flush path: %d\n", -			desc->werr); +	if (!rv) +		return -EIO; +	if (rv < 0) +		return -EINTR; + +	spin_lock_irq(&desc->iuspin); +	rv = desc->werr; +	desc->werr = 0; +	spin_unlock_irq(&desc->iuspin); + +	return usb_translate_errors(rv); + +} + +/* + * You need to send a signal when you react to malicious or defective hardware. + * Also, don't abort when fsync() returned -EINVAL, for older kernels which do + * not implement wdm_flush() will return -EINVAL. + */ +static int wdm_fsync(struct file *file, loff_t start, loff_t end, int datasync) +{ +	return wdm_wait_for_response(file, MAX_SCHEDULE_TIMEOUT); +} -	return usb_translate_errors(desc->werr); +/* + * Same with wdm_fsync(), except it uses finite timeout in order to react to + * malicious or defective hardware which ceased communication after close() was + * implicitly called due to process termination. + */ +static int wdm_flush(struct file *file, fl_owner_t id) +{ +	return wdm_wait_for_response(file, WDM_FLUSH_TIMEOUT);  }  static __poll_t wdm_poll(struct file *file, struct poll_table_struct *wait) @@ -729,6 +766,7 @@ static const struct file_operations wdm_fops = {  	.owner =	THIS_MODULE,  	.read =		wdm_read,  	.write =	wdm_write, +	.fsync =	wdm_fsync,  	.open =		wdm_open,  	.flush =	wdm_flush,  	.release =	wdm_release, | 
