* [RFC] usb: uas: implement .change_queue_depth to allow per-device queue depth override
@ 2026-10-04 11:41 Luca Cecchi
2026-10-08 11:00 ` Oliver Neukum
0 siblings, 1 reply; 16+ messages in thread
From: Luca Cecchi @ 2026-10-04 11:41 UTC (permalink / raw)
To: linux-usb; +Cc: linux-scsi, oneukum
Hi Oliver,
First time writing to this list, so apologies in advance if anything about
the format is off. I ran into a UAS bridge that locks up under deep
concurrent command queueing, worked around it, and ended up with a small
generic patch that I think could be useful beyond my specific device. I'd
like to hear whether this approach makes sense before trying to turn it
into a proper patch submission.
THE GAP
uas_host_template does not implement .change_queue_depth. Because of that,
the standard sysfs queue_depth attribute stays read-only for every UAS
device, not just mine: scsi_sysfs.c:sdev_store_queue_depth() requires
sht->change_queue_depth to be non-NULL before it allows a write.
uas_probe() already defaults can_queue to qdepth - 2, with a comment
acknowledging that "some bridge firmwares" need extra margin:
/*
* 1 tag is reserved for untagged commands +
* 1 tag to avoid off by one errors in some bridge firmwares
*/
shost->can_queue = devinfo->qdepth - 2;
That margin isn't enough for every bridge. Right now the only way to work
around a bridge that needs more headroom is the IGNORE_UAS quirk, which
disables UAS entirely and falls back to BOT/usb-storage - a large
performance cost for a queue-depth problem.
THE PATCH
--- a/drivers/usb/storage/uas.c
+++ b/drivers/usb/storage/uas.c
@@ -910,6 +910,23 @@ static int uas_sdev_configure(struct scsi_device *sdev,
return 0;
}
+/*
+ * uas does not implement .change_queue_depth, so the standard sysfs
+ * queue_depth attribute stays read-only for every UAS device (see
+ * scsi_sysfs.c:sdev_store_queue_depth(), which requires
+ * sht->change_queue_depth != NULL). Some bridge chips become
+ * unstable under deep command queueing; wiring this up lets affected
+ * users lower the depth for just their device (e.g. via a udev rule
+ * matching idVendor/idProduct), without disabling UAS altogether.
+ * Default behaviour (qdepth - 2) is unchanged unless userspace asks
+ * for less.
+ */
+static int uas_change_queue_depth(struct scsi_device *sdev, int depth)
+{
+ struct uas_dev_info *devinfo = (struct uas_dev_info
*)sdev->host->hostdata;
+ int max_depth = devinfo->qdepth - 2;
+
+ if (depth > max_depth)
+ depth = max_depth;
+ return scsi_change_queue_depth(sdev, depth);
+}
+
static const struct scsi_host_template uas_host_template = {
.module = THIS_MODULE,
.name = "uas",
@@ -917,6 +934,7 @@ static const struct scsi_host_template uas_host_template = {
.target_alloc = uas_target_alloc,
.sdev_init = uas_sdev_init,
.sdev_configure = uas_sdev_configure,
+ .change_queue_depth = uas_change_queue_depth,
.eh_abort_handler = uas_eh_abort_handler,
.eh_host_reset_handler = uas_eh_host_reset_handler,
.this_id = -1,
Nothing changes by default for any device. It only becomes possible to
ask for a lower depth, per device, through the existing sysfs interface -
e.g. a udev rule matching a specific idVendor/idProduct:
ACTION=="add", SUBSYSTEM=="scsi", ATTR{type}=="0", \
ATTRS{idVendor}=="21c4", ATTRS{idProduct}=="0003", \
RUN+="/bin/sh -c 'echo 24 > /sys%p/queue_depth'"
THE CASE THAT LED HERE
Device: Lexar ES3 external SSD enclosure, VID:PID 21c4:0003, USB 3.x
SuperSpeed (qdepth=32, can_queue=30 by default). Host: Lenovo 82KU, AMD
Ryzen 5 5500U, onboard AMD Renoir/Cezanne USB 3.1 controller [1022:1639].
Kernel: 7.2.7 (CachyOS-BORE; BORE only touches the CPU scheduler, verified
it doesn't touch USB/storage).
Under sustained heavy concurrent random writes (32 processes, one
outstanding command each, 64KiB blocks, O_DIRECT), the bridge stops
responding entirely after a period of healthy operation (anywhere from a
few seconds to several minutes). No STALL, NAK, or malformed reply visible
on the bus - the device simply goes silent. After the 30s default SCSI
timeout, the kernel aborts every in-flight command (uas_eh_abort_handler)
and resets the device (uas_eh_host_reset_handler), successfully.
A usbmon + Wireshark capture of one such event shows a clean 3-pipe UAS
cycle (32B command on EP 0x01, 64KiB data on EP 0x04, status on EP 0x82,
~160us per cycle) right up to the last normal packet at 10:27:45.752012,
followed by complete silence - zero packets in either direction - for
exactly 30.610467 seconds, until the first abort-triggered URB
cancellation at 10:28:16.362479.
I compared against the same device on the same host under Windows 11
(UASPStor driver): no lockup, 733MB/s sustained, max latency 194ms. A
USBPcap capture there showed Windows also keeping up to 30 commands truly
in flight (not serialized), using stream IDs 2-31; Linux stock
(can_queue=30) uses 1-30. Same count, one-off range, both inside the valid
1-31 window for MaxPStreams=5 - so "Linux reuses tags out of range" isn't
the explanation, and tag reuse cadence on Windows was comparable or
faster than what I measured on Linux. I could not fully isolate why the
same chip behaves differently between the two stacks (different
filesystem, and USB scheduling under the hood may differ in ways not
visible at this level), but empirically:
- can_queue=30 (the current default -2): locked up in every single test
run (4/4), including one using parameters matching the Windows
comparison exactly.
- Depth capped at 24 (via fio numjobs, before this patch existed): zero
lockups across multiple 360s runs and a continuous 1-hour soak test
(1.36TB written), at 819-823MB/s - faster than the Windows run.
- After implementing this patch plus the udev rule above: 6 consecutive
clean runs, including with the application still requesting 32
concurrent commands, confirming the kernel-enforced cap protects the
device even when userspace asks for more than it's given.
QUESTION
Does this seem like a reasonable way to expose this, or would you rather
see it handled differently (e.g. through the quirks table with a numeric
value instead of a boolean, if that fits the existing infrastructure
better)? Happy to adjust the implementation, rerun tests, or send more
logs/captures if useful. I can also follow up with a proper patch
(Signed-off-by, etc.) once the approach itself looks right to you.
Disclosure: the investigation (usbmon/Wireshark analysis, the Windows
comparison, the fio testing methodology) and this patch were carried out
with the assistance of an AI coding assistant (Claude, Anthropic), under
my direction and with every result independently verified against kernel
source and live system behaviour before being included here.
Note: my first attempt at this email bounced from both lists for
containing an HTML part (a mail client default I didn't catch in time) -
apologies for the noise if a duplicate reaches you, Oliver.
Thanks for maintaining this driver.
Cecchi Luca
luca.cecchi.info@gmail.com
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC] usb: uas: implement .change_queue_depth to allow per-device queue depth override
2026-10-04 11:41 [RFC] usb: uas: implement .change_queue_depth to allow per-device queue depth override Luca Cecchi
@ 2026-10-08 11:00 ` Oliver Neukum
2026-10-08 11:26 ` [PATCH] " Luca Cecchi
0 siblings, 1 reply; 16+ messages in thread
From: Oliver Neukum @ 2026-10-08 11:00 UTC (permalink / raw)
To: Luca Cecchi, linux-usb; +Cc: linux-scsi, oneukum
Hi Luca,
On 04.10.26 13:41, Luca Cecchi wrote:
> QUESTION
>
> Does this seem like a reasonable way to expose this, or would you rather
Your patch is very good. Can you please resend it?
It is a bit corrupted by the mailer.
> see it handled differently (e.g. through the quirks table with a numeric
> value instead of a boolean, if that fits the existing infrastructure
> better)? Happy to adjust the implementation, rerun tests, or send more
I would prefer both approaches. The reason I think that a udev
rule is insufficient is that it inevitably leaves the window
for a lockup to happen before udev can act.
Could you also implement a quirk that limits queue depth to, let's say
three quarters of the maximum?
Regards
Oliver
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH] usb: uas: implement .change_queue_depth to allow per-device queue depth override
2026-10-08 11:00 ` Oliver Neukum
@ 2026-10-08 11:26 ` Luca Cecchi
2026-10-08 11:36 ` sashiko-bot
2026-10-08 12:26 ` [PATCH v2 1/2] " Luca Cecchi
0 siblings, 2 replies; 16+ messages in thread
From: Luca Cecchi @ 2026-10-08 11:26 UTC (permalink / raw)
To: linux-usb; +Cc: linux-scsi, oneukum, Luca Cecchi
uas_host_template does not implement .change_queue_depth. Because of
that, the standard sysfs queue_depth attribute stays read-only for
every UAS device, not just mine: scsi_sysfs.c:sdev_store_queue_depth()
requires sht->change_queue_depth to be non-NULL before it allows a
write.
uas_probe() already defaults can_queue to qdepth - 2, with a comment
acknowledging that "some bridge firmwares" need extra margin. That
margin isn't enough for every bridge. Right now the only way to work
around a bridge that needs more headroom is the IGNORE_UAS quirk,
which disables UAS entirely and falls back to BOT/usb-storage - a
large performance cost for a queue-depth problem.
This patch wires up .change_queue_depth so affected users can lower
the depth for just their device (e.g. via a udev rule matching
idVendor/idProduct), without disabling UAS altogether. Default
behaviour (qdepth - 2) is unchanged unless userspace asks for less.
Tested against a Lexar ES3 external SSD enclosure (VID:PID 21c4:0003)
that locks up under sustained heavy concurrent random writes at the
default queue depth. Capping the depth to 24 via this interface
eliminated the lockup across 6 consecutive test runs, including a
continuous 1-hour soak test (1.36TB written, ~822MB/s, no
uas_eh_abort_handler events).
Signed-off-by: Luca Cecchi <luca.cecchi.info@gmail.com>
---
drivers/usb/storage/uas.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c
index 2651629..74fc438 100644
--- a/drivers/usb/storage/uas.c
+++ b/drivers/usb/storage/uas.c
@@ -910,6 +910,27 @@ static int uas_sdev_configure(struct scsi_device *sdev,
return 0;
}
+/*
+ * uas does not implement .change_queue_depth, so the standard sysfs
+ * queue_depth attribute stays read-only for every UAS device (see
+ * scsi_sysfs.c:sdev_store_queue_depth(), which requires
+ * sht->change_queue_depth != NULL). Some bridge chips become
+ * unstable under deep command queueing; wiring this up lets affected
+ * users lower the depth for just their device (e.g. via a udev rule
+ * matching idVendor/idProduct), without disabling UAS altogether.
+ * Default behaviour (qdepth - 2) is unchanged unless userspace asks
+ * for less.
+ */
+static int uas_change_queue_depth(struct scsi_device *sdev, int depth)
+{
+ struct uas_dev_info *devinfo = (struct uas_dev_info *)sdev->host->hostdata;
+ int max_depth = devinfo->qdepth - 2;
+
+ if (depth > max_depth)
+ depth = max_depth;
+ return scsi_change_queue_depth(sdev, depth);
+}
+
static const struct scsi_host_template uas_host_template = {
.module = THIS_MODULE,
.name = "uas",
@@ -917,6 +938,7 @@ static const struct scsi_host_template uas_host_template = {
.target_alloc = uas_target_alloc,
.sdev_init = uas_sdev_init,
.sdev_configure = uas_sdev_configure,
+ .change_queue_depth = uas_change_queue_depth,
.eh_abort_handler = uas_eh_abort_handler,
.eh_host_reset_handler = uas_eh_host_reset_handler,
.this_id = -1,
--
2.55.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH] usb: uas: implement .change_queue_depth to allow per-device queue depth override
2026-10-08 11:26 ` [PATCH] " Luca Cecchi
@ 2026-10-08 11:36 ` sashiko-bot
2026-10-08 12:26 ` [PATCH v2 1/2] " Luca Cecchi
1 sibling, 0 replies; 16+ messages in thread
From: sashiko-bot @ 2026-10-08 11:36 UTC (permalink / raw)
To: Luca Cecchi; +Cc: linux-usb, linux-scsi
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] uas: negative queue depth underflow via uas_change_queue_depth()
--
commit 9d52d5cb6887ffe601d9599b2dbfb63939e5e572
Author: Luca Cecchi <luca.cecchi.info@gmail.com>
usb: uas: implement .change_queue_depth to allow per-device queue depth override
This commit implements .change_queue_depth for UAS devices to allow
per-device queue depth overrides. This helps affected users lower the
depth for just their device without disabling UAS altogether.
> diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c
> index 8655edbd66b16..55291b4d0b825 100644
> --- a/drivers/usb/storage/uas.c
> +++ b/drivers/usb/storage/uas.c
[ ... ]
> @@ -910,6 +910,27 @@ static int uas_sdev_configure(struct scsi_device *sdev,
> return 0;
> }
>
> +/*
> + * uas does not implement .change_queue_depth, so the standard sysfs
> + * queue_depth attribute stays read-only for every UAS device (see
> + * scsi_sysfs.c:sdev_store_queue_depth(), which requires
> + * sht->change_queue_depth != NULL). Some bridge chips become
> + * unstable under deep command queueing; wiring this up lets affected
> + * users lower the depth for just their device (e.g. via a udev rule
> + * matching idVendor/idProduct), without disabling UAS altogether.
> + * Default behaviour (qdepth - 2) is unchanged unless userspace asks
> + * for less.
> + */
> +static int uas_change_queue_depth(struct scsi_device *sdev, int depth)
> +{
> + struct uas_dev_info *devinfo = (struct uas_dev_info *)sdev->host->hostdata;
> + int max_depth = devinfo->qdepth - 2;
> +
> + if (depth > max_depth)
> + depth = max_depth;
> + return scsi_change_queue_depth(sdev, depth);
> +}
[Severity: High]
Can this underflow queue_depth in the block layer if stream allocation
fails during a reset?
If a USB reset fails (e.g., via uas_post_reset() or uas_reset_resume()),
uas_configure_endpoints() can overwrite devinfo->qdepth with a negative
error code:
drivers/usb/storage/uas.c:uas_configure_endpoints() {
...
devinfo->qdepth = usb_alloc_streams(devinfo->intf, eps + 1,
3, MAX_CMNDS, GFP_NOIO);
if (devinfo->qdepth < 0)
return devinfo->qdepth;
...
}
If devinfo->qdepth is negative (e.g. -ENOMEM), uas_change_queue_depth()
will calculate a negative max_depth and clamp the requested depth to this
negative limit.
When this negative depth is passed to scsi_change_queue_depth(), it
bypasses the depth > 0 condition but is still passed to
blk_set_queue_depth():
drivers/scsi/scsi.c:scsi_change_queue_depth() {
...
if (depth > 0) {
sdev->queue_depth = depth;
wmb();
}
if (sdev->request_queue)
blk_set_queue_depth(sdev->request_queue, depth);
...
}
Because blk_set_queue_depth() expects an unsigned int, the negative
value can underflow to a massive integer, corrupting q->queue_depth and
potentially breaking block layer QoS mechanisms (like WBT or Kyber).
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008112645.1820678-1-luca.cecchi.info@gmail.com?part=1
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2 1/2] usb: uas: implement .change_queue_depth to allow per-device queue depth override
2026-10-08 11:26 ` [PATCH] " Luca Cecchi
2026-10-08 11:36 ` sashiko-bot
@ 2026-10-08 12:26 ` Luca Cecchi
2026-10-08 12:26 ` [PATCH v2 2/2] usb: uas: add US_FL_QDEPTH_075 quirk to cap queue depth at probe time Luca Cecchi
` (3 more replies)
1 sibling, 4 replies; 16+ messages in thread
From: Luca Cecchi @ 2026-10-08 12:26 UTC (permalink / raw)
To: linux-usb; +Cc: linux-scsi, oneukum, Luca Cecchi
uas_host_template does not implement .change_queue_depth. Because of
that, the standard sysfs queue_depth attribute stays read-only for
every UAS device, not just mine: scsi_sysfs.c:sdev_store_queue_depth()
requires sht->change_queue_depth to be non-NULL before it allows a
write.
uas_probe() already defaults can_queue to qdepth - 2, with a comment
acknowledging that "some bridge firmwares" need extra margin. That
margin isn't enough for every bridge. Right now the only way to work
around a bridge that needs more headroom is the IGNORE_UAS quirk,
which disables UAS entirely and falls back to BOT/usb-storage - a
large performance cost for a queue-depth problem.
This patch wires up .change_queue_depth so affected users can lower
the depth for just their device (e.g. via a udev rule matching
idVendor/idProduct), without disabling UAS altogether. Default
behaviour (qdepth - 2) is unchanged unless userspace asks for less.
Tested against a Lexar ES3 external SSD enclosure (VID:PID 21c4:0003)
that locks up under sustained heavy concurrent random writes at the
default queue depth. Capping the depth to 24 via this interface
eliminated the lockup across 6 consecutive test runs, including a
continuous 1-hour soak test (1.36TB written, ~822MB/s, no
uas_eh_abort_handler events).
v2: guard against devinfo->qdepth holding a negative error code left
over from a failed uas_configure_endpoints() call in
uas_post_reset()/uas_reset_resume() (the device stays live after
such a failure); without the guard a negative depth would reach
scsi_change_queue_depth() and underflow the unsigned queue_depth
in the block layer (found by Sashiko AI review)
Re-tested on the same Lexar ES3 hardware: writing 0 or a negative
value to the sysfs queue_depth attribute is now rejected cleanly
(queue_depth stays at the previously set 24, no error in dmesg),
confirming the "depth < 1" guard behaves correctly against real
userspace input. I was not able to reproduce the specific failure
path itself (devinfo->qdepth left negative by a failed
usb_alloc_streams() during reset) on this machine - the running
kernel has CONFIG_FAULT_INJECTION disabled, so I could not force
that call to fail on demand. That half of the fix is verified by
code inspection only, not by a live reproduction.
Also switched devinfo's lookup to sdev->hostdata (same pattern
already used by uas_sdev_configure() and the other scsi_device-
level callbacks in this file) instead of casting
sdev->host->hostdata, which was pushing the line past 80 columns.
Signed-off-by: Luca Cecchi <luca.cecchi.info@gmail.com>
---
drivers/usb/storage/uas.c | 33 +++++++++++++++++++++++++++++++++
1 file changed, 33 insertions(+)
diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c
index 2651629..971a47f 100644
--- a/drivers/usb/storage/uas.c
+++ b/drivers/usb/storage/uas.c
@@ -910,6 +910,38 @@ static int uas_sdev_configure(struct scsi_device *sdev,
return 0;
}
+/*
+ * uas does not implement .change_queue_depth, so the standard sysfs
+ * queue_depth attribute stays read-only for every UAS device (see
+ * scsi_sysfs.c:sdev_store_queue_depth(), which requires
+ * sht->change_queue_depth != NULL). Some bridge chips become
+ * unstable under deep command queueing; wiring this up lets affected
+ * users lower the depth for just their device (e.g. via a udev rule
+ * matching idVendor/idProduct), without disabling UAS altogether.
+ * Default behaviour (qdepth - 2) is unchanged unless userspace asks
+ * for less.
+ */
+static int uas_change_queue_depth(struct scsi_device *sdev, int depth)
+{
+ struct uas_dev_info *devinfo = sdev->hostdata;
+ int max_depth = devinfo->qdepth - 2;
+
+ /*
+ * devinfo->qdepth can be left holding a negative error code (e.g.
+ * -ENOMEM from usb_alloc_streams()) if a reset via uas_post_reset()
+ * or uas_reset_resume() fails to reconfigure the endpoints; the
+ * device stays live in that case. Refuse rather than let a
+ * negative depth reach scsi_change_queue_depth() and underflow
+ * the unsigned queue_depth in the block layer.
+ */
+ if (depth < 1 || max_depth < 1)
+ return -EINVAL;
+
+ if (depth > max_depth)
+ depth = max_depth;
+ return scsi_change_queue_depth(sdev, depth);
+}
+
static const struct scsi_host_template uas_host_template = {
.module = THIS_MODULE,
.name = "uas",
@@ -917,6 +949,7 @@ static const struct scsi_host_template uas_host_template = {
.target_alloc = uas_target_alloc,
.sdev_init = uas_sdev_init,
.sdev_configure = uas_sdev_configure,
+ .change_queue_depth = uas_change_queue_depth,
.eh_abort_handler = uas_eh_abort_handler,
.eh_host_reset_handler = uas_eh_host_reset_handler,
.this_id = -1,
--
2.55.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 2/2] usb: uas: add US_FL_QDEPTH_075 quirk to cap queue depth at probe time
2026-10-08 12:26 ` [PATCH v2 1/2] " Luca Cecchi
@ 2026-10-08 12:26 ` Luca Cecchi
2026-10-08 12:33 ` sashiko-bot
2026-10-08 12:32 ` [PATCH v2 1/2] usb: uas: implement .change_queue_depth to allow per-device queue depth override sashiko-bot
` (2 subsequent siblings)
3 siblings, 1 reply; 16+ messages in thread
From: Luca Cecchi @ 2026-10-08 12:26 UTC (permalink / raw)
To: linux-usb; +Cc: linux-scsi, oneukum, Luca Cecchi
The previous patch in this series ("usb: uas: implement
.change_queue_depth to allow per-device queue depth override") lets
userspace lower a device's queue depth via the standard sysfs
attribute, e.g. through a udev rule matching idVendor/idProduct.
That is not sufficient on its own: relying on udev to act after the
device is already probed and in use leaves a window, right after
probe, during which the device is already live with the unmodified
(and, for some bridges, unsafe) default depth. A lockup can happen in
that window before udev has a chance to write the reduced value.
This adds US_FL_QDEPTH_075, a quirk flag that caps can_queue to 3/4 of
the device's reported maximum directly in uas_probe(), before the
device is ever added to the SCSI layer. This closes the race window
entirely for devices known to need it, while leaving the
.change_queue_depth path available for anyone who wants to tune
further.
Applied to the Lexar ES3 enclosure (21c4:0003) already described in
the previous patch: qdepth=32, so this caps can_queue to 24 - exactly
the value empirically found stable during that investigation (zero
lockups across multiple 360s runs and a 1-hour soak test at that
depth, vs. lockups on every run at the unmodified qdepth-2 default).
Signed-off-by: Luca Cecchi <luca.cecchi.info@gmail.com>
---
drivers/usb/storage/uas.c | 10 ++++++++++
drivers/usb/storage/unusual_uas.h | 7 +++++++
include/linux/usb_usual.h | 2 ++
3 files changed, 19 insertions(+)
diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c
index 971a47f..95374dc 100644
--- a/drivers/usb/storage/uas.c
+++ b/drivers/usb/storage/uas.c
@@ -1085,6 +1085,16 @@ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id)
*/
shost->can_queue = devinfo->qdepth - 2;
+ /*
+ * Some bridge chips lock up under sustained deep command queueing
+ * even within the margin above. For those, US_FL_QDEPTH_075 caps
+ * the depth to 3/4 of the device's reported maximum at probe time,
+ * closing the window a udev-triggered .change_queue_depth write
+ * would otherwise leave open between probe and udev acting.
+ */
+ if (devinfo->flags & US_FL_QDEPTH_075)
+ shost->can_queue = (devinfo->qdepth * 3) / 4;
+
usb_set_intfdata(intf, shost);
result = scsi_add_host(shost, &intf->dev);
if (result)
diff --git a/drivers/usb/storage/unusual_uas.h b/drivers/usb/storage/unusual_uas.h
index d6f86d5..d41f206 100644
--- a/drivers/usb/storage/unusual_uas.h
+++ b/drivers/usb/storage/unusual_uas.h
@@ -167,6 +167,13 @@ UNUSUAL_DEV(0x2109, 0x0711, 0x0000, 0x9999,
USB_SC_DEVICE, USB_PR_DEVICE, NULL,
US_FL_NO_ATA_1X),
+/* Reported-by: Luca Cecchi <luca.cecchi.info@gmail.com> */
+UNUSUAL_DEV(0x21c4, 0x0003, 0x0000, 0x9999,
+ "Lexar",
+ "ES3",
+ USB_SC_DEVICE, USB_PR_DEVICE, NULL,
+ US_FL_QDEPTH_075),
+
/* Reported-by: Icenowy Zheng <icenowy@aosc.io> */
UNUSUAL_DEV(0x2537, 0x1068, 0x0000, 0x9999,
"Norelsys",
diff --git a/include/linux/usb_usual.h b/include/linux/usb_usual.h
index 712363c..b436c34 100644
--- a/include/linux/usb_usual.h
+++ b/include/linux/usb_usual.h
@@ -88,6 +88,8 @@
/* Cannot handle WRITE_SAME */ \
US_FLAG(SENSE_AFTER_SYNC, 0x80000000) \
/* Do REQUEST_SENSE after SYNCHRONIZE_CACHE */ \
+ US_FLAG(QDEPTH_075, 0x100000000ULL) \
+ /* Cap UAS queue depth to 3/4 of device max at probe time */
#define US_FLAG(name, value) US_FL_##name = value ,
enum { US_DO_ALL_FLAGS };
--
2.55.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH v2 1/2] usb: uas: implement .change_queue_depth to allow per-device queue depth override
2026-10-08 12:26 ` [PATCH v2 1/2] " Luca Cecchi
2026-10-08 12:26 ` [PATCH v2 2/2] usb: uas: add US_FL_QDEPTH_075 quirk to cap queue depth at probe time Luca Cecchi
@ 2026-10-08 12:32 ` sashiko-bot
2026-10-08 13:32 ` Oliver Neukum
2026-10-08 14:21 ` Alan Stern
3 siblings, 0 replies; 16+ messages in thread
From: sashiko-bot @ 2026-10-08 12:32 UTC (permalink / raw)
To: Luca Cecchi; +Cc: linux-scsi, linux-usb
> uas_host_template does not implement .change_queue_depth. Because of
> that, the standard sysfs queue_depth attribute stays read-only for
> every UAS device, not just mine: scsi_sysfs.c:sdev_store_queue_depth()
> requires sht->change_queue_depth to be non-NULL before it allows a
> write.
>
> uas_probe() already defaults can_queue to qdepth - 2, with a comment
> acknowledging that "some bridge firmwares" need extra margin. That
> margin isn't enough for every bridge. Right now the only way to work
> around a bridge that needs more headroom is the IGNORE_UAS quirk,
> which disables UAS entirely and falls back to BOT/usb-storage - a
> large performance cost for a queue-depth problem.
>
> This patch wires up .change_queue_depth so affected users can lower
> the depth for just their device (e.g. via a udev rule matching
> [ ... ]
> already used by uas_sdev_configure() and the other scsi_device-
> level callbacks in this file) instead of casting
> sdev->host->hostdata, which was pushing the line past 80 columns.
>
> Signed-off-by: Luca Cecchi <luca.cecchi.info@gmail.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008122604.1862534-1-luca.cecchi.info@gmail.com?part=1
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 2/2] usb: uas: add US_FL_QDEPTH_075 quirk to cap queue depth at probe time
2026-10-08 12:26 ` [PATCH v2 2/2] usb: uas: add US_FL_QDEPTH_075 quirk to cap queue depth at probe time Luca Cecchi
@ 2026-10-08 12:33 ` sashiko-bot
0 siblings, 0 replies; 16+ messages in thread
From: sashiko-bot @ 2026-10-08 12:33 UTC (permalink / raw)
To: Luca Cecchi; +Cc: linux-scsi, linux-usb
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] usb-storage: truncation of US_FL_QDEPTH_075 on 32-bit architectures
- [Medium] usb-storage: UAS queue depth cap overrides and violates 2-tag reserve for small qdepths
--
commit e6046cf877ab5ed09e6d0dc6b5f9a570cdfb3bd0
Author: Luca Cecchi <luca.cecchi.info@gmail.com>
usb: uas: add US_FL_QDEPTH_075 quirk to cap queue depth at probe time
This patch introduces the US_FL_QDEPTH_075 quirk to cap the queue depth of
specific USB attached SCSI devices at 3/4 of their reported maximum during
probe time. It applies this quirk to the Lexar ES3 enclosure to prevent
firmware lockups under sustained deep command queueing.
> diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c
> index f745c9ee02d35..975598c6a6613 100644
> --- a/drivers/usb/storage/uas.c
> +++ b/drivers/usb/storage/uas.c
> @@ -1085,6 +1085,16 @@ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id)
> */
> shost->can_queue = devinfo->qdepth - 2;
>
> + /*
> + * Some bridge chips lock up under sustained deep command queueing
> + * even within the margin above. For those, US_FL_QDEPTH_075 caps
> + * the depth to 3/4 of the device's reported maximum at probe time,
> + * closing the window a udev-triggered .change_queue_depth write
> + * would otherwise leave open between probe and udev acting.
> + */
> + if (devinfo->flags & US_FL_QDEPTH_075)
> + shost->can_queue = (devinfo->qdepth * 3) / 4;
[Severity: Medium]
Does this unconditionally override the previously calculated safe queue depth,
violating the 2-tag reserve for devices with small queue depths?
If a device or host controller limits qdepth to a small value, for example 4,
the original logic safely restricts can_queue to 2 to reserve 1 tag for
untagged commands and 1 tag for firmware off-by-one errors.
With this change, (4 * 3) / 4 evaluates to 3, which accidentally inflates
can_queue instead of capping it, leaving only 1 tag reserved.
Could this use min_t(int, shost->can_queue, (devinfo->qdepth * 3) / 4) to
ensure the baseline reservations are always respected?
> diff --git a/drivers/usb/storage/unusual_uas.h b/drivers/usb/storage/unusual_uas.h
> index d6f86d5db3bf2..d41f206faea13 100644
> --- a/drivers/usb/storage/unusual_uas.h
> +++ b/drivers/usb/storage/unusual_uas.h
[ ... ]
> +/* Reported-by: Luca Cecchi <luca.cecchi.info@gmail.com> */
> +UNUSUAL_DEV(0x21c4, 0x0003, 0x0000, 0x9999,
> + "Lexar",
> + "ES3",
> + USB_SC_DEVICE, USB_PR_DEVICE, NULL,
> + US_FL_QDEPTH_075),
[Severity: High]
Will the US_FL_QDEPTH_075 quirk be silently discarded on 32-bit architectures?
The usb_device_id struct uses kernel_ulong_t for the driver_info field,
which is a 32-bit unsigned long on 32-bit platforms.
Because US_FL_QDEPTH_075 is defined as a 33-bit value:
include/linux/usb_usual.h:
US_FLAG(QDEPTH_075, 0x100000000ULL)
When UNUSUAL_DEV sets this flag in the table here, casting the 64-bit value
to kernel_ulong_t will silently truncate the upper bits to 0 on 32-bit
platforms. This leaves affected devices exposed to the firmware lockups this
patch intends to fix.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008122604.1862534-1-luca.cecchi.info@gmail.com?part=2
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 1/2] usb: uas: implement .change_queue_depth to allow per-device queue depth override
2026-10-08 12:26 ` [PATCH v2 1/2] " Luca Cecchi
2026-10-08 12:26 ` [PATCH v2 2/2] usb: uas: add US_FL_QDEPTH_075 quirk to cap queue depth at probe time Luca Cecchi
2026-10-08 12:32 ` [PATCH v2 1/2] usb: uas: implement .change_queue_depth to allow per-device queue depth override sashiko-bot
@ 2026-10-08 13:32 ` Oliver Neukum
2026-10-08 14:21 ` Alan Stern
3 siblings, 0 replies; 16+ messages in thread
From: Oliver Neukum @ 2026-10-08 13:32 UTC (permalink / raw)
To: Luca Cecchi, linux-usb; +Cc: linux-scsi, oneukum
On 08.10.26 14:26, Luca Cecchi wrote:
>
> v2: guard against devinfo->qdepth holding a negative error code left
> over from a failed uas_configure_endpoints() call in
Sorry, overlapping mails.
Please drop this part. Sashiko was too nice about this issue.
It is a general bug. qdepth must never be negative. It is
a general issue. I've submitted a patch. Your patch
just hit the issue.
Sorry
Oliver
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 1/2] usb: uas: implement .change_queue_depth to allow per-device queue depth override
2026-10-08 12:26 ` [PATCH v2 1/2] " Luca Cecchi
` (2 preceding siblings ...)
2026-10-08 13:32 ` Oliver Neukum
@ 2026-10-08 14:21 ` Alan Stern
2026-10-08 20:19 ` Luca Cecchi
3 siblings, 1 reply; 16+ messages in thread
From: Alan Stern @ 2026-10-08 14:21 UTC (permalink / raw)
To: Luca Cecchi; +Cc: linux-usb, linux-scsi, oneukum
On Thu, Oct 08, 2026 at 02:26:03PM +0200, Luca Cecchi wrote:
> uas_host_template does not implement .change_queue_depth. Because of
> that, the standard sysfs queue_depth attribute stays read-only for
> every UAS device, not just mine: scsi_sysfs.c:sdev_store_queue_depth()
> requires sht->change_queue_depth to be non-NULL before it allows a
> write.
>
> uas_probe() already defaults can_queue to qdepth - 2, with a comment
> acknowledging that "some bridge firmwares" need extra margin. That
> margin isn't enough for every bridge. Right now the only way to work
> around a bridge that needs more headroom is the IGNORE_UAS quirk,
> which disables UAS entirely and falls back to BOT/usb-storage - a
> large performance cost for a queue-depth problem.
>
> This patch wires up .change_queue_depth so affected users can lower
> the depth for just their device (e.g. via a udev rule matching
> idVendor/idProduct), without disabling UAS altogether. Default
> behaviour (qdepth - 2) is unchanged unless userspace asks for less.
>
> Tested against a Lexar ES3 external SSD enclosure (VID:PID 21c4:0003)
> that locks up under sustained heavy concurrent random writes at the
> default queue depth. Capping the depth to 24 via this interface
> eliminated the lockup across 6 consecutive test runs, including a
> continuous 1-hour soak test (1.36TB written, ~822MB/s, no
> uas_eh_abort_handler events).
>
> v2: guard against devinfo->qdepth holding a negative error code left
> over from a failed uas_configure_endpoints() call in
> uas_post_reset()/uas_reset_resume() (the device stays live after
> such a failure); without the guard a negative depth would reach
> scsi_change_queue_depth() and underflow the unsigned queue_depth
> in the block layer (found by Sashiko AI review)
>
> Re-tested on the same Lexar ES3 hardware: writing 0 or a negative
> value to the sysfs queue_depth attribute is now rejected cleanly
> (queue_depth stays at the previously set 24, no error in dmesg),
> confirming the "depth < 1" guard behaves correctly against real
> userspace input. I was not able to reproduce the specific failure
> path itself (devinfo->qdepth left negative by a failed
> usb_alloc_streams() during reset) on this machine - the running
> kernel has CONFIG_FAULT_INJECTION disabled, so I could not force
> that call to fail on demand. That half of the fix is verified by
> code inspection only, not by a live reproduction.
>
> Also switched devinfo's lookup to sdev->hostdata (same pattern
> already used by uas_sdev_configure() and the other scsi_device-
> level callbacks in this file) instead of casting
> sdev->host->hostdata, which was pushing the line past 80 columns.
>
> Signed-off-by: Luca Cecchi <luca.cecchi.info@gmail.com>
> ---
> drivers/usb/storage/uas.c | 33 +++++++++++++++++++++++++++++++++
> 1 file changed, 33 insertions(+)
>
> diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c
> index 2651629..971a47f 100644
> --- a/drivers/usb/storage/uas.c
> +++ b/drivers/usb/storage/uas.c
> @@ -910,6 +910,38 @@ static int uas_sdev_configure(struct scsi_device *sdev,
> return 0;
> }
>
> +/*
> + * uas does not implement .change_queue_depth, so the standard sysfs
> + * queue_depth attribute stays read-only for every UAS device (see
> + * scsi_sysfs.c:sdev_store_queue_depth(), which requires
> + * sht->change_queue_depth != NULL).
This sentence will no longer be true after the patch is merged. It does
not belong here.
> Some bridge chips become
> + * unstable under deep command queueing; wiring this up lets affected
> + * users lower the depth for just their device (e.g. via a udev rule
> + * matching idVendor/idProduct), without disabling UAS altogether.
> + * Default behaviour (qdepth - 2) is unchanged unless userspace asks
> + * for less.
> + */
In fact, does anything in this comment tell the reader something that
isn't already obvious? Maybe no part of the comment is needed. Or just
a minimal comment, such as:
/*
* Implementing .change_queue_depth allows users to lower the per-device
* depth queue via sysfs.
*/
Alan Stern
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 1/2] usb: uas: implement .change_queue_depth to allow per-device queue depth override
2026-10-08 14:21 ` Alan Stern
@ 2026-10-08 20:19 ` Luca Cecchi
2026-10-09 8:03 ` Luca Cecchi
0 siblings, 1 reply; 16+ messages in thread
From: Luca Cecchi @ 2026-10-08 20:19 UTC (permalink / raw)
To: oneukum; +Cc: linux-usb, linux-scsi, stern, Luca Cecchi
Hi Oliver, Alan,
(Resending in plain text - my previous reply went out as HTML and got bounced
by the list software. Apologies for the noise if it reaches you twice.)
Quick status update before sending v3. Both pending items are already fixed
locally: dropped the qdepth-guard per Oliver's note (that's a separate general
issue, not something this patch should carry a workaround for), and trimmed
the comment per Alan's feedback.
While re-validating today, something changed the picture: repeating our
original load test (the one that found the safe queue-depth threshold)
against the exact same Lexar ES3 unit, but now also on Windows with a
completely different driver stack (UASPStor, not Linux's uas), reproduces the
same command-timeout/reset behavior we'd previously only seen on Linux. Since
the physical drive/bridge is the only thing in common between the two
reproductions, this points to the test unit itself having degraded since our
first round of testing on Oct 4, rather than anything in the driver or the
patch.
We'd rather not send v3 until we can re-validate it properly, which isn't
meaningful right now if the hardware itself is behaving inconsistently. We'll
follow up once we've sorted out whether this drive is still usable for
testing - hopefully it hasn't given up on us for good.
Thanks again for the time and the careful feedback on this, it's genuinely
appreciated.
Luca
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 1/2] usb: uas: implement .change_queue_depth to allow per-device queue depth override
2026-10-08 20:19 ` Luca Cecchi
@ 2026-10-09 8:03 ` Luca Cecchi
2026-10-09 8:12 ` [PATCH v3 " Luca Cecchi
0 siblings, 1 reply; 16+ messages in thread
From: Luca Cecchi @ 2026-10-09 8:03 UTC (permalink / raw)
To: oneukum; +Cc: linux-usb, linux-scsi, stern, Luca Cecchi
Hi Oliver, Alan,
Follow-up to yesterday's note. Good news: the test drive wasn't dying after all -
turned out to be heat/stress from several hours of back-to-back testing the day
before. After letting it rest, it's back to behaving exactly like it did during
our original Oct 4 testing.
Re-validated v3 on bare metal today, same Lexar ES3, same exact load test as
Oct 4 (24 sync writers, randwrite, 64KiB, direct=1, 360s each run):
- Confirmed the quirk applies on its own at probe time (queue_depth=24) even
with the udev rule disabled, so it's not just masking a udev race.
- Two repeat runs of the original test: 840-843 MB/s, zero
uas_eh_abort_handler/uas_eh_host_reset_handler events (matches the original
822-823 MB/s baseline).
- Same test run concurrently with a second USB flash drive under load on the
same controller: no effect on the Lexar's throughput or stability.
- Unplugged/replugged the Lexar mid-load: in-flight commands were cleaned up
correctly (uas_zap_pending, no hang), device re-enumerated as SuperSpeed,
uas re-bound, and the quirk re-applied queue_depth=24 on the new probe
without any help from udev.
- Mixed read/write load (70/30): 614 MB/s read + 263 MB/s write, zero events.
Will send v3 shortly.
Luca
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v3 1/2] usb: uas: implement .change_queue_depth to allow per-device queue depth override
2026-10-09 8:03 ` Luca Cecchi
@ 2026-10-09 8:12 ` Luca Cecchi
2026-10-09 8:12 ` [PATCH v3 2/2] usb: uas: add US_FL_QDEPTH_075 quirk to cap queue depth at probe time Luca Cecchi
2026-10-09 8:29 ` [PATCH v3 1/2] usb: uas: implement .change_queue_depth to allow per-device queue depth override sashiko-bot
0 siblings, 2 replies; 16+ messages in thread
From: Luca Cecchi @ 2026-10-09 8:12 UTC (permalink / raw)
To: linux-usb; +Cc: linux-scsi, oneukum, stern, Luca Cecchi
uas_host_template does not implement .change_queue_depth. Because of
that, the standard sysfs queue_depth attribute stays read-only for
every UAS device, not just mine: scsi_sysfs.c:sdev_store_queue_depth()
requires sht->change_queue_depth to be non-NULL before it allows a
write.
uas_probe() already defaults can_queue to qdepth - 2, with a comment
acknowledging that "some bridge firmwares" need extra margin. That
margin isn't enough for every bridge. Right now the only way to work
around a bridge that needs more headroom is the IGNORE_UAS quirk,
which disables UAS entirely and falls back to BOT/usb-storage - a
large performance cost for a queue-depth problem.
This patch wires up .change_queue_depth so affected users can lower
the depth for just their device (e.g. via a udev rule matching
idVendor/idProduct), without disabling UAS altogether. Default
behaviour (qdepth - 2) is unchanged unless userspace asks for less.
Tested against a Lexar ES3 external SSD enclosure (VID:PID 21c4:0003)
that locks up under sustained heavy concurrent random writes at the
default queue depth. Capping the depth to 24 via this interface
eliminated the lockup across 6 consecutive test runs, including a
continuous 1-hour soak test (1.36TB written, ~822MB/s, no
uas_eh_abort_handler events).
v2: guarded against devinfo->qdepth holding a negative error code
left over from a failed uas_configure_endpoints() call in
uas_post_reset()/uas_reset_resume() (found by Sashiko AI review)
v3: dropped that guard per Oliver's direct reply - qdepth going
negative is a general bug independent of this patch, which he is
fixing separately at the source. This patch just happened to hit
the symptom; it carries no workaround for it anymore.
Trimmed the comment above uas_change_queue_depth() per Alan
Stern's feedback - it described the pre-patch state rather than
the code itself, which won't be true after this is merged.
Also switched devinfo's lookup to sdev->hostdata (same pattern
already used by uas_sdev_configure() and the other scsi_device-
level callbacks in this file) instead of casting
sdev->host->hostdata, which was pushing the line past 80 columns.
Re-validated on bare metal with the same Lexar ES3 unit: two
6-minute runs of the original load test (24 sync writers,
randwrite, 64KiB, direct=1) at 840-843MB/s with zero
uas_eh_abort_handler/uas_eh_host_reset_handler events, plus a
mixed 70/30 read/write run (614MB/s read + 263MB/s write, zero
events) and a run concurrent with a second USB flash drive on the
same controller (no effect on throughput or stability).
Signed-off-by: Luca Cecchi <luca.cecchi.info@gmail.com>
---
drivers/usb/storage/uas.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c
index 2651629..1b65e03 100644
--- a/drivers/usb/storage/uas.c
+++ b/drivers/usb/storage/uas.c
@@ -910,6 +910,20 @@ static int uas_sdev_configure(struct scsi_device *sdev,
return 0;
}
+/*
+ * Implementing .change_queue_depth allows users to lower the per-device
+ * queue depth via sysfs.
+ */
+static int uas_change_queue_depth(struct scsi_device *sdev, int depth)
+{
+ struct uas_dev_info *devinfo = sdev->hostdata;
+ int max_depth = devinfo->qdepth - 2;
+
+ if (depth > max_depth)
+ depth = max_depth;
+ return scsi_change_queue_depth(sdev, depth);
+}
+
static const struct scsi_host_template uas_host_template = {
.module = THIS_MODULE,
.name = "uas",
@@ -917,6 +931,7 @@ static const struct scsi_host_template uas_host_template = {
.target_alloc = uas_target_alloc,
.sdev_init = uas_sdev_init,
.sdev_configure = uas_sdev_configure,
+ .change_queue_depth = uas_change_queue_depth,
.eh_abort_handler = uas_eh_abort_handler,
.eh_host_reset_handler = uas_eh_host_reset_handler,
.this_id = -1,
--
2.55.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v3 2/2] usb: uas: add US_FL_QDEPTH_075 quirk to cap queue depth at probe time
2026-10-09 8:12 ` [PATCH v3 " Luca Cecchi
@ 2026-10-09 8:12 ` Luca Cecchi
2026-10-09 8:20 ` sashiko-bot
2026-10-09 8:29 ` [PATCH v3 1/2] usb: uas: implement .change_queue_depth to allow per-device queue depth override sashiko-bot
1 sibling, 1 reply; 16+ messages in thread
From: Luca Cecchi @ 2026-10-09 8:12 UTC (permalink / raw)
To: linux-usb; +Cc: linux-scsi, oneukum, stern, Luca Cecchi
The previous patch in this series ("usb: uas: implement
.change_queue_depth to allow per-device queue depth override") lets
userspace lower a device's queue depth via the standard sysfs
attribute, but only after udev has had a chance to act. That still
leaves a window between probe and udev's write where the device runs
at the full default depth.
This adds US_FL_QDEPTH_075: it caps can_queue to 3/4 of the device's
reported maximum directly in uas_probe(), closing that window for
devices that need it. Applied here to the Lexar ES3 enclosure
(21c4:0003) from the previous patch.
v3: Sashiko AI review found two real issues in v2 (first version of
this patch, sent alongside v2 of patch 1), both fixed:
- [High] US_FL_QDEPTH_075 is bit 32 (0x100000000ULL - all 32 bits
below it are already allocated upstream). UNUSUAL_DEV()'s
driver_info field is kernel_ulong_t, i.e. a 32-bit unsigned
long on 32-bit arches - storing this flag there would silently
truncate to 0 at compile time, disabling the quirk on 32-bit
kernels with no warning. Confirmed by checking
include/linux/mod_devicetable.h's usb_device_id definition.
Fix: dropped the UNUSUAL_DEV() table entry for the Lexar device
entirely. uas-detect.h's uas_use_uas_driver() already has
precedent for exactly this situation - the ASMedia/Seagate/
HIKSEMI checks there match idVendor/idProduct directly and OR
the flag into a local u64 variable, never touching driver_info.
Added the Lexar check the same way. devinfo->flags itself is a
plain u64 with no 32-bit-platform narrowing anywhere in that
path, so this sidesteps the truncation entirely.
- [Medium] the 3/4 cap in uas_probe() unconditionally overwrote
can_queue, which could raise it instead of capping it for
devices with a small qdepth (e.g. qdepth=4: the existing
default is qdepth-2=2, but (4*3)/4=3 is larger, defeating the
point of a cap). Fixed with min_t() against the existing
qdepth-2 value - this quirk may now only lower can_queue, never
raise it.
Re-validated on bare metal with the same Lexar ES3 unit after the
drive turned out to have been thermally stressed from several
hours of back-to-back testing (confirmed by reproducing the same
command-timeout/reset behaviour on Windows with a completely
different driver stack, UASPStor - ruling out the driver/patch as
the cause). After letting the drive rest:
- Confirmed the quirk applies on its own at probe time
(queue_depth=24) even with the udev rule from the previous
patch disabled, so it isn't just masking a udev race.
- Two 6-minute runs of the original load test (24 sync writers,
randwrite, 64KiB, direct=1): 840-843MB/s, zero
uas_eh_abort_handler/uas_eh_host_reset_handler events.
- Same test run concurrently with a second USB flash drive under
load on the same controller: no effect on throughput or
stability.
- Unplugged/replugged the drive mid-load: in-flight commands were
cleaned up correctly (uas_zap_pending, no hang), the device
re-enumerated as SuperSpeed, uas re-bound, and the quirk
re-applied queue_depth=24 on the new probe without any help
from udev.
- Mixed 70/30 read/write load: 614MB/s read + 263MB/s write, zero
events.
Signed-off-by: Luca Cecchi <luca.cecchi.info@gmail.com>
---
drivers/usb/storage/uas-detect.h | 13 +++++++++++++
drivers/usb/storage/uas.c | 13 +++++++++++++
include/linux/usb_usual.h | 2 ++
3 files changed, 28 insertions(+)
diff --git a/drivers/usb/storage/uas-detect.h b/drivers/usb/storage/uas-detect.h
index 4d3b49e..5dd52d6 100644
--- a/drivers/usb/storage/uas-detect.h
+++ b/drivers/usb/storage/uas-detect.h
@@ -129,6 +129,19 @@ static int uas_use_uas_driver(struct usb_interface *intf,
(udev->product && !strcmp(udev->product, "MD202")))
flags |= US_FL_IGNORE_UAS;
+ /*
+ * This Lexar ES3 enclosure locks up under sustained deep command
+ * queueing. US_FL_QDEPTH_075 doesn't fit in the UNUSUAL_DEV()
+ * table's driver_info (kernel_ulong_t, only 32 bits on 32-bit
+ * arches - this flag is bit 32), so it's set here instead,
+ * same as the ASMedia/Seagate/HIKSEMI cases above.
+ *
+ * Reported-by: Luca Cecchi <luca.cecchi.info@gmail.com>
+ */
+ if (le16_to_cpu(udev->descriptor.idVendor) == 0x21c4 &&
+ le16_to_cpu(udev->descriptor.idProduct) == 0x0003)
+ flags |= US_FL_QDEPTH_075;
+
usb_stor_adjust_quirks(udev, &flags);
if (flags & US_FL_IGNORE_UAS) {
diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c
index 1b65e03..4cdf7f8 100644
--- a/drivers/usb/storage/uas.c
+++ b/drivers/usb/storage/uas.c
@@ -1067,6 +1067,19 @@ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id)
*/
shost->can_queue = devinfo->qdepth - 2;
+ /*
+ * Some bridge chips lock up under sustained deep command queueing
+ * even within the margin above. For those, US_FL_QDEPTH_075 caps
+ * the depth to 3/4 of the device's reported maximum at probe time,
+ * closing the window a udev-triggered .change_queue_depth write
+ * would otherwise leave open between probe and udev acting. Take
+ * the min with the existing margin above - this must only ever
+ * lower can_queue, never raise it back up for low-qdepth devices.
+ */
+ if (devinfo->flags & US_FL_QDEPTH_075)
+ shost->can_queue = min_t(int, shost->can_queue,
+ (devinfo->qdepth * 3) / 4);
+
usb_set_intfdata(intf, shost);
result = scsi_add_host(shost, &intf->dev);
if (result)
diff --git a/include/linux/usb_usual.h b/include/linux/usb_usual.h
index 712363c..b436c34 100644
--- a/include/linux/usb_usual.h
+++ b/include/linux/usb_usual.h
@@ -88,6 +88,8 @@
/* Cannot handle WRITE_SAME */ \
US_FLAG(SENSE_AFTER_SYNC, 0x80000000) \
/* Do REQUEST_SENSE after SYNCHRONIZE_CACHE */ \
+ US_FLAG(QDEPTH_075, 0x100000000ULL) \
+ /* Cap UAS queue depth to 3/4 of device max at probe time */
#define US_FLAG(name, value) US_FL_##name = value ,
enum { US_DO_ALL_FLAGS };
--
2.55.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH v3 2/2] usb: uas: add US_FL_QDEPTH_075 quirk to cap queue depth at probe time
2026-10-09 8:12 ` [PATCH v3 2/2] usb: uas: add US_FL_QDEPTH_075 quirk to cap queue depth at probe time Luca Cecchi
@ 2026-10-09 8:20 ` sashiko-bot
0 siblings, 0 replies; 16+ messages in thread
From: sashiko-bot @ 2026-10-09 8:20 UTC (permalink / raw)
To: Luca Cecchi; +Cc: linux-usb, linux-scsi
> The previous patch in this series ("usb: uas: implement
> .change_queue_depth to allow per-device queue depth override") lets
> userspace lower a device's queue depth via the standard sysfs
> attribute, but only after udev has had a chance to act. That still
> leaves a window between probe and udev's write where the device runs
> at the full default depth.
>
> This adds US_FL_QDEPTH_075: it caps can_queue to 3/4 of the device's
> reported maximum directly in uas_probe(), closing that window for
> devices that need it. Applied here to the Lexar ES3 enclosure
> (21c4:0003) from the previous patch.
>
> v3: Sashiko AI review found two real issues in v2 (first version of
> this patch, sent alongside v2 of patch 1), both fixed:
>
> [ ... ]
> from udev.
> - Mixed 70/30 read/write load: 614MB/s read + 263MB/s write, zero
> events.
>
> Signed-off-by: Luca Cecchi <luca.cecchi.info@gmail.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261009081225.304319-1-luca.cecchi.info@gmail.com?part=2
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 1/2] usb: uas: implement .change_queue_depth to allow per-device queue depth override
2026-10-09 8:12 ` [PATCH v3 " Luca Cecchi
2026-10-09 8:12 ` [PATCH v3 2/2] usb: uas: add US_FL_QDEPTH_075 quirk to cap queue depth at probe time Luca Cecchi
@ 2026-10-09 8:29 ` sashiko-bot
1 sibling, 0 replies; 16+ messages in thread
From: sashiko-bot @ 2026-10-09 8:29 UTC (permalink / raw)
To: Luca Cecchi; +Cc: linux-scsi, linux-usb
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] usb: uas: unsigned underflow in block queue depth via negative qdepth
- [High] usb: uas: negative qdepth corrupts block layer queue depth via sysfs
- [Medium] locking: data race on devinfo->qdepth between sysfs uas_change_queue_depth and uas_configure_endpoints
--
commit 4c7e73054e14e754e6e1a2516899bccc20bf7379
Author: Luca Cecchi <luca.cecchi.info@gmail.com>
usb: uas: implement .change_queue_depth to allow per-device queue depth override
This commit implements the .change_queue_depth callback for the UAS driver.
This allows users to dynamically lower the per-device queue depth via sysfs
to work around bridge firmware lockups without having to disable UAS
entirely.
> diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c
> index 8655edbd66b16..0a9ae15be2bd6 100644
> --- a/drivers/usb/storage/uas.c
> +++ b/drivers/usb/storage/uas.c
[ ... ]
> @@ -910,6 +910,20 @@ static int uas_sdev_configure(struct scsi_device *sdev,
> return 0;
> }
>
> +/*
> + * Implementing .change_queue_depth allows users to lower the per-device
> + * queue depth via sysfs.
> + */
> +static int uas_change_queue_depth(struct scsi_device *sdev, int depth)
> +{
> + struct uas_dev_info *devinfo = sdev->hostdata;
> + int max_depth = devinfo->qdepth - 2;
[Severity: Medium]
Could this read of devinfo->qdepth race with a concurrent USB bus reset?
If a user writes to the queue_depth sysfs attribute while a reset
(like uas_post_reset() or uas_reset_resume()) is occurring,
uas_configure_endpoints() can write to devinfo->qdepth before any locks
are acquired by the reset caller:
drivers/usb/storage/uas.c:uas_configure_endpoints() {
...
devinfo->qdepth = usb_alloc_streams(devinfo->intf, eps + 1,
3, MAX_CMNDS, GFP_NOIO);
...
}
Because uas_change_queue_depth() reads devinfo->qdepth without holding
shost->host_lock or devinfo->lock, can this lack of mutual exclusion lead
to an inconsistent observation of the queue depth?
[Severity: High]
Does this code expose the block layer to queue depth underflows?
If uas_configure_endpoints() fails during a USB reset, it can return a
negative error code (such as -ENOMEM or -22) which is assigned directly to
devinfo->qdepth.
If this happens, sdev->host->can_queue still retains its positive probe-time
value, so sysfs writes to queue_depth still pass validation in
sdev_store_queue_depth().
When this happens, max_depth is calculated as a negative value (e.g. -24).
> +
> + if (depth > max_depth)
> + depth = max_depth;
> + return scsi_change_queue_depth(sdev, depth);
The requested depth is then capped to this negative max_depth and passed to
scsi_change_queue_depth() in drivers/scsi/scsi.c.
scsi_change_queue_depth() forwards this negative value to
blk_set_queue_depth(..., unsigned int depth) in block/blk-settings.c, where
it will implicitly cast to a massive unsigned integer.
Can this heavily corrupt the block layer's queue limits and lead to tag
exhaustion or memory corruption? Even if the underlying negative qdepth is
a separate issue, does wiring up change_queue_depth expose this previously
unreachable vulnerability to sysfs?
> +}
> +
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261009081225.304319-1-luca.cecchi.info@gmail.com?part=1
^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2026-10-09 8:29 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-04 11:41 [RFC] usb: uas: implement .change_queue_depth to allow per-device queue depth override Luca Cecchi
2026-10-08 11:00 ` Oliver Neukum
2026-10-08 11:26 ` [PATCH] " Luca Cecchi
2026-10-08 11:36 ` sashiko-bot
2026-10-08 12:26 ` [PATCH v2 1/2] " Luca Cecchi
2026-10-08 12:26 ` [PATCH v2 2/2] usb: uas: add US_FL_QDEPTH_075 quirk to cap queue depth at probe time Luca Cecchi
2026-10-08 12:33 ` sashiko-bot
2026-10-08 12:32 ` [PATCH v2 1/2] usb: uas: implement .change_queue_depth to allow per-device queue depth override sashiko-bot
2026-10-08 13:32 ` Oliver Neukum
2026-10-08 14:21 ` Alan Stern
2026-10-08 20:19 ` Luca Cecchi
2026-10-09 8:03 ` Luca Cecchi
2026-10-09 8:12 ` [PATCH v3 " Luca Cecchi
2026-10-09 8:12 ` [PATCH v3 2/2] usb: uas: add US_FL_QDEPTH_075 quirk to cap queue depth at probe time Luca Cecchi
2026-10-09 8:20 ` sashiko-bot
2026-10-09 8:29 ` [PATCH v3 1/2] usb: uas: implement .change_queue_depth to allow per-device queue depth override sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox