diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2017-05-04 18:03:51 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2017-05-04 18:03:51 -0700 |
commit | 8f28472a739e8e39adc6e64ee5b460df039f0e4f (patch) | |
tree | 979e35f3d1d2be94c06c942bcdc9ee68cbebaacb /drivers/usb/class/cdc-acm.c | |
parent | 4ac4d584886a4f47f8ff3bca0f32ff9a2987d3e5 (diff) | |
parent | c034a43e72dda58e4a184d71f5502ef356e04453 (diff) |
Merge tag 'usb-4.12-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb
Pull USB updates from Greg KH:
"Here is the big USB patchset for 4.12-rc1.
Lots of good stuff here, after many many many attempts, the kernel
finally has a working typeC interface, many thanks to Heikki and
Guenter and others who have taken the time to get this merged. It
wasn't an easy path for them at all.
There's also a staging driver that uses this new api, which is why
it's coming in through this tree.
Along with that, there's the usual huge number of changes for gadget
drivers, xhci, and other stuff. Johan also finally refactored pretty
much every driver that was looking at USB endpoints to do it in a
common way, which will help prevent any "badly-formed" devices from
causing problems in drivers. That too wasn't a simple task.
All of these have been in linux-next for a while with no reported
issues"
* tag 'usb-4.12-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (263 commits)
staging: typec: Fairchild FUSB302 Type-c chip driver
staging: typec: Type-C Port Controller Interface driver (tcpci)
staging: typec: USB Type-C Port Manager (tcpm)
usb: host: xhci: remove #ifdef around PM functions
usb: musb: don't mark of_dev_auxdata as initdata
usb: misc: legousbtower: Fix buffers on stack
USB: Revert "cdc-wdm: fix "out-of-sync" due to missing notifications"
usb: Make sure usb/phy/of gets built-in
USB: storage: e-mail update in drivers/usb/storage/unusual_devs.h
usb: host: xhci: print correct command ring address
usb: host: xhci: delete sp_dma_buffers for scratchpad
usb: host: xhci: using correct specification chapter reference for DCBAAP
xhci: switch to pci_alloc_irq_vectors
usb: host: xhci-plat: set resume_quirk() for R-Car controllers
usb: host: xhci-plat: add resume_quirk()
usb: host: xhci-plat: enable clk in resume timing
usb: host: plat: Enable xHCI plat runtime PM
USB: serial: ftdi_sio: add device ID for Microsemi/Arrow SF2PLUS Dev Kit
USB: serial: constify static arrays
usb: fix some references for /proc/bus/usb
...
Diffstat (limited to 'drivers/usb/class/cdc-acm.c')
-rw-r--r-- | drivers/usb/class/cdc-acm.c | 151 |
1 files changed, 101 insertions, 50 deletions
diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index d5388938bc7a..5357d83bbda2 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -36,6 +36,7 @@ #include <linux/errno.h> #include <linux/init.h> #include <linux/slab.h> +#include <linux/log2.h> #include <linux/tty.h> #include <linux/serial.h> #include <linux/tty_driver.h> @@ -283,39 +284,13 @@ static DEVICE_ATTR(iCountryCodeRelDate, S_IRUGO, show_country_rel_date, NULL); * Interrupt handlers for various ACM device responses */ -/* control interface reports status changes with "interrupt" transfers */ -static void acm_ctrl_irq(struct urb *urb) +static void acm_process_notification(struct acm *acm, unsigned char *buf) { - struct acm *acm = urb->context; - struct usb_cdc_notification *dr = urb->transfer_buffer; - unsigned char *data; int newctrl; int difference; - int retval; - int status = urb->status; + struct usb_cdc_notification *dr = (struct usb_cdc_notification *)buf; + unsigned char *data = buf + sizeof(struct usb_cdc_notification); - switch (status) { - case 0: - /* success */ - break; - case -ECONNRESET: - case -ENOENT: - case -ESHUTDOWN: - /* this urb is terminated, clean up */ - dev_dbg(&acm->control->dev, - "%s - urb shutting down with status: %d\n", - __func__, status); - return; - default: - dev_dbg(&acm->control->dev, - "%s - nonzero urb status received: %d\n", - __func__, status); - goto exit; - } - - usb_mark_last_busy(acm->dev); - - data = (unsigned char *)(dr + 1); switch (dr->bNotificationType) { case USB_CDC_NOTIFY_NETWORK_CONNECTION: dev_dbg(&acm->control->dev, @@ -323,7 +298,15 @@ static void acm_ctrl_irq(struct urb *urb) break; case USB_CDC_NOTIFY_SERIAL_STATE: + if (le16_to_cpu(dr->wLength) != 2) { + dev_dbg(&acm->control->dev, + "%s - malformed serial state\n", __func__); + break; + } + newctrl = get_unaligned_le16(data); + dev_dbg(&acm->control->dev, + "%s - serial state: 0x%x\n", __func__, newctrl); if (!acm->clocal && (acm->ctrlin & ~newctrl & ACM_CTRL_DCD)) { dev_dbg(&acm->control->dev, @@ -359,13 +342,86 @@ static void acm_ctrl_irq(struct urb *urb) default: dev_dbg(&acm->control->dev, - "%s - unknown notification %d received: index %d " - "len %d data0 %d data1 %d\n", + "%s - unknown notification %d received: index %d len %d\n", __func__, - dr->bNotificationType, dr->wIndex, - dr->wLength, data[0], data[1]); + dr->bNotificationType, dr->wIndex, dr->wLength); + } +} + +/* control interface reports status changes with "interrupt" transfers */ +static void acm_ctrl_irq(struct urb *urb) +{ + struct acm *acm = urb->context; + struct usb_cdc_notification *dr = urb->transfer_buffer; + unsigned int current_size = urb->actual_length; + unsigned int expected_size, copy_size, alloc_size; + int retval; + int status = urb->status; + + switch (status) { + case 0: + /* success */ break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + acm->nb_index = 0; + dev_dbg(&acm->control->dev, + "%s - urb shutting down with status: %d\n", + __func__, status); + return; + default: + dev_dbg(&acm->control->dev, + "%s - nonzero urb status received: %d\n", + __func__, status); + goto exit; + } + + usb_mark_last_busy(acm->dev); + + if (acm->nb_index) + dr = (struct usb_cdc_notification *)acm->notification_buffer; + + /* size = notification-header + (optional) data */ + expected_size = sizeof(struct usb_cdc_notification) + + le16_to_cpu(dr->wLength); + + if (current_size < expected_size) { + /* notification is transmitted fragmented, reassemble */ + if (acm->nb_size < expected_size) { + if (acm->nb_size) { + kfree(acm->notification_buffer); + acm->nb_size = 0; + } + alloc_size = roundup_pow_of_two(expected_size); + /* + * kmalloc ensures a valid notification_buffer after a + * use of kfree in case the previous allocation was too + * small. Final freeing is done on disconnect. + */ + acm->notification_buffer = + kmalloc(alloc_size, GFP_ATOMIC); + if (!acm->notification_buffer) + goto exit; + acm->nb_size = alloc_size; + } + + copy_size = min(current_size, + expected_size - acm->nb_index); + + memcpy(&acm->notification_buffer[acm->nb_index], + urb->transfer_buffer, copy_size); + acm->nb_index += copy_size; + current_size = acm->nb_index; + } + + if (current_size >= expected_size) { + /* notification complete */ + acm_process_notification(acm, (unsigned char *)dr); + acm->nb_index = 0; } + exit: retval = usb_submit_urb(urb, GFP_ATOMIC); if (retval && retval != -EPERM) @@ -1174,6 +1230,7 @@ static int acm_probe(struct usb_interface *intf, int combined_interfaces = 0; struct device *tty_dev; int rv = -ENOMEM; + int res; /* normal quirks */ quirks = (unsigned long)id->driver_info; @@ -1274,23 +1331,12 @@ static int acm_probe(struct usb_interface *intf, 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; + res = usb_find_common_endpoints(data_interface->cur_altsetting, + &epread, &epwrite, &epctrl, NULL); + if (res) + return res; + + goto made_compressed_probe; } skip_normal_probe: @@ -1488,6 +1534,9 @@ skip_countries: epctrl->bInterval ? epctrl->bInterval : 16); acm->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; acm->ctrlurb->transfer_dma = acm->ctrl_dma; + acm->notification_buffer = NULL; + acm->nb_index = 0; + acm->nb_size = 0; dev_info(&intf->dev, "ttyACM%d: USB ACM device\n", minor); @@ -1580,6 +1629,8 @@ static void acm_disconnect(struct usb_interface *intf) usb_free_coherent(acm->dev, acm->ctrlsize, acm->ctrl_buffer, acm->ctrl_dma); acm_read_buffers_free(acm); + kfree(acm->notification_buffer); + if (!acm->combined_interfaces) usb_driver_release_interface(&acm_driver, intf == acm->control ? acm->data : acm->control); |