* [PATCH v9 01/11] ata: libata-core: pin SCSI devices during port detach
2026-10-06 23:41 [PATCH v9 00/11] ata: add multi-LUN support for ATAPI devices Phil Pemberton
@ 2026-10-06 23:41 ` Phil Pemberton
2026-10-06 23:41 ` [PATCH v9 02/11] ata: libata-zpodd: reference SCSI devices in PM callbacks Phil Pemberton
` (9 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: Phil Pemberton @ 2026-10-06 23:41 UTC (permalink / raw)
To: linux-ide, linux-scsi
Cc: linux-kernel, Damien Le Moal, Niklas Cassel,
James E . J . Bottomley, Martin K . Petersen, Hannes Reinecke,
Phil Pemberton, stable
ata_port_detach() drops ap->lock before removing dev->sdev. Concurrent
sysfs deletion can release the device before scsi_remove_device() uses it.
Take a device reference and clear the slot under ap->lock, then remove
the saved device. Release the reference outside the lock because device
release can sleep.
Use get_device() to hold the reference during host-driver unload, when
scsi_device_get() can fail its module reference check. The SCSI core
uses the same approach for target removal.
Fixes: 84d76529c650 ("ata: libata-core: Fix port and device removal")
Link: https://lore.kernel.org/linux-ide/20260731220740.7E14E1F00ACF@smtp.kernel.org/
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Phil Pemberton <philpem@philpem.me.uk>
---
drivers/ata/libata-core.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c
index 92233fb25051..5518525357ce 100644
--- a/drivers/ata/libata-core.c
+++ b/drivers/ata/libata-core.c
@@ -26,6 +26,7 @@
*/
#include <linux/kernel.h>
+#include <linux/device.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/init.h>
@@ -6388,10 +6389,15 @@ static void ata_port_detach(struct ata_port *ap)
ata_for_each_link(link, ap, HOST_FIRST) {
ata_for_each_dev(dev, link, ALL) {
if (dev->sdev) {
+ struct scsi_device *sdev = dev->sdev;
+
+ /* The host driver may already be unloading. */
+ get_device(&sdev->sdev_gendev);
+ dev->sdev = NULL;
spin_unlock_irqrestore(ap->lock, flags);
- scsi_remove_device(dev->sdev);
+ scsi_remove_device(sdev);
+ put_device(&sdev->sdev_gendev);
spin_lock_irqsave(ap->lock, flags);
- dev->sdev = NULL;
}
}
}
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH v9 02/11] ata: libata-zpodd: reference SCSI devices in PM callbacks
2026-10-06 23:41 [PATCH v9 00/11] ata: add multi-LUN support for ATAPI devices Phil Pemberton
2026-10-06 23:41 ` [PATCH v9 01/11] ata: libata-core: pin SCSI devices during port detach Phil Pemberton
@ 2026-10-06 23:41 ` Phil Pemberton
2026-10-06 23:59 ` sashiko-bot
2026-10-06 23:41 ` [PATCH v9 03/11] scsi: scsi_devinfo: preserve full-width quirk identifiers Phil Pemberton
` (8 subsequent siblings)
10 siblings, 1 reply; 17+ messages in thread
From: Phil Pemberton @ 2026-10-06 23:41 UTC (permalink / raw)
To: linux-ide, linux-scsi
Cc: linux-kernel, Damien Le Moal, Niklas Cassel,
James E . J . Bottomley, Martin K . Petersen, Hannes Reinecke,
Phil Pemberton, stable
The ZPODD wake callback accesses dev->sdev without locking or taking a
reference. Concurrent sysfs deletion can release the device before the
callback accesses its runtime PM state. The disk-event enable and disable
paths have the same problem.
Take a SCSI device reference under ap->lock, skipping missing or deleting
devices. Drop the lock before accessing PM or disk-event state and before
releasing the reference.
Fixes: f064a20dded8 ("libata: move acpi notification code to zpodd")
Link: https://lore.kernel.org/linux-ide/20260611030131.5285D1F00893@smtp.kernel.org/
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Phil Pemberton <philpem@philpem.me.uk>
---
drivers/ata/libata-zpodd.c | 41 +++++++++++++++++++++++++++++++++-----
1 file changed, 36 insertions(+), 5 deletions(-)
diff --git a/drivers/ata/libata-zpodd.c b/drivers/ata/libata-zpodd.c
index 414e7c63bd85..581eb41ea0f3 100644
--- a/drivers/ata/libata-zpodd.c
+++ b/drivers/ata/libata-zpodd.c
@@ -173,6 +173,22 @@ bool zpodd_zpready(struct ata_device *dev)
return zpodd->zp_ready;
}
+/* Return a referenced SCSI device; the caller must drop it outside ap->lock. */
+static struct scsi_device *zpodd_get_sdev(struct ata_device *dev)
+{
+ struct ata_port *ap = dev->link->ap;
+ struct scsi_device *sdev;
+ unsigned long flags;
+
+ spin_lock_irqsave(ap->lock, flags);
+ sdev = dev->sdev;
+ if (sdev && scsi_device_get(sdev))
+ sdev = NULL;
+ spin_unlock_irqrestore(ap->lock, flags);
+
+ return sdev;
+}
+
/*
* Enable runtime wake capability through ACPI and set the powered_off flag,
* this flag will be used during resume to decide what operations are needed
@@ -184,8 +200,12 @@ bool zpodd_zpready(struct ata_device *dev)
void zpodd_enable_run_wake(struct ata_device *dev)
{
struct zpodd *zpodd = dev->zpodd;
+ struct scsi_device *sdev = zpodd_get_sdev(dev);
- sdev_disable_disk_events(dev->sdev);
+ if (sdev) {
+ sdev_disable_disk_events(sdev);
+ scsi_device_put(sdev);
+ }
zpodd->powered_off = true;
acpi_pm_set_device_wakeup(&dev->tdev, true);
@@ -218,6 +238,7 @@ void zpodd_disable_run_wake(struct ata_device *dev)
void zpodd_post_poweron(struct ata_device *dev)
{
struct zpodd *zpodd = dev->zpodd;
+ struct scsi_device *sdev;
if (!zpodd->powered_off)
return;
@@ -233,19 +254,29 @@ void zpodd_post_poweron(struct ata_device *dev)
zpodd->zp_sampled = false;
zpodd->zp_ready = false;
- sdev_enable_disk_events(dev->sdev);
+ sdev = zpodd_get_sdev(dev);
+ if (sdev) {
+ sdev_enable_disk_events(sdev);
+ scsi_device_put(sdev);
+ }
}
static void zpodd_wake_dev(acpi_handle handle, u32 event, void *context)
{
struct ata_device *ata_dev = context;
struct zpodd *zpodd = ata_dev->zpodd;
- struct device *dev = &ata_dev->sdev->sdev_gendev;
+ struct scsi_device *sdev;
- if (event == ACPI_NOTIFY_DEVICE_WAKE && pm_runtime_suspended(dev)) {
+ if (event != ACPI_NOTIFY_DEVICE_WAKE)
+ return;
+ sdev = zpodd_get_sdev(ata_dev);
+ if (!sdev)
+ return;
+ if (pm_runtime_suspended(&sdev->sdev_gendev)) {
zpodd->from_notify = true;
- pm_runtime_resume(dev);
+ pm_runtime_resume(&sdev->sdev_gendev);
}
+ scsi_device_put(sdev);
}
static void ata_acpi_add_pm_notifier(struct ata_device *dev)
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH v9 02/11] ata: libata-zpodd: reference SCSI devices in PM callbacks
2026-10-06 23:41 ` [PATCH v9 02/11] ata: libata-zpodd: reference SCSI devices in PM callbacks Phil Pemberton
@ 2026-10-06 23:59 ` sashiko-bot
0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-10-06 23:59 UTC (permalink / raw)
To: Phil Pemberton; +Cc: cassel, dlemoal, linux-ide, linux-scsi
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] ata: TOCTOU race in zpodd wake can cause phantom tray ejections
- [Medium] ata: libata-zpodd: NULL pointer dereference in zpodd_wake_dev()
--
commit f9410c07ec8a633f31a2392169e048611d2e22b5
Author: Phil Pemberton <philpem@philpem.me.uk>
Subject: ata: libata-zpodd: reference SCSI devices in PM callbacks
This commit takes a SCSI device reference under ap->lock to prevent concurrent
sysfs deletion from releasing the device before PM callbacks access it.
Missing or deleting devices are skipped, and locks are dropped before
accessing PM state or releasing the reference.
Link: https://lore.kernel.org/linux-ide/20260611030131.5285D1F00893@smtp.kernel.org/
> diff --git a/drivers/ata/libata-zpodd.c b/drivers/ata/libata-zpodd.c
> index 414e7c63bd850..581eb41ea0f3c 100644
> --- a/drivers/ata/libata-zpodd.c
> +++ b/drivers/ata/libata-zpodd.c
[ ... ]
> static void zpodd_wake_dev(acpi_handle handle, u32 event, void *context)
> {
> struct ata_device *ata_dev = context;
> struct zpodd *zpodd = ata_dev->zpodd;
> - struct device *dev = &ata_dev->sdev->sdev_gendev;
> + struct scsi_device *sdev;
>
> - if (event == ACPI_NOTIFY_DEVICE_WAKE && pm_runtime_suspended(dev)) {
> + if (event != ACPI_NOTIFY_DEVICE_WAKE)
> + return;
> + sdev = zpodd_get_sdev(ata_dev);
> + if (!sdev)
> + return;
> + if (pm_runtime_suspended(&sdev->sdev_gendev)) {
[Severity: Medium]
Could this lockless check of pm_runtime_suspended() race with a concurrent
runtime PM resume?
A race between an ACPI wake event (e.g. user pressing the eject button) and a
concurrent runtime PM resume can cause the from_notify flag to be set right
after the resume path reads it as false.
If a concurrent resume is already progressing, it can execute
zpodd_post_poweron() and read from_notify as false:
drivers/ata/libata-zpodd.c:zpodd_post_poweron() {
...
if (zpodd->from_notify) {
zpodd->from_notify = false;
if (zpodd->mech_type == ODD_MECH_TYPE_DRAWER)
eject_tray(dev);
}
...
}
Then, zpodd_wake_dev() might set from_notify = true right after, leaving it
persistently true until the next power-on cycle.
This defers the tray ejection to the next resume cycle, potentially causing a
phantom tray ejection. Alternatively, if the PM state just changed from
suspended, the ACPI handler might skip setting the flag entirely, losing the
eject event.
> zpodd->from_notify = true;
[Severity: Medium]
Can this dereference a NULL zpodd pointer if an ACPI event fires concurrently?
If a memory allocation failure occurs during the initial zpodd_init() call,
dev->zpodd remains NULL.
If the device later undergoes EH revalidation and kzalloc succeeds,
ata_acpi_add_pm_notifier() is called before dev->zpodd is assigned:
drivers/ata/libata-zpodd.c:zpodd_init() {
...
ata_acpi_add_pm_notifier(dev);
zpodd->dev = dev;
dev->zpodd = zpodd;
...
}
If an ACPI wake event fires concurrently at this exact moment, zpodd_wake_dev()
will execute asynchronously.
Because the device was previously successfully probed, dev->sdev is valid and
zpodd_get_sdev() returns a valid SCSI device.
If the SCSI device is runtime suspended (which can happen during a hotplug or
PHY event before the SCSI layer fully resumes it), pm_runtime_suspended()
evaluates to true.
This leads to zpodd->from_notify = true executing while zpodd is still NULL,
causing a kernel oops.
> - pm_runtime_resume(dev);
> + pm_runtime_resume(&sdev->sdev_gendev);
> }
> + scsi_device_put(sdev);
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261006234202.1139018-1-philpem@philpem.me.uk?part=2
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v9 03/11] scsi: scsi_devinfo: preserve full-width quirk identifiers
2026-10-06 23:41 [PATCH v9 00/11] ata: add multi-LUN support for ATAPI devices Phil Pemberton
2026-10-06 23:41 ` [PATCH v9 01/11] ata: libata-core: pin SCSI devices during port detach Phil Pemberton
2026-10-06 23:41 ` [PATCH v9 02/11] ata: libata-zpodd: reference SCSI devices in PM callbacks Phil Pemberton
@ 2026-10-06 23:41 ` Phil Pemberton
2026-10-06 23:41 ` [PATCH v9 04/11] scsi: sr: initialize logical block size before registering the disk Phil Pemberton
` (7 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: Phil Pemberton @ 2026-10-06 23:41 UTC (permalink / raw)
To: linux-ide, linux-scsi
Cc: linux-kernel, Damien Le Moal, Niklas Cassel,
James E . J . Bottomley, Martin K . Petersen, Hannes Reinecke,
Phil Pemberton, stable
strscpy_pad() reserves a byte for NUL termination, truncating quirk
identifiers that fill their fields. An eight-character vendor such as
MATSHITA loses its final character and fails to match. A sixteen-character
model loses its final character too; prefix matching then allows the
quirk to match other models with the same first fifteen characters.
Copy the full vendor and model fields. Pad shorter strings with NUL for
compatible entries and spaces for dynamic entries. Lookup uses bounded
strnlen() and memcmp(), so full-width identifiers need no terminator.
Shorter model names continue to match prefixes.
Fixes: 1b60c86dd992 ("scsi: devinfo: Replace strncpy() and manual pad")
Link: https://lore.kernel.org/linux-ide/20260611025249.D23191F00893@smtp.kernel.org/
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Phil Pemberton <philpem@philpem.me.uk>
---
drivers/scsi/scsi_devinfo.c | 11 +++--------
1 file changed, 3 insertions(+), 8 deletions(-)
diff --git a/drivers/scsi/scsi_devinfo.c b/drivers/scsi/scsi_devinfo.c
index 88a911b35c94..5e37461e61e8 100644
--- a/drivers/scsi/scsi_devinfo.c
+++ b/drivers/scsi/scsi_devinfo.c
@@ -289,14 +289,9 @@ static void scsi_strcpy_devinfo(char *name, char *to, size_t to_length,
from_length = strlen(from);
- /*
- * null pad and null terminate if compatible
- * otherwise space pad
- */
- if (compatible)
- strscpy_pad(to, from, to_length);
- else
- memcpy_and_pad(to, to_length, from, from_length, ' ');
+ /* Full-width identifiers need all bytes; shorter ones are padded. */
+ memcpy_and_pad(to, to_length, from, from_length,
+ compatible ? '\0' : ' ');
if (from_length > to_length)
printk(KERN_WARNING "%s: %s string '%s' is too long\n",
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH v9 04/11] scsi: sr: initialize logical block size before registering the disk
2026-10-06 23:41 [PATCH v9 00/11] ata: add multi-LUN support for ATAPI devices Phil Pemberton
` (2 preceding siblings ...)
2026-10-06 23:41 ` [PATCH v9 03/11] scsi: scsi_devinfo: preserve full-width quirk identifiers Phil Pemberton
@ 2026-10-06 23:41 ` Phil Pemberton
2026-10-06 23:53 ` sashiko-bot
2026-10-06 23:41 ` [PATCH v9 05/11] scsi: sr: retry unfinished media revalidation on the next open Phil Pemberton
` (6 subsequent siblings)
10 siblings, 1 reply; 17+ messages in thread
From: Phil Pemberton @ 2026-10-06 23:41 UTC (permalink / raw)
To: linux-ide, linux-scsi
Cc: linux-kernel, Damien Le Moal, Niklas Cassel,
James E . J . Bottomley, Martin K . Petersen, Hannes Reinecke,
Phil Pemberton
sr_probe() sets sdev->sector_size to 2048, but the request queue keeps its
512-byte default until revalidation finds ready media. Booting a COMPAQ
PD-1 LF-1195C with a PD inserted leaves the empty CD LUN with mismatched
block sizes. Later CD reads can fail with "unaligned transfer".
After initial revalidation, set the queue's logical block size to
sdev->sector_size if they differ. Do this before registering the disk,
and unwind the probe if the update fails.
Revalidation must run first so ready media can supply the actual block
size. Applying the 2048-byte default earlier would raise the physical
block size and minimum I/O size, which stay raised if revalidation later
finds 512-byte sectors.
Assisted-by: LLM
Signed-off-by: Phil Pemberton <philpem@philpem.me.uk>
---
drivers/scsi/sr.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/drivers/scsi/sr.c b/drivers/scsi/sr.c
index c36c54ecd354..268f15a4fdb8 100644
--- a/drivers/scsi/sr.c
+++ b/drivers/scsi/sr.c
@@ -619,6 +619,7 @@ static void sr_release(struct cdrom_device_info *cdi)
static int sr_probe(struct scsi_device *sdev)
{
struct device *dev = &sdev->sdev_gendev;
+ struct queue_limits lim;
struct gendisk *disk;
struct scsi_cd *cd;
int minor, error;
@@ -701,6 +702,16 @@ static int sr_probe(struct scsi_device *sdev)
if (error)
goto unregister_cdrom;
+ /* If no medium was ready, match the queue to the driver's guess. */
+ if (queue_logical_block_size(sdev->request_queue) != sdev->sector_size) {
+ lim = queue_limits_start_update(sdev->request_queue);
+ lim.logical_block_size = sdev->sector_size;
+ error = queue_limits_commit_update_frozen(sdev->request_queue,
+ &lim);
+ if (error)
+ goto unregister_cdrom;
+ }
+
error = device_add_disk(&sdev->sdev_gendev, disk, NULL);
if (error)
goto unregister_cdrom;
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH v9 04/11] scsi: sr: initialize logical block size before registering the disk
2026-10-06 23:41 ` [PATCH v9 04/11] scsi: sr: initialize logical block size before registering the disk Phil Pemberton
@ 2026-10-06 23:53 ` sashiko-bot
0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-10-06 23:53 UTC (permalink / raw)
To: Phil Pemberton; +Cc: dlemoal, linux-scsi, cassel, linux-ide
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] scsi: sr: physical_block_size permanently stuck at 2048 after hot-plugging 512-byte media
--
commit e4bcc9c7bb99e2ad5359e3deb3e003674d2e6c22
Author: Phil Pemberton <philpem@philpem.me.uk>
scsi: sr: initialize logical block size before registering the disk
This commit updates the logical block size of the request queue to match
the driver's default sector size (2048 bytes) if the drive is empty during
probe. This attempts to prevent mismatched block sizes when reading from
media inserted later.
> diff --git a/drivers/scsi/sr.c b/drivers/scsi/sr.c
> index c36c54ecd354b..268f15a4fdb88 100644
> --- a/drivers/scsi/sr.c
> +++ b/drivers/scsi/sr.c
[ ... ]
> @@ -701,6 +702,16 @@ static int sr_probe(struct scsi_device *sdev)
> if (error)
> goto unregister_cdrom;
>
> + /* If no medium was ready, match the queue to the driver's guess. */
> + if (queue_logical_block_size(sdev->request_queue) != sdev->sector_size) {
> + lim = queue_limits_start_update(sdev->request_queue);
> + lim.logical_block_size = sdev->sector_size;
> + error = queue_limits_commit_update_frozen(sdev->request_queue,
> + &lim);
> + if (error)
> + goto unregister_cdrom;
> + }
> +
[Severity: High]
Does this logic introduce a regression where physical_block_size becomes
permanently stuck at 2048 when a 512-byte medium is inserted later?
If a drive is empty during sr_probe(), this code promotes the
logical_block_size to 2048. Because the block layer one-way promotes
physical_block_size to match logical_block_size, blk_validate_limits()
will automatically latch physical_block_size to 2048:
block/blk-settings.c:blk_validate_limits() {
...
if (lim->physical_block_size < lim->logical_block_size) {
lim->physical_block_size = lim->logical_block_size;
...
}
When a 512-byte medium is later hot-plugged, sr_revalidate_disk() shrinks
logical_block_size back to 512:
drivers/scsi/sr.c:sr_revalidate_disk() {
...
lim = queue_limits_start_update(q);
lim.logical_block_size = sector_size;
lim.features |= BLK_FEAT_ROTATIONAL;
return queue_limits_commit_update_frozen(q, &lim);
...
}
However, blk_validate_limits() never automatically shrinks
physical_block_size. Could this leave physical_block_size and io_min
permanently at 2048, continuing to cause mismatched block size issues?
> error = device_add_disk(&sdev->sdev_gendev, disk, NULL);
> if (error)
> goto unregister_cdrom;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261006234202.1139018-1-philpem@philpem.me.uk?part=4
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v9 05/11] scsi: sr: retry unfinished media revalidation on the next open
2026-10-06 23:41 [PATCH v9 00/11] ata: add multi-LUN support for ATAPI devices Phil Pemberton
` (3 preceding siblings ...)
2026-10-06 23:41 ` [PATCH v9 04/11] scsi: sr: initialize logical block size before registering the disk Phil Pemberton
@ 2026-10-06 23:41 ` Phil Pemberton
2026-10-06 23:52 ` sashiko-bot
2026-10-06 23:41 ` [PATCH v9 06/11] ata: libata-scsi: add atapi_max_lun module parameter Phil Pemberton
` (5 subsequent siblings)
10 siblings, 1 reply; 17+ messages in thread
From: Phil Pemberton @ 2026-10-06 23:41 UTC (permalink / raw)
To: linux-ide, linux-scsi
Cc: linux-kernel, Damien Le Moal, Niklas Cassel,
James E . J . Bottomley, Martin K . Petersen, Hannes Reinecke,
Phil Pemberton
A media-change event can arrive while a new disc is spinning up.
Revalidation then returns before updating capacity and queue limits. If
there is no further event, later opens keep the old disc's capacity.
This occurs on a COMPAQ PD-1 LF-1195C after a CD to PD to CD swap.
Keep revalidation pending until it completes, including the queue-limits
update, and retry on the next open. This also covers a CD LUN that is
unready at probe. Access the flag during probe and serialized block opens.
For blocking opens, return -ENOMEDIUM if revalidation reports NOT READY
with ASC/ASCQ 04/01 (becoming ready), leaving the flag set. The drive can
become ready before cdrom_open() checks it again; failing this open
prevents access with stale capacity. sr_drive_status() already maps this
sense code to -ENOMEDIUM for CD-ROM data opens. Probe, nonblocking opens
and other sense results retain their existing behavior.
READ CAPACITY errors still use the existing fallback for audio CDs and
older drives. That fallback can retain stale capacity and clear the flag.
Assisted-by: LLM
Signed-off-by: Phil Pemberton <philpem@philpem.me.uk>
---
drivers/scsi/sr.c | 28 +++++++++++++++++++---------
drivers/scsi/sr.h | 2 ++
2 files changed, 21 insertions(+), 9 deletions(-)
diff --git a/drivers/scsi/sr.c b/drivers/scsi/sr.c
index 268f15a4fdb8..9c80c9abb757 100644
--- a/drivers/scsi/sr.c
+++ b/drivers/scsi/sr.c
@@ -473,23 +473,33 @@ static blk_status_t sr_init_command(struct scsi_cmnd *SCpnt)
return BLK_STS_IOERR;
}
-static int sr_revalidate_disk(struct scsi_cd *cd)
+static int sr_revalidate_disk(struct scsi_cd *cd, bool fail_if_becoming_ready)
{
struct request_queue *q = cd->device->request_queue;
struct scsi_sense_hdr sshdr;
struct queue_limits lim;
- int sector_size;
-
- /* if the unit is not ready, nothing more to do */
- if (scsi_test_unit_ready(cd->device, SR_TIMEOUT, MAX_RETRIES, &sshdr))
+ int sector_size, ret;
+
+ /* Keep revalidation pending if the medium is not ready yet. */
+ cd->needs_revalidate = true;
+ if (scsi_test_unit_ready(cd->device, SR_TIMEOUT, MAX_RETRIES, &sshdr)) {
+ /* Do not let a later readiness check admit stale geometry. */
+ if (fail_if_becoming_ready && scsi_sense_valid(&sshdr) &&
+ sshdr.sense_key == NOT_READY &&
+ sshdr.asc == 0x04 && sshdr.ascq == 0x01)
+ return -ENOMEDIUM;
return 0;
+ }
sr_cd_check(&cd->cdi);
sector_size = get_sectorsize(cd);
lim = queue_limits_start_update(q);
lim.logical_block_size = sector_size;
lim.features |= BLK_FEAT_ROTATIONAL;
- return queue_limits_commit_update_frozen(q, &lim);
+ ret = queue_limits_commit_update_frozen(q, &lim);
+ if (!ret)
+ cd->needs_revalidate = false;
+ return ret;
}
static int sr_block_open(struct gendisk *disk, blk_mode_t mode)
@@ -502,8 +512,8 @@ static int sr_block_open(struct gendisk *disk, blk_mode_t mode)
return -ENXIO;
scsi_autopm_get_device(sdev);
- if (disk_check_media_change(disk)) {
- ret = sr_revalidate_disk(cd);
+ if (disk_check_media_change(disk) || cd->needs_revalidate) {
+ ret = sr_revalidate_disk(cd, !(mode & BLK_OPEN_NDELAY));
if (ret)
goto out;
}
@@ -698,7 +708,7 @@ static int sr_probe(struct scsi_device *sdev)
blk_pm_runtime_init(sdev->request_queue, dev);
dev_set_drvdata(dev, cd);
- error = sr_revalidate_disk(cd);
+ error = sr_revalidate_disk(cd, false);
if (error)
goto unregister_cdrom;
diff --git a/drivers/scsi/sr.h b/drivers/scsi/sr.h
index 2d92f9cb6fec..b718ac87e82d 100644
--- a/drivers/scsi/sr.h
+++ b/drivers/scsi/sr.h
@@ -48,6 +48,8 @@ typedef struct scsi_cd {
bool ignore_get_event:1; /* GET_EVENT is unreliable, use TUR */
struct cdrom_device_info cdi;
+ /* Protected by disk->open_mutex after probe. */
+ bool needs_revalidate;
struct mutex lock;
struct gendisk *disk;
} Scsi_CD;
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH v9 05/11] scsi: sr: retry unfinished media revalidation on the next open
2026-10-06 23:41 ` [PATCH v9 05/11] scsi: sr: retry unfinished media revalidation on the next open Phil Pemberton
@ 2026-10-06 23:52 ` sashiko-bot
0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-10-06 23:52 UTC (permalink / raw)
To: Phil Pemberton; +Cc: linux-scsi, dlemoal, cassel, linux-ide
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] scsi: sr: uninitialized stack memory read of sshdr in sr_revalidate_disk()
--
commit 6831e2d4751ec5b84e9b7b08a5e93e7b6123ef75
Author: Phil Pemberton <philpem@philpem.me.uk>
scsi: sr: retry unfinished media revalidation on the next open
This commit updates the scsi sr driver to keep media revalidation pending
until it completes, handling cases where a new disc is still spinning up.
It introduces a mechanism to return -ENOMEDIUM for blocking opens if the
drive reports it is becoming ready, ensuring the queue limits update is
eventually completed.
[Severity: Medium]
Could the new check in sr_revalidate_disk() result in an uninitialized stack
memory read if the scsi command fails early?
In drivers/scsi/sr.c at line 485 within sr_revalidate_disk(), the code
evaluates scsi_sense_valid(&sshdr) and reads sshdr.sense_key, sshdr.asc,
and sshdr.ascq. However, sshdr is allocated on the stack without
zero-initialization.
If scsi_test_unit_ready() encounters an early failure (such as an allocation
failure in scsi_alloc_request() returning -ENOMEM or -ENXIO), it returns the
negative error code immediately without populating sshdr. Since the new logic
checks scsi_sense_valid() whenever scsi_test_unit_ready() returns non-zero,
it will read the uninitialized sshdr.response_code.
If the random stack data happens to match the 0x70 mask required by
scsi_sense_valid(), the code will further evaluate the uninitialized
sense_key, asc, and ascq fields. Can this cause unrelated errors to be
incorrectly masked as -ENOMEDIUM, or trigger KMSAN uninitialized memory
warnings?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261006234202.1139018-1-philpem@philpem.me.uk?part=5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v9 06/11] ata: libata-scsi: add atapi_max_lun module parameter
2026-10-06 23:41 [PATCH v9 00/11] ata: add multi-LUN support for ATAPI devices Phil Pemberton
` (4 preceding siblings ...)
2026-10-06 23:41 ` [PATCH v9 05/11] scsi: sr: retry unfinished media revalidation on the next open Phil Pemberton
@ 2026-10-06 23:41 ` Phil Pemberton
2026-10-06 23:41 ` [PATCH v9 07/11] ata: libata-scsi: convert dev->sdev to per-LUN array Phil Pemberton
` (4 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: Phil Pemberton @ 2026-10-06 23:41 UTC (permalink / raw)
To: linux-ide, linux-scsi
Cc: linux-kernel, Damien Le Moal, Niklas Cassel,
James E . J . Bottomley, Martin K . Petersen, Hannes Reinecke,
Phil Pemberton, Hannes Reinecke
libata sets shost->max_lun to 1, so SCSI scans stop at LUN 0. Panasonic
LF-1195C and COMPAQ PD-1 drives expose their PD media on LUN 1, and
Nakamichi MJ-x.y CD changers expose a LUN for each disc slot.
Add the atapi_max_lun module parameter to set the host's scan limit.
Keep the default of 1 and clamp the value to 1..8, covering SCSI-2 LUNs
0..7. Later patches enable automatic scanning of additional LUNs for
ATAPI devices marked BLIST_FORCELUN.
Reviewed-by: Hannes Reinecke <hare@suse.de>
Reviewed-by: Damien Le Moal <dlemoal@kernel.org>
Assisted-by: LLM
Signed-off-by: Phil Pemberton <philpem@philpem.me.uk>
---
drivers/ata/libata-core.c | 5 +++++
drivers/ata/libata-scsi.c | 2 +-
drivers/ata/libata.h | 1 +
include/linux/libata.h | 1 +
4 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c
index 5518525357ce..f2f5ad727d20 100644
--- a/drivers/ata/libata-core.c
+++ b/drivers/ata/libata-core.c
@@ -123,6 +123,11 @@ int atapi_passthru16 = 1;
module_param(atapi_passthru16, int, 0444);
MODULE_PARM_DESC(atapi_passthru16, "Enable ATA_16 passthru for ATAPI devices (0=off, 1=on [default])");
+int atapi_max_lun = 1;
+module_param(atapi_max_lun, int, 0444);
+MODULE_PARM_DESC(atapi_max_lun,
+ "Number of LUNs to scan on ATAPI devices flagged BLIST_FORCELUN (1 [default] = LUN 0 only, 8 = all SCSI-2 LUNs 0..7)");
+
int libata_fua = 0;
module_param_named(fua, libata_fua, int, 0444);
MODULE_PARM_DESC(fua, "FUA support (0=off [default], 1=on)");
diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c
index 8f9aa97a519d..3131d84204ed 100644
--- a/drivers/ata/libata-scsi.c
+++ b/drivers/ata/libata-scsi.c
@@ -5230,7 +5230,7 @@ int ata_scsi_add_hosts(struct ata_host *host, const struct scsi_host_template *s
shost->transportt = &ata_scsi_transportt;
shost->unique_id = ap->print_id;
shost->max_id = 16;
- shost->max_lun = 1;
+ shost->max_lun = clamp(atapi_max_lun, 1, ATAPI_MAX_LUN);
shost->max_channel = 1;
shost->max_cmd_len = 32;
diff --git a/drivers/ata/libata.h b/drivers/ata/libata.h
index 48274691cd0e..aedc6b61196b 100644
--- a/drivers/ata/libata.h
+++ b/drivers/ata/libata.h
@@ -33,6 +33,7 @@ enum {
#define ATA_PORT_TYPE_NAME "ata_port"
extern int atapi_passthru16;
+extern int atapi_max_lun;
extern int libata_fua;
extern int libata_noacpi;
extern int libata_allow_tpm;
diff --git a/include/linux/libata.h b/include/linux/libata.h
index 48bde275968d..03511280800f 100644
--- a/include/linux/libata.h
+++ b/include/linux/libata.h
@@ -180,6 +180,7 @@ enum {
ATA_SHORT_PAUSE = 16,
ATAPI_MAX_DRAIN = 16 << 10,
+ ATAPI_MAX_LUN = 8, /* SCSI-2 cap (LUN values 0..7) */
ATA_ALL_DEVICES = (1 << ATA_MAX_DEVICES) - 1,
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH v9 07/11] ata: libata-scsi: convert dev->sdev to per-LUN array
2026-10-06 23:41 [PATCH v9 00/11] ata: add multi-LUN support for ATAPI devices Phil Pemberton
` (5 preceding siblings ...)
2026-10-06 23:41 ` [PATCH v9 06/11] ata: libata-scsi: add atapi_max_lun module parameter Phil Pemberton
@ 2026-10-06 23:41 ` Phil Pemberton
2026-10-06 23:58 ` sashiko-bot
2026-10-06 23:41 ` [PATCH v9 08/11] ata: libata-scsi: route non-zero LUN commands for multi-LUN ATAPI Phil Pemberton
` (3 subsequent siblings)
10 siblings, 1 reply; 17+ messages in thread
From: Phil Pemberton @ 2026-10-06 23:41 UTC (permalink / raw)
To: linux-ide, linux-scsi
Cc: linux-kernel, Damien Le Moal, Niklas Cassel,
James E . J . Bottomley, Martin K . Petersen, Hannes Reinecke,
Phil Pemberton
Multi-LUN ATAPI devices share one ata_device and expose several SCSI
devices. Replace dev->sdev with an eight-slot array indexed by LUN and
update its callers. Single-LUN devices use slot 0; unused slots are NULL.
Reject out-of-range scan requests with -EINVAL.
Take references to populated slots under ap->lock before resuming or
rescanning them. Release all references outside the lock because device
release can sleep. If one LUN needs a retry, continue with the others,
keep ATA_DFLAG_RESUMING set and schedule another pass.
Pass the referenced LUN-0 device to ata_scsi_assign_ofnode(). Concurrent
removal can clear its array slot while the reference is held.
Clear slots under ap->lock before removing devices. Schedule ATA detach
when the last populated LUN is destroyed.
Assisted-by: LLM
Signed-off-by: Phil Pemberton <philpem@philpem.me.uk>
---
drivers/ata/libata-acpi.c | 9 +-
drivers/ata/libata-core.c | 11 ++-
drivers/ata/libata-scsi.c | 167 +++++++++++++++++++++----------------
drivers/ata/libata-zpodd.c | 48 ++++++-----
include/linux/libata.h | 10 ++-
5 files changed, 144 insertions(+), 101 deletions(-)
diff --git a/drivers/ata/libata-acpi.c b/drivers/ata/libata-acpi.c
index 4433f626246b..2d1662f6f064 100644
--- a/drivers/ata/libata-acpi.c
+++ b/drivers/ata/libata-acpi.c
@@ -153,10 +153,13 @@ static void ata_acpi_uevent(struct ata_port *ap, struct ata_device *dev,
char *envp[] = { event_string, NULL };
if (dev) {
- if (dev->sdev)
- kobj = &dev->sdev->sdev_gendev.kobj;
- } else
+ struct scsi_device *sdev = ata_dev_scsi_device(dev, 0);
+
+ if (sdev)
+ kobj = &sdev->sdev_gendev.kobj;
+ } else {
kobj = &ap->dev->kobj;
+ }
if (kobj) {
snprintf(event_string, 20, "BAY_EVENT=%d", event);
diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c
index f2f5ad727d20..8cf1ea24fe0b 100644
--- a/drivers/ata/libata-core.c
+++ b/drivers/ata/libata-core.c
@@ -6393,12 +6393,15 @@ static void ata_port_detach(struct ata_port *ap)
/* Remove scsi devices */
ata_for_each_link(link, ap, HOST_FIRST) {
ata_for_each_dev(dev, link, ALL) {
- if (dev->sdev) {
- struct scsi_device *sdev = dev->sdev;
+ int lun;
- /* The host driver may already be unloading. */
+ for (lun = ATAPI_MAX_LUN - 1; lun >= 0; lun--) {
+ struct scsi_device *sdev = dev->sdev[lun];
+
+ if (!sdev)
+ continue;
get_device(&sdev->sdev_gendev);
- dev->sdev = NULL;
+ dev->sdev[lun] = NULL;
spin_unlock_irqrestore(ap->lock, flags);
scsi_remove_device(sdev);
put_device(&sdev->sdev_gendev);
diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c
index 3131d84204ed..68d6ca1f791d 100644
--- a/drivers/ata/libata-scsi.c
+++ b/drivers/ata/libata-scsi.c
@@ -1135,7 +1135,9 @@ int ata_scsi_dev_config(struct scsi_device *sdev, struct queue_limits *lim,
if (dev->flags & ATA_DFLAG_TRUSTED)
sdev->security_supported = 1;
- dev->sdev = sdev;
+ if (sdev->lun >= ATAPI_MAX_LUN)
+ return -EINVAL;
+ dev->sdev[sdev->lun] = sdev;
return 0;
}
@@ -1206,10 +1208,10 @@ EXPORT_SYMBOL_GPL(ata_scsi_sdev_configure);
*
* @sdev is about to be destroyed for hot/warm unplugging. If
* this unplugging was initiated by libata as indicated by NULL
- * dev->sdev, this function doesn't have to do anything.
+ * dev->sdev[], this function doesn't have to do anything.
* Otherwise, SCSI layer initiated warm-unplug is in progress.
- * Clear dev->sdev, schedule the device for ATA detach and invoke
- * EH.
+ * Clear the per-LUN slot; when the last populated LUN is destroyed,
+ * schedule ATA-level detach via EH.
*
* LOCKING:
* Defined by SCSI layer. We don't really care.
@@ -1224,11 +1226,23 @@ void ata_scsi_sdev_destroy(struct scsi_device *sdev)
spin_lock_irqsave(ap->lock, flags);
dev = __ata_scsi_find_dev(ap, sdev);
- if (dev && dev->sdev) {
- /* SCSI device already in CANCEL state, no need to offline it */
- dev->sdev = NULL;
- dev->flags |= ATA_DFLAG_DETACH;
- ata_port_schedule_eh(ap);
+ if (dev && sdev->lun < ATAPI_MAX_LUN &&
+ dev->sdev[sdev->lun] == sdev) {
+ int lun;
+ bool last;
+
+ dev->sdev[sdev->lun] = NULL;
+ last = true;
+ for (lun = 0; lun < ATAPI_MAX_LUN; lun++) {
+ if (dev->sdev[lun]) {
+ last = false;
+ break;
+ }
+ }
+ if (last) {
+ dev->flags |= ATA_DFLAG_DETACH;
+ ata_port_schedule_eh(ap);
+ }
}
spin_unlock_irqrestore(ap->lock, flags);
@@ -3052,12 +3066,9 @@ static void atapi_qc_complete(struct ata_queued_cmd *qc)
*
* If door lock fails, always clear sdev->locked to
* avoid this infinite loop.
- *
- * This may happen before SCSI scan is complete. Make
- * sure qc->dev->sdev isn't NULL before dereferencing.
*/
- if (qc->cdb[0] == ALLOW_MEDIUM_REMOVAL && qc->dev->sdev)
- qc->dev->sdev->locked = 0;
+ if (qc->cdb[0] == ALLOW_MEDIUM_REMOVAL)
+ qc->scsicmd->device->locked = 0;
if (cmd->result)
ata_scsi_qc_done(qc, false, 0);
@@ -5259,9 +5270,9 @@ int ata_scsi_add_hosts(struct ata_host *host, const struct scsi_host_template *s
}
#ifdef CONFIG_OF
-static void ata_scsi_assign_ofnode(struct ata_device *dev, struct ata_port *ap)
+static void ata_scsi_assign_ofnode(struct scsi_device *sdev,
+ struct ata_device *dev, struct ata_port *ap)
{
- struct scsi_device *sdev = dev->sdev;
struct device *d = ap->host->dev;
struct device_node *np = d->of_node;
struct device_node *child;
@@ -5281,7 +5292,8 @@ static void ata_scsi_assign_ofnode(struct ata_device *dev, struct ata_port *ap)
}
}
#else
-static void ata_scsi_assign_ofnode(struct ata_device *dev, struct ata_port *ap)
+static void ata_scsi_assign_ofnode(struct scsi_device *sdev,
+ struct ata_device *dev, struct ata_port *ap)
{
}
#endif
@@ -5299,7 +5311,7 @@ void ata_scsi_scan_host(struct ata_port *ap, int sync)
struct scsi_device *sdev;
int channel = 0, id = 0;
- if (dev->sdev)
+ if (dev->sdev[0])
continue;
if (ata_is_host_link(link))
@@ -5310,11 +5322,11 @@ void ata_scsi_scan_host(struct ata_port *ap, int sync)
sdev = __scsi_add_device(ap->scsi_host, channel, id, 0,
NULL);
if (!IS_ERR(sdev)) {
- dev->sdev = sdev;
- ata_scsi_assign_ofnode(dev, ap);
+ dev->sdev[0] = sdev;
+ ata_scsi_assign_ofnode(sdev, dev, ap);
scsi_device_put(sdev);
} else {
- dev->sdev = NULL;
+ dev->sdev[0] = NULL;
}
}
}
@@ -5325,7 +5337,7 @@ void ata_scsi_scan_host(struct ata_port *ap, int sync)
*/
ata_for_each_link(link, ap, EDGE) {
ata_for_each_dev(dev, link, ENABLED) {
- if (!dev->sdev)
+ if (!dev->sdev[0])
goto exit_loop;
}
}
@@ -5366,7 +5378,7 @@ void ata_scsi_scan_host(struct ata_port *ap, int sync)
*
* This function is called from ata_eh_detach_dev() and is responsible for
* taking the SCSI device attached to @dev offline. This function is
- * called with host lock which protects dev->sdev against clearing.
+ * called with host lock which protects dev->sdev[] against clearing.
*
* LOCKING:
* spin_lock_irqsave(host lock)
@@ -5376,11 +5388,16 @@ void ata_scsi_scan_host(struct ata_port *ap, int sync)
*/
bool ata_scsi_offline_dev(struct ata_device *dev)
{
- if (dev->sdev) {
- scsi_device_set_state(dev->sdev, SDEV_OFFLINE);
- return true;
+ bool found = false;
+ int lun;
+
+ for (lun = ATAPI_MAX_LUN - 1; lun >= 0; lun--) {
+ if (dev->sdev[lun]) {
+ scsi_device_set_state(dev->sdev[lun], SDEV_OFFLINE);
+ found = true;
+ }
}
- return false;
+ return found;
}
/**
@@ -5396,49 +5413,38 @@ bool ata_scsi_offline_dev(struct ata_device *dev)
static void ata_scsi_remove_dev(struct ata_device *dev)
{
struct ata_port *ap = dev->link->ap;
- struct scsi_device *sdev;
+ struct scsi_device *sdevs[ATAPI_MAX_LUN] = {};
unsigned long flags;
+ int lun;
- /* Alas, we need to grab scan_mutex to ensure SCSI device
- * state doesn't change underneath us and thus
- * scsi_device_get() always succeeds. The mutex locking can
- * be removed if there is __scsi_device_get() interface which
- * increments reference counts regardless of device state.
- */
mutex_lock(&ap->scsi_host->scan_mutex);
spin_lock_irqsave(ap->lock, flags);
- /* clearing dev->sdev is protected by host lock */
- sdev = dev->sdev;
- dev->sdev = NULL;
+ for (lun = ATAPI_MAX_LUN - 1; lun >= 0; lun--) {
+ struct scsi_device *sdev = dev->sdev[lun];
+
+ dev->sdev[lun] = NULL;
+ if (!sdev)
+ continue;
- if (sdev) {
- /* If user initiated unplug races with us, sdev can go
- * away underneath us after the host lock and
- * scan_mutex are released. Hold onto it.
- */
if (scsi_device_get(sdev) == 0) {
- /* The following ensures the attached sdev is
- * offline on return from ata_scsi_offline_dev()
- * regardless it wins or loses the race
- * against this function.
- */
scsi_device_set_state(sdev, SDEV_OFFLINE);
+ sdevs[lun] = sdev;
} else {
WARN_ON(1);
- sdev = NULL;
}
}
spin_unlock_irqrestore(ap->lock, flags);
mutex_unlock(&ap->scsi_host->scan_mutex);
- if (sdev) {
+ for (lun = ATAPI_MAX_LUN - 1; lun >= 0; lun--) {
+ if (!sdevs[lun])
+ continue;
ata_dev_info(dev, "detaching (SCSI %s)\n",
- dev_name(&sdev->sdev_gendev));
-
- scsi_remove_device(sdev);
- scsi_device_put(sdev);
+ dev_name(&sdevs[lun]->sdev_gendev));
+ scsi_remove_device(sdevs[lun]);
+ scsi_device_put(sdevs[lun]);
}
}
@@ -5475,9 +5481,12 @@ static void ata_scsi_handle_link_detach(struct ata_link *link)
*/
void ata_scsi_media_change_notify(struct ata_device *dev)
{
- if (dev->sdev)
- sdev_evt_send_simple(dev->sdev, SDEV_EVT_MEDIA_CHANGE,
- GFP_ATOMIC);
+ int lun;
+
+ for (lun = 0; lun < ATAPI_MAX_LUN; lun++)
+ if (dev->sdev[lun])
+ sdev_evt_send_simple(dev->sdev[lun],
+ SDEV_EVT_MEDIA_CHANGE, GFP_ATOMIC);
}
/**
@@ -5610,7 +5619,9 @@ void ata_scsi_dev_rescan(struct work_struct *work)
ata_for_each_link(link, ap, EDGE) {
ata_for_each_dev(dev, link, ENABLED) {
- struct scsi_device *sdev = dev->sdev;
+ struct scsi_device *sdevs[ATAPI_MAX_LUN] = {};
+ bool retry = false;
+ int lun, err;
/*
* If the port was suspended before this was scheduled,
@@ -5619,34 +5630,42 @@ void ata_scsi_dev_rescan(struct work_struct *work)
if (ap->pflags & ATA_PFLAG_SUSPENDED)
goto unlock_ap;
- if (!sdev)
- continue;
- if (scsi_device_get(sdev))
- continue;
+ for (lun = 0; lun < ATAPI_MAX_LUN; lun++) {
+ if (dev->sdev[lun] &&
+ !scsi_device_get(dev->sdev[lun]))
+ sdevs[lun] = dev->sdev[lun];
+ }
do_resume = dev->flags & ATA_DFLAG_RESUMING;
- spin_unlock_irqrestore(ap->lock, flags);
- if (do_resume) {
- ret = scsi_resume_device(sdev);
- if (ret == -EWOULDBLOCK) {
- scsi_device_put(sdev);
- goto unlock_scan;
+ for (lun = 0; lun < ATAPI_MAX_LUN; lun++) {
+ if (!sdevs[lun])
+ continue;
+
+ spin_unlock_irqrestore(ap->lock, flags);
+ if (do_resume) {
+ err = scsi_resume_device(sdevs[lun]);
+ if (err == -EWOULDBLOCK)
+ goto put_sdev;
}
- dev->flags &= ~ATA_DFLAG_RESUMING;
+ err = scsi_rescan_device(sdevs[lun]);
+put_sdev:
+ /* A deferred LUN must not prevent scanning its siblings. */
+ if (err) {
+ ret = err;
+ retry = true;
+ }
+ scsi_device_put(sdevs[lun]);
+ spin_lock_irqsave(ap->lock, flags);
}
- ret = scsi_rescan_device(sdev);
- scsi_device_put(sdev);
- spin_lock_irqsave(ap->lock, flags);
- if (ret)
- goto unlock_ap;
+ if (do_resume && !retry)
+ dev->flags &= ~ATA_DFLAG_RESUMING;
}
}
unlock_ap:
spin_unlock_irqrestore(ap->lock, flags);
-unlock_scan:
mutex_unlock(&ap->scsi_scan_mutex);
/* Reschedule with a delay if scsi_rescan_device() returned an error */
diff --git a/drivers/ata/libata-zpodd.c b/drivers/ata/libata-zpodd.c
index 581eb41ea0f3..bbdcaaa35798 100644
--- a/drivers/ata/libata-zpodd.c
+++ b/drivers/ata/libata-zpodd.c
@@ -174,14 +174,14 @@ bool zpodd_zpready(struct ata_device *dev)
}
/* Return a referenced SCSI device; the caller must drop it outside ap->lock. */
-static struct scsi_device *zpodd_get_sdev(struct ata_device *dev)
+static struct scsi_device *zpodd_get_sdev(struct ata_device *dev, unsigned int lun)
{
struct ata_port *ap = dev->link->ap;
struct scsi_device *sdev;
unsigned long flags;
spin_lock_irqsave(ap->lock, flags);
- sdev = dev->sdev;
+ sdev = dev->sdev[lun];
if (sdev && scsi_device_get(sdev))
sdev = NULL;
spin_unlock_irqrestore(ap->lock, flags);
@@ -200,11 +200,15 @@ static struct scsi_device *zpodd_get_sdev(struct ata_device *dev)
void zpodd_enable_run_wake(struct ata_device *dev)
{
struct zpodd *zpodd = dev->zpodd;
- struct scsi_device *sdev = zpodd_get_sdev(dev);
+ int lun;
- if (sdev) {
- sdev_disable_disk_events(sdev);
- scsi_device_put(sdev);
+ for (lun = 0; lun < ATAPI_MAX_LUN; lun++) {
+ struct scsi_device *sdev = zpodd_get_sdev(dev, lun);
+
+ if (sdev) {
+ sdev_disable_disk_events(sdev);
+ scsi_device_put(sdev);
+ }
}
zpodd->powered_off = true;
@@ -238,7 +242,7 @@ void zpodd_disable_run_wake(struct ata_device *dev)
void zpodd_post_poweron(struct ata_device *dev)
{
struct zpodd *zpodd = dev->zpodd;
- struct scsi_device *sdev;
+ int lun;
if (!zpodd->powered_off)
return;
@@ -254,10 +258,13 @@ void zpodd_post_poweron(struct ata_device *dev)
zpodd->zp_sampled = false;
zpodd->zp_ready = false;
- sdev = zpodd_get_sdev(dev);
- if (sdev) {
- sdev_enable_disk_events(sdev);
- scsi_device_put(sdev);
+ for (lun = 0; lun < ATAPI_MAX_LUN; lun++) {
+ struct scsi_device *sdev = zpodd_get_sdev(dev, lun);
+
+ if (sdev) {
+ sdev_enable_disk_events(sdev);
+ scsi_device_put(sdev);
+ }
}
}
@@ -265,18 +272,21 @@ static void zpodd_wake_dev(acpi_handle handle, u32 event, void *context)
{
struct ata_device *ata_dev = context;
struct zpodd *zpodd = ata_dev->zpodd;
- struct scsi_device *sdev;
+ int lun;
if (event != ACPI_NOTIFY_DEVICE_WAKE)
return;
- sdev = zpodd_get_sdev(ata_dev);
- if (!sdev)
- return;
- if (pm_runtime_suspended(&sdev->sdev_gendev)) {
- zpodd->from_notify = true;
- pm_runtime_resume(&sdev->sdev_gendev);
+ for (lun = 0; lun < ATAPI_MAX_LUN; lun++) {
+ struct scsi_device *sdev = zpodd_get_sdev(ata_dev, lun);
+
+ if (!sdev)
+ continue;
+ if (pm_runtime_suspended(&sdev->sdev_gendev)) {
+ zpodd->from_notify = true;
+ pm_runtime_resume(&sdev->sdev_gendev);
+ }
+ scsi_device_put(sdev);
}
- scsi_device_put(sdev);
}
static void ata_acpi_add_pm_notifier(struct ata_device *dev)
diff --git a/include/linux/libata.h b/include/linux/libata.h
index 03511280800f..b9831be8dae2 100644
--- a/include/linux/libata.h
+++ b/include/linux/libata.h
@@ -733,7 +733,7 @@ struct ata_device {
unsigned int devno; /* 0 or 1 */
u64 quirks; /* List of broken features */
unsigned long flags; /* ATA_DFLAG_xxx */
- struct scsi_device *sdev; /* attached SCSI device */
+ struct scsi_device *sdev[ATAPI_MAX_LUN]; /* per-LUN SCSI devices */
void *private_data;
#ifdef CONFIG_ATA_ACPI
union acpi_object *gtf_cache;
@@ -1730,6 +1730,14 @@ static inline unsigned int ata_dev_absent(const struct ata_device *dev)
return ata_class_absent(dev->class);
}
+static inline struct scsi_device *
+ata_dev_scsi_device(struct ata_device *dev, unsigned int lun)
+{
+ if (WARN_ON_ONCE(lun >= ATAPI_MAX_LUN))
+ return NULL;
+ return dev->sdev[lun];
+}
+
/*
* link helpers
*/
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH v9 07/11] ata: libata-scsi: convert dev->sdev to per-LUN array
2026-10-06 23:41 ` [PATCH v9 07/11] ata: libata-scsi: convert dev->sdev to per-LUN array Phil Pemberton
@ 2026-10-06 23:58 ` sashiko-bot
2026-10-07 2:38 ` Phil Pemberton
0 siblings, 1 reply; 17+ messages in thread
From: sashiko-bot @ 2026-10-06 23:58 UTC (permalink / raw)
To: Phil Pemberton; +Cc: cassel, linux-scsi, dlemoal, linux-ide
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] ata: UAF in ata_acpi_uevent() accessing freed scsi_device kobject
- [Medium] of: memory leak in ata_scsi_assign_ofnode() due to missing of_node_put()
- [Medium] ata: libata-zpodd: stale from_notify flag in zpodd_wake_dev() causes unintended tray ejection
- [Medium] ata: redundant scsi_resume_device() calls on already-resumed LUNs due to shared device flag
--
commit 6719cc4aa927e2dd12a61aea767af00a2eb99075
Author: Phil Pemberton <philpem@philpem.me.uk>
ata: libata-scsi: convert dev->sdev to per-LUN array
Multi-LUN ATAPI devices share one ata_device and expose several SCSI
devices. Replace dev->sdev with an eight-slot array indexed by LUN and
update its callers. Single-LUN devices use slot 0; unused slots are NULL.
> diff --git a/drivers/ata/libata-acpi.c b/drivers/ata/libata-acpi.c
> index 4433f626246ba..2d1662f6f0642 100644
> --- a/drivers/ata/libata-acpi.c
> +++ b/drivers/ata/libata-acpi.c
> @@ -153,10 +153,13 @@ static void ata_acpi_uevent(struct ata_port *ap, struct ata_device *dev,
> char *envp[] = { event_string, NULL };
>
> if (dev) {
> - if (dev->sdev)
> - kobj = &dev->sdev->sdev_gendev.kobj;
> - } else
> + struct scsi_device *sdev = ata_dev_scsi_device(dev, 0);
> +
> + if (sdev)
> + kobj = &sdev->sdev_gendev.kobj;
[Severity: High]
Is this scsi_device kobject safe from being freed during this call?
The scsi_device is retrieved via ata_dev_scsi_device() without taking
ap->lock or acquiring a reference with scsi_device_get(). A concurrent
unplug could drop the reference count and free the device object before
kobject_uevent_env() executes, leading to a use-after-free.
> + } else {
> kobj = &ap->dev->kobj;
> + }
>
> if (kobj) {
[ ... ]
> diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c
> index 3131d84204ed7..68d6ca1f791dd 100644
> --- a/drivers/ata/libata-scsi.c
> +++ b/drivers/ata/libata-scsi.c
[ ... ]
> @@ -5259,9 +5270,9 @@ int ata_scsi_add_hosts(struct ata_host *host, const struct scsi_host_template *s
> }
>
> #ifdef CONFIG_OF
> -static void ata_scsi_assign_ofnode(struct ata_device *dev, struct ata_port *ap)
> +static void ata_scsi_assign_ofnode(struct scsi_device *sdev,
> + struct ata_device *dev, struct ata_port *ap)
> {
[Severity: Medium]
Does ata_scsi_assign_ofnode() leak a device tree node reference?
When for_each_available_child_of_node() inside this function finds a
matching port, it assigns the child to sdev->sdev_gendev.of_node and
returns early. The iterator implicitly acquires a reference to the child
node on each iteration. Since the SCSI and driver cores do not
automatically call of_node_put() on the device of_node during teardown,
this leaves a persistent reference leak.
> - struct scsi_device *sdev = dev->sdev;
> struct device *d = ap->host->dev;
> struct device_node *np = d->of_node;
> struct device_node *child;
[ ... ]
> @@ -5610,7 +5619,9 @@ void ata_scsi_dev_rescan(struct work_struct *work)
>
> ata_for_each_link(link, ap, EDGE) {
> ata_for_each_dev(dev, link, ENABLED) {
> - struct scsi_device *sdev = dev->sdev;
> + struct scsi_device *sdevs[ATAPI_MAX_LUN] = {};
> + bool retry = false;
> + int lun, err;
>
> /*
> * If the port was suspended before this was scheduled,
[ ... ]
> - spin_unlock_irqrestore(ap->lock, flags);
> - if (do_resume) {
> - ret = scsi_resume_device(sdev);
> - if (ret == -EWOULDBLOCK) {
> - scsi_device_put(sdev);
> - goto unlock_scan;
> + for (lun = 0; lun < ATAPI_MAX_LUN; lun++) {
> + if (!sdevs[lun])
> + continue;
> +
> + spin_unlock_irqrestore(ap->lock, flags);
> + if (do_resume) {
> + err = scsi_resume_device(sdevs[lun]);
> + if (err == -EWOULDBLOCK)
> + goto put_sdev;
> }
> - dev->flags &= ~ATA_DFLAG_RESUMING;
> + err = scsi_rescan_device(sdevs[lun]);
[Severity: Medium]
Will this cause redundant resume attempts on already-resumed LUNs?
When do_resume is true, both scsi_resume_device() and scsi_rescan_device()
execute for every valid LUN without checking if that specific LUN was
already successfully resumed in a previous pass.
> +put_sdev:
> + /* A deferred LUN must not prevent scanning its siblings. */
> + if (err) {
> + ret = err;
> + retry = true;
> + }
> + scsi_device_put(sdevs[lun]);
> + spin_lock_irqsave(ap->lock, flags);
> }
> - ret = scsi_rescan_device(sdev);
> - scsi_device_put(sdev);
> - spin_lock_irqsave(ap->lock, flags);
>
> - if (ret)
> - goto unlock_ap;
> + if (do_resume && !retry)
> + dev->flags &= ~ATA_DFLAG_RESUMING;
[Severity: Medium]
If any LUN sets retry to true by returning -EWOULDBLOCK, the per-device
ATA_DFLAG_RESUMING flag is kept set. Consequently, the next execution of
the workqueue will see do_resume as true and unconditionally re-resume all
sibling LUNs, even the ones that previously succeeded.
> }
> }
[ ... ]
> diff --git a/drivers/ata/libata-zpodd.c b/drivers/ata/libata-zpodd.c
> index 581eb41ea0f3c..bbdcaaa357980 100644
> --- a/drivers/ata/libata-zpodd.c
> +++ b/drivers/ata/libata-zpodd.c
[ ... ]
> @@ -265,18 +272,21 @@ static void zpodd_wake_dev(acpi_handle handle, u32 event, void *context)
> {
> struct ata_device *ata_dev = context;
> struct zpodd *zpodd = ata_dev->zpodd;
> - struct scsi_device *sdev;
> + int lun;
>
> if (event != ACPI_NOTIFY_DEVICE_WAKE)
> return;
> - sdev = zpodd_get_sdev(ata_dev);
> - if (!sdev)
> - return;
> - if (pm_runtime_suspended(&sdev->sdev_gendev)) {
> - zpodd->from_notify = true;
> - pm_runtime_resume(&sdev->sdev_gendev);
> + for (lun = 0; lun < ATAPI_MAX_LUN; lun++) {
> + struct scsi_device *sdev = zpodd_get_sdev(ata_dev, lun);
> +
> + if (!sdev)
> + continue;
> + if (pm_runtime_suspended(&sdev->sdev_gendev)) {
> + zpodd->from_notify = true;
> + pm_runtime_resume(&sdev->sdev_gendev);
> + }
[Severity: Medium]
Can the from_notify flag become stuck in a true state here?
Setting this per-ATA-device flag inside a per-LUN loop allows the flag to
be incorrectly left true. If the first suspended LUN causes an actual
resume, from_notify is set and then cleared in zpodd_post_poweron() when
EH runs. For subsequent suspended LUNs, from_notify is set to true again,
but pm_runtime_resume() finds the parent already active and does not
trigger EH. This leaves from_notify set to true, which will cause an
unintended physical tray ejection during the next power-on.
> + scsi_device_put(sdev);
> }
> - scsi_device_put(sdev);
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261006234202.1139018-1-philpem@philpem.me.uk?part=7
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH v9 07/11] ata: libata-scsi: convert dev->sdev to per-LUN array
2026-10-06 23:58 ` sashiko-bot
@ 2026-10-07 2:38 ` Phil Pemberton
0 siblings, 0 replies; 17+ messages in thread
From: Phil Pemberton @ 2026-10-07 2:38 UTC (permalink / raw)
To: sashiko-reviews; +Cc: cassel, linux-scsi, dlemoal, linux-ide
On 07/10/2026 00:58, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
> - [High] ata: UAF in ata_acpi_uevent() accessing freed scsi_device kobject
This is pre-existing. I've prepared a separate fix which should resolve
this issue and will submit it separately.
> - [Medium] of: memory leak in ata_scsi_assign_ofnode() due to missing of_node_put()
Pre-existing again. I think this is also best handled as a separate
independent fix.
> - [Medium] ata: libata-zpodd: stale from_notify flag in zpodd_wake_dev() causes unintended tray ejection
Noted, fixed in my local v10, will wait for other reviewers' comments.
> - [Medium] ata: redundant scsi_resume_device() calls on already-resumed LUNs due to shared device flag
Noted and fixed as above
--
Phil.
philpem@philpem.me.uk
https://www.philpem.me.uk/
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v9 08/11] ata: libata-scsi: route non-zero LUN commands for multi-LUN ATAPI
2026-10-06 23:41 [PATCH v9 00/11] ata: add multi-LUN support for ATAPI devices Phil Pemberton
` (6 preceding siblings ...)
2026-10-06 23:41 ` [PATCH v9 07/11] ata: libata-scsi: convert dev->sdev to per-LUN array Phil Pemberton
@ 2026-10-06 23:41 ` Phil Pemberton
2026-10-06 23:41 ` [PATCH v9 09/11] scsi: add BLIST_NO_LUN_1F blacklist flag Phil Pemberton
` (2 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: Phil Pemberton @ 2026-10-06 23:41 UTC (permalink / raw)
To: linux-ide, linux-scsi
Cc: linux-kernel, Damien Le Moal, Niklas Cassel,
James E . J . Bottomley, Martin K . Petersen, Hannes Reinecke,
Phil Pemberton
Allow nonzero LUNs in __ata_scsi_find_dev(). Keep this lookup independent
of device class so removal can clear the per-LUN pointer after error
handling disables the device. Restrict nonzero LUN commands to ATAPI
in ata_scsi_find_dev().
Encode nonzero LUNs in CDB byte 1, bits 7:5, as required by older SCSI-2
ATAPI devices. Reject values above 7 with DID_ERROR. Check this encoding
limit directly, since SAS hosts can have much larger shost->max_lun
values. Preserve byte 1 for LUN 0, where modern MMC commands use the upper
bits for command-specific fields.
Pass the failed command's LUN to atapi_eh_request_sense() so error handling
reads sense from the correct unit. Internal reset and ZPODD commands
continue to use LUN 0.
Assisted-by: LLM
Signed-off-by: Phil Pemberton <philpem@philpem.me.uk>
---
drivers/ata/libata-eh.c | 10 +++++++---
drivers/ata/libata-scsi.c | 25 ++++++++++++++++++++++---
drivers/ata/libata-zpodd.c | 2 +-
drivers/ata/libata.h | 4 ++--
4 files changed, 32 insertions(+), 9 deletions(-)
diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c
index c93c24b22d9c..7177e4bddc2a 100644
--- a/drivers/ata/libata-eh.c
+++ b/drivers/ata/libata-eh.c
@@ -1535,6 +1535,7 @@ static bool ata_eh_request_sense(struct ata_queued_cmd *qc)
/**
* atapi_eh_request_sense - perform ATAPI REQUEST_SENSE
* @dev: device to perform REQUEST_SENSE to
+ * @lun: logical unit to request sense from
* @sense_buf: result sense data buffer (SCSI_SENSE_BUFFERSIZE bytes long)
* @dfl_sense_key: default sense key to use
*
@@ -1547,14 +1548,16 @@ static bool ata_eh_request_sense(struct ata_queued_cmd *qc)
* RETURNS:
* 0 on success, AC_ERR_* mask on failure
*/
-unsigned int atapi_eh_request_sense(struct ata_device *dev,
- u8 *sense_buf, u8 dfl_sense_key)
+unsigned int atapi_eh_request_sense(struct ata_device *dev, u8 lun,
+ u8 *sense_buf, u8 dfl_sense_key)
{
u8 cdb[ATAPI_CDB_LEN] =
{ REQUEST_SENSE, 0, 0, 0, SCSI_SENSE_BUFFERSIZE, 0 };
struct ata_port *ap = dev->link->ap;
struct ata_taskfile tf;
+ cdb[1] = lun << 5;
+
memset(sense_buf, 0, SCSI_SENSE_BUFFERSIZE);
/* initialize sense_buf with the error register,
@@ -1700,6 +1703,7 @@ static unsigned int ata_eh_analyze_tf(struct ata_queued_cmd *qc)
case ATA_DEV_ATAPI:
if (!ata_port_is_frozen(qc->ap)) {
tmp = atapi_eh_request_sense(qc->dev,
+ qc->scsicmd->device->lun,
qc->scsicmd->sense_buffer,
qc->result_tf.error >> 4);
if (!tmp)
@@ -3531,7 +3535,7 @@ static int atapi_eh_clear_ua(struct ata_device *dev)
if (!err_mask || sense_key != UNIT_ATTENTION)
return 0;
- err_mask = atapi_eh_request_sense(dev, sense_buffer, sense_key);
+ err_mask = atapi_eh_request_sense(dev, 0, sense_buffer, sense_key);
if (err_mask) {
ata_dev_warn(dev, "failed to clear "
"UNIT ATTENTION (err_mask=0x%x)\n", err_mask);
diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c
index 68d6ca1f791d..471daf63382a 100644
--- a/drivers/ata/libata-scsi.c
+++ b/drivers/ata/libata-scsi.c
@@ -3112,6 +3112,21 @@ static unsigned int atapi_xlat(struct ata_queued_cmd *qc)
memset(qc->cdb, 0, dev->cdb_len);
memcpy(qc->cdb, scmd->cmnd, scmd->cmd_len);
+ /*
+ * SCSI-2 CDB LUN encoding: bits 7:5 of byte 1 (3-bit field).
+ * Byte 1 is left untouched for LUN 0, since commands which use
+ * the full byte (e.g. MMC SECURITY PROTOCOL IN/OUT, where it is
+ * the 8-bit security protocol) must pass through unmodified.
+ */
+ if (unlikely(scmd->device->lun)) {
+ if (scmd->device->lun >= ATAPI_MAX_LUN) {
+ scmd->result = DID_ERROR << 16;
+ return 1;
+ }
+ qc->cdb[1] = (qc->cdb[1] & 0x1f) |
+ ((u8)scmd->device->lun << 5);
+ }
+
qc->complete_fn = atapi_qc_complete;
qc->tf.flags |= ATA_TFLAG_ISADDR | ATA_TFLAG_DEVICE;
@@ -3222,13 +3237,13 @@ static struct ata_device *__ata_scsi_find_dev(struct ata_port *ap,
{
int devno;
- /* skip commands not addressed to targets we simulate */
+ /* Teardown must still find devices whose class was changed by EH. */
if (!sata_pmp_attached(ap)) {
- if (unlikely(scsidev->channel || scsidev->lun))
+ if (unlikely(scsidev->channel))
return NULL;
devno = scsidev->id;
} else {
- if (unlikely(scsidev->id || scsidev->lun))
+ if (unlikely(scsidev->id))
return NULL;
devno = scsidev->channel;
}
@@ -3263,6 +3278,10 @@ ata_scsi_find_dev(struct ata_port *ap, const struct scsi_device *scsidev)
if (unlikely(!dev || !ata_dev_enabled(dev)))
return NULL;
+ /* Only ATAPI devices can route commands to nonzero LUNs. */
+ if (scsidev->lun && dev->class != ATA_DEV_ATAPI)
+ return NULL;
+
return dev;
}
diff --git a/drivers/ata/libata-zpodd.c b/drivers/ata/libata-zpodd.c
index bbdcaaa35798..ef39142ec5f7 100644
--- a/drivers/ata/libata-zpodd.c
+++ b/drivers/ata/libata-zpodd.c
@@ -113,7 +113,7 @@ static bool zpready(struct ata_device *dev)
return false;
sense_buf = dev->sector_buf;
- ret = atapi_eh_request_sense(dev, sense_buf, sense_key);
+ ret = atapi_eh_request_sense(dev, 0, sense_buf, sense_key);
if (ret)
return false;
diff --git a/drivers/ata/libata.h b/drivers/ata/libata.h
index aedc6b61196b..ca60d0e1d31c 100644
--- a/drivers/ata/libata.h
+++ b/drivers/ata/libata.h
@@ -216,8 +216,8 @@ extern int ata_ering_map(struct ata_ering *ering,
void *arg);
enum scsi_disposition ata_eh_decide_disposition(struct ata_queued_cmd *qc);
extern unsigned int atapi_eh_tur(struct ata_device *dev, u8 *r_sense_key);
-extern unsigned int atapi_eh_request_sense(struct ata_device *dev,
- u8 *sense_buf, u8 dfl_sense_key);
+unsigned int atapi_eh_request_sense(struct ata_device *dev, u8 lun,
+ u8 *sense_buf, u8 dfl_sense_key);
/* libata-pmp.c */
#ifdef CONFIG_SATA_PMP
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH v9 09/11] scsi: add BLIST_NO_LUN_1F blacklist flag
2026-10-06 23:41 [PATCH v9 00/11] ata: add multi-LUN support for ATAPI devices Phil Pemberton
` (7 preceding siblings ...)
2026-10-06 23:41 ` [PATCH v9 08/11] ata: libata-scsi: route non-zero LUN commands for multi-LUN ATAPI Phil Pemberton
@ 2026-10-06 23:41 ` Phil Pemberton
2026-10-06 23:42 ` [PATCH v9 10/11] ata: libata-scsi: probe additional LUNs for multi-LUN ATAPI devices Phil Pemberton
2026-10-06 23:42 ` [PATCH v9 11/11] scsi: scsi_devinfo: add COMPAQ PD-1 multi-LUN ATAPI device quirk Phil Pemberton
10 siblings, 0 replies; 17+ messages in thread
From: Phil Pemberton @ 2026-10-06 23:41 UTC (permalink / raw)
To: linux-ide, linux-scsi
Cc: linux-kernel, Damien Le Moal, Niklas Cassel,
James E . J . Bottomley, Martin K . Petersen, Hannes Reinecke,
Phil Pemberton
Some multi-LUN devices report unpopulated LUNs with PQ=0/PDT=0x1f.
SCSI scanning adds these as spurious "No Device" entries.
Add BLIST_NO_LUN_1F to set pdt_1f_for_no_lun from scsi_devinfo before
scsi_probe_and_add_lun() checks PDT=0x1f. Setting it here also covers
LUN 0.
Give pdt_1f_for_no_lun its own bool. Sharing bitfield storage with
expecting_lun_change allows scan-time writes and concurrent I/O updates
to overwrite each other.
Assisted-by: LLM
Signed-off-by: Phil Pemberton <philpem@philpem.me.uk>
---
drivers/scsi/scsi_scan.c | 3 +++
include/scsi/scsi_device.h | 4 ++--
include/scsi/scsi_devinfo.h | 6 +++---
3 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/drivers/scsi/scsi_scan.c b/drivers/scsi/scsi_scan.c
index 0f0f243c2561..e34824623294 100644
--- a/drivers/scsi/scsi_scan.c
+++ b/drivers/scsi/scsi_scan.c
@@ -1298,6 +1298,9 @@ static int scsi_probe_and_add_lun(struct Scsi_Host *shost,
* PDT=00h Direct-access device (floppy)
* PDT=1Fh none (no FDD connected to the requested logical unit)
*/
+ if (bflags & BLIST_NO_LUN_1F)
+ starget->pdt_1f_for_no_lun = 1;
+
if (((result[0] >> 5) == 1 || starget->pdt_1f_for_no_lun) &&
(result[0] & 0x1f) == 0x1f &&
!scsi_is_wlun(lun)) {
diff --git a/include/scsi/scsi_device.h b/include/scsi/scsi_device.h
index 8694eeadd753..a81a7c8cce18 100644
--- a/include/scsi/scsi_device.h
+++ b/include/scsi/scsi_device.h
@@ -360,13 +360,13 @@ struct scsi_target {
unsigned int single_lun:1; /* Indicates we should only
* allow I/O to one of the luns
* for the device at a time. */
- unsigned int pdt_1f_for_no_lun:1; /* PDT = 0x1f
- * means no lun present. */
unsigned int no_report_luns:1; /* Don't use
* REPORT LUNS for scanning. */
unsigned int expecting_lun_change:1; /* A device has reported
* a 3F/0E UA, other devices on
* the same target will also. */
+ /* Keep scan-time writes separate from I/O-updated bitfields. */
+ bool pdt_1f_for_no_lun; /* PDT = 0x1f means no LUN. */
/* commands actually active on LLD. */
atomic_t target_busy;
atomic_t target_blocked;
diff --git a/include/scsi/scsi_devinfo.h b/include/scsi/scsi_devinfo.h
index 1d79a3b536ce..6957b0705510 100644
--- a/include/scsi/scsi_devinfo.h
+++ b/include/scsi/scsi_devinfo.h
@@ -34,7 +34,8 @@
#define BLIST_NOSTARTONADD ((__force blist_flags_t)(1ULL << 12))
/* do not ask for VPD page size first on some broken targets */
#define BLIST_NO_VPD_SIZE ((__force blist_flags_t)(1ULL << 13))
-#define __BLIST_UNUSED_14 ((__force blist_flags_t)(1ULL << 14))
+/* PDT 0x1f with PQ 0 means no LUN present (e.g. some ATAPI multi-LUN) */
+#define BLIST_NO_LUN_1F ((__force blist_flags_t)(1ULL << 14))
#define __BLIST_UNUSED_15 ((__force blist_flags_t)(1ULL << 15))
#define __BLIST_UNUSED_16 ((__force blist_flags_t)(1ULL << 16))
/* try REPORT_LUNS even for SCSI-2 devs (if HBA supports more than 8 LUNs) */
@@ -77,8 +78,7 @@
#define __BLIST_HIGH_UNUSED (~(__BLIST_LAST_USED | \
(__force blist_flags_t) \
((__force __u64)__BLIST_LAST_USED - 1ULL)))
-#define __BLIST_UNUSED_MASK (__BLIST_UNUSED_14 | \
- __BLIST_UNUSED_15 | \
+#define __BLIST_UNUSED_MASK (__BLIST_UNUSED_15 | \
__BLIST_UNUSED_16 | \
__BLIST_UNUSED_24 | \
__BLIST_UNUSED_27 | \
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH v9 10/11] ata: libata-scsi: probe additional LUNs for multi-LUN ATAPI devices
2026-10-06 23:41 [PATCH v9 00/11] ata: add multi-LUN support for ATAPI devices Phil Pemberton
` (8 preceding siblings ...)
2026-10-06 23:41 ` [PATCH v9 09/11] scsi: add BLIST_NO_LUN_1F blacklist flag Phil Pemberton
@ 2026-10-06 23:42 ` Phil Pemberton
2026-10-06 23:42 ` [PATCH v9 11/11] scsi: scsi_devinfo: add COMPAQ PD-1 multi-LUN ATAPI device quirk Phil Pemberton
10 siblings, 0 replies; 17+ messages in thread
From: Phil Pemberton @ 2026-10-06 23:42 UTC (permalink / raw)
To: linux-ide, linux-scsi
Cc: linux-kernel, Damien Le Moal, Niklas Cassel,
James E . J . Bottomley, Martin K . Petersen, Hannes Reinecke,
Phil Pemberton
After adding LUN 0 of an ATAPI device marked BLIST_FORCELUN, scan the
target's remaining LUNs using scsi_scan_target(). The scan is bounded by
shost->max_lun, set through atapi_max_lun.
Also accept explicit user scans for LUNs below shost->max_lun. Scan the
selected ATAPI targets after error handling completes, using
SCSI_SCAN_MANUAL and scsi_scan_mutex to serialize with hotplug. This lets
users restore a deleted secondary LUN while LUN 0 remains attached.
BLIST_NO_LUN_1F lets the SCSI layer skip LUNs reported as absent with
PQ=0/PDT=0x1f. ata_scsi_dev_config() rejects LUNs outside dev->sdev[].
Assign the OF node before dropping the SCSI device reference, using the
referenced device so concurrent removal cannot invalidate the pointer.
Assisted-by: LLM
Signed-off-by: Phil Pemberton <philpem@philpem.me.uk>
---
drivers/ata/libata-scsi.c | 57 ++++++++++++++++++++++++++++++++++-----
1 file changed, 50 insertions(+), 7 deletions(-)
diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c
index 471daf63382a..0ee6bdeb81f0 100644
--- a/drivers/ata/libata-scsi.c
+++ b/drivers/ata/libata-scsi.c
@@ -26,6 +26,7 @@
#include <scsi/scsi_device.h>
#include <scsi/scsi_tcq.h>
#include <scsi/scsi_transport.h>
+#include <scsi/scsi_devinfo.h>
#include <linux/libata.h>
#include <linux/hdreg.h>
#include <linux/uaccess.h>
@@ -5340,13 +5341,25 @@ void ata_scsi_scan_host(struct ata_port *ap, int sync)
sdev = __scsi_add_device(ap->scsi_host, channel, id, 0,
NULL);
- if (!IS_ERR(sdev)) {
- dev->sdev[0] = sdev;
- ata_scsi_assign_ofnode(sdev, dev, ap);
- scsi_device_put(sdev);
- } else {
+ if (IS_ERR(sdev)) {
dev->sdev[0] = NULL;
+ continue;
}
+
+ /*
+ * For multi-LUN ATAPI (BLIST_FORCELUN), trigger a
+ * sequential scan for this target. pdt_1f_for_no_lun,
+ * set during LUN 0 configure, ensures non-responding
+ * LUNs are silently skipped; dev->sdev[] is populated
+ * by ata_scsi_dev_config() during the scan.
+ */
+ if (dev->class == ATA_DEV_ATAPI &&
+ sdev->sdev_bflags & BLIST_FORCELUN)
+ scsi_scan_target(&ap->scsi_host->shost_gendev,
+ channel, id, SCAN_WILD_CARD,
+ SCSI_SCAN_RESCAN);
+ ata_scsi_assign_ofnode(sdev, dev, ap);
+ scsi_device_put(sdev);
}
}
@@ -5554,7 +5567,7 @@ void ata_scsi_hotplug(struct work_struct *work)
* @lun: LUN to scan
*
* This function is called when user explicitly requests bus
- * scan. Set probe pending flag and invoke EH.
+ * scan. Set probe pending flag and invoke EH, then scan ATAPI LUNs.
*
* LOCKING:
* SCSI layer (we don't care)
@@ -5569,7 +5582,7 @@ int ata_scsi_user_scan(struct Scsi_Host *shost, unsigned int channel,
unsigned long flags;
int devno, rc = 0;
- if (lun != SCAN_WILD_CARD && lun)
+ if (lun != SCAN_WILD_CARD && lun >= shost->max_lun)
return -EINVAL;
if (!sata_pmp_attached(ap)) {
@@ -5610,6 +5623,36 @@ int ata_scsi_user_scan(struct Scsi_Host *shost, unsigned int channel,
} else
spin_unlock_irqrestore(ap->lock, flags);
+ if (!rc) {
+ struct ata_link *link;
+ struct ata_device *dev;
+
+ /* EH does not scan missing LUNs on an already attached target. */
+ mutex_lock(&ap->scsi_scan_mutex);
+ ata_for_each_link(link, ap, EDGE) {
+ ata_for_each_dev(dev, link, ENABLED) {
+ unsigned int scan_channel = 0, scan_id = 0;
+
+ if (dev->class != ATA_DEV_ATAPI)
+ continue;
+ if (ata_is_host_link(link))
+ scan_id = dev->devno;
+ else
+ scan_channel = link->pmp;
+ if (channel != SCAN_WILD_CARD &&
+ channel != scan_channel)
+ continue;
+ if (id != SCAN_WILD_CARD && id != scan_id)
+ continue;
+
+ scsi_scan_target(&shost->shost_gendev,
+ scan_channel, scan_id, lun,
+ SCSI_SCAN_MANUAL);
+ }
+ }
+ mutex_unlock(&ap->scsi_scan_mutex);
+ }
+
return rc;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH v9 11/11] scsi: scsi_devinfo: add COMPAQ PD-1 multi-LUN ATAPI device quirk
2026-10-06 23:41 [PATCH v9 00/11] ata: add multi-LUN support for ATAPI devices Phil Pemberton
` (9 preceding siblings ...)
2026-10-06 23:42 ` [PATCH v9 10/11] ata: libata-scsi: probe additional LUNs for multi-LUN ATAPI devices Phil Pemberton
@ 2026-10-06 23:42 ` Phil Pemberton
10 siblings, 0 replies; 17+ messages in thread
From: Phil Pemberton @ 2026-10-06 23:42 UTC (permalink / raw)
To: linux-ide, linux-scsi
Cc: linux-kernel, Damien Le Moal, Niklas Cassel,
James E . J . Bottomley, Martin K . Petersen, Hannes Reinecke,
Phil Pemberton, Martin K . Petersen
The COMPAQ PD-1 and Panasonic LF-1195C combine a CD-ROM on LUN 0 with
a phase-change rewritable (PD) drive on LUN 1.
Add a COMPAQ PD-1 quirk with BLIST_FORCELUN to enable multi-LUN scanning
and BLIST_SINGLELUN to serialize commands across LUNs. Set BLIST_NO_LUN_1F
to skip unpopulated LUNs, which report PQ=0/PDT=0x1f.
Reviewed-by: Damien Le Moal <dlemoal@kernel.org>
Reviewed-by: Hannes Reinecke <hare@kernel.org>
Reviewed-by: Martin K. Petersen <martin.petersen@oracle.com>
Assisted-by: LLM
Signed-off-by: Phil Pemberton <philpem@philpem.me.uk>
---
drivers/scsi/scsi_devinfo.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/scsi/scsi_devinfo.c b/drivers/scsi/scsi_devinfo.c
index 5e37461e61e8..f9196b0d6f4d 100644
--- a/drivers/scsi/scsi_devinfo.c
+++ b/drivers/scsi/scsi_devinfo.c
@@ -150,6 +150,8 @@ static struct {
{"COMPAQ", "MSA1000", NULL, BLIST_SPARSELUN | BLIST_NOSTARTONADD},
{"COMPAQ", "MSA1000 VOLUME", NULL, BLIST_SPARSELUN | BLIST_NOSTARTONADD},
{"COMPAQ", "HSV110", NULL, BLIST_REPORTLUN2 | BLIST_NOSTARTONADD},
+ {"COMPAQ", "PD-1", NULL, BLIST_FORCELUN | BLIST_SINGLELUN |
+ BLIST_NO_LUN_1F},
{"DDN", "SAN DataDirector", "*", BLIST_SPARSELUN},
{"DEC", "HSG80", NULL, BLIST_REPORTLUN2 | BLIST_NOSTARTONADD},
{"DELL", "PV660F", NULL, BLIST_SPARSELUN},
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread