Commit 19445816 authored by Bjørn Mork's avatar Bjørn Mork Committed by Greg Kroah-Hartman

USB: Revert "cdc-wdm: fix "out-of-sync" due to missing notifications"

This reverts commit 833415a3 ("cdc-wdm: fix "out-of-sync" due to
missing notifications")

There have been several reports of wdm_read returning unexpected EIO
errors with QMI devices using the qmi_wwan driver. The reporters
confirm that reverting prevents these errors. I have been unable to
reproduce the bug myself, and have no explanation to offer either. But
reverting is the safe choice here, given that the commit was an
attempt to work around a firmware problem.  Living with a firmware
problem is still better than adding driver bugs.
Reported-by: default avatarKasper Holtze <kasper@holtze.dk>
Reported-by: default avatarAleksander Morgado <aleksander@aleksander.es>
Reported-by: default avatarDaniele Palmas <dnlplm@gmail.com>
Cc: <stable@vger.kernel.org> # v4.9+
Fixes: 833415a3 ("cdc-wdm: fix "out-of-sync" due to missing notifications")
Signed-off-by: default avatarBjørn Mork <bjorn@mork.no>
Acked-by: default avatarOliver Neukum <oneukum@suse.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 3d615964
...@@ -58,7 +58,6 @@ MODULE_DEVICE_TABLE (usb, wdm_ids); ...@@ -58,7 +58,6 @@ MODULE_DEVICE_TABLE (usb, wdm_ids);
#define WDM_SUSPENDING 8 #define WDM_SUSPENDING 8
#define WDM_RESETTING 9 #define WDM_RESETTING 9
#define WDM_OVERFLOW 10 #define WDM_OVERFLOW 10
#define WDM_DRAIN_ON_OPEN 11
#define WDM_MAX 16 #define WDM_MAX 16
...@@ -182,7 +181,7 @@ static void wdm_in_callback(struct urb *urb) ...@@ -182,7 +181,7 @@ static void wdm_in_callback(struct urb *urb)
"nonzero urb status received: -ESHUTDOWN\n"); "nonzero urb status received: -ESHUTDOWN\n");
goto skip_error; goto skip_error;
case -EPIPE: case -EPIPE:
dev_dbg(&desc->intf->dev, dev_err(&desc->intf->dev,
"nonzero urb status received: -EPIPE\n"); "nonzero urb status received: -EPIPE\n");
break; break;
default: default:
...@@ -210,25 +209,6 @@ static void wdm_in_callback(struct urb *urb) ...@@ -210,25 +209,6 @@ static void wdm_in_callback(struct urb *urb)
desc->reslength = length; desc->reslength = length;
} }
} }
/*
* Handling devices with the WDM_DRAIN_ON_OPEN flag set:
* If desc->resp_count is unset, then the urb was submitted
* without a prior notification. If the device returned any
* data, then this implies that it had messages queued without
* notifying us. Continue reading until that queue is flushed.
*/
if (!desc->resp_count) {
if (!length) {
/* do not propagate the expected -EPIPE */
desc->rerr = 0;
goto unlock;
}
dev_dbg(&desc->intf->dev, "got %d bytes without notification\n", length);
set_bit(WDM_RESPONDING, &desc->flags);
usb_submit_urb(desc->response, GFP_ATOMIC);
}
skip_error: skip_error:
set_bit(WDM_READ, &desc->flags); set_bit(WDM_READ, &desc->flags);
wake_up(&desc->wait); wake_up(&desc->wait);
...@@ -243,7 +223,6 @@ static void wdm_in_callback(struct urb *urb) ...@@ -243,7 +223,6 @@ static void wdm_in_callback(struct urb *urb)
service_outstanding_interrupt(desc); service_outstanding_interrupt(desc);
} }
unlock:
spin_unlock(&desc->iuspin); spin_unlock(&desc->iuspin);
} }
...@@ -686,17 +665,6 @@ static int wdm_open(struct inode *inode, struct file *file) ...@@ -686,17 +665,6 @@ static int wdm_open(struct inode *inode, struct file *file)
dev_err(&desc->intf->dev, dev_err(&desc->intf->dev,
"Error submitting int urb - %d\n", rv); "Error submitting int urb - %d\n", rv);
rv = usb_translate_errors(rv); rv = usb_translate_errors(rv);
} else if (test_bit(WDM_DRAIN_ON_OPEN, &desc->flags)) {
/*
* Some devices keep pending messages queued
* without resending notifications. We must
* flush the message queue before we can
* assume a one-to-one relationship between
* notifications and messages in the queue
*/
dev_dbg(&desc->intf->dev, "draining queued data\n");
set_bit(WDM_RESPONDING, &desc->flags);
rv = usb_submit_urb(desc->response, GFP_KERNEL);
} }
} else { } else {
rv = 0; rv = 0;
...@@ -803,8 +771,7 @@ static void wdm_rxwork(struct work_struct *work) ...@@ -803,8 +771,7 @@ static void wdm_rxwork(struct work_struct *work)
/* --- hotplug --- */ /* --- hotplug --- */
static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor *ep, static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor *ep,
u16 bufsize, int (*manage_power)(struct usb_interface *, int), u16 bufsize, int (*manage_power)(struct usb_interface *, int))
bool drain_on_open)
{ {
int rv = -ENOMEM; int rv = -ENOMEM;
struct wdm_device *desc; struct wdm_device *desc;
...@@ -891,68 +858,6 @@ static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor ...@@ -891,68 +858,6 @@ static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor
desc->manage_power = manage_power; desc->manage_power = manage_power;
/*
* "drain_on_open" enables a hack to work around a firmware
* issue observed on network functions, in particular MBIM
* functions.
*
* Quoting section 7 of the CDC-WMC r1.1 specification:
*
* "The firmware shall interpret GetEncapsulatedResponse as a
* request to read response bytes. The firmware shall send
* the next wLength bytes from the response. The firmware
* shall allow the host to retrieve data using any number of
* GetEncapsulatedResponse requests. The firmware shall
* return a zero- length reply if there are no data bytes
* available.
*
* The firmware shall send ResponseAvailable notifications
* periodically, using any appropriate algorithm, to inform
* the host that there is data available in the reply
* buffer. The firmware is allowed to send ResponseAvailable
* notifications even if there is no data available, but
* this will obviously reduce overall performance."
*
* These requirements, although they make equally sense, are
* often not implemented by network functions. Some firmwares
* will queue data indefinitely, without ever resending a
* notification. The result is that the driver and firmware
* loses "syncronization" if the driver ever fails to respond
* to a single notification, something which easily can happen
* on release(). When this happens, the driver will appear to
* never receive notifications for the most current data. Each
* notification will only cause a single read, which returns
* the oldest data in the firmware's queue.
*
* The "drain_on_open" hack resolves the situation by draining
* data from the firmware until none is returned, without a
* prior notification.
*
* This will inevitably race with the firmware, risking that
* we read data from the device before handling the associated
* notification. To make things worse, some of the devices
* needing the hack do not implement the "return zero if no
* data is available" requirement either. Instead they return
* an error on the subsequent read in this case. This means
* that "winning" the race can cause an unexpected EIO to
* userspace.
*
* "winning" the race is more likely on resume() than on
* open(), and the unexpected error is more harmful in the
* middle of an open session. The hack is therefore only
* applied on open(), and not on resume() where it logically
* would be equally necessary. So we define open() as the only
* driver <-> device "syncronization point". Should we happen
* to lose a notification after open(), then syncronization
* will be lost until release()
*
* The hack should not be enabled for CDC WDM devices
* conforming to the CDC-WMC r1.1 specification. This is
* ensured by setting drain_on_open to false in wdm_probe().
*/
if (drain_on_open)
set_bit(WDM_DRAIN_ON_OPEN, &desc->flags);
spin_lock(&wdm_device_list_lock); spin_lock(&wdm_device_list_lock);
list_add(&desc->device_list, &wdm_device_list); list_add(&desc->device_list, &wdm_device_list);
spin_unlock(&wdm_device_list_lock); spin_unlock(&wdm_device_list_lock);
...@@ -1006,7 +911,7 @@ static int wdm_probe(struct usb_interface *intf, const struct usb_device_id *id) ...@@ -1006,7 +911,7 @@ static int wdm_probe(struct usb_interface *intf, const struct usb_device_id *id)
goto err; goto err;
ep = &iface->endpoint[0].desc; ep = &iface->endpoint[0].desc;
rv = wdm_create(intf, ep, maxcom, &wdm_manage_power, false); rv = wdm_create(intf, ep, maxcom, &wdm_manage_power);
err: err:
return rv; return rv;
...@@ -1038,7 +943,7 @@ struct usb_driver *usb_cdc_wdm_register(struct usb_interface *intf, ...@@ -1038,7 +943,7 @@ struct usb_driver *usb_cdc_wdm_register(struct usb_interface *intf,
{ {
int rv = -EINVAL; int rv = -EINVAL;
rv = wdm_create(intf, ep, bufsize, manage_power, true); rv = wdm_create(intf, ep, bufsize, manage_power);
if (rv < 0) if (rv < 0)
goto err; goto err;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment