Skip to content

Commit

Permalink
usb: cdc-wdm: avoid setting WDM_READ for ZLP-s
Browse files Browse the repository at this point in the history
[ Upstream commit 387602d ]

Don't set WDM_READ flag in wdm_in_callback() for ZLP-s, otherwise when
userspace tries to poll for available data, it might - incorrectly -
believe there is something available, and when it tries to non-blocking
read it, it might get stuck in the read loop.

For example this is what glib does for non-blocking read (briefly):

  1. poll()
  2. if poll returns with non-zero, starts a read data loop:
    a. loop on poll() (EINTR disabled)
    b. if revents was set, reads data
      I. if read returns with EINTR or EAGAIN, goto 2.a.
      II. otherwise return with data

So if ZLP sets WDM_READ (#1), we expect data, and try to read it (#2).
But as that was a ZLP, and we are doing non-blocking read, wdm_read()
returns with EAGAIN (#2.b.I), so loop again, and try to read again
(#2.a.).

With glib, we might stuck in this loop forever, as EINTR is disabled
(#2.a).

Signed-off-by: Robert Hodaszi <robert.hodaszi@digi.com>
Acked-by: Oliver Neukum <oneukum@suse.com>
Link: https://lore.kernel.org/r/20250403144004.3889125-1-robert.hodaszi@digi.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
  • Loading branch information
Robert Hodaszi authored and Greg Kroah-Hartman committed Jul 6, 2025
1 parent 5463f3a commit c243ae4
Showing 1 changed file with 9 additions and 14 deletions.
23 changes: 9 additions & 14 deletions drivers/usb/class/cdc-wdm.c
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ struct wdm_device {
u16 wMaxCommand;
u16 wMaxPacketSize;
__le16 inum;
int reslength;
int length;
int read;
int count;
Expand Down Expand Up @@ -214,6 +213,11 @@ static void wdm_in_callback(struct urb *urb)
if (desc->rerr == 0 && status != -EPIPE)
desc->rerr = status;

if (length == 0) {
dev_dbg(&desc->intf->dev, "received ZLP\n");
goto skip_zlp;
}

if (length + desc->length > desc->wMaxCommand) {
/* The buffer would overflow */
set_bit(WDM_OVERFLOW, &desc->flags);
Expand All @@ -222,18 +226,18 @@ static void wdm_in_callback(struct urb *urb)
if (!test_bit(WDM_OVERFLOW, &desc->flags)) {
memmove(desc->ubuf + desc->length, desc->inbuf, length);
desc->length += length;
desc->reslength = length;
}
}
skip_error:

if (desc->rerr) {
/*
* Since there was an error, userspace may decide to not read
* any data after poll'ing.
* If there was a ZLP or an error, userspace may decide to not
* read any data after poll'ing.
* We should respond to further attempts from the device to send
* data, so that we can get unstuck.
*/
skip_zlp:
schedule_work(&desc->service_outs_intr);
} else {
set_bit(WDM_READ, &desc->flags);
Expand Down Expand Up @@ -585,15 +589,6 @@ static ssize_t wdm_read
goto retry;
}

if (!desc->reslength) { /* zero length read */
dev_dbg(&desc->intf->dev, "zero length - clearing WDM_READ\n");
clear_bit(WDM_READ, &desc->flags);
rv = service_outstanding_interrupt(desc);
spin_unlock_irq(&desc->iuspin);
if (rv < 0)
goto err;
goto retry;
}
cntr = desc->length;
spin_unlock_irq(&desc->iuspin);
}
Expand Down Expand Up @@ -1015,7 +1010,7 @@ static void service_interrupt_work(struct work_struct *work)

spin_lock_irq(&desc->iuspin);
service_outstanding_interrupt(desc);
if (!desc->resp_count) {
if (!desc->resp_count && (desc->length || desc->rerr)) {
set_bit(WDM_READ, &desc->flags);
wake_up(&desc->wait);
}
Expand Down

0 comments on commit c243ae4

Please sign in to comment.