summaryrefslogtreecommitdiffstats
path: root/drivers/usb/class
diff options
context:
space:
mode:
authorTobias Herzog <t-herzog@gmx.de>2017-03-30 16:15:11 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2017-04-01 05:05:03 -0400
commitea2583529cd17ab313dfe3cabf2215f38c6399a7 (patch)
tree1878bcfb54bd49c7a2133e19f373c6babef1e784 /drivers/usb/class
parent1bb9914e1730417d530de9ed37e59efdc647146b (diff)
cdc-acm: reassemble fragmented notifications
USB devices may have very limited endpoint packet sizes, so that notifications can not be transferred within one single usb packet. Reassembling of multiple packages may be necessary. Signed-off-by: Tobias Herzog <t-herzog@gmx.de> Acked-by: Oliver Neukum <oneukum@suse.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb/class')
-rw-r--r--drivers/usb/class/cdc-acm.c112
-rw-r--r--drivers/usb/class/cdc-acm.h3
2 files changed, 86 insertions, 29 deletions
diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c
index cdabe76960bc..eb854dd4ed5b 100644
--- a/drivers/usb/class/cdc-acm.c
+++ b/drivers/usb/class/cdc-acm.c
@@ -36,6 +36,7 @@
36#include <linux/errno.h> 36#include <linux/errno.h>
37#include <linux/init.h> 37#include <linux/init.h>
38#include <linux/slab.h> 38#include <linux/slab.h>
39#include <linux/log2.h>
39#include <linux/tty.h> 40#include <linux/tty.h>
40#include <linux/serial.h> 41#include <linux/serial.h>
41#include <linux/tty_driver.h> 42#include <linux/tty_driver.h>
@@ -283,39 +284,13 @@ static DEVICE_ATTR(iCountryCodeRelDate, S_IRUGO, show_country_rel_date, NULL);
283 * Interrupt handlers for various ACM device responses 284 * Interrupt handlers for various ACM device responses
284 */ 285 */
285 286
286/* control interface reports status changes with "interrupt" transfers */ 287static void acm_process_notification(struct acm *acm, unsigned char *buf)
287static void acm_ctrl_irq(struct urb *urb)
288{ 288{
289 struct acm *acm = urb->context;
290 struct usb_cdc_notification *dr = urb->transfer_buffer;
291 unsigned char *data;
292 int newctrl; 289 int newctrl;
293 int difference; 290 int difference;
294 int retval; 291 struct usb_cdc_notification *dr = (struct usb_cdc_notification *)buf;
295 int status = urb->status; 292 unsigned char *data = buf + sizeof(struct usb_cdc_notification);
296
297 switch (status) {
298 case 0:
299 /* success */
300 break;
301 case -ECONNRESET:
302 case -ENOENT:
303 case -ESHUTDOWN:
304 /* this urb is terminated, clean up */
305 dev_dbg(&acm->control->dev,
306 "%s - urb shutting down with status: %d\n",
307 __func__, status);
308 return;
309 default:
310 dev_dbg(&acm->control->dev,
311 "%s - nonzero urb status received: %d\n",
312 __func__, status);
313 goto exit;
314 }
315 293
316 usb_mark_last_busy(acm->dev);
317
318 data = (unsigned char *)(dr + 1);
319 switch (dr->bNotificationType) { 294 switch (dr->bNotificationType) {
320 case USB_CDC_NOTIFY_NETWORK_CONNECTION: 295 case USB_CDC_NOTIFY_NETWORK_CONNECTION:
321 dev_dbg(&acm->control->dev, 296 dev_dbg(&acm->control->dev,
@@ -368,9 +343,83 @@ static void acm_ctrl_irq(struct urb *urb)
368 "%s - unknown notification %d received: index %d len %d\n", 343 "%s - unknown notification %d received: index %d len %d\n",
369 __func__, 344 __func__,
370 dr->bNotificationType, dr->wIndex, dr->wLength); 345 dr->bNotificationType, dr->wIndex, dr->wLength);
346 }
347}
371 348
349/* control interface reports status changes with "interrupt" transfers */
350static void acm_ctrl_irq(struct urb *urb)
351{
352 struct acm *acm = urb->context;
353 struct usb_cdc_notification *dr = urb->transfer_buffer;
354 unsigned int current_size = urb->actual_length;
355 unsigned int expected_size, copy_size, alloc_size;
356 int retval;
357 int status = urb->status;
358
359 switch (status) {
360 case 0:
361 /* success */
372 break; 362 break;
363 case -ECONNRESET:
364 case -ENOENT:
365 case -ESHUTDOWN:
366 /* this urb is terminated, clean up */
367 acm->nb_index = 0;
368 dev_dbg(&acm->control->dev,
369 "%s - urb shutting down with status: %d\n",
370 __func__, status);
371 return;
372 default:
373 dev_dbg(&acm->control->dev,
374 "%s - nonzero urb status received: %d\n",
375 __func__, status);
376 goto exit;
377 }
378
379 usb_mark_last_busy(acm->dev);
380
381 if (acm->nb_index)
382 dr = (struct usb_cdc_notification *)acm->notification_buffer;
383
384 /* size = notification-header + (optional) data */
385 expected_size = sizeof(struct usb_cdc_notification) +
386 le16_to_cpu(dr->wLength);
387
388 if (current_size < expected_size) {
389 /* notification is transmitted fragmented, reassemble */
390 if (acm->nb_size < expected_size) {
391 if (acm->nb_size) {
392 kfree(acm->notification_buffer);
393 acm->nb_size = 0;
394 }
395 alloc_size = roundup_pow_of_two(expected_size);
396 /*
397 * kmalloc ensures a valid notification_buffer after a
398 * use of kfree in case the previous allocation was too
399 * small. Final freeing is done on disconnect.
400 */
401 acm->notification_buffer =
402 kmalloc(alloc_size, GFP_ATOMIC);
403 if (!acm->notification_buffer)
404 goto exit;
405 acm->nb_size = alloc_size;
406 }
407
408 copy_size = min(current_size,
409 expected_size - acm->nb_index);
410
411 memcpy(&acm->notification_buffer[acm->nb_index],
412 urb->transfer_buffer, copy_size);
413 acm->nb_index += copy_size;
414 current_size = acm->nb_index;
373 } 415 }
416
417 if (current_size >= expected_size) {
418 /* notification complete */
419 acm_process_notification(acm, (unsigned char *)dr);
420 acm->nb_index = 0;
421 }
422
374exit: 423exit:
375 retval = usb_submit_urb(urb, GFP_ATOMIC); 424 retval = usb_submit_urb(urb, GFP_ATOMIC);
376 if (retval && retval != -EPERM) 425 if (retval && retval != -EPERM)
@@ -1483,6 +1532,9 @@ skip_countries:
1483 epctrl->bInterval ? epctrl->bInterval : 16); 1532 epctrl->bInterval ? epctrl->bInterval : 16);
1484 acm->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; 1533 acm->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
1485 acm->ctrlurb->transfer_dma = acm->ctrl_dma; 1534 acm->ctrlurb->transfer_dma = acm->ctrl_dma;
1535 acm->notification_buffer = NULL;
1536 acm->nb_index = 0;
1537 acm->nb_size = 0;
1486 1538
1487 dev_info(&intf->dev, "ttyACM%d: USB ACM device\n", minor); 1539 dev_info(&intf->dev, "ttyACM%d: USB ACM device\n", minor);
1488 1540
@@ -1575,6 +1627,8 @@ static void acm_disconnect(struct usb_interface *intf)
1575 usb_free_coherent(acm->dev, acm->ctrlsize, acm->ctrl_buffer, acm->ctrl_dma); 1627 usb_free_coherent(acm->dev, acm->ctrlsize, acm->ctrl_buffer, acm->ctrl_dma);
1576 acm_read_buffers_free(acm); 1628 acm_read_buffers_free(acm);
1577 1629
1630 kfree(acm->notification_buffer);
1631
1578 if (!acm->combined_interfaces) 1632 if (!acm->combined_interfaces)
1579 usb_driver_release_interface(&acm_driver, intf == acm->control ? 1633 usb_driver_release_interface(&acm_driver, intf == acm->control ?
1580 acm->data : acm->control); 1634 acm->data : acm->control);
diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h
index c980f11cdf56..b51913836409 100644
--- a/drivers/usb/class/cdc-acm.h
+++ b/drivers/usb/class/cdc-acm.h
@@ -98,6 +98,9 @@ struct acm {
98 struct acm_wb *putbuffer; /* for acm_tty_put_char() */ 98 struct acm_wb *putbuffer; /* for acm_tty_put_char() */
99 int rx_buflimit; 99 int rx_buflimit;
100 spinlock_t read_lock; 100 spinlock_t read_lock;
101 u8 *notification_buffer; /* to reassemble fragmented notifications */
102 unsigned int nb_index;
103 unsigned int nb_size;
101 int write_used; /* number of non-empty write buffers */ 104 int write_used; /* number of non-empty write buffers */
102 int transmitting; 105 int transmitting;
103 spinlock_t write_lock; 106 spinlock_t write_lock;