diff options
Diffstat (limited to 'drivers/usb/class/cdc-acm.c')
| -rw-r--r-- | drivers/usb/class/cdc-acm.c | 151 | 
1 files changed, 85 insertions, 66 deletions
| diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 39ddb5585ded..ca7a61190dd9 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -147,17 +147,29 @@ static inline int acm_set_control(struct acm *acm, int control)  #define acm_send_break(acm, ms) \  	acm_ctrl_msg(acm, USB_CDC_REQ_SEND_BREAK, ms, NULL, 0) -static void acm_kill_urbs(struct acm *acm) +static void acm_poison_urbs(struct acm *acm)  {  	int i; -	usb_kill_urb(acm->ctrlurb); +	usb_poison_urb(acm->ctrlurb);  	for (i = 0; i < ACM_NW; i++) -		usb_kill_urb(acm->wb[i].urb); +		usb_poison_urb(acm->wb[i].urb);  	for (i = 0; i < acm->rx_buflimit; i++) -		usb_kill_urb(acm->read_urbs[i]); +		usb_poison_urb(acm->read_urbs[i]);  } +static void acm_unpoison_urbs(struct acm *acm) +{ +	int i; + +	for (i = 0; i < acm->rx_buflimit; i++) +		usb_unpoison_urb(acm->read_urbs[i]); +	for (i = 0; i < ACM_NW; i++) +		usb_unpoison_urb(acm->wb[i].urb); +	usb_unpoison_urb(acm->ctrlurb); +} + +  /*   * Write buffer management.   * All of these assume proper locks taken by the caller. @@ -226,9 +238,10 @@ static int acm_start_wb(struct acm *acm, struct acm_wb *wb)  	rc = usb_submit_urb(wb->urb, GFP_ATOMIC);  	if (rc < 0) { -		dev_err(&acm->data->dev, -			"%s - usb_submit_urb(write bulk) failed: %d\n", -			__func__, rc); +		if (rc != -EPERM) +			dev_err(&acm->data->dev, +				"%s - usb_submit_urb(write bulk) failed: %d\n", +				__func__, rc);  		acm_write_done(acm, wb);  	}  	return rc; @@ -313,8 +326,10 @@ static void acm_process_notification(struct acm *acm, unsigned char *buf)  			acm->iocount.dsr++;  		if (difference & ACM_CTRL_DCD)  			acm->iocount.dcd++; -		if (newctrl & ACM_CTRL_BRK) +		if (newctrl & ACM_CTRL_BRK) {  			acm->iocount.brk++; +			tty_insert_flip_char(&acm->port, 0, TTY_BREAK); +		}  		if (newctrl & ACM_CTRL_RI)  			acm->iocount.rng++;  		if (newctrl & ACM_CTRL_FRAMING) @@ -480,11 +495,6 @@ static void acm_read_bulk_callback(struct urb *urb)  	dev_vdbg(&acm->data->dev, "got urb %d, len %d, status %d\n",  		rb->index, urb->actual_length, status); -	if (!acm->dev) { -		dev_dbg(&acm->data->dev, "%s - disconnected\n", __func__); -		return; -	} -  	switch (status) {  	case 0:  		usb_mark_last_busy(acm->dev); @@ -649,7 +659,8 @@ static void acm_port_dtr_rts(struct tty_port *port, int raise)  	res = acm_set_control(acm, val);  	if (res && (acm->ctrl_caps & USB_CDC_CAP_LINE)) -		dev_err(&acm->control->dev, "failed to set dtr/rts\n"); +		/* This is broken in too many devices to spam the logs */ +		dev_dbg(&acm->control->dev, "failed to set dtr/rts\n");  }  static int acm_port_activate(struct tty_port *port, struct tty_struct *tty) @@ -731,6 +742,7 @@ static void acm_port_shutdown(struct tty_port *port)  	 * Need to grab write_lock to prevent race with resume, but no need to  	 * hold it due to the tty-port initialised flag.  	 */ +	acm_poison_urbs(acm);  	spin_lock_irq(&acm->write_lock);  	spin_unlock_irq(&acm->write_lock); @@ -747,7 +759,8 @@ static void acm_port_shutdown(struct tty_port *port)  		usb_autopm_put_interface_async(acm->control);  	} -	acm_kill_urbs(acm); +	acm_unpoison_urbs(acm); +  }  static void acm_tty_cleanup(struct tty_struct *tty) @@ -916,8 +929,7 @@ static int get_serial_info(struct tty_struct *tty, struct serial_struct *ss)  {  	struct acm *acm = tty->driver_data; -	ss->xmit_fifo_size = acm->writesize; -	ss->baud_base = le32_to_cpu(acm->line.dwDTERate); +	ss->line = acm->minor;  	ss->close_delay	= jiffies_to_msecs(acm->port.close_delay) / 10;  	ss->closing_wait = acm->port.closing_wait == ASYNC_CLOSING_WAIT_NONE ?  				ASYNC_CLOSING_WAIT_NONE : @@ -929,7 +941,6 @@ static int set_serial_info(struct tty_struct *tty, struct serial_struct *ss)  {  	struct acm *acm = tty->driver_data;  	unsigned int closing_wait, close_delay; -	unsigned int old_closing_wait, old_close_delay;  	int retval = 0;  	close_delay = msecs_to_jiffies(ss->close_delay * 10); @@ -937,20 +948,12 @@ static int set_serial_info(struct tty_struct *tty, struct serial_struct *ss)  			ASYNC_CLOSING_WAIT_NONE :  			msecs_to_jiffies(ss->closing_wait * 10); -	/* we must redo the rounding here, so that the values match */ -	old_close_delay	= jiffies_to_msecs(acm->port.close_delay) / 10; -	old_closing_wait = acm->port.closing_wait == ASYNC_CLOSING_WAIT_NONE ? -				ASYNC_CLOSING_WAIT_NONE : -				jiffies_to_msecs(acm->port.closing_wait) / 10; -  	mutex_lock(&acm->port.mutex);  	if (!capable(CAP_SYS_ADMIN)) { -		if ((ss->close_delay != old_close_delay) || -		    (ss->closing_wait != old_closing_wait)) +		if ((close_delay != acm->port.close_delay) || +		    (closing_wait != acm->port.closing_wait))  			retval = -EPERM; -		else -			retval = -EOPNOTSUPP;  	} else {  		acm->port.close_delay  = close_delay;  		acm->port.closing_wait = closing_wait; @@ -1296,13 +1299,6 @@ skip_normal_probe:  	if (!combined_interfaces && intf != control_interface)  		return -ENODEV; -	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; -	} - -  	if (data_interface->cur_altsetting->desc.bNumEndpoints < 2 ||  	    control_interface->cur_altsetting->desc.bNumEndpoints == 0)  		return -EINVAL; @@ -1323,8 +1319,8 @@ made_compressed_probe:  	dev_dbg(&intf->dev, "interfaces are valid\n");  	acm = kzalloc(sizeof(struct acm), GFP_KERNEL); -	if (acm == NULL) -		goto alloc_fail; +	if (!acm) +		return -ENOMEM;  	tty_port_init(&acm->port);  	acm->port.ops = &acm_port_ops; @@ -1341,7 +1337,7 @@ made_compressed_probe:  	minor = acm_alloc_minor(acm);  	if (minor < 0) -		goto alloc_fail1; +		goto err_put_port;  	acm->minor = minor;  	acm->dev = usb_dev; @@ -1372,15 +1368,15 @@ made_compressed_probe:  	buf = usb_alloc_coherent(usb_dev, ctrlsize, GFP_KERNEL, &acm->ctrl_dma);  	if (!buf) -		goto alloc_fail1; +		goto err_put_port;  	acm->ctrl_buffer = buf;  	if (acm_write_buffers_alloc(acm) < 0) -		goto alloc_fail2; +		goto err_free_ctrl_buffer;  	acm->ctrlurb = usb_alloc_urb(0, GFP_KERNEL);  	if (!acm->ctrlurb) -		goto alloc_fail3; +		goto err_free_write_buffers;  	for (i = 0; i < num_rx_buf; i++) {  		struct acm_rb *rb = &(acm->read_buffers[i]); @@ -1389,13 +1385,13 @@ made_compressed_probe:  		rb->base = usb_alloc_coherent(acm->dev, readsize, GFP_KERNEL,  								&rb->dma);  		if (!rb->base) -			goto alloc_fail4; +			goto err_free_read_urbs;  		rb->index = i;  		rb->instance = acm;  		urb = usb_alloc_urb(0, GFP_KERNEL);  		if (!urb) -			goto alloc_fail4; +			goto err_free_read_urbs;  		urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;  		urb->transfer_dma = rb->dma; @@ -1416,8 +1412,8 @@ made_compressed_probe:  		struct acm_wb *snd = &(acm->wb[i]);  		snd->urb = usb_alloc_urb(0, GFP_KERNEL); -		if (snd->urb == NULL) -			goto alloc_fail5; +		if (!snd->urb) +			goto err_free_write_urbs;  		if (usb_endpoint_xfer_int(epwrite))  			usb_fill_int_urb(snd->urb, usb_dev, acm->out, @@ -1435,7 +1431,7 @@ made_compressed_probe:  	i = device_create_file(&intf->dev, &dev_attr_bmCapabilities);  	if (i < 0) -		goto alloc_fail5; +		goto err_free_write_urbs;  	if (h.usb_cdc_country_functional_desc) { /* export the country data */  		struct usb_cdc_country_functional_desc * cfd = @@ -1480,20 +1476,21 @@ skip_countries:  	acm->nb_index = 0;  	acm->nb_size = 0; -	dev_info(&intf->dev, "ttyACM%d: USB ACM device\n", minor); -  	acm->line.dwDTERate = cpu_to_le32(9600);  	acm->line.bDataBits = 8;  	acm_set_line(acm, &acm->line); -	usb_driver_claim_interface(&acm_driver, data_interface, acm); -	usb_set_intfdata(data_interface, acm); +	if (!acm->combined_interfaces) { +		rv = usb_driver_claim_interface(&acm_driver, data_interface, acm); +		if (rv) +			goto err_remove_files; +	}  	tty_dev = tty_port_register_device(&acm->port, acm_tty_driver, minor,  			&control_interface->dev);  	if (IS_ERR(tty_dev)) {  		rv = PTR_ERR(tty_dev); -		goto alloc_fail6; +		goto err_release_data_interface;  	}  	if (quirks & CLEAR_HALT_CONDITIONS) { @@ -1501,32 +1498,39 @@ skip_countries:  		usb_clear_halt(usb_dev, acm->out);  	} +	dev_info(&intf->dev, "ttyACM%d: USB ACM device\n", minor); +  	return 0; -alloc_fail6: + +err_release_data_interface: +	if (!acm->combined_interfaces) { +		/* Clear driver data so that disconnect() returns early. */ +		usb_set_intfdata(data_interface, NULL); +		usb_driver_release_interface(&acm_driver, data_interface); +	} +err_remove_files:  	if (acm->country_codes) {  		device_remove_file(&acm->control->dev,  				&dev_attr_wCountryCodes);  		device_remove_file(&acm->control->dev,  				&dev_attr_iCountryCodeRelDate); -		kfree(acm->country_codes);  	}  	device_remove_file(&acm->control->dev, &dev_attr_bmCapabilities); -alloc_fail5: -	usb_set_intfdata(intf, NULL); +err_free_write_urbs:  	for (i = 0; i < ACM_NW; i++)  		usb_free_urb(acm->wb[i].urb); -alloc_fail4: +err_free_read_urbs:  	for (i = 0; i < num_rx_buf; i++)  		usb_free_urb(acm->read_urbs[i]);  	acm_read_buffers_free(acm);  	usb_free_urb(acm->ctrlurb); -alloc_fail3: +err_free_write_buffers:  	acm_write_buffers_free(acm); -alloc_fail2: +err_free_ctrl_buffer:  	usb_free_coherent(usb_dev, ctrlsize, acm->ctrl_buffer, acm->ctrl_dma); -alloc_fail1: +err_put_port:  	tty_port_put(&acm->port); -alloc_fail: +  	return rv;  } @@ -1540,8 +1544,14 @@ static void acm_disconnect(struct usb_interface *intf)  	if (!acm)  		return; -	mutex_lock(&acm->mutex);  	acm->disconnected = true; +	/* +	 * there is a circular dependency. acm_softint() can resubmit +	 * the URBs in error handling so we need to block any +	 * submission right away +	 */ +	acm_poison_urbs(acm); +	mutex_lock(&acm->mutex);  	if (acm->country_codes) {  		device_remove_file(&acm->control->dev,  				&dev_attr_wCountryCodes); @@ -1560,7 +1570,6 @@ static void acm_disconnect(struct usb_interface *intf)  		tty_kref_put(tty);  	} -	acm_kill_urbs(acm);  	cancel_delayed_work_sync(&acm->dwork);  	tty_unregister_device(acm_tty_driver, acm->minor); @@ -1602,7 +1611,7 @@ static int acm_suspend(struct usb_interface *intf, pm_message_t message)  	if (cnt)  		return 0; -	acm_kill_urbs(acm); +	acm_poison_urbs(acm);  	cancel_delayed_work_sync(&acm->dwork);  	acm->urbs_in_error_delay = 0; @@ -1620,6 +1629,8 @@ static int acm_resume(struct usb_interface *intf)  	if (--acm->susp_count)  		goto out; +	acm_unpoison_urbs(acm); +  	if (tty_port_initialized(&acm->port)) {  		rv = usb_submit_urb(acm->ctrlurb, GFP_ATOMIC); @@ -1902,9 +1913,17 @@ static const struct usb_device_id acm_ids[] = {  #endif  #if IS_ENABLED(CONFIG_USB_SERIAL_XR) -	{ USB_DEVICE(0x04e2, 0x1410),   /* Ignore XR21V141X USB to Serial converter */ -	.driver_info = IGNORE_DEVICE, -	}, +	{ USB_DEVICE(0x04e2, 0x1400), .driver_info = IGNORE_DEVICE }, +	{ USB_DEVICE(0x04e2, 0x1401), .driver_info = IGNORE_DEVICE }, +	{ USB_DEVICE(0x04e2, 0x1402), .driver_info = IGNORE_DEVICE }, +	{ USB_DEVICE(0x04e2, 0x1403), .driver_info = IGNORE_DEVICE }, +	{ USB_DEVICE(0x04e2, 0x1410), .driver_info = IGNORE_DEVICE }, +	{ USB_DEVICE(0x04e2, 0x1411), .driver_info = IGNORE_DEVICE }, +	{ USB_DEVICE(0x04e2, 0x1412), .driver_info = IGNORE_DEVICE }, +	{ USB_DEVICE(0x04e2, 0x1414), .driver_info = IGNORE_DEVICE }, +	{ USB_DEVICE(0x04e2, 0x1420), .driver_info = IGNORE_DEVICE }, +	{ USB_DEVICE(0x04e2, 0x1422), .driver_info = IGNORE_DEVICE }, +	{ USB_DEVICE(0x04e2, 0x1424), .driver_info = IGNORE_DEVICE },  #endif  	/*Samsung phone in firmware update mode */ | 
