Received: by 10.223.185.116 with SMTP id b49csp2396917wrg; Mon, 5 Mar 2018 02:03:29 -0800 (PST) X-Google-Smtp-Source: AG47ELt/DTFh5pM9uhOv4+ze1R2awIv4MTA5KNL7cUj8Y4wrLWKWz8GblUXcNL8pbRcOkpRAeeYO X-Received: by 10.101.101.217 with SMTP id y25mr11649135pgv.165.1520244209097; Mon, 05 Mar 2018 02:03:29 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1520244209; cv=none; d=google.com; s=arc-20160816; b=tlG5dgxL8LoxBOMFjRra+Iq2Ifkruozxc8mLMy60w5YBw8e8AjSpQTSnDbwdsvnMXV 32e1dJdlhpMM6gYYVBnS0/RUBltyyUy6AFrt2JfWTYN3BbnGuATuxTgli4B+loN9oRCv vtZLJceCUfFsB4cSP8tL+c6U43zK3mi/Ua9KrB1oCa3SQItgcYgUl+/nFw7n/C4jvl4+ xIGOfzY4EUW7MztViMwbhkRXEOnT9nyYj1ZrzEkDWi+UKv/LIRhZMQTKTEAcU94a6Aa6 jBfMdL5495kb9BUqeBo1tz5Y4oPNjiANgWkSToeVXmiJthAIkAmEB29yjPfTHeOklvZN y9wg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:message-id:date:subject:cc:to:from :dkim-signature:arc-authentication-results; bh=N5EJ3n19Ryt0Qtg5H2smS4q0w3UGhmq1fsJ0OEahl/Q=; b=qmlT+psRVrNLiXIjT5BavoG3XdFsH7ooIhWwPS6SpQfEZ2eANCvKLP4niJbH0QX1wa H559NP/E2e0TOkWEs8DaKgnjqEIB6tEndO1JuqkRjw5ZVEE1GkQRThl9VZkCK2aY28Ym MXpNl6raTcrQ5ZajBz8pJeoeWHwPTaC4SxyzKQaj6AnUywqqTDdODDayW1ZpBr2BMmdo x5wU4XqIVT3cbaP9hlP2X6VDqYzjLhfbJlo67PObQLqvmIGqanfx1Dhl9I93+hk6yP5m fO5sNyMPlz7x5FNSmvp2FD3p8Rn9zwfnRlJRKoMEf3nQZtM7LZC8WUX6Ww0W6e5ZeVyO 2Xiw== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@gmail.com header.s=20161025 header.b=THvakpcd; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id n15-v6si3855776pll.587.2018.03.05.02.03.14; Mon, 05 Mar 2018 02:03:29 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@gmail.com header.s=20161025 header.b=THvakpcd; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S933559AbeCEJ4e (ORCPT + 99 others); Mon, 5 Mar 2018 04:56:34 -0500 Received: from mail-wr0-f193.google.com ([209.85.128.193]:46366 "EHLO mail-wr0-f193.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S933155AbeCEJ4b (ORCPT ); Mon, 5 Mar 2018 04:56:31 -0500 Received: by mail-wr0-f193.google.com with SMTP id m12so16517862wrm.13; Mon, 05 Mar 2018 01:56:30 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id; bh=N5EJ3n19Ryt0Qtg5H2smS4q0w3UGhmq1fsJ0OEahl/Q=; b=THvakpcdIqWbprHF6rtSFDCLDY/ekBqMRRjWHAOSKUyPiXXecOwgP7ulPHfTJ9Pglc ZkhtZPYM4Rp3uZqjy7I9fK3TJvwGkC5A/Dw5zZoARRBAzCYZxs+07gqwqIuz+XqWB+nk iVoRrD5GJlbYNv4Frr2Oxbavi0eGQwPbyky4ejnhH7+6o5vnEDK3+vpdnfQySe2FvHpG WmwzllrfJ+WhC6ujDXdyuvUlTIg/yDoHvRXyPzalzNuGptksLkKI7zLSrbPmeX48Dxhs CRT88bSFyjSqAdlZBopvd0fgKUtrPFX6wEHBC8mXW4jRctrfnJ748WjSDlQCvl4O6lxk 8zQA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=N5EJ3n19Ryt0Qtg5H2smS4q0w3UGhmq1fsJ0OEahl/Q=; b=IHhEMgbH7wdnt9B00VeHn72vCiZj5lpmnmgrc8YJ6U9GYuGPlqGAL7eH6hQz9ffbOe Zz0phkFc8g+9oXKLfjykWM4kYq8GWjvFseivGqw/khKKXQSDH2Q9/gCt29sXiyIc8W45 brQQQSy9LLzmuL9+qV8odp3EFaV5WEAaphkri288I1fcdO/ujw4qz2fzLAvKsfnz+0Fd JdDDwiZynvr5P+GHk5mR8vD1LTK6HTFkmF2CxwrURhmL/A1Jj4gjE+JSerzehbAl/PPX UlMDGnCbTgQtgFCd46hl3ffh1ZchnLd4deQnTwv5HJ7Qg6t2GK78N9QDRqqcx7/SOK7I P9Zg== X-Gm-Message-State: APf1xPDD+hvgnzcAtZ2xnSe70MIJmfZv2hWYT+s8MNUW7FtnaVa4e8nj a9nADwzNjT2a0GYKYNGCpxg= X-Received: by 10.223.182.156 with SMTP id j28mr11832929wre.66.1520243789527; Mon, 05 Mar 2018 01:56:29 -0800 (PST) Received: from localhost.localdomain (146.187.3.109.rev.sfr.net. [109.3.187.146]) by smtp.gmail.com with ESMTPSA id t91sm20205866wrc.21.2018.03.05.01.56.27 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 05 Mar 2018 01:56:28 -0800 (PST) From: Romain Izard To: Oliver Neukum , Greg Kroah-Hartman Cc: linux-usb@vger.kernel.org, linux-serial@vger.kernel.org, linux-kernel@vger.kernel.org, Romain Izard Subject: [RFC PATCH] cdc-acm: do not drop data from fast devices Date: Mon, 5 Mar 2018 10:55:39 +0100 Message-Id: <20180305095539.13698-1-romain.izard.pro@gmail.com> X-Mailer: git-send-email 2.14.1 Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org There are some devices using their USB CDC-ACM interfaces as a debug port that are able to send data at a very high speed, but with the current driver implementation it is not possible to receive it when using a relatively slow embedded system without dropping an important part of the data. The existing driver uses the throttling mechanism of the TTY line discipline to regulate the speed of the data transmitted from the port to the upper layers. This throttling prevents URBs from being resubmitted, slowing down the transfer on the USB line. But the existing code does not work correctly when the internal bufferring gets filled. The TTY buffer is 4096 bytes large, throttling when there are only 128 free bytes left, and unthrottling when there are only 128 bytes available. But the TTY buffer is filled from an intermediate flip buffer that contains up to 64 KiB of data, and each time unthrottle() is called 16 URBs are scheduled by the driver, sending up to 16 KiB to the flip buffer. As the result of tty_insert_flip_string() is not checked in the URB reception callback, data can be lost when the flip buffer is filled faster than the TTY is emptied. Moreover, as the URB callbacks are called from a tasklet context, whereas throttling is called from the system workqueue, it is possible for the throttling to be delayed by high traffic in the tasklet. As a result, completed URBs can be resubmitted even if the flip buffer is full, and we request more data from the device only to drop it immediately. To prevent this problem, the URBs whose data did not reach the flip buffer are placed in a waiting list, which is only processed when the serial port is unthrottled. Signed-off-by: Romain Izard -- This is working when using the normal line discipline on ttyACM. But there is a big hole in this: other line disciplines do not use throttling and thus unthrottle is never called. The URBs will never get resubmitted, and the port is dead. Unfortunately, there is no notification when the flip buffer is ready to receive new data, so the only alternative would be to schedule a timer polling the flip buffer. But with an additional asynchronous process in the mix, the code starts to be very brittle. I believe this issue is not limited to ttyACM. As the TTY layer is not performance-oriented, it can be easy to overwhelm devices with a low available processing power. In my case, a modem sending a sustained 2 MB/s on a debug port (exported as a CDC-ACM port) was enough to trigger the issue. The code handling the CDC-ACM class and the generic USB serial port is very similar when it comes to URB handling, so all drivers that rely on that code have the same issue. But in general, it seems to me that there is no code in the kernel that checks the return value of tty_insert_flip_string(). This means that we are working with the assumption that the kernel will consume the data faster than the source can send it, or that the upper layer will be able or willing to throttle it fast enough to avoid losing data. --- drivers/usb/class/cdc-acm.c | 80 ++++++++++++++++++++++++++++++++++++++++----- drivers/usb/class/cdc-acm.h | 4 +++ 2 files changed, 75 insertions(+), 9 deletions(-) diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 7b366a6c0b49..833fa0a43ddd 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -150,12 +150,18 @@ static inline int acm_set_control(struct acm *acm, int control) static void acm_kill_urbs(struct acm *acm) { int i; + struct acm_rb *rb, *t; usb_kill_urb(acm->ctrlurb); for (i = 0; i < ACM_NW; i++) usb_kill_urb(acm->wb[i].urb); for (i = 0; i < acm->rx_buflimit; i++) usb_kill_urb(acm->read_urbs[i]); + list_for_each_entry_safe(rb, t, &acm->wait_list, node) { + set_bit(rb->index, &acm->read_urbs_free); + rb->offset = 0; + list_del_init(&rb->node); + } } /* @@ -454,14 +460,27 @@ static int acm_submit_read_urbs(struct acm *acm, gfp_t mem_flags) return 0; } -static void acm_process_read_urb(struct acm *acm, struct urb *urb) +static int acm_process_read_urb(struct acm *acm, struct urb *urb) { + int flipped, remainder; + struct acm_rb *rb = urb->context; if (!urb->actual_length) - return; + return 0; + flipped = tty_insert_flip_string(&acm->port, + urb->transfer_buffer + rb->offset, + urb->actual_length - rb->offset); + rb->offset += flipped; + remainder = urb->actual_length - rb->offset; + if (remainder != 0) + dev_dbg(&acm->data->dev, + "remaining data: usb %d len %d offset %d flipped %d\n", + rb->index, urb->actual_length, rb->offset, flipped); + else + rb->offset = 0; - tty_insert_flip_string(&acm->port, urb->transfer_buffer, - urb->actual_length); tty_flip_buffer_push(&acm->port); + + return remainder; } static void acm_read_bulk_callback(struct urb *urb) @@ -474,17 +493,29 @@ 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); - set_bit(rb->index, &acm->read_urbs_free); - if (!acm->dev) { dev_dbg(&acm->data->dev, "%s - disconnected\n", __func__); + set_bit(rb->index, &acm->read_urbs_free); return; } + if (status == 0) { + int rem = urb->actual_length; + + usb_mark_last_busy(acm->dev); + if (list_empty_careful(&acm->wait_list)) + rem = acm_process_read_urb(acm, urb); + if (rem) { + dev_vdbg(&acm->data->dev, + "queuing urb %d\n", rb->index); + list_add_tail(&rb->node, &acm->wait_list); + return; + } + } + set_bit(rb->index, &acm->read_urbs_free); + switch (status) { case 0: - usb_mark_last_busy(acm->dev); - acm_process_read_urb(acm, urb); break; case -EPIPE: set_bit(EVENT_RX_STALL, &acm->flags); @@ -518,6 +549,8 @@ static void acm_read_bulk_callback(struct urb *urb) acm_submit_read_urb(acm, rb->index, GFP_ATOMIC); } else { spin_unlock_irqrestore(&acm->read_lock, flags); + dev_vdbg(&acm->data->dev, + "urb %d is suspended by throttling\n", rb->index); } } @@ -549,8 +582,14 @@ static void acm_softint(struct work_struct *work) if (test_bit(EVENT_RX_STALL, &acm->flags)) { if (!(usb_autopm_get_interface(acm->data))) { + struct acm_rb *rb, *t; for (i = 0; i < acm->rx_buflimit; i++) usb_kill_urb(acm->read_urbs[i]); + list_for_each_entry_safe(rb, t, &acm->wait_list, node) { + set_bit(rb->index, &acm->read_urbs_free); + rb->offset = 0; + list_del_init(&rb->node); + } usb_clear_halt(acm->dev, acm->in); acm_submit_read_urbs(acm, GFP_KERNEL); usb_autopm_put_interface(acm->data); @@ -901,14 +940,32 @@ static void acm_tty_unthrottle(struct tty_struct *tty) { struct acm *acm = tty->driver_data; unsigned int was_throttled; + struct acm_rb *rb, *t; + bool processed = false; + int rem = 0; spin_lock_irq(&acm->read_lock); + + list_for_each_entry_safe(rb, t, &acm->wait_list, node) { + dev_vdbg(&acm->data->dev, "processing urb %d: %d/%d\n", + rb->index, rb->offset, rb->urb->actual_length); + rem = acm_process_read_urb(acm, rb->urb); + if (rem) { + spin_unlock_irq(&acm->read_lock); + return; + } + processed = true; + set_bit(rb->index, &acm->read_urbs_free); + rb->offset = 0; + list_del_init(&rb->node); + } + was_throttled = acm->throttled; acm->throttled = 0; acm->throttle_req = 0; spin_unlock_irq(&acm->read_lock); - if (was_throttled) + if (was_throttled || processed) acm_submit_read_urbs(acm, GFP_KERNEL); } @@ -1430,6 +1487,8 @@ static int acm_probe(struct usb_interface *intf, if (!acm->ctrlurb) goto alloc_fail5; + INIT_LIST_HEAD(&acm->wait_list); + for (i = 0; i < num_rx_buf; i++) { struct acm_rb *rb = &(acm->read_buffers[i]); struct urb *urb; @@ -1445,6 +1504,9 @@ static int acm_probe(struct usb_interface *intf, if (!urb) goto alloc_fail6; + rb->offset = 0; + rb->urb = urb; + INIT_LIST_HEAD(&rb->node); urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; urb->transfer_dma = rb->dma; if (usb_endpoint_xfer_int(epread)) diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h index eacc116e83da..cee849a79371 100644 --- a/drivers/usb/class/cdc-acm.h +++ b/drivers/usb/class/cdc-acm.h @@ -77,7 +77,10 @@ struct acm_rb { unsigned char *base; dma_addr_t dma; int index; + struct urb *urb; struct acm *instance; + unsigned int offset; + struct list_head node; }; struct acm { @@ -96,6 +99,7 @@ struct acm { unsigned long read_urbs_free; struct urb *read_urbs[ACM_NR]; struct acm_rb read_buffers[ACM_NR]; + struct list_head wait_list; struct acm_wb *putbuffer; /* for acm_tty_put_char() */ int rx_buflimit; spinlock_t read_lock; -- 2.14.1