* [PATCH v4 00/28] Native SCSI Multipath support
@ 2026-07-23 9:35 John Garry
2026-07-23 9:35 ` [PATCH v4 01/28] libmultipath: Add initial framework John Garry
` (27 more replies)
0 siblings, 28 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 9:35 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
NATIVE SCSI MULTIPATH:
This series introduces native SCSI multipath support. It is intended as
an alternative to dm-mpath.
This support aims to provide a multipath-enabled SCSI block device/
gendisk.
For a SCSI device to support native multipath, either of the following
conditions must be satisfied:
a. unique ID in VPD page 83 and ALUA support and scsi_multipath modparam
enabled
b. unique ID in VPD page 83 and scsi_multipath_always modparam enabled
This series relies on reading sdev->access_state to get path information.
This path information would be provided by ALUA. ALUA support which does
not rely on device handlers has already been discussed at
https://lore.kernel.org/linux-scsi/7755e98f-5619-48ba-bcfc-b64eec930c40@oracle.com/
and support will be added in the next phase.
New classes of devices are added:
- scsi_mpath_device
- scsi_mpath_disk
These are required since a multipath scsi_device has no common scsi host.
An example of the sysfs files and directories for these new classes is as
follows:
$ ls -l /sys/class/scsi_mpath_device/scsi_mpath_device0/
total 0
-rw-r--r-- 1 root root 4096 Feb 25 11:59 iopolicy
drwxr-xr-x 2 root root 0 Feb 25 11:59 multipath
drwxr-xr-x 2 root root 0 Feb 25 11:59 power
lrwxrwxrwx 1 root root 0 Feb 25 11:59 subsystem ->
../../../../class/scsi_mpath_device
-rw-r--r-- 1 root root 4096 Feb 25 11:58 uevent
-r--r--r-- 1 root root 4096 Feb 25 11:59 vpd_id
$ ls -l /sys/class/scsi_mpath_device/scsi_mpath_device0/multipath/
total 0
lrwxrwxrwx 1 root root 0 Feb 25 11:59 8:0:0:0 ->
../../../../platform/host8/session1/target8:0:0/8:0:0:0
lrwxrwxrwx 1 root root 0 Feb 25 11:59 9:0:0:0 ->
../../../../platform/host9/session2/target9:0:0/9:0:0:0
$ cat /sys/class/scsi_mpath_device/scsi_mpath_device0/vpd_id
naa.600140505200a986f0043c9afa1fd077
$ cat /sys/class/scsi_mpath_device/scsi_mpath_device0/iopolicy
numa
$
$ ls -l /sys/class/scsi_mpath_disk/scsi_mpath_disk0/
total 0
drwxr-xr-x 2 root root 0 Feb 25 12:00 power
drwxr-xr-x 11 root root 0 Feb 25 11:58 sdc
lrwxrwxrwx 1 root root 0 Feb 25 11:58 subsystem ->
../../../../class/scsi_mpath_disk
-rw-r--r-- 1 root root 4096 Feb 25 11:58 uevent
$ ls -l /sys/class/scsi_mpath_disk/scsi_mpath_disk0/sdc/multipath/
total 0
lrwxrwxrwx 1 root root 0 Feb 25 12:00 sdc:0 ->
../../../../../platform/host8/session1/target8:0:0/8:0:0:0/block/sdc:0
lrwxrwxrwx 1 root root 0 Feb 25 12:00 sdc:1 ->
../../../../../platform/host9/session2/target9:0:0/9:0:0:0/block/sdc:1
$ ls -l /dev/sdc
brw-rw---- 1 root disk 8, 32 Feb 25 11:58 /dev/sdc
The scsi_device and scsi_disk classes otherwise remain unmodified.
However, the per-path block device is hidden in /dev/. Furthermore,
multipathed block devices have a new naming scheme, sdX:Y, where
X is the scsi multipath device index and Y is the path index.
No multipath sg support is added. We still have a per-path sg device.
Since the SCSI block device is multipath enabled, we can access
multipathed scsi_ioctl() through that block device.
For failover, we take the approach of cloning bio's and re-submitting them
in full (for failover errors).
LIBMULTIPATH:
This series introduces also libmultipath. It is essentially a refactoring
of NVME multipath support, so we can have a common library to also support
native SCSI multipath.
Much of the code is taken directly from the NVMe multipath code. However,
NVMe specifics are removed. A template structure is provided so the driver
-may provide callbacks for driver specifics, like ANA support for NVMe.
Important new structures introduced include:
- mpath_head
These contain much of the multipath-specific functionality from
nvme_ns_head, including a pointer to the gendisk structure and
a path SRCU-based array.
- mpath_device
This is the per-path structure, and contains much the same
multipath-specific functionality in nvme_ns
libmultipath provides functionality for path management, path selection,
data path, and failover handling.
Full series also available at
https://github.com/johnpgarry/linux/commits/scsi-multipath-v7.2-v4/
Differences to v3:
- drop for now any libmultipath specific NVMe support, like cdev and PR
support
- deal with issues sashiko/codex found in libmultipath:
- add checks in mpath_add_device() for setting pointers
- add mpath_delete_device() -> mpath_synchronize() call
- add numa iopolicy check for disabled path
- fix trace in mpath_bdev_submit_bio()
- fix race in mpath_head_uninit() / mpath_put_head()
- add ioctl check in mpath_bdev_ioctl()
- fix sync ordering in mpath_remove_disk()
- drop unused arg in mpath_iopolicy_store()
- fix mpath_delayed_removal_secs_show() print format
- memset mpath_head in mpath_head_init()
- deal with issues sashiko/codex found in scsi-multipath support:
- change TPG check in scsi_mpath_dev_alloc()
- use scsi_multipath_param_set() -> __sysfs_match_string()
- add scsi_mpath_revalidate_paths()
- add sdev check in scsi_mpath_clone_end_io()
- fix sdev->access_state masking in scsi_mpath_is_disabled() and
scsi_mpath_is_optimized()
- properly set default iopolicy in scsi_mpath_alloc_head()
- fix error path in scsi_mpath_alloc_head()
- fix possible race in scsi_mpath_find_head()
- fix scsi_mpath_{start, end}_request() for partitions
- fix scsi_end_request() -> scsi_mpath_end_request() order to deal with
partial completions
- don't call __scsi_internal_device_block_nowait() ->
scsi_mpath_dev_clear_path()
- call scsi_internal_device_unblock() -> scsi_mpath_revalidate_paths()
- fix sd.c sys attr references to sd_mpath_disk
- handle delayed removal race in sd_mpath_remove_head()
- default index = -1 in sd_probe()
John Garry (28):
libmultipath: Add initial framework
libmultipath: Add basic gendisk support
libmultipath: Add path selection support
libmultipath: Add bio handling
libmultipath: Add support for mpath_device management
libmultipath: Add delayed removal support
libmultipath: Add sysfs helpers
libmultipath: Add mpath_bdev_report_zones()
libmultipath: Add support for block device IOCTL
libmultipath: Add mpath_bdev_getgeo()
libmultipath: Add mpath_bdev_get_unique_id()
scsi-multipath: introduce basic SCSI device support
scsi-multipath: introduce scsi_device head structure
scsi-multipath: provide sysfs link from to scsi_device
scsi-multipath: support iopolicy
scsi-multipath: clone each bio
scsi-multipath: clear path when device is blocked
scsi-multipath: revalidate paths upon device unblock
scsi-multipath: failover handling
scsi-multipath: provide callbacks for path state
scsi-multipath: add scsi_mpath_{start,end}_request()
scsi-multipath: add delayed disk removal support
scsi: sd: add multipath disk class
scsi: sd: add multipath disk attr groups
scsi: sd: support multipath disk
scsi: sd: add mpath_dev file
scsi: sd: add mpath_numa_nodes dev attribute
scsi: sd: add mpath_queue_depth dev attribute
drivers/scsi/Kconfig | 10 +
drivers/scsi/Makefile | 1 +
drivers/scsi/scsi.c | 8 +-
drivers/scsi/scsi_lib.c | 16 +-
drivers/scsi/scsi_multipath.c | 640 +++++++++++++++++++++++
drivers/scsi/scsi_scan.c | 4 +
drivers/scsi/scsi_sysfs.c | 10 +
drivers/scsi/sd.c | 585 ++++++++++++++++++++-
drivers/scsi/sd.h | 3 +
include/linux/multipath.h | 158 ++++++
include/scsi/scsi_cmnd.h | 6 +
include/scsi/scsi_device.h | 2 +
include/scsi/scsi_driver.h | 4 +
include/scsi/scsi_host.h | 4 +
include/scsi/scsi_multipath.h | 119 +++++
lib/Kconfig | 6 +
lib/Makefile | 2 +
lib/multipath.c | 960 ++++++++++++++++++++++++++++++++++
18 files changed, 2514 insertions(+), 24 deletions(-)
create mode 100644 drivers/scsi/scsi_multipath.c
create mode 100644 include/linux/multipath.h
create mode 100644 include/scsi/scsi_multipath.h
create mode 100644 lib/multipath.c
--
2.43.7
^ permalink raw reply [flat|nested] 74+ messages in thread
* [PATCH v4 01/28] libmultipath: Add initial framework
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
@ 2026-07-23 9:35 ` John Garry
2026-07-23 9:50 ` sashiko-bot
2026-07-23 9:36 ` [PATCH v4 02/28] libmultipath: Add basic gendisk support John Garry
` (26 subsequent siblings)
27 siblings, 1 reply; 74+ messages in thread
From: John Garry @ 2026-07-23 9:35 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
Add initial framework for libmultipath. libmultipath is a library for
multipath-capable block drivers, such as NVMe. The main function is to
support path management, path selection, and failover handling.
Basic support to add and remove the head structure - mpath_head - is
included.
This main purpose of this structure is to manage available paths and path
selection. It is quite similar to the multipath functionality in
nvme_ns_head. It also manages the multipath gendisk.
Each path is represented by the mpath_device structure. It should hold a
pointer to the per-path gendisk and also a list element for all siblings
of paths. For NVMe, there would be a mpath_device per nvme_ns.
All the libmultipath code is more or less taken from
drivers/nvme/host/multipath.c, which was originally authored by Christoph
Hellwig <hch@lst.de>.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
include/linux/multipath.h | 28 +++++++++++++++++
lib/Kconfig | 6 ++++
lib/Makefile | 2 ++
lib/multipath.c | 66 +++++++++++++++++++++++++++++++++++++++
4 files changed, 102 insertions(+)
create mode 100644 include/linux/multipath.h
create mode 100644 lib/multipath.c
diff --git a/include/linux/multipath.h b/include/linux/multipath.h
new file mode 100644
index 0000000000000..e98b4b241020a
--- /dev/null
+++ b/include/linux/multipath.h
@@ -0,0 +1,28 @@
+
+#ifndef _LIBMULTIPATH_H
+#define _LIBMULTIPATH_H
+
+#include <linux/blkdev.h>
+#include <linux/srcu.h>
+
+struct mpath_device {
+ struct list_head siblings;
+ struct gendisk *disk;
+};
+
+struct mpath_head {
+ struct srcu_struct srcu;
+ struct list_head dev_list; /* list of all mpath_devs */
+ struct mutex lock;
+
+ refcount_t refcount;
+
+ struct mpath_device __rcu *current_path[MAX_NUMNODES];
+};
+
+int mpath_get_head(struct mpath_head *mpath_head);
+void mpath_put_head(struct mpath_head *mpath_head);
+int mpath_head_init(struct mpath_head *mpath_head);
+void mpath_head_uninit(struct mpath_head *mpath_head);
+
+#endif // _LIBMULTIPATH_H
diff --git a/lib/Kconfig b/lib/Kconfig
index 55748b68714e0..d0258bef374a1 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -636,3 +636,9 @@ config UNION_FIND
config MIN_HEAP
bool
+
+config LIBMULTIPATH
+ bool "MULTIPATH BLOCK DRIVER LIBRARY"
+ depends on BLOCK
+ help
+ If you say yes here then you get a multipath lib for block drivers
diff --git a/lib/Makefile b/lib/Makefile
index 7f75cc6edf94a..7ba5e13be4171 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -334,3 +334,5 @@ CONTEXT_ANALYSIS_test_context-analysis.o := y
obj-$(CONFIG_CONTEXT_ANALYSIS_TEST) += test_context-analysis.o
subdir-$(CONFIG_FORTIFY_SOURCE) += test_fortify
+
+obj-$(CONFIG_LIBMULTIPATH) += multipath.o
diff --git a/lib/multipath.c b/lib/multipath.c
new file mode 100644
index 0000000000000..9cc398d266adf
--- /dev/null
+++ b/lib/multipath.c
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2017-2018 Christoph Hellwig.
+ * Copyright (c) 2026 Oracle and/or its affiliates.
+ */
+#include <linux/module.h>
+#include <linux/multipath.h>
+
+static struct workqueue_struct *mpath_wq;
+
+int mpath_get_head(struct mpath_head *mpath_head)
+{
+ if (!refcount_inc_not_zero(&mpath_head->refcount))
+ return -ENXIO;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mpath_get_head);
+
+void mpath_put_head(struct mpath_head *mpath_head)
+{
+ refcount_t *refcount = &mpath_head->refcount;
+
+ if (refcount_dec_and_test(&mpath_head->refcount))
+ wake_up_var(refcount);
+}
+EXPORT_SYMBOL_GPL(mpath_put_head);
+
+void mpath_head_uninit(struct mpath_head *mpath_head)
+{
+ refcount_t *refcount = &mpath_head->refcount;
+
+ if (!refcount_dec_and_test(refcount))
+ wait_var_event(refcount, !refcount_read(refcount));
+ cleanup_srcu_struct(&mpath_head->srcu);
+}
+EXPORT_SYMBOL_GPL(mpath_head_uninit);
+
+int mpath_head_init(struct mpath_head *mpath_head)
+{
+ memset(mpath_head, 0, sizeof(*mpath_head));
+ INIT_LIST_HEAD(&mpath_head->dev_list);
+ mutex_init(&mpath_head->lock);
+ refcount_set(&mpath_head->refcount, 1);
+
+ return init_srcu_struct(&mpath_head->srcu);
+}
+EXPORT_SYMBOL_GPL(mpath_head_init);
+
+static int __init mpath_init(void)
+{
+ mpath_wq = alloc_workqueue("mpath-wq",
+ WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_SYSFS, 0);
+ if (!mpath_wq)
+ return -ENOMEM;
+ return 0;
+}
+
+static void __exit mpath_exit(void)
+{
+ destroy_workqueue(mpath_wq);
+}
+
+module_init(mpath_init);
+module_exit(mpath_exit);
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("libmultipath");
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 02/28] libmultipath: Add basic gendisk support
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
2026-07-23 9:35 ` [PATCH v4 01/28] libmultipath: Add initial framework John Garry
@ 2026-07-23 9:36 ` John Garry
2026-07-23 9:57 ` sashiko-bot
2026-07-23 9:36 ` [PATCH v4 03/28] libmultipath: Add path selection support John Garry
` (25 subsequent siblings)
27 siblings, 1 reply; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
Add support to allocate and free a multipath gendisk.
NVMe has almost like-for-like equivalents here:
- mpath_alloc_head_disk() -> nvme_mpath_alloc_disk()
- multipath_partition_scan_work() -> nvme_partition_scan_work()
- mpath_remove_disk() -> nvme_remove_head()
- mpath_device_set_live() -> nvme_mpath_set_live()
struct mpath_head_template is introduced as a method for drivers to
provide custom multipath functionality.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
include/linux/multipath.h | 38 +++++++++++++++
lib/multipath.c | 99 +++++++++++++++++++++++++++++++++++++++
2 files changed, 137 insertions(+)
diff --git a/include/linux/multipath.h b/include/linux/multipath.h
index e98b4b241020a..ca8589d5cd8b2 100644
--- a/include/linux/multipath.h
+++ b/include/linux/multipath.h
@@ -5,11 +5,19 @@
#include <linux/blkdev.h>
#include <linux/srcu.h>
+extern const struct block_device_operations mpath_ops;
+
struct mpath_device {
+ struct mpath_head *mpath_head;
struct list_head siblings;
struct gendisk *disk;
};
+struct mpath_head_template {
+};
+
+#define MPATH_HEAD_DISK_LIVE 0
+
struct mpath_head {
struct srcu_struct srcu;
struct list_head dev_list; /* list of all mpath_devs */
@@ -17,12 +25,42 @@ struct mpath_head {
refcount_t refcount;
+ unsigned long flags;
+ struct gendisk *disk;
+ struct work_struct partition_scan_work;
+ struct device *parent;
+ const struct attribute_group **disk_groups;
+ const struct mpath_head_template *mpdt;
struct mpath_device __rcu *current_path[MAX_NUMNODES];
};
+static inline struct mpath_head *mpath_bd_device_to_head(struct device *dev)
+{
+ return dev_get_drvdata(dev);
+}
+
+static inline struct mpath_head *mpath_gendisk_to_head(struct gendisk *disk)
+{
+ return mpath_bd_device_to_head(disk_to_dev(disk));
+}
+
int mpath_get_head(struct mpath_head *mpath_head);
void mpath_put_head(struct mpath_head *mpath_head);
int mpath_head_init(struct mpath_head *mpath_head);
void mpath_head_uninit(struct mpath_head *mpath_head);
+void mpath_put_disk(struct mpath_head *mpath_head);
+void mpath_remove_disk(struct mpath_head *mpath_head);
+int mpath_alloc_head_disk(struct mpath_head *mpath_head,
+ struct queue_limits *lim, int numa_node);
+void mpath_device_set_live(struct mpath_device *mpath_device);
+
+static inline bool is_mpath_disk(struct gendisk *disk)
+{
+ #if IS_ENABLED(CONFIG_LIBMULTIPATH)
+ return disk->fops == &mpath_ops;
+ #else
+ return false;
+ #endif
+}
#endif // _LIBMULTIPATH_H
diff --git a/lib/multipath.c b/lib/multipath.c
index 9cc398d266adf..916d01a285284 100644
--- a/lib/multipath.c
+++ b/lib/multipath.c
@@ -35,6 +35,102 @@ void mpath_head_uninit(struct mpath_head *mpath_head)
}
EXPORT_SYMBOL_GPL(mpath_head_uninit);
+static int mpath_bdev_open(struct gendisk *disk, blk_mode_t mode)
+{
+ struct mpath_head *mpath_head = disk->private_data;
+
+ return mpath_get_head(mpath_head);
+}
+
+static void mpath_bdev_release(struct gendisk *disk)
+{
+ struct mpath_head *mpath_head = disk->private_data;
+
+ mpath_put_head(mpath_head);
+}
+
+const struct block_device_operations mpath_ops = {
+ .owner = THIS_MODULE,
+ .open = mpath_bdev_open,
+ .release = mpath_bdev_release,
+};
+EXPORT_SYMBOL_GPL(mpath_ops);
+
+static void multipath_partition_scan_work(struct work_struct *work)
+{
+ struct mpath_head *mpath_head =
+ container_of(work, struct mpath_head, partition_scan_work);
+
+ if (WARN_ON_ONCE(!test_and_clear_bit(GD_SUPPRESS_PART_SCAN,
+ &mpath_head->disk->state)))
+ return;
+
+ mutex_lock(&mpath_head->disk->open_mutex);
+ bdev_disk_changed(mpath_head->disk, false);
+ mutex_unlock(&mpath_head->disk->open_mutex);
+}
+
+void mpath_remove_disk(struct mpath_head *mpath_head)
+{
+ if (test_and_clear_bit(MPATH_HEAD_DISK_LIVE, &mpath_head->flags)) {
+ struct gendisk *disk = mpath_head->disk;
+
+ del_gendisk(disk);
+ }
+}
+EXPORT_SYMBOL_GPL(mpath_remove_disk);
+
+void mpath_put_disk(struct mpath_head *mpath_head)
+{
+ if (!mpath_head->disk)
+ return;
+
+ /* make sure all pending bios are cleaned up */
+ flush_work(&mpath_head->partition_scan_work);
+ put_disk(mpath_head->disk);
+}
+EXPORT_SYMBOL_GPL(mpath_put_disk);
+
+int mpath_alloc_head_disk(struct mpath_head *mpath_head,
+ struct queue_limits *lim, int numa_node)
+{
+ if (!mpath_head->disk_groups || !mpath_head->parent)
+ return -EINVAL;
+
+ mpath_head->disk = blk_alloc_disk(lim, numa_node);
+ if (IS_ERR(mpath_head->disk))
+ return PTR_ERR(mpath_head->disk);
+
+ mpath_head->disk->private_data = mpath_head;
+ mpath_head->disk->fops = &mpath_ops;
+
+ set_bit(GD_SUPPRESS_PART_SCAN, &mpath_head->disk->state);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mpath_alloc_head_disk);
+
+void mpath_device_set_live(struct mpath_device *mpath_device)
+{
+ struct mpath_head *mpath_head = mpath_device->mpath_head;
+ int ret;
+
+ if (!mpath_head->disk)
+ return;
+
+ if (!test_and_set_bit(MPATH_HEAD_DISK_LIVE, &mpath_head->flags)) {
+ dev_set_drvdata(disk_to_dev(mpath_head->disk), mpath_head);
+ ret = device_add_disk(mpath_head->parent, mpath_head->disk,
+ mpath_head->disk_groups);
+ if (ret) {
+ clear_bit(MPATH_HEAD_DISK_LIVE, &mpath_head->flags);
+ return;
+ }
+ queue_work(mpath_wq, &mpath_head->partition_scan_work);
+ }
+}
+EXPORT_SYMBOL_GPL(mpath_device_set_live);
+
int mpath_head_init(struct mpath_head *mpath_head)
{
memset(mpath_head, 0, sizeof(*mpath_head));
@@ -42,6 +138,9 @@ int mpath_head_init(struct mpath_head *mpath_head)
mutex_init(&mpath_head->lock);
refcount_set(&mpath_head->refcount, 1);
+ INIT_WORK(&mpath_head->partition_scan_work,
+ multipath_partition_scan_work);
+
return init_srcu_struct(&mpath_head->srcu);
}
EXPORT_SYMBOL_GPL(mpath_head_init);
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 03/28] libmultipath: Add path selection support
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
2026-07-23 9:35 ` [PATCH v4 01/28] libmultipath: Add initial framework John Garry
2026-07-23 9:36 ` [PATCH v4 02/28] libmultipath: Add basic gendisk support John Garry
@ 2026-07-23 9:36 ` John Garry
2026-07-23 9:55 ` sashiko-bot
2026-07-23 9:36 ` [PATCH v4 04/28] libmultipath: Add bio handling John Garry
` (24 subsequent siblings)
27 siblings, 1 reply; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
Add code for path selection.
NVMe ANA is abstracted into enum mpath_access_state. The motivation here is
so that SCSI ALUA can be used. Callbacks .is_disabled and .is_optimized
are added to get the path access state.
Path selection modes round-robin, NUMA, and queue-depth are added, same
as NVMe supports.
NVMe has almost like-for-like equivalents here:
- __mpath_find_path() -> __nvme_find_path()
- mpath_find_path() -> nvme_find_path()
and similar for all introduced callee functions.
Functions mpath_set_iopolicy() and mpath_get_iopolicy() are added for
setting default iopolicy.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
include/linux/multipath.h | 33 +++++
lib/multipath.c | 254 +++++++++++++++++++++++++++++++++++++-
2 files changed, 286 insertions(+), 1 deletion(-)
diff --git a/include/linux/multipath.h b/include/linux/multipath.h
index ca8589d5cd8b2..6c75654c12f8f 100644
--- a/include/linux/multipath.h
+++ b/include/linux/multipath.h
@@ -7,13 +7,31 @@
extern const struct block_device_operations mpath_ops;
+enum mpath_iopolicy_e {
+ MPATH_IOPOLICY_NUMA,
+ MPATH_IOPOLICY_RR,
+ MPATH_IOPOLICY_QD,
+};
+
+enum mpath_access_state {
+ MPATH_STATE_OPTIMIZED,
+ MPATH_STATE_NONOPTIMIZED,
+ MPATH_STATE_OTHER
+};
+
struct mpath_device {
struct mpath_head *mpath_head;
struct list_head siblings;
struct gendisk *disk;
+ int numa_node;
+ atomic_t *nr_active;
+ enum mpath_access_state access_state;
};
struct mpath_head_template {
+ bool (*is_disabled)(struct mpath_device *);
+ bool (*is_optimized)(struct mpath_device *);
+ const struct attribute_group **device_groups;
};
#define MPATH_HEAD_DISK_LIVE 0
@@ -25,6 +43,7 @@ struct mpath_head {
refcount_t refcount;
+ enum mpath_iopolicy_e *iopolicy;
unsigned long flags;
struct gendisk *disk;
struct work_struct partition_scan_work;
@@ -44,6 +63,14 @@ static inline struct mpath_head *mpath_gendisk_to_head(struct gendisk *disk)
return mpath_bd_device_to_head(disk_to_dev(disk));
}
+static inline enum mpath_iopolicy_e mpath_read_iopolicy(
+ struct mpath_head *mpath_head)
+{
+ return READ_ONCE(*mpath_head->iopolicy);
+}
+void mpath_synchronize(struct mpath_head *mpath_head);
+int mpath_set_iopolicy(const char *str, enum mpath_iopolicy_e *iopolicy);
+int mpath_get_iopolicy(char *buf, int iopolicy);
int mpath_get_head(struct mpath_head *mpath_head);
void mpath_put_head(struct mpath_head *mpath_head);
int mpath_head_init(struct mpath_head *mpath_head);
@@ -63,4 +90,10 @@ static inline bool is_mpath_disk(struct gendisk *disk)
return false;
#endif
}
+
+static inline bool mpath_qd_iopolicy(enum mpath_iopolicy_e *iopolicy)
+{
+ return READ_ONCE(*iopolicy) == MPATH_IOPOLICY_QD;
+}
+
#endif // _LIBMULTIPATH_H
diff --git a/lib/multipath.c b/lib/multipath.c
index 916d01a285284..20bb8cb0ea956 100644
--- a/lib/multipath.c
+++ b/lib/multipath.c
@@ -6,8 +6,245 @@
#include <linux/module.h>
#include <linux/multipath.h>
+static struct mpath_device *mpath_find_path(struct mpath_head *mpath_head);
+
static struct workqueue_struct *mpath_wq;
+static const char * const mpath_iopolicy_names[] = {
+ [MPATH_IOPOLICY_NUMA] = "numa",
+ [MPATH_IOPOLICY_RR] = "round-robin",
+ [MPATH_IOPOLICY_QD] = "queue-depth",
+};
+
+static int mpath_iopolicy_parse(const char *str)
+{
+ return __sysfs_match_string(mpath_iopolicy_names,
+ ARRAY_SIZE(mpath_iopolicy_names), str);
+}
+
+int mpath_set_iopolicy(const char *str, enum mpath_iopolicy_e *iopolicy)
+{
+ int policy;
+
+ if (!str)
+ return -EINVAL;
+ policy = mpath_iopolicy_parse(str);
+ if (policy < 0)
+ return policy;
+ *iopolicy = policy;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mpath_set_iopolicy);
+
+int mpath_get_iopolicy(char *buf, int iopolicy)
+{
+ return sprintf(buf, "%s\n", mpath_iopolicy_names[iopolicy]);
+}
+EXPORT_SYMBOL_GPL(mpath_get_iopolicy);
+
+
+void mpath_synchronize(struct mpath_head *mpath_head)
+{
+ synchronize_srcu(&mpath_head->srcu);
+}
+EXPORT_SYMBOL_GPL(mpath_synchronize);
+
+static bool mpath_path_is_disabled(struct mpath_head *mpath_head,
+ struct mpath_device *mpath_device)
+{
+ return mpath_head->mpdt->is_disabled(mpath_device);
+}
+
+static struct mpath_device *__mpath_find_path(struct mpath_head *mpath_head,
+ int node)
+{
+ int found_distance = INT_MAX, fallback_distance = INT_MAX, distance;
+ struct mpath_device *found = NULL, *fallback = NULL, *mpath_device;
+
+ list_for_each_entry_srcu(mpath_device, &mpath_head->dev_list, siblings,
+ srcu_read_lock_held(&mpath_head->srcu)) {
+ if (mpath_path_is_disabled(mpath_head, mpath_device))
+ continue;
+
+ if (mpath_device->numa_node != NUMA_NO_NODE &&
+ (mpath_read_iopolicy(mpath_head) ==
+ MPATH_IOPOLICY_NUMA))
+ distance = node_distance(node,
+ mpath_device->numa_node);
+ else
+ distance = LOCAL_DISTANCE;
+
+ switch(mpath_device->access_state) {
+ case MPATH_STATE_OPTIMIZED:
+ if (distance < found_distance) {
+ found_distance = distance;
+ found = mpath_device;
+ }
+ break;
+ case MPATH_STATE_NONOPTIMIZED:
+ if (distance < fallback_distance) {
+ fallback_distance = distance;
+ fallback = mpath_device;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!found)
+ found = fallback;
+
+ if (found)
+ rcu_assign_pointer(mpath_head->current_path[node], found);
+
+ return found;
+}
+
+static struct mpath_device *mpath_next_dev(struct mpath_head *mpath_head,
+ struct mpath_device *mpath_dev)
+{
+ mpath_dev = list_next_or_null_rcu(&mpath_head->dev_list,
+ &mpath_dev->siblings, struct mpath_device,
+ siblings);
+
+ if (mpath_dev)
+ return mpath_dev;
+ return list_first_or_null_rcu(&mpath_head->dev_list,
+ struct mpath_device, siblings);
+}
+
+static struct mpath_device *mpath_round_robin_path(
+ struct mpath_head *mpath_head)
+{
+ struct mpath_device *mpath_device, *found = NULL;
+ int node = numa_node_id();
+ enum mpath_access_state access_state_old;
+ struct mpath_device *old =
+ srcu_dereference(mpath_head->current_path[node],
+ &mpath_head->srcu);
+
+ if (unlikely(!old))
+ return __mpath_find_path(mpath_head, node);
+
+ if (list_is_singular(&mpath_head->dev_list)) {
+ if (mpath_path_is_disabled(mpath_head, old))
+ return NULL;
+ return old;
+ }
+
+ for (mpath_device = mpath_next_dev(mpath_head, old);
+ mpath_device && mpath_device != old;
+ mpath_device = mpath_next_dev(mpath_head, mpath_device)) {
+
+ if (mpath_path_is_disabled(mpath_head, mpath_device))
+ continue;
+ if (mpath_device->access_state == MPATH_STATE_OPTIMIZED) {
+ found = mpath_device;
+ goto out;
+ }
+ if (mpath_device->access_state == MPATH_STATE_NONOPTIMIZED)
+ found = mpath_device;
+ }
+
+ /*
+ * The loop above skips the current path for round-robin semantics.
+ * Fall back to the current path if either:
+ * - no other optimized path found and current is optimized,
+ * - no other usable path found and current is usable.
+ */
+ access_state_old = old->access_state;
+ if (!mpath_path_is_disabled(mpath_head, old) &&
+ (access_state_old == MPATH_STATE_OPTIMIZED ||
+ (!found && access_state_old == MPATH_STATE_NONOPTIMIZED)))
+ return old;
+
+ if (!found)
+ return NULL;
+out:
+ rcu_assign_pointer(mpath_head->current_path[node], found);
+
+ return found;
+}
+
+static struct mpath_device *mpath_queue_depth_path(
+ struct mpath_head *mpath_head)
+{
+ struct mpath_device *best_opt = NULL, *mpath_device;
+ struct mpath_device *best_nonopt = NULL;
+ unsigned int min_depth_opt = UINT_MAX, min_depth_nonopt = UINT_MAX;
+ unsigned int depth;
+
+ list_for_each_entry_srcu(mpath_device, &mpath_head->dev_list, siblings,
+ srcu_read_lock_held(&mpath_head->srcu)) {
+
+ if (mpath_path_is_disabled(mpath_head, mpath_device))
+ continue;
+
+ depth = atomic_read(mpath_device->nr_active);
+
+ switch (mpath_device->access_state) {
+ case MPATH_STATE_OPTIMIZED:
+ if (depth < min_depth_opt) {
+ min_depth_opt = depth;
+ best_opt = mpath_device;
+ }
+ break;
+ case MPATH_STATE_NONOPTIMIZED:
+ if (depth < min_depth_nonopt) {
+ min_depth_nonopt = depth;
+ best_nonopt = mpath_device;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (min_depth_opt == 0)
+ return best_opt;
+ }
+
+ return best_opt ? best_opt : best_nonopt;
+}
+
+static inline bool mpath_path_is_optimized(struct mpath_head *mpath_head,
+ struct mpath_device *mpath_device)
+{
+ return mpath_head->mpdt->is_optimized(mpath_device);
+}
+
+static struct mpath_device *mpath_numa_path(struct mpath_head *mpath_head)
+{
+ int node = numa_node_id();
+ struct mpath_device *mpath_device;
+
+ mpath_device = srcu_dereference(mpath_head->current_path[node],
+ &mpath_head->srcu);
+ if (unlikely(!mpath_device))
+ return __mpath_find_path(mpath_head, node);
+ if (unlikely(mpath_path_is_disabled(mpath_head, mpath_device)))
+ return __mpath_find_path(mpath_head, node);
+ if (unlikely(!mpath_path_is_optimized(mpath_head, mpath_device)))
+ return __mpath_find_path(mpath_head, node);
+ return mpath_device;
+}
+
+__maybe_unused
+static struct mpath_device *mpath_find_path(struct mpath_head *mpath_head)
+{
+ enum mpath_iopolicy_e iopolicy = mpath_read_iopolicy(mpath_head);
+
+ switch (iopolicy) {
+ case MPATH_IOPOLICY_QD:
+ return mpath_queue_depth_path(mpath_head);
+ case MPATH_IOPOLICY_RR:
+ return mpath_round_robin_path(mpath_head);
+ default:
+ return mpath_numa_path(mpath_head);
+ }
+}
+
int mpath_get_head(struct mpath_head *mpath_head)
{
if (!refcount_inc_not_zero(&mpath_head->refcount))
@@ -75,6 +312,7 @@ void mpath_remove_disk(struct mpath_head *mpath_head)
if (test_and_clear_bit(MPATH_HEAD_DISK_LIVE, &mpath_head->flags)) {
struct gendisk *disk = mpath_head->disk;
+ mpath_synchronize(mpath_head);
del_gendisk(disk);
}
}
@@ -94,7 +332,8 @@ EXPORT_SYMBOL_GPL(mpath_put_disk);
int mpath_alloc_head_disk(struct mpath_head *mpath_head,
struct queue_limits *lim, int numa_node)
{
- if (!mpath_head->disk_groups || !mpath_head->parent)
+ if (!mpath_head->disk_groups || !mpath_head->parent ||
+ !mpath_head->iopolicy)
return -EINVAL;
mpath_head->disk = blk_alloc_disk(lim, numa_node);
@@ -128,6 +367,19 @@ void mpath_device_set_live(struct mpath_device *mpath_device)
}
queue_work(mpath_wq, &mpath_head->partition_scan_work);
}
+
+ mutex_lock(&mpath_head->lock);
+ if (mpath_path_is_optimized(mpath_head, mpath_device)) {
+ int node, srcu_idx;
+
+ srcu_idx = srcu_read_lock(&mpath_head->srcu);
+ for_each_online_node(node)
+ __mpath_find_path(mpath_head, node);
+ srcu_read_unlock(&mpath_head->srcu, srcu_idx);
+ }
+ mutex_unlock(&mpath_head->lock);
+
+ mpath_synchronize(mpath_head);
}
EXPORT_SYMBOL_GPL(mpath_device_set_live);
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 04/28] libmultipath: Add bio handling
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
` (2 preceding siblings ...)
2026-07-23 9:36 ` [PATCH v4 03/28] libmultipath: Add path selection support John Garry
@ 2026-07-23 9:36 ` John Garry
2026-07-23 9:36 ` [PATCH v4 05/28] libmultipath: Add support for mpath_device management John Garry
` (23 subsequent siblings)
27 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
Add support to submit a bio per-path. In addition, for failover, add
support to requeue a failed bio.
NVMe has almost like-for-like equivalents here:
- nvme_available_path() -> mpath_available_path()
- nvme_requeue_work() -> mpath_requeue_work()
- nvme_ns_head_submit_bio() -> mpath_bdev_submit_bio()
For failover, a driver may want to re-submit a bio, so add support to
clone a bio prior to submission.
A bio which is submitted to a per-path device has flag REQ_MPATH set,
same as what is done for NVMe with REQ_NVME_MPATH.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
include/linux/multipath.h | 22 ++++++++
lib/multipath.c | 108 +++++++++++++++++++++++++++++++++++++-
2 files changed, 128 insertions(+), 2 deletions(-)
diff --git a/include/linux/multipath.h b/include/linux/multipath.h
index 6c75654c12f8f..57d13eb1450cd 100644
--- a/include/linux/multipath.h
+++ b/include/linux/multipath.h
@@ -3,6 +3,7 @@
#define _LIBMULTIPATH_H
#include <linux/blkdev.h>
+#include <linux/blk-mq.h>
#include <linux/srcu.h>
extern const struct block_device_operations mpath_ops;
@@ -29,8 +30,10 @@ struct mpath_device {
};
struct mpath_head_template {
+ bool (*available_path)(struct mpath_device *);
bool (*is_disabled)(struct mpath_device *);
bool (*is_optimized)(struct mpath_device *);
+ struct bio *(*clone_bio)(struct bio *);
const struct attribute_group **device_groups;
};
@@ -44,6 +47,14 @@ struct mpath_head {
refcount_t refcount;
enum mpath_iopolicy_e *iopolicy;
+
+ struct bio_list requeue_list; /* list for requeing bio */
+ spinlock_t requeue_lock;
+ struct work_struct requeue_work; /* work struct for requeue */
+
+ atomic_long_t requeue_no_usable_path_cnt;
+ atomic_long_t fail_no_avail_path_cnt;
+
unsigned long flags;
struct gendisk *disk;
struct work_struct partition_scan_work;
@@ -53,6 +64,13 @@ struct mpath_head {
struct mpath_device __rcu *current_path[MAX_NUMNODES];
};
+#define REQ_MPATH REQ_DRV
+
+static inline bool is_mpath_request(struct request *req)
+{
+ return req->cmd_flags & REQ_MPATH;
+}
+
static inline struct mpath_head *mpath_bd_device_to_head(struct device *dev)
{
return dev_get_drvdata(dev);
@@ -96,4 +114,8 @@ static inline bool mpath_qd_iopolicy(enum mpath_iopolicy_e *iopolicy)
return READ_ONCE(*iopolicy) == MPATH_IOPOLICY_QD;
}
+static inline void mpath_schedule_requeue_work(struct mpath_head *mpath_head)
+{
+ kblockd_schedule_work(&mpath_head->requeue_work);
+}
#endif // _LIBMULTIPATH_H
diff --git a/lib/multipath.c b/lib/multipath.c
index 20bb8cb0ea956..448d879abb354 100644
--- a/lib/multipath.c
+++ b/lib/multipath.c
@@ -5,6 +5,7 @@
*/
#include <linux/module.h>
#include <linux/multipath.h>
+#include <trace/events/block.h>
static struct mpath_device *mpath_find_path(struct mpath_head *mpath_head);
@@ -43,7 +44,6 @@ int mpath_get_iopolicy(char *buf, int iopolicy)
}
EXPORT_SYMBOL_GPL(mpath_get_iopolicy);
-
void mpath_synchronize(struct mpath_head *mpath_head)
{
synchronize_srcu(&mpath_head->srcu);
@@ -230,7 +230,6 @@ static struct mpath_device *mpath_numa_path(struct mpath_head *mpath_head)
return mpath_device;
}
-__maybe_unused
static struct mpath_device *mpath_find_path(struct mpath_head *mpath_head)
{
enum mpath_iopolicy_e iopolicy = mpath_read_iopolicy(mpath_head);
@@ -245,6 +244,81 @@ static struct mpath_device *mpath_find_path(struct mpath_head *mpath_head)
}
}
+static bool mpath_available_path(struct mpath_head *mpath_head)
+{
+ struct mpath_device *mpath_device;
+
+ if (!test_bit(MPATH_HEAD_DISK_LIVE, &mpath_head->flags))
+ return false;
+
+ list_for_each_entry_srcu(mpath_device, &mpath_head->dev_list, siblings,
+ srcu_read_lock_held(&mpath_head->srcu)) {
+ if (mpath_head->mpdt->available_path(mpath_device))
+ return true;
+ }
+
+ return false;
+}
+
+static void mpath_bdev_submit_bio(struct bio *bio)
+{
+ struct mpath_head *mpath_head = bio->bi_bdev->bd_disk->private_data;
+ struct device *dev = mpath_head->parent;
+ struct mpath_device *mpath_device;
+ int srcu_idx;
+
+ /*
+ * The mpath_device might be going away and the bio might be moved to a
+ * different queue in failover, so we need to use the bio_split
+ * pool from the original queue to allocate the bvecs from.
+ */
+ bio = bio_split_to_limits(bio);
+ if (!bio)
+ return;
+
+ srcu_idx = srcu_read_lock(&mpath_head->srcu);
+ mpath_device = mpath_find_path(mpath_head);
+
+ if (likely(mpath_device)) {
+ if (mpath_head->mpdt->clone_bio) {
+ struct bio *orig = bio;
+
+ bio = mpath_head->mpdt->clone_bio(bio);
+ if (!bio) {
+ bio_io_error(orig);
+ goto out;
+ }
+ }
+
+ bio_set_dev(bio, mpath_device->disk->part0);
+ /*
+ * Use BIO_REMAPPED to skip bio_check_eod() when this bio
+ * enters submit_bio_noacct() for the per-path device. The EOD
+ * check already passed on the multipath head.
+ */
+ bio_set_flag(bio, BIO_REMAPPED);
+ bio->bi_opf |= REQ_MPATH;
+ trace_block_bio_remap(bio, disk_devt(mpath_head->disk),
+ bio->bi_iter.bi_sector);
+ submit_bio_noacct(bio);
+ } else if (mpath_available_path(mpath_head)) {
+ dev_warn_ratelimited(dev, "no usable path - requeuing I/O\n");
+
+ spin_lock_irq(&mpath_head->requeue_lock);
+ bio_list_add(&mpath_head->requeue_list, bio);
+ spin_unlock_irq(&mpath_head->requeue_lock);
+ atomic_long_inc(&mpath_head->requeue_no_usable_path_cnt);
+ } else {
+ dev_warn_ratelimited(dev, "no available path - failing I/O\n");
+
+ bio_io_error(bio);
+ atomic_long_inc(&mpath_head->fail_no_avail_path_cnt);
+ }
+
+out:
+ srcu_read_unlock(&mpath_head->srcu, srcu_idx);
+}
+
int mpath_get_head(struct mpath_head *mpath_head)
{
if (!refcount_inc_not_zero(&mpath_head->refcount))
@@ -290,6 +364,7 @@ const struct block_device_operations mpath_ops = {
.owner = THIS_MODULE,
.open = mpath_bdev_open,
.release = mpath_bdev_release,
+ .submit_bio = mpath_bdev_submit_bio,
};
EXPORT_SYMBOL_GPL(mpath_ops);
@@ -307,12 +382,35 @@ static void multipath_partition_scan_work(struct work_struct *work)
mutex_unlock(&mpath_head->disk->open_mutex);
}
+static void mpath_requeue_work(struct work_struct *work)
+{
+ struct mpath_head *mpath_head =
+ container_of(work, struct mpath_head, requeue_work);
+ struct bio *bio, *next;
+
+ spin_lock_irq(&mpath_head->requeue_lock);
+ next = bio_list_get(&mpath_head->requeue_list);
+ spin_unlock_irq(&mpath_head->requeue_lock);
+
+ while ((bio = next) != NULL) {
+ next = bio->bi_next;
+ bio->bi_next = NULL;
+ submit_bio_noacct(bio);
+ }
+}
+
void mpath_remove_disk(struct mpath_head *mpath_head)
{
if (test_and_clear_bit(MPATH_HEAD_DISK_LIVE, &mpath_head->flags)) {
struct gendisk *disk = mpath_head->disk;
+ /*
+ * requeue I/O after MPATH_HEAD_DISK_LIVE has been cleared
+ * to allow multipath to fail all I/O.
+ */
mpath_synchronize(mpath_head);
+ mpath_schedule_requeue_work(mpath_head);
+
del_gendisk(disk);
}
}
@@ -324,6 +422,8 @@ void mpath_put_disk(struct mpath_head *mpath_head)
return;
/* make sure all pending bios are cleaned up */
+ kblockd_schedule_work(&mpath_head->requeue_work);
+ flush_work(&mpath_head->requeue_work);
flush_work(&mpath_head->partition_scan_work);
put_disk(mpath_head->disk);
}
@@ -380,6 +480,7 @@ void mpath_device_set_live(struct mpath_device *mpath_device)
mutex_unlock(&mpath_head->lock);
mpath_synchronize(mpath_head);
+ mpath_schedule_requeue_work(mpath_head);
}
EXPORT_SYMBOL_GPL(mpath_device_set_live);
@@ -392,6 +493,9 @@ int mpath_head_init(struct mpath_head *mpath_head)
INIT_WORK(&mpath_head->partition_scan_work,
multipath_partition_scan_work);
+ INIT_WORK(&mpath_head->requeue_work, mpath_requeue_work);
+ spin_lock_init(&mpath_head->requeue_lock);
+ bio_list_init(&mpath_head->requeue_list);
return init_srcu_struct(&mpath_head->srcu);
}
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 05/28] libmultipath: Add support for mpath_device management
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
` (3 preceding siblings ...)
2026-07-23 9:36 ` [PATCH v4 04/28] libmultipath: Add bio handling John Garry
@ 2026-07-23 9:36 ` John Garry
2026-07-23 9:58 ` sashiko-bot
2026-07-23 9:36 ` [PATCH v4 06/28] libmultipath: Add delayed removal support John Garry
` (22 subsequent siblings)
27 siblings, 1 reply; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
Add support to add or remove a mpath_device as a path.
NVMe has almost like-for-like equivalents here:
- nvme_mpath_clear_current_path() -> mpath_clear_current_path()
- nvme_mpath_add_sysfs_link() -> mpath_add_sysfs_link()
- nvme_mpath_remove_sysfs_link() -> mpath_remove_sysfs_link()
- nvme_mpath_revalidate_paths() -> mpath_revalidate_paths()
The functionality in mpath_clear_paths() and mpath_synchronize() have the
same pattern which is frequently used in the NVMe code.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
include/linux/multipath.h | 14 ++++
lib/multipath.c | 154 ++++++++++++++++++++++++++++++++++++++
2 files changed, 168 insertions(+)
diff --git a/include/linux/multipath.h b/include/linux/multipath.h
index 57d13eb1450cd..6915c2a979642 100644
--- a/include/linux/multipath.h
+++ b/include/linux/multipath.h
@@ -20,10 +20,13 @@ enum mpath_access_state {
MPATH_STATE_OTHER
};
+#define MPATH_DEVICE_SYSFS_ATTR_LINK 0
+
struct mpath_device {
struct mpath_head *mpath_head;
struct list_head siblings;
struct gendisk *disk;
+ unsigned long flags;
int numa_node;
atomic_t *nr_active;
enum mpath_access_state access_state;
@@ -89,6 +92,17 @@ static inline enum mpath_iopolicy_e mpath_read_iopolicy(
void mpath_synchronize(struct mpath_head *mpath_head);
int mpath_set_iopolicy(const char *str, enum mpath_iopolicy_e *iopolicy);
int mpath_get_iopolicy(char *buf, int iopolicy);
+bool mpath_clear_current_path(struct mpath_device *mpath_device);
+void mpath_synchronize(struct mpath_head *mpath_head);
+int mpath_add_device(struct mpath_device *mpath_device,
+ struct mpath_head *mpath_head, struct gendisk *disk,
+ int numa_node, atomic_t *nr_active);
+bool mpath_delete_device(struct mpath_device *mpath_device);
+bool mpath_head_devices_empty(struct mpath_head *mpath_head);
+void mpath_clear_paths(struct mpath_head *mpath_head);
+void mpath_revalidate_paths(struct mpath_head *mpath_head);
+void mpath_add_sysfs_link(struct mpath_head *mpath_head);
+void mpath_remove_sysfs_link(struct mpath_device *mpath_device);
int mpath_get_head(struct mpath_head *mpath_head);
void mpath_put_head(struct mpath_head *mpath_head);
int mpath_head_init(struct mpath_head *mpath_head);
diff --git a/lib/multipath.c b/lib/multipath.c
index 448d879abb354..20c633ad7e1df 100644
--- a/lib/multipath.c
+++ b/lib/multipath.c
@@ -50,6 +50,87 @@ void mpath_synchronize(struct mpath_head *mpath_head)
}
EXPORT_SYMBOL_GPL(mpath_synchronize);
+int mpath_add_device(struct mpath_device *mpath_device,
+ struct mpath_head *mpath_head, struct gendisk *disk,
+ int numa_node, atomic_t *nr_active)
+{
+ if (!disk || !nr_active)
+ return -EINVAL;
+
+ mpath_device->mpath_head = mpath_head;
+ mpath_device->disk = disk;
+ mpath_device->numa_node = numa_node;
+ mpath_device->nr_active = nr_active;
+ mutex_lock(&mpath_head->lock);
+ list_add_tail_rcu(&mpath_device->siblings, &mpath_head->dev_list);
+ mutex_unlock(&mpath_head->lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mpath_add_device);
+
+bool mpath_delete_device(struct mpath_device *mpath_device)
+{
+ bool empty;
+
+ mutex_lock(&mpath_device->mpath_head->lock);
+ list_del_rcu(&mpath_device->siblings);
+ empty = list_empty(&mpath_device->mpath_head->dev_list);
+ mutex_unlock(&mpath_device->mpath_head->lock);
+
+ mpath_synchronize(mpath_device->mpath_head);
+
+ return empty;
+}
+EXPORT_SYMBOL_GPL(mpath_delete_device);
+
+bool mpath_head_devices_empty(struct mpath_head *mpath_head)
+{
+ bool empty;
+
+ mutex_lock(&mpath_head->lock);
+ empty = list_empty(&mpath_head->dev_list);
+ mutex_unlock(&mpath_head->lock);
+
+ return empty;
+}
+EXPORT_SYMBOL_GPL(mpath_head_devices_empty);
+
+bool mpath_clear_current_path(struct mpath_device *mpath_device)
+{
+ struct mpath_head *mpath_head = mpath_device->mpath_head;
+ bool changed = false;
+ int node;
+
+ for_each_node(node) {
+ if (mpath_device ==
+ rcu_access_pointer(mpath_head->current_path[node])) {
+ rcu_assign_pointer(mpath_head->current_path[node],
+ NULL);
+ changed = true;
+ }
+ }
+
+ return changed;
+}
+EXPORT_SYMBOL_GPL(mpath_clear_current_path);
+
+void mpath_clear_paths(struct mpath_head *mpath_head)
+{
+ int node;
+
+ for_each_node(node)
+ rcu_assign_pointer(mpath_head->current_path[node], NULL);
+}
+EXPORT_SYMBOL_GPL(mpath_clear_paths);
+
+void mpath_revalidate_paths(struct mpath_head *mpath_head)
+{
+ mpath_clear_paths(mpath_head);
+ mpath_schedule_requeue_work(mpath_head);
+}
+EXPORT_SYMBOL_GPL(mpath_revalidate_paths);
+
static bool mpath_path_is_disabled(struct mpath_head *mpath_head,
struct mpath_device *mpath_device)
{
@@ -468,6 +549,8 @@ void mpath_device_set_live(struct mpath_device *mpath_device)
queue_work(mpath_wq, &mpath_head->partition_scan_work);
}
+ mpath_add_sysfs_link(mpath_head);
+
mutex_lock(&mpath_head->lock);
if (mpath_path_is_optimized(mpath_head, mpath_device)) {
int node, srcu_idx;
@@ -484,6 +567,77 @@ void mpath_device_set_live(struct mpath_device *mpath_device)
}
EXPORT_SYMBOL_GPL(mpath_device_set_live);
+void mpath_add_sysfs_link(struct mpath_head *mpath_head)
+{
+ struct device *target;
+ struct device *source;
+ int rc, srcu_idx;
+ struct kobject *mpath_gd_kobj;
+ struct mpath_device *mpath_device;
+
+ /*
+ * Ensure head disk node is already added otherwise we may get invalid
+ * kobj for head disk node
+ */
+ if (!test_bit(GD_ADDED, &mpath_head->disk->state))
+ return;
+
+ mpath_gd_kobj = &disk_to_dev(mpath_head->disk)->kobj;
+ srcu_idx = srcu_read_lock(&mpath_head->srcu);
+
+ list_for_each_entry_srcu(mpath_device, &mpath_head->dev_list, siblings,
+ srcu_read_lock_held(&mpath_head->srcu)) {
+ if (!test_bit(GD_ADDED, &mpath_device->disk->state))
+ continue;
+
+ if (test_and_set_bit(MPATH_DEVICE_SYSFS_ATTR_LINK,
+ &mpath_device->flags))
+ continue;
+
+ target = disk_to_dev(mpath_device->disk);
+ source = disk_to_dev(mpath_head->disk);
+ /*
+ * Create sysfs link from head gendisk kobject @kobj to the
+ * ns path gendisk kobject @target->kobj.
+ */
+ rc = sysfs_add_link_to_group(mpath_gd_kobj, "multipath",
+ &target->kobj, dev_name(target));
+
+ if (unlikely(rc)) {
+ dev_err(disk_to_dev(mpath_head->disk),
+ "failed to create link to %s rc=%d\n",
+ dev_name(target), rc);
+ clear_bit(MPATH_DEVICE_SYSFS_ATTR_LINK,
+ &mpath_device->flags);
+ } else {
+ dev_info(source, "Created multipath sysfs link to %s\n",
+ mpath_device->disk->disk_name);
+ }
+ }
+
+ srcu_read_unlock(&mpath_head->srcu, srcu_idx);
+}
+EXPORT_SYMBOL_GPL(mpath_add_sysfs_link);
+
+void mpath_remove_sysfs_link(struct mpath_device *mpath_device)
+{
+ struct device *target;
+ struct kobject *mpath_gd_kobj;
+ struct mpath_head *mpath_head = mpath_device->mpath_head;
+
+ if (!test_bit(MPATH_DEVICE_SYSFS_ATTR_LINK, &mpath_device->flags))
+ return;
+
+ target = disk_to_dev(mpath_device->disk);
+ mpath_gd_kobj = &disk_to_dev(mpath_head->disk)->kobj;
+
+ sysfs_remove_link_from_group(mpath_gd_kobj, "multipath",
+ dev_name(target));
+
+ clear_bit(MPATH_DEVICE_SYSFS_ATTR_LINK, &mpath_device->flags);
+}
+EXPORT_SYMBOL_GPL(mpath_remove_sysfs_link);
+
int mpath_head_init(struct mpath_head *mpath_head)
{
memset(mpath_head, 0, sizeof(*mpath_head));
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 06/28] libmultipath: Add delayed removal support
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
` (4 preceding siblings ...)
2026-07-23 9:36 ` [PATCH v4 05/28] libmultipath: Add support for mpath_device management John Garry
@ 2026-07-23 9:36 ` John Garry
2026-07-23 9:57 ` sashiko-bot
2026-07-23 9:36 ` [PATCH v4 07/28] libmultipath: Add sysfs helpers John Garry
` (21 subsequent siblings)
27 siblings, 1 reply; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
Add support for delayed removal, same as exists for NVMe.
The purpose of this feature is to keep the multipath disk and cdev present
for intermittent periods of no available path.
Helpers mpath_delayed_removal_secs_show() and
mpath_delayed_removal_secs_store() may be used in the driver sysfs code.
The driver is responsible for supplying the removal work callback for
the delayed work.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
include/linux/multipath.h | 18 ++++++++
lib/multipath.c | 92 ++++++++++++++++++++++++++++++++++++++-
2 files changed, 109 insertions(+), 1 deletion(-)
diff --git a/include/linux/multipath.h b/include/linux/multipath.h
index 6915c2a979642..44e26c16b3dc4 100644
--- a/include/linux/multipath.h
+++ b/include/linux/multipath.h
@@ -34,6 +34,7 @@ struct mpath_device {
struct mpath_head_template {
bool (*available_path)(struct mpath_device *);
+ void (*remove_head)(struct mpath_head *);
bool (*is_disabled)(struct mpath_device *);
bool (*is_optimized)(struct mpath_device *);
struct bio *(*clone_bio)(struct bio *);
@@ -41,6 +42,7 @@ struct mpath_head_template {
};
#define MPATH_HEAD_DISK_LIVE 0
+#define MPATH_HEAD_QUEUE_IF_NO_PATH 1
struct mpath_head {
struct srcu_struct srcu;
@@ -58,6 +60,10 @@ struct mpath_head {
atomic_long_t requeue_no_usable_path_cnt;
atomic_long_t fail_no_avail_path_cnt;
+ struct delayed_work remove_work;
+ unsigned long delayed_removal_secs;
+ struct module *drv_module;
+
unsigned long flags;
struct gendisk *disk;
struct work_struct partition_scan_work;
@@ -113,6 +119,11 @@ void mpath_remove_disk(struct mpath_head *mpath_head);
int mpath_alloc_head_disk(struct mpath_head *mpath_head,
struct queue_limits *lim, int numa_node);
void mpath_device_set_live(struct mpath_device *mpath_device);
+bool mpath_can_remove_head(struct mpath_head *mpath_head);
+ssize_t mpath_delayed_removal_secs_show(struct mpath_head *mpath_head,
+ char *buf);
+ssize_t mpath_delayed_removal_secs_store(struct mpath_head *mpath_head,
+ const char *buf, size_t count);
static inline bool is_mpath_disk(struct gendisk *disk)
{
@@ -128,6 +139,13 @@ static inline bool mpath_qd_iopolicy(enum mpath_iopolicy_e *iopolicy)
return READ_ONCE(*iopolicy) == MPATH_IOPOLICY_QD;
}
+static inline bool mpath_head_queue_if_no_path(struct mpath_head *mpath_head)
+{
+ if (test_bit(MPATH_HEAD_QUEUE_IF_NO_PATH, &mpath_head->flags))
+ return true;
+ return false;
+}
+
static inline void mpath_schedule_requeue_work(struct mpath_head *mpath_head)
{
kblockd_schedule_work(&mpath_head->requeue_work);
diff --git a/lib/multipath.c b/lib/multipath.c
index 20c633ad7e1df..1fa4bd6677051 100644
--- a/lib/multipath.c
+++ b/lib/multipath.c
@@ -65,6 +65,9 @@ int mpath_add_device(struct mpath_device *mpath_device,
list_add_tail_rcu(&mpath_device->siblings, &mpath_head->dev_list);
mutex_unlock(&mpath_head->lock);
+ if (cancel_delayed_work(&mpath_head->remove_work))
+ module_put(mpath_head->drv_module);
+
return 0;
}
EXPORT_SYMBOL_GPL(mpath_add_device);
@@ -338,7 +341,17 @@ static bool mpath_available_path(struct mpath_head *mpath_head)
return true;
}
- return false;
+ /*
+ * If "mpath_head->delayed_removal_secs" is set (i.e., non-zero), do
+ * not immediately fail I/O. Instead, requeue the I/O for the configured
+ * duration, anticipating that if there's a transient link failure then
+ * it may recover within this time window. This parameter is exported to
+ * userspace via sysfs, and its default value is zero. It is internally
+ * mapped to MPATH_HEAD_QUEUE_IF_NO_PATH. When delayed_removal_secs is
+ * non-zero, this flag is set to true. When zero, the flag is cleared.
+ */
+ return mpath_head_queue_if_no_path(mpath_head);
+
}
static void mpath_bdev_submit_bio(struct bio *bio)
@@ -480,6 +493,39 @@ static void mpath_requeue_work(struct work_struct *work)
}
}
+bool mpath_can_remove_head(struct mpath_head *mpath_head)
+{
+ bool remove = false;
+
+ mutex_lock(&mpath_head->lock);
+ /*
+ * Ensure that no one could remove this module while the head
+ * remove work is pending.
+ */
+ if (mpath_head_queue_if_no_path(mpath_head) &&
+ try_module_get(mpath_head->drv_module)) {
+
+ mod_delayed_work(mpath_wq, &mpath_head->remove_work,
+ mpath_head->delayed_removal_secs * HZ);
+ } else {
+ remove = true;
+ }
+
+ mutex_unlock(&mpath_head->lock);
+ return remove;
+}
+EXPORT_SYMBOL_GPL(mpath_can_remove_head);
+
+static void mpath_remove_head_work(struct work_struct *work)
+{
+ struct mpath_head *mpath_head = container_of(to_delayed_work(work),
+ struct mpath_head, remove_work);
+ struct module *drv_module = mpath_head->drv_module;
+
+ mpath_head->mpdt->remove_head(mpath_head);
+ module_put(drv_module);
+}
+
void mpath_remove_disk(struct mpath_head *mpath_head)
{
if (test_and_clear_bit(MPATH_HEAD_DISK_LIVE, &mpath_head->flags)) {
@@ -524,6 +570,9 @@ int mpath_alloc_head_disk(struct mpath_head *mpath_head,
mpath_head->disk->private_data = mpath_head;
mpath_head->disk->fops = &mpath_ops;
+ INIT_DELAYED_WORK(&mpath_head->remove_work, mpath_remove_head_work);
+ mpath_head->delayed_removal_secs = 0;
+
set_bit(GD_SUPPRESS_PART_SCAN, &mpath_head->disk->state);
return 0;
@@ -567,6 +616,47 @@ void mpath_device_set_live(struct mpath_device *mpath_device)
}
EXPORT_SYMBOL_GPL(mpath_device_set_live);
+ssize_t mpath_delayed_removal_secs_show(struct mpath_head *mpath_head,
+ char *buf)
+{
+ int ret;
+
+ mutex_lock(&mpath_head->lock);
+ ret = sysfs_emit(buf, "%lu\n", mpath_head->delayed_removal_secs);
+ mutex_unlock(&mpath_head->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(mpath_delayed_removal_secs_show);
+
+ssize_t mpath_delayed_removal_secs_store(struct mpath_head *mpath_head,
+ const char *buf, size_t count)
+{
+ ssize_t ret;
+ int sec;
+
+ ret = kstrtouint(buf, 0, &sec);
+ if (ret < 0)
+ return ret;
+
+ mutex_lock(&mpath_head->lock);
+ mpath_head->delayed_removal_secs = sec;
+ if (sec)
+ set_bit(MPATH_HEAD_QUEUE_IF_NO_PATH, &mpath_head->flags);
+ else
+ clear_bit(MPATH_HEAD_QUEUE_IF_NO_PATH, &mpath_head->flags);
+ mutex_unlock(&mpath_head->lock);
+
+ /*
+ * Ensure that update to MPATH_HEAD_QUEUE_IF_NO_PATH is seen
+ * by its reader.
+ */
+ mpath_synchronize(mpath_head);
+
+ return count;
+}
+EXPORT_SYMBOL_GPL(mpath_delayed_removal_secs_store);
+
void mpath_add_sysfs_link(struct mpath_head *mpath_head)
{
struct device *target;
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 07/28] libmultipath: Add sysfs helpers
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
` (5 preceding siblings ...)
2026-07-23 9:36 ` [PATCH v4 06/28] libmultipath: Add delayed removal support John Garry
@ 2026-07-23 9:36 ` John Garry
2026-07-23 10:05 ` sashiko-bot
2026-07-23 9:36 ` [PATCH v4 08/28] libmultipath: Add mpath_bdev_report_zones() John Garry
` (20 subsequent siblings)
27 siblings, 1 reply; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
Add helpers for driver sysfs code for the following functionality:
- get/set iopolicy with mpath_iopolicy_store() and mpath_iopolicy_show()
- show device path per NUMA node
- "multipath" attribute group, equivalent to nvme_ns_mpath_attr_group
- device groups attribute array, similar to nvme_ns_attr_groups but not
containing NVMe members.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
include/linux/multipath.h | 5 +++
lib/multipath.c | 94 +++++++++++++++++++++++++++++++++++++++
2 files changed, 99 insertions(+)
diff --git a/include/linux/multipath.h b/include/linux/multipath.h
index 44e26c16b3dc4..4dff0911c2002 100644
--- a/include/linux/multipath.h
+++ b/include/linux/multipath.h
@@ -7,6 +7,8 @@
#include <linux/srcu.h>
extern const struct block_device_operations mpath_ops;
+extern const struct attribute_group mpath_attr_group;
+extern const struct attribute_group *mpath_device_groups[];
enum mpath_iopolicy_e {
MPATH_IOPOLICY_NUMA,
@@ -120,6 +122,9 @@ int mpath_alloc_head_disk(struct mpath_head *mpath_head,
struct queue_limits *lim, int numa_node);
void mpath_device_set_live(struct mpath_device *mpath_device);
bool mpath_can_remove_head(struct mpath_head *mpath_head);
+ssize_t mpath_numa_nodes_show(struct mpath_device *mpath_device, char *buf);
+ssize_t mpath_iopolicy_show(enum mpath_iopolicy_e *iopolicy, char *buf);
+bool mpath_iopolicy_store(enum mpath_iopolicy_e *iopolicy, const char *buf);
ssize_t mpath_delayed_removal_secs_show(struct mpath_head *mpath_head,
char *buf);
ssize_t mpath_delayed_removal_secs_store(struct mpath_head *mpath_head,
diff --git a/lib/multipath.c b/lib/multipath.c
index 1fa4bd6677051..3ff4f42a096c2 100644
--- a/lib/multipath.c
+++ b/lib/multipath.c
@@ -616,6 +616,100 @@ void mpath_device_set_live(struct mpath_device *mpath_device)
}
EXPORT_SYMBOL_GPL(mpath_device_set_live);
+static struct attribute dummy_attr = {
+ .name = "dummy",
+};
+
+static struct attribute *mpath_attrs[] = {
+ &dummy_attr,
+ NULL
+};
+
+static bool multipath_sysfs_group_visible(struct kobject *kobj)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct gendisk *disk = dev_to_disk(dev);
+
+ return is_mpath_disk(disk);
+}
+DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(multipath_sysfs)
+
+const struct attribute_group mpath_attr_group = {
+ .name = "multipath",
+ .attrs = mpath_attrs,
+ .is_visible = SYSFS_GROUP_VISIBLE(multipath_sysfs),
+};
+EXPORT_SYMBOL_GPL(mpath_attr_group);
+
+const struct attribute_group *mpath_device_groups[] = {
+ &mpath_attr_group,
+ NULL
+};
+EXPORT_SYMBOL_GPL(mpath_device_groups);
+
+ssize_t mpath_iopolicy_show(enum mpath_iopolicy_e *iopolicy, char *buf)
+{
+ return sysfs_emit(buf, "%s\n",
+ mpath_iopolicy_names[READ_ONCE(*iopolicy)]);
+}
+EXPORT_SYMBOL_GPL(mpath_iopolicy_show);
+
+static void mpath_iopolicy_update(enum mpath_iopolicy_e *iopolicy,
+ int new)
+{
+ int old = READ_ONCE(*iopolicy);
+
+ if (old == new)
+ return;
+
+ WRITE_ONCE(*iopolicy, new);
+
+ pr_info("iopolicy changed from %s to %s\n",
+ mpath_iopolicy_names[old],
+ mpath_iopolicy_names[new]);
+}
+
+bool mpath_iopolicy_store(enum mpath_iopolicy_e *iopolicy, const char *buf)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mpath_iopolicy_names); i++) {
+ if (sysfs_streq(buf, mpath_iopolicy_names[i])) {
+ mpath_iopolicy_update(iopolicy, i);
+ return true;
+ }
+ }
+
+ return false;
+}
+EXPORT_SYMBOL_GPL(mpath_iopolicy_store);
+
+ssize_t mpath_numa_nodes_show(struct mpath_device *mpath_device, char *buf)
+{
+ struct mpath_head *mpath_head = mpath_device->mpath_head;
+ int node, srcu_idx;
+ nodemask_t numa_nodes;
+ struct mpath_device *current_mpath_dev;
+
+ if (mpath_read_iopolicy(mpath_head) != MPATH_IOPOLICY_NUMA)
+ return 0;
+
+ nodes_clear(numa_nodes);
+
+ srcu_idx = srcu_read_lock(&mpath_head->srcu);
+ for_each_node(node) {
+ current_mpath_dev =
+ srcu_dereference(mpath_head->current_path[node],
+ &mpath_head->srcu);
+ if (current_mpath_dev == mpath_device)
+ node_set(node, numa_nodes);
+ }
+ srcu_read_unlock(&mpath_head->srcu, srcu_idx);
+
+ return sysfs_emit(buf, "%*pbl\n", nodemask_pr_args(&numa_nodes));
+}
+EXPORT_SYMBOL_GPL(mpath_numa_nodes_show);
+
ssize_t mpath_delayed_removal_secs_show(struct mpath_head *mpath_head,
char *buf)
{
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 08/28] libmultipath: Add mpath_bdev_report_zones()
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
` (6 preceding siblings ...)
2026-07-23 9:36 ` [PATCH v4 07/28] libmultipath: Add sysfs helpers John Garry
@ 2026-07-23 9:36 ` John Garry
2026-07-23 10:15 ` sashiko-bot
2026-07-23 9:36 ` [PATCH v4 09/28] libmultipath: Add support for block device IOCTL John Garry
` (19 subsequent siblings)
27 siblings, 1 reply; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
Add a multipath handler for block_device_operations.report_zones
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
lib/multipath.c | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/lib/multipath.c b/lib/multipath.c
index 3ff4f42a096c2..0232f51f64742 100644
--- a/lib/multipath.c
+++ b/lib/multipath.c
@@ -44,6 +44,30 @@ int mpath_get_iopolicy(char *buf, int iopolicy)
}
EXPORT_SYMBOL_GPL(mpath_get_iopolicy);
+#ifdef CONFIG_BLK_DEV_ZONED
+static int mpath_bdev_report_zones(struct gendisk *disk, sector_t sector,
+ unsigned int nr_zones, struct blk_report_zones_args *args)
+{
+ struct mpath_head *mpath_head = mpath_gendisk_to_head(disk);
+ struct mpath_device *mpath_device;
+ int srcu_idx, ret = -EWOULDBLOCK;
+
+ srcu_idx = srcu_read_lock(&mpath_head->srcu);
+ mpath_device = mpath_find_path(mpath_head);
+ if (mpath_device) {
+ if (mpath_device->disk->fops->report_zones)
+ ret = mpath_device->disk->fops->report_zones
+ (mpath_device->disk, sector, nr_zones, args);
+ else
+ ret = -EOPNOTSUPP;
+ }
+ srcu_read_unlock(&mpath_head->srcu, srcu_idx);
+ return ret;
+}
+#else
+#define mpath_bdev_report_zones NULL
+#endif /* CONFIG_BLK_DEV_ZONED */
+
void mpath_synchronize(struct mpath_head *mpath_head)
{
synchronize_srcu(&mpath_head->srcu);
@@ -459,6 +483,7 @@ const struct block_device_operations mpath_ops = {
.open = mpath_bdev_open,
.release = mpath_bdev_release,
.submit_bio = mpath_bdev_submit_bio,
+ .report_zones = mpath_bdev_report_zones,
};
EXPORT_SYMBOL_GPL(mpath_ops);
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 09/28] libmultipath: Add support for block device IOCTL
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
` (7 preceding siblings ...)
2026-07-23 9:36 ` [PATCH v4 08/28] libmultipath: Add mpath_bdev_report_zones() John Garry
@ 2026-07-23 9:36 ` John Garry
2026-07-23 10:09 ` sashiko-bot
2026-07-23 9:36 ` [PATCH v4 10/28] libmultipath: Add mpath_bdev_getgeo() John Garry
` (18 subsequent siblings)
27 siblings, 1 reply; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
Add mpath_bdev_ioctl() as a multipath block device IOCTL handler. This
handler calls into the mpath_device bdev fops handler.
The .compat_ioctl handler is given the standard handler.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
lib/multipath.c | 33 +++++++++++++++++++++++++++++++++
1 file changed, 33 insertions(+)
diff --git a/lib/multipath.c b/lib/multipath.c
index 0232f51f64742..fc8ba8fc501f1 100644
--- a/lib/multipath.c
+++ b/lib/multipath.c
@@ -478,11 +478,44 @@ static void mpath_bdev_release(struct gendisk *disk)
mpath_put_head(mpath_head);
}
+static int mpath_bdev_ioctl(struct block_device *bdev, blk_mode_t mode,
+ unsigned int cmd, unsigned long arg)
+{
+ struct gendisk *disk = bdev->bd_disk;
+ struct mpath_head *mpath_head = mpath_gendisk_to_head(disk);
+ struct mpath_device *mpath_device;
+ int srcu_idx, err;
+
+ srcu_idx = srcu_read_lock(&mpath_head->srcu);
+ mpath_device = mpath_find_path(mpath_head);
+ if (!mpath_device) {
+ err = -EWOULDBLOCK;
+ goto out_unlock;
+ }
+
+ if (!mpath_device->disk->fops->ioctl) {
+ err = -EOPNOTSUPP;
+ goto out_unlock;
+ }
+
+ err = mpath_device->disk->fops->ioctl(
+ mpath_device->disk->part0, mode, cmd, arg);
+out_unlock:
+ srcu_read_unlock(&mpath_head->srcu, srcu_idx);
+ return err;
+}
+
const struct block_device_operations mpath_ops = {
.owner = THIS_MODULE,
.open = mpath_bdev_open,
.release = mpath_bdev_release,
.submit_bio = mpath_bdev_submit_bio,
+ .ioctl = mpath_bdev_ioctl,
+ /*
+ * Both NVMe and SCSI use generic blkdev_compat_ptr_ioctl, so would
+ * avoid their custom compat_ioctl implementation.
+ */
+ .compat_ioctl = blkdev_compat_ptr_ioctl,
.report_zones = mpath_bdev_report_zones,
};
EXPORT_SYMBOL_GPL(mpath_ops);
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 10/28] libmultipath: Add mpath_bdev_getgeo()
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
` (8 preceding siblings ...)
2026-07-23 9:36 ` [PATCH v4 09/28] libmultipath: Add support for block device IOCTL John Garry
@ 2026-07-23 9:36 ` John Garry
2026-07-23 9:36 ` [PATCH v4 11/28] libmultipath: Add mpath_bdev_get_unique_id() John Garry
` (17 subsequent siblings)
27 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
Add mpath_bdev_getgeo() as a multipath block device .getgeo handler.
Here we just redirect into the selected mpath_device disk fops->getgeo
handler.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
lib/multipath.c | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/lib/multipath.c b/lib/multipath.c
index fc8ba8fc501f1..b90d4827963ac 100644
--- a/lib/multipath.c
+++ b/lib/multipath.c
@@ -505,6 +505,26 @@ static int mpath_bdev_ioctl(struct block_device *bdev, blk_mode_t mode,
return err;
}
+static int mpath_bdev_getgeo(struct gendisk *disk, struct hd_geometry *geo)
+{
+ struct mpath_head *mpath_head = mpath_gendisk_to_head(disk);
+ int srcu_idx, ret = -EWOULDBLOCK;
+ struct mpath_device *mpath_device;
+
+ srcu_idx = srcu_read_lock(&mpath_head->srcu);
+ mpath_device = mpath_find_path(mpath_head);
+ if (mpath_device) {
+ if (mpath_device->disk->fops->getgeo)
+ ret = mpath_device->disk->fops->getgeo(
+ mpath_device->disk, geo);
+ else
+ ret = -ENOTTY; /* See blkdev_getgeo */
+ }
+ srcu_read_unlock(&mpath_head->srcu, srcu_idx);
+
+ return ret;
+}
+
const struct block_device_operations mpath_ops = {
.owner = THIS_MODULE,
.open = mpath_bdev_open,
@@ -517,6 +537,7 @@ const struct block_device_operations mpath_ops = {
*/
.compat_ioctl = blkdev_compat_ptr_ioctl,
.report_zones = mpath_bdev_report_zones,
+ .getgeo = mpath_bdev_getgeo,
};
EXPORT_SYMBOL_GPL(mpath_ops);
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 11/28] libmultipath: Add mpath_bdev_get_unique_id()
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
` (9 preceding siblings ...)
2026-07-23 9:36 ` [PATCH v4 10/28] libmultipath: Add mpath_bdev_getgeo() John Garry
@ 2026-07-23 9:36 ` John Garry
2026-07-23 9:36 ` [PATCH v4 12/28] scsi-multipath: introduce basic SCSI device support John Garry
` (16 subsequent siblings)
27 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
Add mpath_bdev_get_unique_id() as a multipath block device .get_unique_id
handler.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
lib/multipath.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/lib/multipath.c b/lib/multipath.c
index b90d4827963ac..858801d052ff1 100644
--- a/lib/multipath.c
+++ b/lib/multipath.c
@@ -478,6 +478,27 @@ static void mpath_bdev_release(struct gendisk *disk)
mpath_put_head(mpath_head);
}
+static int mpath_bdev_get_unique_id(struct gendisk *disk, u8 id[16],
+ enum blk_unique_id type)
+{
+ struct mpath_head *mpath_head = mpath_gendisk_to_head(disk);
+ int srcu_idx, ret = -EWOULDBLOCK;
+ struct mpath_device *mpath_device;
+
+ srcu_idx = srcu_read_lock(&mpath_head->srcu);
+ mpath_device = mpath_find_path(mpath_head);
+ if (mpath_device) {
+ if (mpath_device->disk->fops->get_unique_id)
+ ret = mpath_device->disk->fops->get_unique_id(
+ mpath_device->disk, id, type);
+ else
+ ret = 0; /* referencing __dm_get_unique_id() */
+ }
+ srcu_read_unlock(&mpath_head->srcu, srcu_idx);
+
+ return ret;
+}
+
static int mpath_bdev_ioctl(struct block_device *bdev, blk_mode_t mode,
unsigned int cmd, unsigned long arg)
{
@@ -536,6 +557,7 @@ const struct block_device_operations mpath_ops = {
* avoid their custom compat_ioctl implementation.
*/
.compat_ioctl = blkdev_compat_ptr_ioctl,
+ .get_unique_id = mpath_bdev_get_unique_id,
.report_zones = mpath_bdev_report_zones,
.getgeo = mpath_bdev_getgeo,
};
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 12/28] scsi-multipath: introduce basic SCSI device support
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
` (10 preceding siblings ...)
2026-07-23 9:36 ` [PATCH v4 11/28] libmultipath: Add mpath_bdev_get_unique_id() John Garry
@ 2026-07-23 9:36 ` John Garry
2026-07-23 10:14 ` sashiko-bot
2026-07-23 9:36 ` [PATCH v4 13/28] scsi-multipath: introduce scsi_device head structure John Garry
` (15 subsequent siblings)
27 siblings, 1 reply; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
For a scsi_device to support multipath, introduce structure
scsi_mpath_device to hold multipath-specific details.
Like NS structure for NVME, scsi_mpath_device holds the mpath_device
structure to device management and path selection.
A module param are introduced to enable multipath - the following modes
are available:
- on
- off
- always
SCSI multipath will only be available until the following conditions:
- scsi_multipath enabled and implicit ALUA supported and unique ID
available in VPD page 83.
- scsi_multipath always mode and unique ID available in VPD page 83
The scsi_device structure contains a pointer to scsi_mpath_device; having
this pointer set or unset indicates whether multipath is enabled or
disabled for the scsi_device.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/Kconfig | 10 +++
drivers/scsi/Makefile | 1 +
drivers/scsi/scsi.c | 8 +-
drivers/scsi/scsi_multipath.c | 146 ++++++++++++++++++++++++++++++++++
drivers/scsi/scsi_scan.c | 4 +
drivers/scsi/scsi_sysfs.c | 2 +
include/scsi/scsi_device.h | 2 +
include/scsi/scsi_multipath.h | 55 +++++++++++++
8 files changed, 227 insertions(+), 1 deletion(-)
create mode 100644 drivers/scsi/scsi_multipath.c
create mode 100644 include/scsi/scsi_multipath.h
diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig
index c3042393af234..d6c31df454825 100644
--- a/drivers/scsi/Kconfig
+++ b/drivers/scsi/Kconfig
@@ -76,6 +76,16 @@ config SCSI_LIB_KUNIT_TEST
If unsure say N.
+config SCSI_MULTIPATH
+ bool "SCSI multipath support (EXPERIMENTAL)"
+ depends on SCSI_MOD
+ select LIBMULTIPATH
+ help
+ This option enables support for native SCSI multipath support for
+ SCSI host.
+
+ If unsure say N.
+
comment "SCSI support type (disk, tape, CD-ROM)"
depends on SCSI
diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile
index 842c254bb2269..04f9c2e51fcbd 100644
--- a/drivers/scsi/Makefile
+++ b/drivers/scsi/Makefile
@@ -168,6 +168,7 @@ scsi_mod-y += scsi_trace.o scsi_logging.o
scsi_mod-$(CONFIG_PM) += scsi_pm.o
scsi_mod-$(CONFIG_SCSI_DH) += scsi_dh.o
scsi_mod-$(CONFIG_BLK_DEV_BSG) += scsi_bsg.o
+scsi_mod-$(CONFIG_SCSI_MULTIPATH) += scsi_multipath.o
hv_storvsc-y := storvsc_drv.o
diff --git a/drivers/scsi/scsi.c b/drivers/scsi/scsi.c
index 76cdad063f7bc..70aa1dbeacebf 100644
--- a/drivers/scsi/scsi.c
+++ b/drivers/scsi/scsi.c
@@ -64,6 +64,7 @@
#include <scsi/scsi_driver.h>
#include <scsi/scsi_eh.h>
#include <scsi/scsi_host.h>
+#include <scsi/scsi_multipath.h>
#include <scsi/scsi_tcq.h>
#include "scsi_priv.h"
@@ -1042,12 +1043,16 @@ static int __init init_scsi(void)
error = scsi_sysfs_register();
if (error)
goto cleanup_sysctl;
+ error = scsi_multipath_init();
+ if (error)
+ goto cleanup_sysfs;
scsi_netlink_init();
printk(KERN_NOTICE "SCSI subsystem initialized\n");
return 0;
-
+cleanup_sysfs:
+ scsi_sysfs_unregister();
cleanup_sysctl:
scsi_exit_sysctl();
cleanup_hosts:
@@ -1066,6 +1071,7 @@ static int __init init_scsi(void)
static void __exit exit_scsi(void)
{
scsi_netlink_exit();
+ scsi_multipath_exit();
scsi_sysfs_unregister();
scsi_exit_sysctl();
scsi_exit_hosts();
diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
new file mode 100644
index 0000000000000..b9b58a3641aa2
--- /dev/null
+++ b/drivers/scsi/scsi_multipath.c
@@ -0,0 +1,146 @@
+// SPDX-License-Indentifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Oracle Corp
+ *
+ */
+
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_driver.h>
+#include <scsi/scsi_proto.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_multipath.h>
+
+#include "scsi_priv.h"
+
+#define TPGS_MODE_IMPLICIT 0x1
+
+enum {
+ SCSI_MULTIPATH_OFF,
+ SCSI_MULTIPATH_ON,
+ SCSI_MULTIPATH_ALWAYS,
+};
+
+static const char *scsi_multipath_modes[] = {
+ [SCSI_MULTIPATH_OFF] = "off",
+ [SCSI_MULTIPATH_ON] = "on",
+ [SCSI_MULTIPATH_ALWAYS] = "always",
+};
+
+static int scsi_multipath = SCSI_MULTIPATH_OFF;
+
+static int scsi_multipath_param_set(const char *val, const struct kernel_param *kp)
+{
+ int mode;
+
+ if (!val)
+ return -EINVAL;
+ mode = __sysfs_match_string(scsi_multipath_modes,
+ ARRAY_SIZE(scsi_multipath_modes), val);
+
+ if (mode < 0)
+ return mode;
+ scsi_multipath = mode;
+ return 0;
+}
+
+static int scsi_multipath_param_get(char *buf, const struct kernel_param *kp)
+{
+ return sprintf(buf, "%s\n", scsi_multipath_modes[scsi_multipath]);
+}
+
+static const struct kernel_param_ops multipath_param_ops = {
+ .set = scsi_multipath_param_set,
+ .get = scsi_multipath_param_get,
+};
+
+module_param_cb(multipath, &multipath_param_ops, &scsi_multipath, 0444);
+MODULE_PARM_DESC(multipath, "turn on native multipath support, options: on, off, always");
+
+static int scsi_mpath_unique_lun_id(struct scsi_device *sdev)
+{
+ struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
+ int ret;
+
+ ret = scsi_vpd_lun_id(sdev, scsi_mpath_dev->device_id_str,
+ SCSI_MPATH_DEVICE_ID_LEN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int scsi_multipath_sdev_init(struct scsi_device *sdev)
+{
+ struct Scsi_Host *shost = sdev->host;
+ struct scsi_mpath_device *scsi_mpath_dev;
+ struct mpath_device *mpath_device;
+
+ scsi_mpath_dev = kzalloc(sizeof(*scsi_mpath_dev), GFP_KERNEL);
+ if (!scsi_mpath_dev)
+ return -ENOMEM;
+ scsi_mpath_dev->sdev = sdev;
+ sdev->scsi_mpath_dev = scsi_mpath_dev;
+
+ mpath_device = &scsi_mpath_dev->mpath_device;
+ mpath_device->numa_node = dev_to_node(shost->dma_dev);
+ mpath_device->access_state = MPATH_STATE_OPTIMIZED;
+
+ return 0;
+}
+
+static void scsi_multipath_sdev_uninit(struct scsi_device *sdev)
+{
+ kfree(sdev->scsi_mpath_dev);
+ sdev->scsi_mpath_dev = NULL;
+}
+
+int scsi_mpath_dev_alloc(struct scsi_device *sdev)
+{
+ int ret;
+
+ if (scsi_multipath == SCSI_MULTIPATH_OFF)
+ return 0;
+
+ if (!(scsi_device_tpgs(sdev) & TPGS_MODE_IMPLICIT) &&
+ (scsi_multipath != SCSI_MULTIPATH_ALWAYS)) {
+ sdev_printk(KERN_DEBUG, sdev, "IMPLICIT TPGS are required for multipath support\n");
+ return 0;
+ }
+
+ ret = scsi_multipath_sdev_init(sdev);
+ if (ret)
+ return ret;
+
+ ret = scsi_mpath_unique_lun_id(sdev);
+ if (ret < 0)
+ goto out_uninit;
+
+ return 0;
+
+out_uninit:
+ scsi_multipath_sdev_uninit(sdev);
+ return 0;
+}
+
+void scsi_mpath_dev_release(struct scsi_device *sdev)
+{
+ struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
+
+ if (!scsi_mpath_dev)
+ return;
+
+ scsi_multipath_sdev_uninit(sdev);
+}
+
+int __init scsi_multipath_init(void)
+{
+ return 0;
+}
+
+void __exit scsi_multipath_exit(void)
+{
+}
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("scsi_multipath");
diff --git a/drivers/scsi/scsi_scan.c b/drivers/scsi/scsi_scan.c
index e27da038603a2..3f1ac302719c2 100644
--- a/drivers/scsi/scsi_scan.c
+++ b/drivers/scsi/scsi_scan.c
@@ -46,6 +46,7 @@
#include <scsi/scsi_transport.h>
#include <scsi/scsi_dh.h>
#include <scsi/scsi_eh.h>
+#include <scsi/scsi_multipath.h>
#include "scsi_priv.h"
#include "scsi_logging.h"
@@ -1130,6 +1131,9 @@ static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result,
sdev->max_queue_depth = sdev->queue_depth;
WARN_ON_ONCE(sdev->max_queue_depth > sdev->budget_map.depth);
+ if (scsi_mpath_dev_alloc(sdev))
+ return SCSI_SCAN_NO_RESPONSE;
+
/*
* Ok, the device is now all set up, we can
* register it and tell the rest of the kernel
diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
index dfc3559e7e04f..2f80d703ce640 100644
--- a/drivers/scsi/scsi_sysfs.c
+++ b/drivers/scsi/scsi_sysfs.c
@@ -23,6 +23,7 @@
#include <scsi/scsi_transport.h>
#include <scsi/scsi_driver.h>
#include <scsi/scsi_devinfo.h>
+#include <scsi/scsi_multipath.h>
#include "scsi_priv.h"
#include "scsi_logging.h"
@@ -455,6 +456,7 @@ static void scsi_device_dev_release(struct device *dev)
might_sleep();
scsi_dh_release_device(sdev);
+ scsi_mpath_dev_release(sdev);
parent = sdev->sdev_gendev.parent;
diff --git a/include/scsi/scsi_device.h b/include/scsi/scsi_device.h
index 029f5115b2ea0..40a435e66b34e 100644
--- a/include/scsi/scsi_device.h
+++ b/include/scsi/scsi_device.h
@@ -280,6 +280,8 @@ struct scsi_device {
struct device sdev_gendev,
sdev_dev;
+ struct scsi_mpath_device *scsi_mpath_dev;
+
struct work_struct requeue_work;
struct scsi_device_handler *handler;
diff --git a/include/scsi/scsi_multipath.h b/include/scsi/scsi_multipath.h
new file mode 100644
index 0000000000000..d3d410dafd17a
--- /dev/null
+++ b/include/scsi/scsi_multipath.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _SCSI_SCSI_MULTIPATH_H
+#define _SCSI_SCSI_MULTIPATH_H
+
+#include <linux/list.h>
+#include <linux/types.h>
+#include <linux/rcupdate.h>
+#include <linux/workqueue.h>
+#include <linux/mutex.h>
+#include <linux/blk-mq.h>
+#include <linux/multipath.h>
+#include <scsi/scsi.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_dbg.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_devinfo.h>
+#include <scsi/scsi_driver.h>
+
+#ifdef CONFIG_SCSI_MULTIPATH
+#define SCSI_MPATH_DEVICE_ID_LEN 256
+
+struct scsi_mpath_device {
+ struct mpath_device mpath_device;
+ struct scsi_device *sdev;
+
+ char device_id_str[SCSI_MPATH_DEVICE_ID_LEN];
+};
+#define to_scsi_mpath_device(d) \
+ container_of(d, struct scsi_mpath_device, mpath_device)
+
+int scsi_mpath_dev_alloc(struct scsi_device *sdev);
+void scsi_mpath_dev_release(struct scsi_device *sdev);
+int scsi_multipath_init(void);
+void scsi_multipath_exit(void);
+#else /* CONFIG_SCSI_MULTIPATH */
+
+struct scsi_mpath_device {
+};
+
+static inline int scsi_mpath_dev_alloc(struct scsi_device *sdev)
+{
+ return 0;
+}
+static inline void scsi_mpath_dev_release(struct scsi_device *sdev)
+{
+}
+static inline int scsi_multipath_init(void)
+{
+ return 0;
+}
+static inline void scsi_multipath_exit(void)
+{
+}
+#endif /* CONFIG_SCSI_MULTIPATH */
+#endif /* _SCSI_SCSI_MULTIPATH_H */
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 13/28] scsi-multipath: introduce scsi_device head structure
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
` (11 preceding siblings ...)
2026-07-23 9:36 ` [PATCH v4 12/28] scsi-multipath: introduce basic SCSI device support John Garry
@ 2026-07-23 9:36 ` John Garry
2026-07-23 10:16 ` sashiko-bot
2026-07-23 9:36 ` [PATCH v4 14/28] scsi-multipath: provide sysfs link from to scsi_device John Garry
` (14 subsequent siblings)
27 siblings, 1 reply; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
Introduce a scsi_device head structure - scsi_mpath_head - to manage
multipathing for a scsi_device. This is similar to nvme_ns_head structure.
There is no reference in scsi_mpath_head to any disk, as this would be
mananged by the scsi_disk driver.
A list of scsi_mpath_head structures is managed to lookup for matching
multipathed scsi_device's. Matching is done through the scsi_device
unique id.
A new class for multipathed devices is added, scsi_mpath_device_class.
The purpose of this class is for managing the scsi_mpath_head.dev member.
The naming for the scsi_device structure is in form H:C:I:L,
where H is host, C is channel, I is ID, and L is lun.
However, for a multipathed scsi_device, all the naming members may be
different between member scsi_device's. As such, just use a simple
single-number naming index for each scsi_mpath_head.
The sysfs device folder will have links to the scsi_device's so, it will
be possible to lookup the member scsi_device's.
An example sysfs entry is as follows:
# ls -l /sys/class/scsi_mpath_device/scsi_mpath_device0/
total 0
drwxr-xr-x 2 root root 0 Apr 13 15:48 power
lrwxrwxrwx 1 root root 0 Apr 13 15:48 subsystem -> ../../../../class/scsi_mpath_device
-rw-r--r-- 1 root root 4096 Apr 13 15:48 uevent
-r--r--r-- 1 root root 4096 Apr 13 15:48 vpd_id
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/scsi_multipath.c | 195 +++++++++++++++++++++++++++++++++-
drivers/scsi/scsi_sysfs.c | 3 +
include/scsi/scsi_multipath.h | 31 ++++++
3 files changed, 227 insertions(+), 2 deletions(-)
diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
index b9b58a3641aa2..609f8009e4ac3 100644
--- a/drivers/scsi/scsi_multipath.c
+++ b/drivers/scsi/scsi_multipath.c
@@ -29,6 +29,10 @@ static const char *scsi_multipath_modes[] = {
static int scsi_multipath = SCSI_MULTIPATH_OFF;
+static LIST_HEAD(scsi_mpath_heads_list);
+static DEFINE_MUTEX(scsi_mpath_heads_lock);
+static DEFINE_IDA(scsi_multipath_dev_ida);
+
static int scsi_multipath_param_set(const char *val, const struct kernel_param *kp)
{
int mode;
@@ -70,6 +74,54 @@ static int scsi_mpath_unique_lun_id(struct scsi_device *sdev)
return 0;
}
+static void scsi_mpath_head_release(struct device *dev)
+{
+ struct scsi_mpath_head *scsi_mpath_head =
+ container_of(dev, struct scsi_mpath_head, dev);
+ struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
+
+ ida_free(&scsi_multipath_dev_ida, scsi_mpath_head->index);
+ mpath_head_uninit(mpath_head);
+ kfree(scsi_mpath_head);
+}
+
+static ssize_t scsi_mpath_device_vpd_id_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct scsi_mpath_head *scsi_mpath_head =
+ container_of(dev, struct scsi_mpath_head, dev);
+
+ return sysfs_emit(buf, "%s\n", scsi_mpath_head->vpd_id);
+}
+static DEVICE_ATTR(vpd_id, S_IRUGO, scsi_mpath_device_vpd_id_show, NULL);
+
+static struct attribute *scsi_mpath_device_attrs[] = {
+ &dev_attr_vpd_id.attr,
+ NULL
+};
+
+static const struct attribute_group scsi_mpath_device_attrs_group = {
+ .attrs = scsi_mpath_device_attrs,
+};
+
+static bool scsi_multipath_sysfs_group_visible(struct kobject *kobj)
+{
+ return true;
+}
+DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(scsi_multipath_sysfs)
+
+static const struct attribute_group *scsi_mpath_device_groups[] = {
+ &scsi_mpath_device_attrs_group,
+ NULL
+};
+
+static const struct class scsi_mpath_device_class = {
+ .name = "scsi_mpath_device",
+ .dev_groups = scsi_mpath_device_groups,
+ .dev_release = scsi_mpath_head_release,
+};
+
static int scsi_multipath_sdev_init(struct scsi_device *sdev)
{
struct Scsi_Host *shost = sdev->host;
@@ -89,6 +141,71 @@ static int scsi_multipath_sdev_init(struct scsi_device *sdev)
return 0;
}
+static struct mpath_head_template smpdt = {
+};
+
+static struct scsi_mpath_head *scsi_mpath_alloc_head(void)
+{
+ struct scsi_mpath_head *scsi_mpath_head;
+ int ret;
+
+ scsi_mpath_head = kzalloc(sizeof(*scsi_mpath_head), GFP_KERNEL);
+ if (!scsi_mpath_head)
+ return NULL;
+
+ ida_init(&scsi_mpath_head->ida);
+
+ if (mpath_head_init(&scsi_mpath_head->mpath_head))
+ goto out_free;
+ scsi_mpath_head->mpath_head.mpdt = &smpdt;
+
+ scsi_mpath_head->index = ida_alloc(&scsi_multipath_dev_ida, GFP_KERNEL);
+ if (scsi_mpath_head->index < 0)
+ goto out_uninit_head;
+ kref_init(&scsi_mpath_head->ref);
+
+ device_initialize(&scsi_mpath_head->dev);
+ scsi_mpath_head->dev.class = &scsi_mpath_device_class;
+ ret = dev_set_name(&scsi_mpath_head->dev, "scsi_mpath_device%d",
+ scsi_mpath_head->index);
+ if (ret) {
+ put_device(&scsi_mpath_head->dev);
+ return NULL;
+ }
+
+ ret = device_add(&scsi_mpath_head->dev);
+ if (ret) {
+ put_device(&scsi_mpath_head->dev);
+ return NULL;
+ }
+
+ return scsi_mpath_head;
+
+out_uninit_head:
+ mpath_head_uninit(&scsi_mpath_head->mpath_head);
+out_free:
+ kfree(scsi_mpath_head);
+ return NULL;
+}
+
+static struct scsi_mpath_head *scsi_mpath_find_head(
+ struct scsi_mpath_device *scsi_mpath_dev)
+{
+ struct scsi_mpath_head *scsi_mpath_head;
+
+ list_for_each_entry(scsi_mpath_head, &scsi_mpath_heads_list, entry) {
+ if (strncmp(scsi_mpath_head->vpd_id,
+ scsi_mpath_dev->device_id_str,
+ SCSI_MPATH_DEVICE_ID_LEN) == 0) {
+ if (scsi_mpath_get_head(scsi_mpath_head))
+ return NULL;
+ return scsi_mpath_head;
+ }
+ }
+
+ return NULL;
+}
+
static void scsi_multipath_sdev_uninit(struct scsi_device *sdev)
{
kfree(sdev->scsi_mpath_dev);
@@ -97,6 +214,7 @@ static void scsi_multipath_sdev_uninit(struct scsi_device *sdev)
int scsi_mpath_dev_alloc(struct scsi_device *sdev)
{
+ struct scsi_mpath_head *scsi_mpath_head;
int ret;
if (scsi_multipath == SCSI_MULTIPATH_OFF)
@@ -116,13 +234,52 @@ int scsi_mpath_dev_alloc(struct scsi_device *sdev)
if (ret < 0)
goto out_uninit;
- return 0;
+ mutex_lock(&scsi_mpath_heads_lock);
+ scsi_mpath_head = scsi_mpath_find_head(sdev->scsi_mpath_dev);
+ if (scsi_mpath_head)
+ goto found;
+ scsi_mpath_head = scsi_mpath_alloc_head();
+ if (!scsi_mpath_head) {
+ sdev_printk(KERN_NOTICE, sdev, "could not allocate multipath head, device multipathing disabled\n");
+ mutex_unlock(&scsi_mpath_heads_lock);
+ goto out_uninit;
+ }
+
+ strscpy(scsi_mpath_head->vpd_id, sdev->scsi_mpath_dev->device_id_str,
+ SCSI_MPATH_DEVICE_ID_LEN);
+
+ list_add_tail(&scsi_mpath_head->entry, &scsi_mpath_heads_list);
+found:
+ mutex_unlock(&scsi_mpath_heads_lock);
+ ret = ida_alloc(&scsi_mpath_head->ida, GFP_KERNEL);
+ if (ret < 0)
+ goto out_put_head;
+ sdev->scsi_mpath_dev->index = ret;
+ sdev->scsi_mpath_dev->scsi_mpath_head = scsi_mpath_head;
+ return 0;
+out_put_head:
+ scsi_mpath_put_head(scsi_mpath_head);
out_uninit:
scsi_multipath_sdev_uninit(sdev);
return 0;
}
+static void scsi_mpath_remove_head(struct scsi_mpath_device *scsi_mpath_dev)
+{
+ scsi_mpath_put_head(scsi_mpath_dev->scsi_mpath_head);
+ scsi_mpath_dev->scsi_mpath_head = NULL;
+}
+
+void scsi_mpath_remove_device(struct scsi_mpath_device *scsi_mpath_dev)
+{
+ struct scsi_mpath_head *scsi_mpath_head = scsi_mpath_dev->scsi_mpath_head;
+
+ ida_free(&scsi_mpath_head->ida, scsi_mpath_dev->index);
+
+ scsi_mpath_remove_head(scsi_mpath_dev);
+}
+
void scsi_mpath_dev_release(struct scsi_device *sdev)
{
struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
@@ -133,13 +290,47 @@ void scsi_mpath_dev_release(struct scsi_device *sdev)
scsi_multipath_sdev_uninit(sdev);
}
-int __init scsi_multipath_init(void)
+int scsi_mpath_get_head(struct scsi_mpath_head *scsi_mpath_head)
{
+ if (!kref_get_unless_zero(&scsi_mpath_head->ref))
+ return -ENXIO;
return 0;
}
+EXPORT_SYMBOL_GPL(scsi_mpath_get_head);
+
+static void scsi_mpath_free_head(struct kref *ref)
+{
+ struct scsi_mpath_head *scsi_mpath_head =
+ container_of(ref, struct scsi_mpath_head, ref);
+
+ /*
+ * If we race with scsi_mpath_find_head(), then that function may
+ * find this scsi_mpath_head in the heads list; however we would fail
+ * to take a reference to this scsi_mpath_head and continue the search.
+ * As such, it is safe to call device_unregister (and free
+ * scsi_mpath_head) after we delete this head from the list.
+ */
+ mutex_lock(&scsi_mpath_heads_lock);
+ list_del_init(&scsi_mpath_head->entry);
+ mutex_unlock(&scsi_mpath_heads_lock);
+
+ device_unregister(&scsi_mpath_head->dev);
+}
+
+void scsi_mpath_put_head(struct scsi_mpath_head *scsi_mpath_head)
+{
+ kref_put(&scsi_mpath_head->ref, scsi_mpath_free_head);
+}
+EXPORT_SYMBOL_GPL(scsi_mpath_put_head);
+
+int __init scsi_multipath_init(void)
+{
+ return class_register(&scsi_mpath_device_class);
+}
void __exit scsi_multipath_exit(void)
{
+ class_unregister(&scsi_mpath_device_class);
}
MODULE_LICENSE("GPL");
diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
index 2f80d703ce640..d6bbaf424bd4a 100644
--- a/drivers/scsi/scsi_sysfs.c
+++ b/drivers/scsi/scsi_sysfs.c
@@ -1501,6 +1501,9 @@ void __scsi_remove_device(struct scsi_device *sdev)
} else
put_device(&sdev->sdev_dev);
+ if (sdev->scsi_mpath_dev)
+ scsi_mpath_remove_device(sdev->scsi_mpath_dev);
+
/*
* Stop accepting new requests and wait until all queuecommand() and
* scsi_run_queue() invocations have finished before tearing down the
diff --git a/include/scsi/scsi_multipath.h b/include/scsi/scsi_multipath.h
index d3d410dafd17a..50298056181f9 100644
--- a/include/scsi/scsi_multipath.h
+++ b/include/scsi/scsi_multipath.h
@@ -19,12 +19,25 @@
#ifdef CONFIG_SCSI_MULTIPATH
#define SCSI_MPATH_DEVICE_ID_LEN 256
+struct scsi_mpath_head {
+ struct mpath_head mpath_head;
+ char vpd_id[SCSI_MPATH_DEVICE_ID_LEN];
+ struct list_head entry;
+ struct ida ida;
+ struct kref ref;
+ struct device dev;
+ int index;
+};
+
struct scsi_mpath_device {
struct mpath_device mpath_device;
struct scsi_device *sdev;
+ int index;
+ struct scsi_mpath_head *scsi_mpath_head;
char device_id_str[SCSI_MPATH_DEVICE_ID_LEN];
};
+
#define to_scsi_mpath_device(d) \
container_of(d, struct scsi_mpath_device, mpath_device)
@@ -32,8 +45,13 @@ int scsi_mpath_dev_alloc(struct scsi_device *sdev);
void scsi_mpath_dev_release(struct scsi_device *sdev);
int scsi_multipath_init(void);
void scsi_multipath_exit(void);
+void scsi_mpath_remove_device(struct scsi_mpath_device *scsi_mpath_dev);
+int scsi_mpath_get_head(struct scsi_mpath_head *scsi_mpath_head);
+void scsi_mpath_put_head(struct scsi_mpath_head *scsi_mpath_head);
#else /* CONFIG_SCSI_MULTIPATH */
+struct scsi_mpath_head {
+};
struct scsi_mpath_device {
};
@@ -51,5 +69,18 @@ static inline int scsi_multipath_init(void)
static inline void scsi_multipath_exit(void)
{
}
+static inline
+void scsi_mpath_remove_device(struct scsi_mpath_device *scsi_mpath_dev)
+{
+}
+static inline
+int scsi_mpath_get_head(struct scsi_mpath_head *scsi_mpath_head)
+{
+ return 0;
+}
+static inline
+void scsi_mpath_put_head(struct scsi_mpath_head *scsi_mpath_head)
+{
+}
#endif /* CONFIG_SCSI_MULTIPATH */
#endif /* _SCSI_SCSI_MULTIPATH_H */
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 14/28] scsi-multipath: provide sysfs link from to scsi_device
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
` (12 preceding siblings ...)
2026-07-23 9:36 ` [PATCH v4 13/28] scsi-multipath: introduce scsi_device head structure John Garry
@ 2026-07-23 9:36 ` John Garry
2026-07-23 9:36 ` [PATCH v4 15/28] scsi-multipath: support iopolicy John Garry
` (13 subsequent siblings)
27 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, Hannes Reinecke, John Garry
From: John Garry <john.garry@linux.dev>
Provide a link in sysfs from a scsi_mpath_device to member scsi_device's.
An example is as follows:
# ls -l /sys/class/scsi_mpath_device/scsi_mpath_device0/multipath/
total 0
lrwxrwxrwx 1 root root 0 Feb 24 12:01 8:0:0:0 -> ../../../../platform/host8/session1/target8:0:0/8:0:0:0
lrwxrwxrwx 1 root root 0 Feb 24 12:01 9:0:0:0 -> ../../../../platform/host9/session2/target9:0:0/9:0:0:0
Reviewed-by: Hannes Reinecke <hare@suse.de>
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/scsi_multipath.c | 43 +++++++++++++++++++++++++++++++++++
drivers/scsi/scsi_sysfs.c | 5 ++++
include/scsi/scsi_multipath.h | 8 +++++++
3 files changed, 56 insertions(+)
diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
index 609f8009e4ac3..59d1c0b2d3441 100644
--- a/drivers/scsi/scsi_multipath.c
+++ b/drivers/scsi/scsi_multipath.c
@@ -111,11 +111,54 @@ static bool scsi_multipath_sysfs_group_visible(struct kobject *kobj)
}
DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(scsi_multipath_sysfs)
+static struct attribute dummy_attr = {
+ .name = "dummy",
+};
+
+static struct attribute *scsi_mpath_attrs[] = {
+ &dummy_attr,
+ NULL
+};
+
+static const struct attribute_group scsi_mpath_attr_group = {
+ .name = "multipath",
+ .attrs = scsi_mpath_attrs,
+ .is_visible = SYSFS_GROUP_VISIBLE(scsi_multipath_sysfs),
+};
+
static const struct attribute_group *scsi_mpath_device_groups[] = {
&scsi_mpath_device_attrs_group,
+ &scsi_mpath_attr_group,
NULL
};
+void scsi_mpath_add_sysfs_link(struct scsi_device *sdev)
+{
+ struct device *target = &sdev->sdev_gendev;
+ struct scsi_mpath_head *scsi_mpath_head =
+ sdev->scsi_mpath_dev->scsi_mpath_head;
+ struct device *source = &scsi_mpath_head->dev;
+ int error;
+
+ error = sysfs_add_link_to_group(&source->kobj, "multipath",
+ &target->kobj, dev_name(target));
+ if (error) {
+ sdev_printk(KERN_INFO, sdev, "Failed to create mpath sysfs link, error=%d\n",
+ error);
+ }
+}
+
+void scsi_mpath_remove_sysfs_link(struct scsi_device *sdev)
+{
+ struct device *target = &sdev->sdev_gendev;
+ struct scsi_mpath_head *scsi_mpath_head =
+ sdev->scsi_mpath_dev->scsi_mpath_head;
+ struct device *source = &scsi_mpath_head->dev;
+
+ sysfs_remove_link_from_group(&source->kobj, "multipath",
+ dev_name(target));
+}
+
static const struct class scsi_mpath_device_class = {
.name = "scsi_mpath_device",
.dev_groups = scsi_mpath_device_groups,
diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
index d6bbaf424bd4a..9c4f8d4a6f42c 100644
--- a/drivers/scsi/scsi_sysfs.c
+++ b/drivers/scsi/scsi_sysfs.c
@@ -1443,6 +1443,9 @@ int scsi_sysfs_add_sdev(struct scsi_device *sdev)
transport_add_device(&sdev->sdev_gendev);
sdev->is_visible = 1;
+ if (sdev->scsi_mpath_dev)
+ scsi_mpath_add_sysfs_link(sdev);
+
if (IS_ENABLED(CONFIG_BLK_DEV_BSG)) {
sdev->bsg_dev = scsi_bsg_register_queue(sdev);
if (IS_ERR(sdev->bsg_dev)) {
@@ -1495,6 +1498,8 @@ void __scsi_remove_device(struct scsi_device *sdev)
if (IS_ENABLED(CONFIG_BLK_DEV_BSG) && sdev->bsg_dev)
bsg_unregister_queue(sdev->bsg_dev);
+ if (sdev->scsi_mpath_dev)
+ scsi_mpath_remove_sysfs_link(sdev);
device_unregister(&sdev->sdev_dev);
transport_remove_device(dev);
device_del(dev);
diff --git a/include/scsi/scsi_multipath.h b/include/scsi/scsi_multipath.h
index 50298056181f9..a9fd02bc42371 100644
--- a/include/scsi/scsi_multipath.h
+++ b/include/scsi/scsi_multipath.h
@@ -46,6 +46,8 @@ void scsi_mpath_dev_release(struct scsi_device *sdev);
int scsi_multipath_init(void);
void scsi_multipath_exit(void);
void scsi_mpath_remove_device(struct scsi_mpath_device *scsi_mpath_dev);
+void scsi_mpath_add_sysfs_link(struct scsi_device *sdev);
+void scsi_mpath_remove_sysfs_link(struct scsi_device *sdev);
int scsi_mpath_get_head(struct scsi_mpath_head *scsi_mpath_head);
void scsi_mpath_put_head(struct scsi_mpath_head *scsi_mpath_head);
#else /* CONFIG_SCSI_MULTIPATH */
@@ -82,5 +84,11 @@ static inline
void scsi_mpath_put_head(struct scsi_mpath_head *scsi_mpath_head)
{
}
+static inline void scsi_mpath_add_sysfs_link(struct scsi_device *sdev)
+{
+}
+static inline void scsi_mpath_remove_sysfs_link(struct scsi_device *sdev)
+{
+}
#endif /* CONFIG_SCSI_MULTIPATH */
#endif /* _SCSI_SCSI_MULTIPATH_H */
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 15/28] scsi-multipath: support iopolicy
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
` (13 preceding siblings ...)
2026-07-23 9:36 ` [PATCH v4 14/28] scsi-multipath: provide sysfs link from to scsi_device John Garry
@ 2026-07-23 9:36 ` John Garry
2026-07-23 10:20 ` sashiko-bot
2026-07-23 9:36 ` [PATCH v4 16/28] scsi-multipath: clone each bio John Garry
` (12 subsequent siblings)
27 siblings, 1 reply; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
Add support to set the multipath iopolicy.
The iopolicy member is per scsi_mpath_head structure.
A module param is added so that the default iopolicy may be set.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/scsi_multipath.c | 47 +++++++++++++++++++++++++++++++++++
include/scsi/scsi_multipath.h | 3 +++
2 files changed, 50 insertions(+)
diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
index 59d1c0b2d3441..6228ff4be4ff7 100644
--- a/drivers/scsi/scsi_multipath.c
+++ b/drivers/scsi/scsi_multipath.c
@@ -61,6 +61,23 @@ static const struct kernel_param_ops multipath_param_ops = {
module_param_cb(multipath, &multipath_param_ops, &scsi_multipath, 0444);
MODULE_PARM_DESC(multipath, "turn on native multipath support, options: on, off, always");
+static enum mpath_iopolicy_e iopolicy = MPATH_IOPOLICY_NUMA;
+
+static int scsi_mpath_set_iopolicy_param(const char *val, const struct kernel_param *kp)
+{
+ return mpath_set_iopolicy(val, &iopolicy);
+}
+
+static int scsi_mpath_get_iopolicy_param(char *buf, const struct kernel_param *kp)
+{
+ return mpath_get_iopolicy(buf, iopolicy);
+}
+
+module_param_call(multipath_iopolicy, scsi_mpath_set_iopolicy_param,
+ scsi_mpath_get_iopolicy_param, &iopolicy, 0644);
+MODULE_PARM_DESC(multipath_iopolicy,
+ "Default multipath I/O policy; 'numa' (default), 'round-robin' or 'queue-depth'");
+
static int scsi_mpath_unique_lun_id(struct scsi_device *sdev)
{
struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
@@ -96,8 +113,36 @@ static ssize_t scsi_mpath_device_vpd_id_show(struct device *dev,
}
static DEVICE_ATTR(vpd_id, S_IRUGO, scsi_mpath_device_vpd_id_show, NULL);
+static ssize_t scsi_mpath_device_iopolicy_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct scsi_mpath_head *scsi_mpath_head =
+ container_of(dev, struct scsi_mpath_head, dev);
+ struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
+
+ if (!mpath_iopolicy_store(&scsi_mpath_head->iopolicy, buf))
+ return -EINVAL;
+
+ mpath_clear_paths(mpath_head);
+ mpath_schedule_requeue_work(mpath_head);
+ return count;
+}
+
+static ssize_t scsi_mpath_device_iopolicy_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct scsi_mpath_head *scsi_mpath_head =
+ container_of(dev, struct scsi_mpath_head, dev);
+
+ return mpath_iopolicy_show(&scsi_mpath_head->iopolicy, buf);
+}
+
+static DEVICE_ATTR(iopolicy, S_IRUGO | S_IWUSR,
+ scsi_mpath_device_iopolicy_show, scsi_mpath_device_iopolicy_store);
+
static struct attribute *scsi_mpath_device_attrs[] = {
&dev_attr_vpd_id.attr,
+ &dev_attr_iopolicy.attr,
NULL
};
@@ -201,6 +246,8 @@ static struct scsi_mpath_head *scsi_mpath_alloc_head(void)
if (mpath_head_init(&scsi_mpath_head->mpath_head))
goto out_free;
scsi_mpath_head->mpath_head.mpdt = &smpdt;
+ scsi_mpath_head->iopolicy = iopolicy;
+ scsi_mpath_head->mpath_head.iopolicy = &scsi_mpath_head->iopolicy;
scsi_mpath_head->index = ida_alloc(&scsi_multipath_dev_ida, GFP_KERNEL);
if (scsi_mpath_head->index < 0)
diff --git a/include/scsi/scsi_multipath.h b/include/scsi/scsi_multipath.h
index a9fd02bc42371..2dc02313b0496 100644
--- a/include/scsi/scsi_multipath.h
+++ b/include/scsi/scsi_multipath.h
@@ -25,6 +25,7 @@ struct scsi_mpath_head {
struct list_head entry;
struct ida ida;
struct kref ref;
+ enum mpath_iopolicy_e iopolicy;
struct device dev;
int index;
};
@@ -40,6 +41,8 @@ struct scsi_mpath_device {
#define to_scsi_mpath_device(d) \
container_of(d, struct scsi_mpath_device, mpath_device)
+#define to_scsi_mpath_head(d) \
+ container_of(d, struct scsi_mpath_head, mpath_head)
int scsi_mpath_dev_alloc(struct scsi_device *sdev);
void scsi_mpath_dev_release(struct scsi_device *sdev);
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 16/28] scsi-multipath: clone each bio
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
` (14 preceding siblings ...)
2026-07-23 9:36 ` [PATCH v4 15/28] scsi-multipath: support iopolicy John Garry
@ 2026-07-23 9:36 ` John Garry
2026-07-23 10:27 ` sashiko-bot
2026-07-23 9:36 ` [PATCH v4 17/28] scsi-multipath: clear path when device is blocked John Garry
` (11 subsequent siblings)
27 siblings, 1 reply; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
For failover handling, we will take the approach to resubmit each
bio.
However, unlike NVMe, for SCSI there is no guarantee that any bio submitted
is either all or none completed.
As such, for SCSI, for failover handling we will take the approach to
just re-submit the original bio. For this, clone and submit each bio.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/scsi_multipath.c | 33 +++++++++++++++++++++++++++++++++
include/scsi/scsi_multipath.h | 1 +
2 files changed, 34 insertions(+)
diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
index 6228ff4be4ff7..9a4b35c597778 100644
--- a/drivers/scsi/scsi_multipath.c
+++ b/drivers/scsi/scsi_multipath.c
@@ -97,6 +97,7 @@ static void scsi_mpath_head_release(struct device *dev)
container_of(dev, struct scsi_mpath_head, dev);
struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
+ bioset_exit(&scsi_mpath_head->bio_pool);
ida_free(&scsi_multipath_dev_ida, scsi_mpath_head->index);
mpath_head_uninit(mpath_head);
kfree(scsi_mpath_head);
@@ -229,7 +230,34 @@ static int scsi_multipath_sdev_init(struct scsi_device *sdev)
return 0;
}
+static void scsi_mpath_clone_end_io(struct bio *clone)
+{
+ struct bio *master_bio = clone->bi_private;
+
+ master_bio->bi_status = clone->bi_status;
+ bio_put(clone);
+ bio_endio(master_bio);
+}
+
+static struct bio *scsi_mpath_clone_bio(struct bio *bio)
+{
+ struct mpath_head *mpath_head = bio->bi_bdev->bd_disk->private_data;
+ struct scsi_mpath_head *scsi_mpath_head = to_scsi_mpath_head(mpath_head);
+ struct bio *clone;
+
+ clone = bio_alloc_clone(bio->bi_bdev, bio, GFP_NOIO,
+ &scsi_mpath_head->bio_pool);
+ if (!clone)
+ return NULL;
+
+ clone->bi_end_io = scsi_mpath_clone_end_io;
+ clone->bi_private = bio;
+
+ return clone;
+}
+
static struct mpath_head_template smpdt = {
+ .clone_bio = scsi_mpath_clone_bio,
};
static struct scsi_mpath_head *scsi_mpath_alloc_head(void)
@@ -252,6 +280,9 @@ static struct scsi_mpath_head *scsi_mpath_alloc_head(void)
scsi_mpath_head->index = ida_alloc(&scsi_multipath_dev_ida, GFP_KERNEL);
if (scsi_mpath_head->index < 0)
goto out_uninit_head;
+ if (bioset_init(&scsi_mpath_head->bio_pool, BIO_POOL_SIZE,
+ 0, BIOSET_PERCPU_CACHE))
+ goto out_ida_free;
kref_init(&scsi_mpath_head->ref);
device_initialize(&scsi_mpath_head->dev);
@@ -271,6 +302,8 @@ static struct scsi_mpath_head *scsi_mpath_alloc_head(void)
return scsi_mpath_head;
+out_ida_free:
+ ida_free(&scsi_multipath_dev_ida, scsi_mpath_head->index);
out_uninit_head:
mpath_head_uninit(&scsi_mpath_head->mpath_head);
out_free:
diff --git a/include/scsi/scsi_multipath.h b/include/scsi/scsi_multipath.h
index 2dc02313b0496..5cc37acaa664f 100644
--- a/include/scsi/scsi_multipath.h
+++ b/include/scsi/scsi_multipath.h
@@ -26,6 +26,7 @@ struct scsi_mpath_head {
struct ida ida;
struct kref ref;
enum mpath_iopolicy_e iopolicy;
+ struct bio_set bio_pool;
struct device dev;
int index;
};
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 17/28] scsi-multipath: clear path when device is blocked
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
` (15 preceding siblings ...)
2026-07-23 9:36 ` [PATCH v4 16/28] scsi-multipath: clone each bio John Garry
@ 2026-07-23 9:36 ` John Garry
2026-07-23 10:33 ` sashiko-bot
2026-07-23 9:36 ` [PATCH v4 18/28] scsi-multipath: revalidate paths upon device unblock John Garry
` (10 subsequent siblings)
27 siblings, 1 reply; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
Add scsi_mpath_dev_clear_path() to clear a device path when it becomes
blocked.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/scsi_lib.c | 6 +++++-
drivers/scsi/scsi_multipath.c | 10 ++++++++++
include/scsi/scsi_multipath.h | 5 +++++
3 files changed, 20 insertions(+), 1 deletion(-)
diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
index 22e2e3223440d..dafa781288046 100644
--- a/drivers/scsi/scsi_lib.c
+++ b/drivers/scsi/scsi_lib.c
@@ -35,6 +35,7 @@
#include <scsi/scsi_host.h>
#include <scsi/scsi_transport.h> /* scsi_init_limits() */
#include <scsi/scsi_dh.h>
+#include <scsi/scsi_multipath.h>
#include <trace/events/scsi.h>
@@ -3007,13 +3008,16 @@ static void scsi_device_block(struct scsi_device *sdev, void *data)
mutex_lock(&sdev->state_mutex);
err = __scsi_internal_device_block_nowait(sdev);
state = sdev->sdev_state;
- if (err == 0)
+ if (err == 0) {
/*
* scsi_stop_queue() must be called with the state_mutex
* held. Otherwise a simultaneous scsi_start_queue() call
* might unquiesce the queue before we quiesce it.
*/
scsi_stop_queue(sdev);
+ if (sdev->scsi_mpath_dev)
+ scsi_mpath_dev_clear_path(sdev->scsi_mpath_dev);
+ }
mutex_unlock(&sdev->state_mutex);
diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
index 9a4b35c597778..6a2b06c148c1a 100644
--- a/drivers/scsi/scsi_multipath.c
+++ b/drivers/scsi/scsi_multipath.c
@@ -230,6 +230,16 @@ static int scsi_multipath_sdev_init(struct scsi_device *sdev)
return 0;
}
+void scsi_mpath_dev_clear_path(struct scsi_mpath_device *scsi_mpath_dev)
+{
+ struct mpath_device *mpath_device = &scsi_mpath_dev->mpath_device;
+ struct scsi_mpath_head *scsi_mpath_head = scsi_mpath_dev->scsi_mpath_head;
+ struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
+
+ if (mpath_clear_current_path(mpath_device))
+ mpath_synchronize(mpath_head);
+}
+
static void scsi_mpath_clone_end_io(struct bio *clone)
{
struct bio *master_bio = clone->bi_private;
diff --git a/include/scsi/scsi_multipath.h b/include/scsi/scsi_multipath.h
index 5cc37acaa664f..8f58c44d96a77 100644
--- a/include/scsi/scsi_multipath.h
+++ b/include/scsi/scsi_multipath.h
@@ -54,6 +54,7 @@ void scsi_mpath_add_sysfs_link(struct scsi_device *sdev);
void scsi_mpath_remove_sysfs_link(struct scsi_device *sdev);
int scsi_mpath_get_head(struct scsi_mpath_head *scsi_mpath_head);
void scsi_mpath_put_head(struct scsi_mpath_head *scsi_mpath_head);
+void scsi_mpath_dev_clear_path(struct scsi_mpath_device *scsi_mpath_dev);
#else /* CONFIG_SCSI_MULTIPATH */
struct scsi_mpath_head {
@@ -88,6 +89,10 @@ static inline
void scsi_mpath_put_head(struct scsi_mpath_head *scsi_mpath_head)
{
}
+static inline
+void scsi_mpath_dev_clear_path(struct scsi_mpath_device *scsi_mpath_dev)
+{
+}
static inline void scsi_mpath_add_sysfs_link(struct scsi_device *sdev)
{
}
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 18/28] scsi-multipath: revalidate paths upon device unblock
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
` (16 preceding siblings ...)
2026-07-23 9:36 ` [PATCH v4 17/28] scsi-multipath: clear path when device is blocked John Garry
@ 2026-07-23 9:36 ` John Garry
2026-07-23 10:39 ` sashiko-bot
2026-07-23 9:36 ` [PATCH v4 19/28] scsi-multipath: failover handling John Garry
` (9 subsequent siblings)
27 siblings, 1 reply; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
When a scsi_device is unblocked, we need revalidate paths which means that
we clear paths and requeue any queued bios.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/scsi_lib.c | 2 ++
drivers/scsi/scsi_multipath.c | 8 ++++++++
include/scsi/scsi_multipath.h | 5 +++++
3 files changed, 15 insertions(+)
diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
index dafa781288046..1e13aefdb6d64 100644
--- a/drivers/scsi/scsi_lib.c
+++ b/drivers/scsi/scsi_lib.c
@@ -3100,6 +3100,8 @@ static int scsi_internal_device_unblock(struct scsi_device *sdev,
mutex_lock(&sdev->state_mutex);
ret = scsi_internal_device_unblock_nowait(sdev, new_state);
+ if ((ret == 0) && sdev->scsi_mpath_dev)
+ scsi_mpath_revalidate_paths(sdev->scsi_mpath_dev);
mutex_unlock(&sdev->state_mutex);
return ret;
diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
index 6a2b06c148c1a..b896d28a7dd9a 100644
--- a/drivers/scsi/scsi_multipath.c
+++ b/drivers/scsi/scsi_multipath.c
@@ -230,6 +230,14 @@ static int scsi_multipath_sdev_init(struct scsi_device *sdev)
return 0;
}
+void scsi_mpath_revalidate_paths(struct scsi_mpath_device *scsi_mpath_dev)
+{
+ struct scsi_mpath_head *scsi_mpath_head = scsi_mpath_dev->scsi_mpath_head;
+ struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
+
+ mpath_revalidate_paths(mpath_head);
+}
+
void scsi_mpath_dev_clear_path(struct scsi_mpath_device *scsi_mpath_dev)
{
struct mpath_device *mpath_device = &scsi_mpath_dev->mpath_device;
diff --git a/include/scsi/scsi_multipath.h b/include/scsi/scsi_multipath.h
index 8f58c44d96a77..cc77e63c43da3 100644
--- a/include/scsi/scsi_multipath.h
+++ b/include/scsi/scsi_multipath.h
@@ -55,6 +55,7 @@ void scsi_mpath_remove_sysfs_link(struct scsi_device *sdev);
int scsi_mpath_get_head(struct scsi_mpath_head *scsi_mpath_head);
void scsi_mpath_put_head(struct scsi_mpath_head *scsi_mpath_head);
void scsi_mpath_dev_clear_path(struct scsi_mpath_device *scsi_mpath_dev);
+void scsi_mpath_revalidate_paths(struct scsi_mpath_device *scsi_mpath_dev);
#else /* CONFIG_SCSI_MULTIPATH */
struct scsi_mpath_head {
@@ -93,6 +94,10 @@ static inline
void scsi_mpath_dev_clear_path(struct scsi_mpath_device *scsi_mpath_dev)
{
}
+static inline
+void scsi_mpath_revalidate_paths(struct scsi_mpath_device *scsi_mpath_dev)
+{
+}
static inline void scsi_mpath_add_sysfs_link(struct scsi_device *sdev)
{
}
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 19/28] scsi-multipath: failover handling
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
` (17 preceding siblings ...)
2026-07-23 9:36 ` [PATCH v4 18/28] scsi-multipath: revalidate paths upon device unblock John Garry
@ 2026-07-23 9:36 ` John Garry
2026-07-23 10:36 ` sashiko-bot
2026-07-23 9:36 ` [PATCH v4 20/28] scsi-multipath: provide callbacks for path state John Garry
` (8 subsequent siblings)
27 siblings, 1 reply; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
Failover occurs when the scsi_cmnd has failed and it is discovered that the
target scsi_device has transport down.
For a scsi command which suffers failover, requeue the master bio of each
bio attached to its request.
A bio which for which failover occurs is handled in
scsi_mpath_clone_end_io(). Failover is detected for blk_path_error()
occuring, same as how dm-mpath detects this.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/scsi_multipath.c | 42 +++++++++++++++++++++++++++++++++++
1 file changed, 42 insertions(+)
diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
index b896d28a7dd9a..a482b6ede21aa 100644
--- a/drivers/scsi/scsi_multipath.c
+++ b/drivers/scsi/scsi_multipath.c
@@ -248,11 +248,53 @@ void scsi_mpath_dev_clear_path(struct scsi_mpath_device *scsi_mpath_dev)
mpath_synchronize(mpath_head);
}
+static inline void bio_list_add_clone(struct bio_list *bl,
+ struct bio *clone)
+{
+ struct bio *master_bio = clone->bi_private;
+
+ if (bl->tail)
+ bl->tail->bi_next = master_bio;
+ else
+ bl->head = master_bio;
+ bl->tail = master_bio;
+ bio_put(clone);
+}
+
static void scsi_mpath_clone_end_io(struct bio *clone)
{
struct bio *master_bio = clone->bi_private;
master_bio->bi_status = clone->bi_status;
+
+ if (clone->bi_status && blk_path_error(clone->bi_status)) {
+ struct block_device *bi_bdev = clone->bi_bdev;
+ struct request_queue *q = bi_bdev->bd_queue;
+ struct scsi_device *sdev = scsi_device_from_queue(q);
+ struct scsi_mpath_device *scsi_mpath_dev;
+ struct mpath_device *mpath_device;
+ struct mpath_head *mpath_head;
+ unsigned long flags;
+
+ if (!sdev) {
+ master_bio->bi_status = clone->bi_status;
+ goto end_bio;
+ }
+
+ scsi_mpath_dev = sdev->scsi_mpath_dev;
+ mpath_device = &scsi_mpath_dev->mpath_device;
+ mpath_head = mpath_device->mpath_head;
+
+ spin_lock_irqsave(&mpath_head->requeue_lock, flags);
+ bio_list_add_clone(&mpath_head->requeue_list, clone);
+ spin_unlock_irqrestore(&mpath_head->requeue_lock, flags);
+
+ mpath_schedule_requeue_work(mpath_head);
+ scsi_device_put(sdev);
+ return;
+ }
+
+end_bio:
bio_put(clone);
bio_endio(master_bio);
}
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 20/28] scsi-multipath: provide callbacks for path state
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
` (18 preceding siblings ...)
2026-07-23 9:36 ` [PATCH v4 19/28] scsi-multipath: failover handling John Garry
@ 2026-07-23 9:36 ` John Garry
2026-07-23 10:36 ` sashiko-bot
2026-07-23 9:36 ` [PATCH v4 21/28] scsi-multipath: add scsi_mpath_{start,end}_request() John Garry
` (7 subsequent siblings)
27 siblings, 1 reply; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
Provide callbacks for .is_disabled, .is_optimized, and .available_path.
These all use scsi_device.sdev_state and scsi_device.access_state.
Member scsi_device.access_state will be driven by ALUA. Currently
only device handlers support this, and in future we will have core
SCSI support for implicit ALUA (not relying on device handlers).
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/scsi_multipath.c | 49 +++++++++++++++++++++++++++++++++++
1 file changed, 49 insertions(+)
diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
index a482b6ede21aa..e566d47199fcc 100644
--- a/drivers/scsi/scsi_multipath.c
+++ b/drivers/scsi/scsi_multipath.c
@@ -316,7 +316,56 @@ static struct bio *scsi_mpath_clone_bio(struct bio *bio)
return clone;
}
+static bool scsi_mpath_is_disabled(struct mpath_device *mpath_device)
+{
+ struct scsi_mpath_device *scsi_mpath_dev =
+ to_scsi_mpath_device(mpath_device);
+ struct scsi_device *sdev = scsi_mpath_dev->sdev;
+ unsigned char access_state = READ_ONCE(sdev->access_state) &
+ SCSI_ACCESS_STATE_MASK;
+
+ if (sdev->sdev_state != SDEV_RUNNING)
+ return true;
+
+ if (access_state == SCSI_ACCESS_STATE_OPTIMAL ||
+ access_state == SCSI_ACCESS_STATE_ACTIVE)
+ return false;
+
+ return true;
+}
+
+static bool scsi_mpath_is_optimized(struct mpath_device *mpath_device)
+{
+ struct scsi_mpath_device *scsi_mpath_dev =
+ to_scsi_mpath_device(mpath_device);
+ struct scsi_device *sdev = scsi_mpath_dev->sdev;
+ unsigned char access_state = READ_ONCE(sdev->access_state) &
+ SCSI_ACCESS_STATE_MASK;
+
+ if (sdev->sdev_state != SDEV_RUNNING)
+ return false;
+
+ return access_state == SCSI_ACCESS_STATE_OPTIMAL;
+}
+
+static bool scsi_mpath_available_path(struct mpath_device *mpath_device)
+{
+ struct scsi_mpath_device *scsi_mpath_dev =
+ to_scsi_mpath_device(mpath_device);
+ struct scsi_device *sdev = scsi_mpath_dev->sdev;
+ enum scsi_device_state sdev_state = sdev->sdev_state;
+
+ if (sdev_state == SDEV_RUNNING || sdev_state == SDEV_QUIESCE ||
+ sdev_state == SDEV_BLOCK || sdev_state == SDEV_CREATED_BLOCK)
+ return true;
+
+ return false;
+}
+
static struct mpath_head_template smpdt = {
+ .is_disabled = scsi_mpath_is_disabled,
+ .is_optimized = scsi_mpath_is_optimized,
+ .available_path = scsi_mpath_available_path,
.clone_bio = scsi_mpath_clone_bio,
};
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 21/28] scsi-multipath: add scsi_mpath_{start,end}_request()
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
` (19 preceding siblings ...)
2026-07-23 9:36 ` [PATCH v4 20/28] scsi-multipath: provide callbacks for path state John Garry
@ 2026-07-23 9:36 ` John Garry
2026-07-23 10:32 ` sashiko-bot
2026-07-23 9:36 ` [PATCH v4 22/28] scsi-multipath: add delayed disk removal support John Garry
` (6 subsequent siblings)
27 siblings, 1 reply; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
Add scsi_mpath_{start,end}_request() to handle updating private multipath
request data, like nvme_mpath_{start,end}_request().
New member Scsi_Host.mpath_nr_active is added. This is required for queue
depth multipath iopolicy.
For NVMe, this count is per controller. The reason is that many NSes may
be connected to a controller, so congestion should be judged at
controller level.
SCSI has no definition of a controller, but SCSI host is a comparable
concept.
Indeed, many SCSI disks may be connected to the same SCSI host, so it
makes sense to count number of active requests at this point. However,
for a transport like iSCSI Initiator over TCP/IP, we have a separate SCSI
host per SCSI device (so there the count would be same at SCSI device
level).
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/scsi_lib.c | 8 +++++-
drivers/scsi/scsi_multipath.c | 53 +++++++++++++++++++++++++++++++++++
include/scsi/scsi_cmnd.h | 6 ++++
include/scsi/scsi_host.h | 4 +++
include/scsi/scsi_multipath.h | 11 ++++++++
5 files changed, 81 insertions(+), 1 deletion(-)
diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
index 1e13aefdb6d64..3dda25a05f78c 100644
--- a/drivers/scsi/scsi_lib.c
+++ b/drivers/scsi/scsi_lib.c
@@ -669,7 +669,10 @@ static bool scsi_end_request(struct request *req, blk_status_t error,
struct scsi_device *sdev = cmd->device;
struct request_queue *q = sdev->request_queue;
- if (blk_update_request(req, error, bytes))
+ if (is_mpath_request(req)) {
+ if (scsi_mpath_end_request(req, error, bytes))
+ return true;
+ } else if (blk_update_request(req, error, bytes))
return true;
if (q->limits.features & BLK_FEAT_ADD_RANDOM)
@@ -1917,6 +1920,9 @@ static blk_status_t scsi_queue_rq(struct blk_mq_hw_ctx *hctx,
memset(cmd->sense_buffer, 0, SCSI_SENSE_BUFFERSIZE);
cmd->submitter = SUBMITTED_BY_BLOCK_LAYER;
+ if (is_mpath_request(req))
+ scsi_mpath_start_request(req);
+
blk_mq_start_request(req);
if (blk_mq_is_reserved_rq(req)) {
reason = shost->hostt->queue_reserved_command(shost, cmd);
diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
index e566d47199fcc..6c50fdde70d65 100644
--- a/drivers/scsi/scsi_multipath.c
+++ b/drivers/scsi/scsi_multipath.c
@@ -555,6 +555,59 @@ void scsi_mpath_put_head(struct scsi_mpath_head *scsi_mpath_head)
}
EXPORT_SYMBOL_GPL(scsi_mpath_put_head);
+void scsi_mpath_start_request(struct request *req)
+{
+ struct scsi_cmnd *scmd = blk_mq_rq_to_pdu(req);
+ struct scsi_device *sdev = scmd->device;
+ struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
+ struct scsi_mpath_head *scsi_mpath_head =
+ scsi_mpath_dev->scsi_mpath_head;
+ struct bio *clone = req->bio;
+ struct bio *master = clone->bi_private;
+
+ if (mpath_qd_iopolicy(&scsi_mpath_head->iopolicy) &&
+ !(scmd->flags & SCMD_MPATH_CNT_ACTIVE)) {
+ struct Scsi_Host *shost = sdev->host;
+
+ atomic_inc(&shost->mpath_nr_active);
+ scmd->flags |= SCMD_MPATH_CNT_ACTIVE;
+ }
+
+ if (!blk_queue_io_stat(master->bi_bdev->bd_queue) || blk_rq_is_passthrough(req) ||
+ (scmd->flags & SCMD_MPATH_IO_STATS))
+ return;
+
+ scmd->flags |= SCMD_MPATH_IO_STATS;
+ scmd->start_time = bdev_start_io_acct(master->bi_bdev, req_op(req),
+ jiffies);
+ scmd->start_bytes = blk_rq_bytes(req);
+}
+
+bool scsi_mpath_end_request(struct request *req, blk_status_t error,
+ unsigned int nr_bytes)
+{
+ struct scsi_cmnd *scmd = blk_mq_rq_to_pdu(req);
+ struct scsi_device *sdev = scmd->device;
+ struct bio *clone = req->bio, *master = clone->bi_private;
+
+ if (blk_update_request(req, error, nr_bytes))
+ return true;
+
+ if (scmd->flags & SCMD_MPATH_CNT_ACTIVE) {
+ struct Scsi_Host *shost = sdev->host;
+
+ atomic_dec_if_positive(&shost->mpath_nr_active);
+ }
+
+ if (!(scmd->flags & SCMD_MPATH_IO_STATS))
+ return false;
+ bdev_end_io_acct(master->bi_bdev, req_op(req),
+ scmd->start_bytes >> SECTOR_SHIFT,
+ scmd->start_time);
+
+ return false;
+}
+
int __init scsi_multipath_init(void)
{
return class_register(&scsi_mpath_device_class);
diff --git a/include/scsi/scsi_cmnd.h b/include/scsi/scsi_cmnd.h
index 8ecfb94049db5..3ff425c5f1e56 100644
--- a/include/scsi/scsi_cmnd.h
+++ b/include/scsi/scsi_cmnd.h
@@ -60,6 +60,8 @@ struct scsi_pointer {
#define SCMD_FAIL_IF_RECOVERING (1 << 4)
/* flags preserved across unprep / reprep */
#define SCMD_PRESERVED_FLAGS (SCMD_INITIALIZED | SCMD_FAIL_IF_RECOVERING)
+#define SCMD_MPATH_IO_STATS (1 << 5)
+#define SCMD_MPATH_CNT_ACTIVE (1 << 6)
/* for scmd->state */
#define SCMD_STATE_COMPLETE 0
@@ -139,6 +141,10 @@ struct scsi_cmnd {
* to release this memory. (The memory
* obtained by scsi_malloc is guaranteed
* to be at an address < 16Mb). */
+ #ifdef CONFIG_SCSI_MULTIPATH
+ unsigned long start_time;
+ unsigned int start_bytes;
+ #endif
int result; /* Status code from lower level driver */
};
diff --git a/include/scsi/scsi_host.h b/include/scsi/scsi_host.h
index 7e2011830ba4b..cf4fff3ea1a8e 100644
--- a/include/scsi/scsi_host.h
+++ b/include/scsi/scsi_host.h
@@ -753,6 +753,10 @@ struct Scsi_Host {
/* Delay for runtime autosuspend */
int rpm_autosuspend_delay;
+ #ifdef CONFIG_SCSI_MULTIPATH
+ atomic_t mpath_nr_active;
+ #endif
+
/*
* We should ensure that this is aligned, both for better performance
* and also because some compilers (m68k) don't automatically force
diff --git a/include/scsi/scsi_multipath.h b/include/scsi/scsi_multipath.h
index cc77e63c43da3..c3398e9f03f16 100644
--- a/include/scsi/scsi_multipath.h
+++ b/include/scsi/scsi_multipath.h
@@ -56,6 +56,9 @@ int scsi_mpath_get_head(struct scsi_mpath_head *scsi_mpath_head);
void scsi_mpath_put_head(struct scsi_mpath_head *scsi_mpath_head);
void scsi_mpath_dev_clear_path(struct scsi_mpath_device *scsi_mpath_dev);
void scsi_mpath_revalidate_paths(struct scsi_mpath_device *scsi_mpath_dev);
+void scsi_mpath_start_request(struct request *req);
+bool scsi_mpath_end_request(struct request *req, blk_status_t error,
+ unsigned int nr_bytes);
#else /* CONFIG_SCSI_MULTIPATH */
struct scsi_mpath_head {
@@ -98,6 +101,14 @@ static inline
void scsi_mpath_revalidate_paths(struct scsi_mpath_device *scsi_mpath_dev)
{
}
+static inline void scsi_mpath_start_request(struct request *req)
+{
+}
+static inline bool scsi_mpath_end_request(struct request *req, blk_status_t error,
+ unsigned int nr_bytes)
+{
+ return false;
+}
static inline void scsi_mpath_add_sysfs_link(struct scsi_device *sdev)
{
}
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 22/28] scsi-multipath: add delayed disk removal support
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
` (20 preceding siblings ...)
2026-07-23 9:36 ` [PATCH v4 21/28] scsi-multipath: add scsi_mpath_{start,end}_request() John Garry
@ 2026-07-23 9:36 ` John Garry
2026-07-23 10:39 ` sashiko-bot
2026-07-23 9:36 ` [PATCH v4 23/28] scsi: sd: add multipath disk class John Garry
` (5 subsequent siblings)
27 siblings, 1 reply; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
Add support in core code for delayed disk removal support. In this, the
callback calls into the scsi_driver to do the necessary removal work.
The scsi_disk driver (sd) must ensure that the scsi_mpath_device does not
go away while the delayed removal work is active, i.e. it must keep a
reference.
No reference to the scsi_disk multipath structures are kept outside that
driver, so that driver needs to provide a scsi_driver.mpath_remove_head
callback to do the necessary work.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/scsi_multipath.c | 18 ++++++++++++++++++
include/scsi/scsi_driver.h | 4 ++++
2 files changed, 22 insertions(+)
diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
index 6c50fdde70d65..056b9c35d1144 100644
--- a/drivers/scsi/scsi_multipath.c
+++ b/drivers/scsi/scsi_multipath.c
@@ -362,7 +362,25 @@ static bool scsi_mpath_available_path(struct mpath_device *mpath_device)
return false;
}
+static int scsi_mpath_remove_head_drv(struct device_driver *drv, void *data)
+{
+ struct scsi_mpath_head *scsi_mpath_head = data;
+ struct scsi_driver *scsi_driver = to_scsi_driver(drv);
+
+ if (scsi_driver->mpath_remove_head)
+ scsi_driver->mpath_remove_head(scsi_mpath_head);
+
+ return 0;
+}
+
+static void scsi_mpath_remove_head_work(struct mpath_head *mpath_head)
+{
+ bus_for_each_drv(&scsi_bus_type, NULL, to_scsi_mpath_head(mpath_head),
+ scsi_mpath_remove_head_drv);
+}
+
static struct mpath_head_template smpdt = {
+ .remove_head = scsi_mpath_remove_head_work,
.is_disabled = scsi_mpath_is_disabled,
.is_optimized = scsi_mpath_is_optimized,
.available_path = scsi_mpath_available_path,
diff --git a/include/scsi/scsi_driver.h b/include/scsi/scsi_driver.h
index 249cea724abd1..d92b63d357f2a 100644
--- a/include/scsi/scsi_driver.h
+++ b/include/scsi/scsi_driver.h
@@ -8,6 +8,7 @@
struct module;
struct request;
+struct scsi_mpath_head;
struct scsi_driver {
struct device_driver gendrv;
@@ -22,6 +23,9 @@ struct scsi_driver {
int (*done)(struct scsi_cmnd *);
int (*eh_action)(struct scsi_cmnd *, int);
void (*eh_reset)(struct scsi_cmnd *);
+ #ifdef CONFIG_SCSI_MULTIPATH
+ void (*mpath_remove_head)(struct scsi_mpath_head *);
+ #endif
};
#define to_scsi_driver(drv) \
container_of((drv), struct scsi_driver, gendrv)
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 23/28] scsi: sd: add multipath disk class
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
` (21 preceding siblings ...)
2026-07-23 9:36 ` [PATCH v4 22/28] scsi-multipath: add delayed disk removal support John Garry
@ 2026-07-23 9:36 ` John Garry
2026-07-23 10:39 ` sashiko-bot
2026-07-23 9:36 ` [PATCH v4 24/28] scsi: sd: add multipath disk attr groups John Garry
` (4 subsequent siblings)
27 siblings, 1 reply; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
Add a new class, sd_mpath_disk_class, which is the multipath version of
the scsi_disk class.
Structure sd_mpath_disk is introduced to manage the multipath gendisk.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/sd.c | 43 ++++++++++++++++++++++++++++++++++++++++++-
drivers/scsi/sd.h | 3 +++
2 files changed, 45 insertions(+), 1 deletion(-)
diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
index 599e75f333343..242a15bc2c5bb 100644
--- a/drivers/scsi/sd.c
+++ b/drivers/scsi/sd.c
@@ -70,6 +70,7 @@
#include <scsi/scsi_ioctl.h>
#include <scsi/scsicam.h>
#include <scsi/scsi_common.h>
+#include <scsi/scsi_multipath.h>
#include "sd.h"
#include "scsi_priv.h"
@@ -113,6 +114,39 @@ static mempool_t *sd_page_pool;
static mempool_t *sd_large_page_pool;
static atomic_t sd_large_page_pool_users = ATOMIC_INIT(0);
static struct lock_class_key sd_bio_compl_lkclass;
+#ifdef CONFIG_SCSI_MULTIPATH
+struct sd_mpath_disk {
+ struct scsi_mpath_head *scsi_mpath_head;
+};
+
+static void sd_mpath_disk_release(struct device *dev)
+{
+}
+
+static const struct class sd_mpath_disk_class = {
+ .name = "scsi_mpath_disk",
+ .dev_release = sd_mpath_disk_release,
+};
+
+static int sd_mpath_class_register(void)
+{
+ return class_register(&sd_mpath_disk_class);
+}
+
+static void sd_mpath_class_unregister(void)
+{
+ class_unregister(&sd_mpath_disk_class);
+}
+#else /* CONFIG_SCSI_MULTIPATH */
+static int sd_mpath_class_register(void)
+{
+ return 0;
+}
+
+static void sd_mpath_class_unregister(void)
+{
+}
+#endif
static const char *sd_cache_types[] = {
"write through", "none", "write back",
@@ -4453,11 +4487,15 @@ static int __init init_sd(void)
if (err)
goto err_out;
+ err = sd_mpath_class_register();
+ if (err)
+ goto err_out_class;
+
sd_page_pool = mempool_create_page_pool(SD_MEMPOOL_SIZE, 0);
if (!sd_page_pool) {
printk(KERN_ERR "sd: can't init discard page pool\n");
err = -ENOMEM;
- goto err_out_class;
+ goto err_out_mpath_class;
}
err = scsi_register_driver(&sd_template);
@@ -4468,6 +4506,8 @@ static int __init init_sd(void)
err_out_driver:
mempool_destroy(sd_page_pool);
+err_out_mpath_class:
+ sd_mpath_class_unregister();
err_out_class:
class_unregister(&sd_disk_class);
err_out:
@@ -4493,6 +4533,7 @@ static void __exit exit_sd(void)
mempool_destroy(sd_large_page_pool);
class_unregister(&sd_disk_class);
+ sd_mpath_class_unregister();
for (i = 0; i < SD_MAJORS; i++)
unregister_blkdev(sd_major(i), "sd");
diff --git a/drivers/scsi/sd.h b/drivers/scsi/sd.h
index 574af82430169..304b24644d942 100644
--- a/drivers/scsi/sd.h
+++ b/drivers/scsi/sd.h
@@ -83,6 +83,9 @@ struct zoned_disk_info {
struct scsi_disk {
struct scsi_device *device;
+ #ifdef CONFIG_SCSI_MULTIPATH
+ struct sd_mpath_disk *sd_mpath_disk;
+ #endif
/*
* disk_dev is used to show attributes in /sys/class/scsi_disk/,
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 24/28] scsi: sd: add multipath disk attr groups
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
` (22 preceding siblings ...)
2026-07-23 9:36 ` [PATCH v4 23/28] scsi: sd: add multipath disk class John Garry
@ 2026-07-23 9:36 ` John Garry
2026-07-23 10:47 ` sashiko-bot
2026-07-23 9:36 ` [PATCH v4 25/28] scsi: sd: support multipath disk John Garry
` (3 subsequent siblings)
27 siblings, 1 reply; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
Set multipath disk attr groups, which includes delayed disk removal and
everything from mpath_attr_group.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/sd.c | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
index 242a15bc2c5bb..36c745cbb2619 100644
--- a/drivers/scsi/sd.c
+++ b/drivers/scsi/sd.c
@@ -4006,6 +4006,42 @@ static int sd_format_disk_name(char *prefix, int index, char *buf, int buflen)
return 0;
}
+static ssize_t sd_mpath_device_delayed_removal_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct mpath_head *mpath_head = mpath_bd_device_to_head(dev);
+
+ return mpath_delayed_removal_secs_store(mpath_head, buf, count);
+}
+
+static ssize_t sd_mpath_device_delayed_removal_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mpath_head *mpath_head = mpath_bd_device_to_head(dev);
+
+ return mpath_delayed_removal_secs_show(mpath_head, buf);
+}
+
+static DEVICE_ATTR(delayed_removal_secs, S_IRUGO | S_IWUSR,
+ sd_mpath_device_delayed_removal_show,
+ sd_mpath_device_delayed_removal_store);
+
+static struct attribute *sd_mpath_disk_attrs[] = {
+ &dev_attr_delayed_removal_secs.attr,
+ NULL
+};
+
+static const struct attribute_group sd_mpath_disk_attr_group = {
+ .attrs = sd_mpath_disk_attrs,
+};
+
+__maybe_unused
+static const struct attribute_group *sd_mpath_disk_attr_groups[] = {
+ &sd_mpath_disk_attr_group,
+ &mpath_attr_group,
+ NULL
+};
+
/**
* sd_probe - called during driver initialization and whenever a
* new scsi device is attached to the system. It is called once
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 25/28] scsi: sd: support multipath disk
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
` (23 preceding siblings ...)
2026-07-23 9:36 ` [PATCH v4 24/28] scsi: sd: add multipath disk attr groups John Garry
@ 2026-07-23 9:36 ` John Garry
2026-07-23 10:47 ` sashiko-bot
2026-07-23 9:36 ` [PATCH v4 26/28] scsi: sd: add mpath_dev file John Garry
` (2 subsequent siblings)
27 siblings, 1 reply; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
Add support to attach a multipath disk.
We still allocate the gendisk per path, and this is required for the
per-path submission. However, those gendisks are marked as hidden. Those
disks are named sdX:Y, where X is the multipath disk index and Y is the
per-path index.
A global list of sd_mpath_disks is kept for matching scsi_device's.
The multipath gendisk has the name and disk->major/minor set to minic a
scsi_disk.
The following is an example of relevant scsi_disk and block sysfs
directories:
$ ls -l /sys/block/ | grep sdc
lrwxrwxrwx 1 root root 0 Feb 24 16:01 sdc -> ../devices/virtual/scsi_mpath_disk/0/sdc
lrwxrwxrwx 1 root root 0 Feb 24 16:01 sdc:0 -> ../devices/platform/host8/session1/target8:0:0/8:0:0:0/block/sdc:0
lrwxrwxrwx 1 root root 0 Feb 24 16:02 sdc:1 -> ../devices/platform/host9/session2/target9:0:0/9:0:0:0/block/sdc:1
$ ls -l /sys/class/scsi_mpath_disk/scsi_mpath_disk0/
total 0
drwxr-xr-x 2 root root 0 Feb 24 16:03 power
drwxr-xr-x 11 root root 0 Feb 24 16:01 sdc
lrwxrwxrwx 1 root root 0 Feb 24 16:01 subsystem -> ../../../../class/scsi_mpath_disk
-rw-r--r-- 1 root root 4096 Feb 24 16:01 uevent
$ ls -l /sys/class/scsi_mpath_disk/scsi_mpath_disk0/sdc/multipath/
total 0
lrwxrwxrwx 1 root root 0 Feb 24 16:20 sdc:0 -> ../../../../../platform/host8/session1/target8:0:0/8:0:0:0/block/sdc:0
lrwxrwxrwx 1 root root 0 Feb 24 16:20 sdc:1 -> ../../../../../platform/host9/session2/target9:0:0/9:0:0:0/block/sdc:1
$ ls -l /dev/sdc*
brw-rw---- 1 root disk 8, 32 Feb 24 16:01 /dev/sdc
brw-rw---- 1 root disk 8, 33 Feb 24 16:01 /dev/sdc1
brw-rw---- 1 root disk 8, 34 Feb 24 16:01 /dev/sdc2
$ lsblk /dev/sdc
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sdc 8:32 0 600M 0 disk
|-sdc1 8:33 0 9M 0 part
`-sdc2 8:34 0 568M 0 part
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/sd.c | 425 +++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 405 insertions(+), 20 deletions(-)
diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
index 36c745cbb2619..d5a37b1b312c3 100644
--- a/drivers/scsi/sd.c
+++ b/drivers/scsi/sd.c
@@ -115,12 +115,30 @@ static mempool_t *sd_large_page_pool;
static atomic_t sd_large_page_pool_users = ATOMIC_INIT(0);
static struct lock_class_key sd_bio_compl_lkclass;
#ifdef CONFIG_SCSI_MULTIPATH
+static LIST_HEAD(sd_mpath_disks_list);
+static DEFINE_MUTEX(sd_mpath_disks_lock);
+
struct sd_mpath_disk {
+ struct device dev;
+ int disk_index;
+ int disk_count;
+ struct list_head entry;
struct scsi_mpath_head *scsi_mpath_head;
};
static void sd_mpath_disk_release(struct device *dev)
{
+ struct sd_mpath_disk *sd_mpath_disk =
+ container_of(dev, struct sd_mpath_disk, dev);
+ struct scsi_mpath_head *scsi_mpath_head =
+ sd_mpath_disk->scsi_mpath_head;
+ struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
+
+ mpath_put_disk(mpath_head);
+ ida_free(&sd_index_ida, sd_mpath_disk->disk_index);
+ scsi_mpath_put_head(scsi_mpath_head);
+
+ kfree(sd_mpath_disk);
}
static const struct class sd_mpath_disk_class = {
@@ -817,7 +835,8 @@ static void scsi_disk_release(struct device *dev)
{
struct scsi_disk *sdkp = to_scsi_disk(dev);
- ida_free(&sd_index_ida, sdkp->index);
+ if (sdkp->index >= 0)
+ ida_free(&sd_index_ida, sdkp->index);
put_device(&sdkp->device->sdev_gendev);
free_opal_dev(sdkp->opal_dev);
@@ -4006,6 +4025,91 @@ static int sd_format_disk_name(char *prefix, int index, char *buf, int buflen)
return 0;
}
+#ifdef CONFIG_SCSI_MULTIPATH
+static int sd_mpath_revalidate_head(struct scsi_disk *sdkp)
+{
+ struct sd_mpath_disk *sd_mpath_disk = sdkp->sd_mpath_disk;
+ struct scsi_mpath_head *scsi_mpath_head = sd_mpath_disk->scsi_mpath_head;
+ struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
+ struct gendisk *disk = mpath_head->disk;
+ struct queue_limits *sdkp_lim = &sdkp->disk->queue->limits;
+ struct queue_limits lim;
+ unsigned int memflags;
+ int ret;
+
+ lim = queue_limits_start_update(disk->queue);
+ memflags = blk_mq_freeze_queue(disk->queue);
+
+ lim.logical_block_size = sdkp_lim->logical_block_size;
+ lim.physical_block_size = sdkp_lim->physical_block_size;
+ lim.io_min = sdkp_lim->io_min;
+ lim.io_opt = sdkp_lim->io_opt;
+
+ queue_limits_stack_bdev(&lim, sdkp->disk->part0, 0,
+ disk->disk_name);
+
+ /* TODO: setup integrity limits */
+ lim.max_write_streams = sdkp_lim->max_write_streams;
+ lim.write_stream_granularity = sdkp_lim->write_stream_granularity;
+ ret = queue_limits_commit_update(disk->queue, &lim);
+
+ set_capacity_and_notify(disk, get_capacity(sdkp->disk));
+
+ blk_mq_unfreeze_queue(disk->queue, memflags);
+
+ return ret;
+}
+static int sd_mpath_get_disk(struct sd_mpath_disk *sd_mpath_disk)
+{
+ if (!get_device(&sd_mpath_disk->dev))
+ return -ENXIO;
+ return 0;
+}
+
+static void sd_mpath_put_disk(struct sd_mpath_disk *sd_mpath_disk)
+{
+ put_device(&sd_mpath_disk->dev);
+}
+
+static struct sd_mpath_disk *sd_mpath_find_disk(
+ struct scsi_mpath_head *scsi_mpath_head)
+{
+ struct sd_mpath_disk *sd_mpath_disk;
+ int ret;
+
+ list_for_each_entry(sd_mpath_disk, &sd_mpath_disks_list, entry) {
+ ret = sd_mpath_get_disk(sd_mpath_disk);
+ if (ret)
+ continue;
+
+ if (sd_mpath_disk->scsi_mpath_head == scsi_mpath_head)
+ return sd_mpath_disk;
+
+ sd_mpath_put_disk(sd_mpath_disk);
+ }
+
+ return NULL;
+}
+
+static int sd_mpath_add_disk(struct scsi_disk *sdkp)
+{
+ struct scsi_device *sdp = sdkp->device;
+ struct scsi_mpath_device *scsi_mpath_dev = sdp->scsi_mpath_dev;
+ struct mpath_device *mpath_device = &scsi_mpath_dev->mpath_device;
+ struct sd_mpath_disk *sd_mpath_disk = sdkp->sd_mpath_disk;
+ struct scsi_mpath_head *scsi_mpath_head =
+ sd_mpath_disk->scsi_mpath_head;
+ struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
+ int ret;
+
+ ret = mpath_add_device(mpath_device, mpath_head, sdkp->disk,
+ dev_to_node(sdp->host->dma_dev), &sdp->host->mpath_nr_active);
+ if (ret)
+ return ret;
+ mpath_device_set_live(mpath_device);
+ return 0;
+}
+
static ssize_t sd_mpath_device_delayed_removal_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
@@ -4035,13 +4139,258 @@ static const struct attribute_group sd_mpath_disk_attr_group = {
.attrs = sd_mpath_disk_attrs,
};
-__maybe_unused
static const struct attribute_group *sd_mpath_disk_attr_groups[] = {
&sd_mpath_disk_attr_group,
&mpath_attr_group,
NULL
};
+static int sd_mpath_probe(struct scsi_disk *sdkp)
+{
+ struct scsi_device *sdp = sdkp->device;
+ struct scsi_mpath_device *scsi_mpath_dev = sdp->scsi_mpath_dev;
+ struct device *dma_dev = sdp->host->dma_dev;
+ struct scsi_mpath_head *scsi_mpath_head =
+ scsi_mpath_dev->scsi_mpath_head;
+ struct sd_mpath_disk *sd_mpath_disk;
+ struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
+ char disk_name[DISK_NAME_LEN - 2];
+ struct queue_limits lim;
+ struct gendisk *disk;
+ int error;
+
+ /*
+ * sd_mpath_disks_list is kept locked if no disk found.
+ * Otherwise an extra reference is taken.
+ */
+ mutex_lock(&sd_mpath_disks_lock);
+ sd_mpath_disk = sd_mpath_find_disk(scsi_mpath_head);
+ if (sd_mpath_disk) {
+ error = sized_strscpy(disk_name, mpath_head->disk->disk_name,
+ sizeof(disk_name));
+ if (error < 0) {
+ /*
+ * Should not happen as would fail for the same when
+ * allocating the sd_mpath_disk
+ */
+ sd_mpath_put_disk(sd_mpath_disk);
+ mutex_unlock(&sd_mpath_disks_lock);
+ return error;
+ }
+ sd_mpath_disk->disk_count++;
+ mutex_unlock(&sd_mpath_disks_lock);
+
+ goto found;
+ }
+
+ sd_mpath_disk = kzalloc(sizeof(*sd_mpath_disk), GFP_KERNEL);
+ if (!sd_mpath_disk) {
+ error = -ENOMEM;
+ goto out_unlock;
+ }
+
+ sd_mpath_disk->scsi_mpath_head = scsi_mpath_head;
+ device_initialize(&sd_mpath_disk->dev);
+ sd_mpath_disk->dev.class = &sd_mpath_disk_class;
+
+ blk_set_stacking_limits(&lim);
+ lim.dma_alignment = 3;
+ lim.features |= BLK_FEAT_IO_STAT | BLK_FEAT_NOWAIT |
+ BLK_FEAT_POLL | BLK_FEAT_ATOMIC_WRITES;
+
+ mpath_head->parent = &sd_mpath_disk->dev;
+ mpath_head->drv_module = THIS_MODULE;
+ mpath_head->disk_groups = sd_mpath_disk_attr_groups;
+ error = mpath_alloc_head_disk(mpath_head, &lim,
+ dev_to_node(dma_dev));
+ if (error)
+ goto out_free_disk;
+ disk = mpath_head->disk;
+
+ error = ida_alloc(&sd_index_ida, GFP_KERNEL);
+ if (error < 0) {
+ sdev_printk(KERN_WARNING, sdp, "sd_mpath_probe: memory exhausted.\n");
+ goto out_put_disk;
+ }
+ sd_mpath_disk->disk_index = error;
+ error = sd_format_disk_name("sd", sd_mpath_disk->disk_index,
+ disk->disk_name, DISK_NAME_LEN);
+ if (error)
+ goto out_free_index;
+
+ error = sized_strscpy(disk_name, mpath_head->disk->disk_name,
+ sizeof(disk_name));
+ if (error < 0)
+ goto out_free_index;
+
+ /* undone in sd_mpath_disk_release() */
+ scsi_mpath_get_head(scsi_mpath_head);
+
+ error = dev_set_name(&sd_mpath_disk->dev, "scsi_mpath_disk%d",
+ scsi_mpath_head->index);
+ if (error) {
+ put_device(&sd_mpath_disk->dev);
+ goto out_unlock;
+ }
+
+ error = device_add(&sd_mpath_disk->dev);
+ if (error) {
+ put_device(&sd_mpath_disk->dev);
+ goto out_unlock;
+ }
+
+ list_add_tail(&sd_mpath_disk->entry, &sd_mpath_disks_list);
+ disk->major = sd_major((sd_mpath_disk->disk_index & 0xf0) >> 4);
+ disk->first_minor = ((sd_mpath_disk->disk_index & 0xf) << 4) |
+ (sd_mpath_disk->disk_index & 0xfff00);
+ disk->minors = SD_MINORS;
+
+ sd_mpath_disk->disk_count = 1;
+ mutex_unlock(&sd_mpath_disks_lock);
+found:
+ sdkp->sd_mpath_disk = sd_mpath_disk;
+ sdkp->disk->flags |= GENHD_FL_HIDDEN;
+ snprintf(sdkp->disk->disk_name, DISK_NAME_LEN, "%s:%d",
+ disk_name, scsi_mpath_dev->index);
+
+ sdkp->index = -1;
+ return 0;
+
+out_free_index:
+ ida_free(&sd_index_ida, sd_mpath_disk->disk_index);
+out_put_disk:
+ mpath_put_disk(mpath_head);
+out_free_disk:
+ kfree(sd_mpath_disk);
+out_unlock:
+ mutex_unlock(&sd_mpath_disks_lock);
+ return error;
+}
+
+static void sd_mpath_remove(struct scsi_disk *sdkp)
+{
+ struct sd_mpath_disk *sd_mpath_disk = sdkp->sd_mpath_disk;
+ struct scsi_device *sdp = sdkp->device;
+ struct scsi_mpath_device *scsi_mpath_dev = sdp->scsi_mpath_dev;
+ struct mpath_device *mpath_device = &scsi_mpath_dev->mpath_device;
+ struct scsi_mpath_head *scsi_mpath_head = sd_mpath_disk->scsi_mpath_head;
+ struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
+ bool remove = false;
+
+ mpath_synchronize(mpath_head);
+
+ if (mpath_clear_current_path(mpath_device))
+ mpath_synchronize(mpath_head);
+
+ mpath_delete_device(mpath_device);
+
+ mutex_lock(&sd_mpath_disks_lock);
+ sd_mpath_disk->disk_count--;
+ if (!sd_mpath_disk->disk_count && mpath_can_remove_head(mpath_head)) {
+ list_del_init(&sd_mpath_disk->entry);
+ remove = true;
+ }
+ mutex_unlock(&sd_mpath_disks_lock);
+ mpath_remove_sysfs_link(mpath_device);
+ mpath_device->disk = NULL;
+
+ if (remove) {
+ device_del(&sd_mpath_disk->dev);
+ mpath_remove_disk(mpath_head);
+ }
+ sd_mpath_put_disk(sd_mpath_disk);
+}
+
+static void sd_mpath_remove_head(struct scsi_mpath_head *scsi_mpath_head)
+{
+ struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
+ struct sd_mpath_disk *sd_mpath_disk;
+ struct device *dev = &scsi_mpath_head->dev;
+
+ mutex_lock(&sd_mpath_disks_lock);
+ sd_mpath_disk = sd_mpath_find_disk(scsi_mpath_head);
+ if (!sd_mpath_disk) {
+ dev_warn(dev, "could not find mpath disk\n");
+ mutex_unlock(&sd_mpath_disks_lock);
+ return;
+ }
+
+ if (sd_mpath_disk->disk_count) {
+ dev_dbg(dev, "non-zero multipath disk count in removal\n");
+ sd_mpath_put_disk(sd_mpath_disk);
+ mutex_unlock(&sd_mpath_disks_lock);
+ return;
+ }
+
+ list_del_init(&sd_mpath_disk->entry);
+ mutex_unlock(&sd_mpath_disks_lock);
+
+ device_del(&sd_mpath_disk->dev);
+ mpath_remove_disk(mpath_head);
+ sd_mpath_put_disk(sd_mpath_disk);
+}
+
+/*
+ * Always calls for a failed probe, so we need to handle that some structures
+ * have not been setup.
+ */
+static void sd_mpath_fail_probe(struct scsi_disk *sdkp)
+{
+ struct sd_mpath_disk *sd_mpath_disk = sdkp->sd_mpath_disk;
+ struct scsi_mpath_device *scsi_mpath_dev;
+ struct mpath_device *mpath_device;
+ struct scsi_device *sdp = sdkp->device;
+ struct scsi_mpath_head *scsi_mpath_head;
+ struct mpath_head *mpath_head;
+ bool remove = false;
+
+ if (!sd_mpath_disk)
+ return;
+
+ scsi_mpath_dev = sdp->scsi_mpath_dev;
+ mpath_device = &scsi_mpath_dev->mpath_device;
+ scsi_mpath_head = sd_mpath_disk->scsi_mpath_head;
+ mpath_head = &scsi_mpath_head->mpath_head;
+
+ mutex_lock(&sd_mpath_disks_lock);
+ sd_mpath_disk->disk_count--;
+ if (!sd_mpath_disk->disk_count) {
+ list_del_init(&sd_mpath_disk->entry);
+ remove = true;
+ }
+ mutex_unlock(&sd_mpath_disks_lock);
+ mpath_device->disk = NULL;
+
+ if (remove) {
+ device_del(&sd_mpath_disk->dev);
+ mpath_remove_disk(mpath_head);
+ }
+ sd_mpath_put_disk(sd_mpath_disk);
+}
+
+#else /* CONFIG_SCSI_MULTIPATH */
+static int sd_mpath_probe(struct scsi_disk *sdkp)
+{
+ return 0;
+}
+static void sd_mpath_remove(struct scsi_disk *sdkp)
+{
+ return;
+}
+static void sd_mpath_fail_probe(struct scsi_disk *sdkp)
+{
+
+}
+static int sd_mpath_revalidate_head(struct scsi_disk *sdkp)
+{
+ return 0;
+}
+static int sd_mpath_add_disk(struct scsi_disk *sdkp)
+{
+ return 0;
+}
+#endif
+
/**
* sd_probe - called during driver initialization and whenever a
* new scsi device is attached to the system. It is called once
@@ -4065,7 +4414,7 @@ static int sd_probe(struct scsi_device *sdp)
struct device *dev = &sdp->sdev_gendev;
struct scsi_disk *sdkp;
struct gendisk *gd;
- int index;
+ int index = -1;
int error;
scsi_autopm_get_device(sdp);
@@ -4094,22 +4443,33 @@ static int sd_probe(struct scsi_device *sdp)
&sd_bio_compl_lkclass);
if (!gd)
goto out_free;
+ sdkp->disk = gd;
+ sdkp->device = sdp;
- index = ida_alloc(&sd_index_ida, GFP_KERNEL);
- if (index < 0) {
- sdev_printk(KERN_WARNING, sdp, "sd_probe: memory exhausted.\n");
- goto out_put;
- }
+ if (sdp->scsi_mpath_dev) {
+ error = sd_mpath_probe(sdkp);
+ if (error)
+ goto out_put;
+ } else {
+ index = ida_alloc(&sd_index_ida, GFP_KERNEL);
+ if (index < 0) {
+ sdev_printk(KERN_WARNING, sdp, "sd_probe: memory exhausted.\n");
+ goto out_put;
+ }
- error = sd_format_disk_name("sd", index, gd->disk_name, DISK_NAME_LEN);
- if (error) {
- sdev_printk(KERN_WARNING, sdp, "SCSI disk (sd) name length exceeded.\n");
- goto out_free_index;
+ error = sd_format_disk_name("sd", index, gd->disk_name,
+ DISK_NAME_LEN);
+ if (error) {
+ sdev_printk(KERN_WARNING, sdp, "SCSI disk (sd) name length exceeded.\n");
+ goto out_free_index;
+ }
+ sdkp->index = index;
+
+ gd->major = sd_major((index & 0xf0) >> 4);
+ gd->first_minor = ((index & 0xf) << 4) | (index & 0xfff00);
+ gd->minors = SD_MINORS;
}
- sdkp->device = sdp;
- sdkp->disk = gd;
- sdkp->index = index;
sdkp->max_retries = SD_MAX_RETRIES;
atomic_set(&sdkp->openers, 0);
atomic_set(&sdkp->device->ioerr_cnt, 0);
@@ -4129,6 +4489,7 @@ static int sd_probe(struct scsi_device *sdp)
error = device_add(&sdkp->disk_dev);
if (error) {
+ sd_mpath_fail_probe(sdkp);
put_device(&sdkp->disk_dev);
put_disk(gd);
goto out;
@@ -4136,10 +4497,6 @@ static int sd_probe(struct scsi_device *sdp)
dev_set_drvdata(dev, sdkp);
- gd->major = sd_major((index & 0xf0) >> 4);
- gd->first_minor = ((index & 0xf) << 4) | (index & 0xfff00);
- gd->minors = SD_MINORS;
-
gd->fops = &sd_fops;
gd->private_data = sdkp;
@@ -4158,11 +4515,18 @@ static int sd_probe(struct scsi_device *sdp)
sd_revalidate_disk(gd);
if (sdp->sector_size > PAGE_SIZE) {
if (sd_large_pool_create()) {
+ sd_mpath_fail_probe(sdkp);
error = -ENOMEM;
goto out_free_index;
}
}
+ if (sdp->scsi_mpath_dev) {
+ error = sd_mpath_revalidate_head(sdkp);
+ if (error)
+ sdev_printk(KERN_WARNING, sdp, "could not revalidate multipath limits\n");
+ }
+
if (sdp->removable) {
gd->flags |= GENHD_FL_REMOVABLE;
gd->events |= DISK_EVENT_MEDIA_CHANGE;
@@ -4177,6 +4541,7 @@ static int sd_probe(struct scsi_device *sdp)
error = device_add_disk(dev, gd, NULL);
if (error) {
+ sd_mpath_fail_probe(sdkp);
device_unregister(&sdkp->disk_dev);
put_disk(gd);
if (sdp->sector_size > PAGE_SIZE)
@@ -4184,6 +4549,19 @@ static int sd_probe(struct scsi_device *sdp)
goto out;
}
+ if (sdp->scsi_mpath_dev) {
+ error = sd_mpath_add_disk(sdkp);
+ if (error) {
+ sd_mpath_fail_probe(sdkp);
+ device_del(&sdkp->disk_dev);
+ del_gendisk(sdkp->disk);
+
+ put_disk(sdkp->disk);
+ if (sdp->sector_size > PAGE_SIZE)
+ sd_large_pool_destroy();
+ goto out;
+ }
+ }
if (sdkp->security) {
sdkp->opal_dev = init_opal_dev(sdkp, &sd_sec_submit);
if (sdkp->opal_dev)
@@ -4197,7 +4575,8 @@ static int sd_probe(struct scsi_device *sdp)
return 0;
out_free_index:
- ida_free(&sd_index_ida, index);
+ if (index >= 0)
+ ida_free(&sd_index_ida, index);
out_put:
put_disk(gd);
out_free:
@@ -4325,6 +4704,9 @@ static void sd_remove(struct scsi_device *sdp)
struct device *dev = &sdp->sdev_gendev;
struct scsi_disk *sdkp = dev_get_drvdata(dev);
+ if (sdp->scsi_mpath_dev)
+ sd_mpath_remove(sdkp);
+
scsi_autopm_get_device(sdkp->device);
device_del(&sdkp->disk_dev);
@@ -4493,6 +4875,9 @@ static struct scsi_driver sd_template = {
.resume = sd_resume,
.init_command = sd_init_command,
.uninit_command = sd_uninit_command,
+ #ifdef CONFIG_SCSI_MULTIPATH
+ .mpath_remove_head = sd_mpath_remove_head,
+ #endif
.done = sd_done,
.eh_action = sd_eh_action,
.eh_reset = sd_eh_reset,
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 26/28] scsi: sd: add mpath_dev file
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
` (24 preceding siblings ...)
2026-07-23 9:36 ` [PATCH v4 25/28] scsi: sd: support multipath disk John Garry
@ 2026-07-23 9:36 ` John Garry
2026-07-23 11:07 ` sashiko-bot
2026-07-23 9:36 ` [PATCH v4 27/28] scsi: sd: add mpath_numa_nodes dev attribute John Garry
2026-07-23 9:36 ` [PATCH v4 28/28] scsi: sd: add mpath_queue_depth " John Garry
27 siblings, 1 reply; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
Since per-path gendisk is hidden, we have no
Add a mpath_dev file so that the multipath disk can be looked up from
per-path gendisk directory.
The following is an example of this usage:
$ ls -l /dev/sdc
brw-rw---- 1 root disk 8, 32 Feb 24 16:08 /dev/sdc
$ cat /sys/class/scsi_mpath_disk/scsi_mpath_disk0/sdc/multipath/sdc:0/mpath_dev
8:32
This can be used by a util like lsscsi, which would find that the gendisk
for the per-path scsi_device is missing.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/sd.c | 51 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 50 insertions(+), 1 deletion(-)
diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
index d5a37b1b312c3..f8c9096e6c43c 100644
--- a/drivers/scsi/sd.c
+++ b/drivers/scsi/sd.c
@@ -4059,6 +4059,53 @@ static int sd_mpath_revalidate_head(struct scsi_disk *sdkp)
return ret;
}
+
+static ssize_t sd_mpath_dev_show(struct device *dev,
+ struct device_attribute *attr, char *page)
+{
+ struct gendisk *gd = dev_to_disk(dev);
+ struct scsi_disk *sdkp = gd->private_data;
+ struct scsi_device *sdev = sdkp->device;
+ struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
+ struct scsi_mpath_head *scsi_mpath_head = scsi_mpath_dev->scsi_mpath_head;
+ struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
+ struct gendisk *disk = mpath_head->disk;
+ struct device *disk_dev = disk_to_dev(disk);
+
+ return print_dev_t(page, disk_dev->devt);
+}
+static DEVICE_ATTR(mpath_dev, 0444, sd_mpath_dev_show, NULL);
+
+static struct attribute *sd_mpath_dev_attrs[] = {
+ &dev_attr_mpath_dev.attr,
+ NULL
+};
+
+static umode_t sd_mpath_dev_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int i)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct gendisk *gd = dev_to_disk(dev);
+ struct scsi_disk *sdkp = gd->private_data;
+ struct scsi_device *sdev = sdkp->device;
+ struct scsi_mpath_device *scsi_mpath_device = sdev->scsi_mpath_dev;
+
+ if (!scsi_mpath_device)
+ return 0;
+
+ return attr->mode;
+}
+
+static const struct attribute_group sd_mpath_dev_attr_group = {
+ .is_visible = sd_mpath_dev_attr_is_visible,
+ .attrs = sd_mpath_dev_attrs,
+};
+
+static const struct attribute_group *sd_mpath_dev_groups[] = {
+ &sd_mpath_dev_attr_group,
+ NULL
+};
+
static int sd_mpath_get_disk(struct sd_mpath_disk *sd_mpath_disk)
{
if (!get_device(&sd_mpath_disk->dev))
@@ -4389,6 +4436,8 @@ static int sd_mpath_add_disk(struct scsi_disk *sdkp)
{
return 0;
}
+
+#define sd_mpath_dev_groups NULL
#endif
/**
@@ -4539,7 +4588,7 @@ static int sd_probe(struct scsi_device *sdp)
sdp->host->rpm_autosuspend_delay);
}
- error = device_add_disk(dev, gd, NULL);
+ error = device_add_disk(dev, gd, sd_mpath_dev_groups);
if (error) {
sd_mpath_fail_probe(sdkp);
device_unregister(&sdkp->disk_dev);
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 27/28] scsi: sd: add mpath_numa_nodes dev attribute
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
` (25 preceding siblings ...)
2026-07-23 9:36 ` [PATCH v4 26/28] scsi: sd: add mpath_dev file John Garry
@ 2026-07-23 9:36 ` John Garry
2026-07-23 10:52 ` sashiko-bot
2026-07-23 9:36 ` [PATCH v4 28/28] scsi: sd: add mpath_queue_depth " John Garry
27 siblings, 1 reply; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
Add an attribute to show multipath NUMA node per-path (scsi_disk).
The following is an example of reading the file:
$ cat /sys/devices/platform/host8/session1/target8:0:0/8:0:0:0/block/sdc:0/numa_
mpath_numa_nodes
0-3
$ cat /sys/devices/platform/host9/session2/target9:0:0/9:0:0:0/block/sdc:1/numa_
mpath_numa_nodes
$
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/sd.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
index f8c9096e6c43c..7255df6e3e971 100644
--- a/drivers/scsi/sd.c
+++ b/drivers/scsi/sd.c
@@ -4076,8 +4076,22 @@ static ssize_t sd_mpath_dev_show(struct device *dev,
}
static DEVICE_ATTR(mpath_dev, 0444, sd_mpath_dev_show, NULL);
+static ssize_t sd_mpath_numa_nodes_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gendisk *gd = dev_to_disk(dev);
+ struct scsi_disk *sdkp = gd->private_data;
+ struct scsi_device *sdev = sdkp->device;
+ struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
+ struct mpath_device *mpath_device = &scsi_mpath_dev->mpath_device;
+
+ return mpath_numa_nodes_show(mpath_device, buf);
+}
+static DEVICE_ATTR(mpath_numa_nodes, 0444, sd_mpath_numa_nodes_show, NULL);
+
static struct attribute *sd_mpath_dev_attrs[] = {
&dev_attr_mpath_dev.attr,
+ &dev_attr_mpath_numa_nodes.attr,
NULL
};
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* [PATCH v4 28/28] scsi: sd: add mpath_queue_depth dev attribute
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
` (26 preceding siblings ...)
2026-07-23 9:36 ` [PATCH v4 27/28] scsi: sd: add mpath_numa_nodes dev attribute John Garry
@ 2026-07-23 9:36 ` John Garry
27 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 9:36 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
Add a queue_depth file so that the multipath dynamic queue depth can be
looked up from per-path gendisk (scsi_disk) directory.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/sd.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
index 7255df6e3e971..7b87404b70d6f 100644
--- a/drivers/scsi/sd.c
+++ b/drivers/scsi/sd.c
@@ -4089,9 +4089,27 @@ static ssize_t sd_mpath_numa_nodes_show(struct device *dev,
}
static DEVICE_ATTR(mpath_numa_nodes, 0444, sd_mpath_numa_nodes_show, NULL);
+static ssize_t sd_mpath_queue_depth_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gendisk *gd = dev_to_disk(dev);
+ struct scsi_disk *sdkp = gd->private_data;
+ struct scsi_device *sdev = sdkp->device;
+ struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
+ struct scsi_mpath_head *scsi_mpath_head = scsi_mpath_dev->scsi_mpath_head;
+ struct Scsi_Host *shost = sdev->host;
+
+ if (!mpath_qd_iopolicy(&scsi_mpath_head->iopolicy))
+ return 0;
+
+ return sysfs_emit(buf, "%d\n", atomic_read(&shost->mpath_nr_active));
+}
+static DEVICE_ATTR(mpath_queue_depth, 0444, sd_mpath_queue_depth_show, NULL);
+
static struct attribute *sd_mpath_dev_attrs[] = {
&dev_attr_mpath_dev.attr,
&dev_attr_mpath_numa_nodes.attr,
+ &dev_attr_mpath_queue_depth.attr,
NULL
};
--
2.43.7
^ permalink raw reply related [flat|nested] 74+ messages in thread
* Re: [PATCH v4 01/28] libmultipath: Add initial framework
2026-07-23 9:35 ` [PATCH v4 01/28] libmultipath: Add initial framework John Garry
@ 2026-07-23 9:50 ` sashiko-bot
2026-07-23 10:19 ` John Garry
0 siblings, 1 reply; 74+ messages in thread
From: sashiko-bot @ 2026-07-23 9:50 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The workqueue `mpath_wq` is allocated in `mpath_init()` but never used in the module.
--
--- Patch [1]: [PATCH v4 01/28] libmultipath: Add initial framework ---
commit 170d80e77c0bd0720941ff23ec89cb2a3960288f
Author: John Garry <john.g.garry@oracle.com>
libmultipath: Add initial framework
This commit adds an initial framework for libmultipath, a library for
multipath-capable block drivers. It includes basic support to add and remove
the head structure, mpath_head, and introduces the mpath_device structure.
> diff --git a/lib/multipath.c b/lib/multipath.c
> new file mode 100644
> index 0000000000000..9cc398d266adf
> --- /dev/null
> +++ b/lib/multipath.c
[ ... ]
> +static int __init mpath_init(void)
> +{
> + mpath_wq = alloc_workqueue("mpath-wq",
> + WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_SYSFS, 0);
[Severity: Low]
Is this workqueue meant to be used in this patch?
The workqueue mpath_wq is allocated in mpath_init() and destroyed in
mpath_exit(), but no work is ever queued to it. Does this allocate an unused
resource, or is it intended to be used by subsequent patches?
> + if (!mpath_wq)
> + return -ENOMEM;
> + return 0;
> +}
> +
> +static void __exit mpath_exit(void)
> +{
> + destroy_workqueue(mpath_wq);
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723093627.2327456-1-john.g.garry@oracle.com?part=1
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 03/28] libmultipath: Add path selection support
2026-07-23 9:36 ` [PATCH v4 03/28] libmultipath: Add path selection support John Garry
@ 2026-07-23 9:55 ` sashiko-bot
2026-07-23 10:28 ` John Garry
0 siblings, 1 reply; 74+ messages in thread
From: sashiko-bot @ 2026-07-23 9:55 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] The round-robin path selector can enter an infinite loop (SRCU lockup) if the currently selected path is removed concurrently.
- [Medium] Calling `list_is_singular()` on an SRCU-protected list introduces data races and state inconsistencies.
- [Low] Missing `WRITE_ONCE()` when updating `iopolicy` causes a data race with lockless readers.
--
--- Patch [3]: [PATCH v4 03/28] libmultipath: Add path selection support ---
commit 84fab111a99eb088e0b8abed39b8163220a9318d
Author: John Garry <john.g.garry@oracle.com>
libmultipath: Add path selection support
Add code for path selection. NVMe ANA is abstracted into enum
mpath_access_state so that SCSI ALUA can be used. Path selection modes
round-robin, NUMA, and queue-depth are added.
> diff --git a/lib/multipath.c b/lib/multipath.c
> index 916d01a285284..20bb8cb0ea956 100644
> --- a/lib/multipath.c
> +++ b/lib/multipath.c
[ ... ]
> +int mpath_set_iopolicy(const char *str, enum mpath_iopolicy_e *iopolicy)
> +{
> + int policy;
> +
> + if (!str)
> + return -EINVAL;
> + policy = mpath_iopolicy_parse(str);
> + if (policy < 0)
> + return policy;
> + *iopolicy = policy;
[Severity: Low]
Does this code need a WRITE_ONCE() for the assignment?
mpath_read_iopolicy() safely accesses this locklessly via READ_ONCE(), but
this plain store could cause a data race violating LKMM guidelines and
triggering KCSAN warnings.
> +
> + return 0;
> +}
[ ... ]
> +static struct mpath_device *mpath_round_robin_path(
> + struct mpath_head *mpath_head)
> +{
> + struct mpath_device *mpath_device, *found = NULL;
> + int node = numa_node_id();
> + enum mpath_access_state access_state_old;
> + struct mpath_device *old =
> + srcu_dereference(mpath_head->current_path[node],
> + &mpath_head->srcu);
> +
> + if (unlikely(!old))
> + return __mpath_find_path(mpath_head, node);
> +
> + if (list_is_singular(&mpath_head->dev_list)) {
> + if (mpath_path_is_disabled(mpath_head, old))
> + return NULL;
> + return old;
> + }
[Severity: Medium]
Is it safe to use list_is_singular() on an SRCU-protected list here?
list_is_singular() reads head->prev, which is a writer-only pointer in
RCU-protected lists, introducing a data race.
Additionally, if old was just concurrently unlinked leaving exactly one other
active device in the list, list_is_singular() evaluates to true. Could this
cause the function to incorrectly return the unlinked device instead of the
actual remaining device?
> +
> + for (mpath_device = mpath_next_dev(mpath_head, old);
> + mpath_device && mpath_device != old;
> + mpath_device = mpath_next_dev(mpath_head, mpath_device)) {
[Severity: High]
Can this loop become infinite if the currently selected path is removed
concurrently?
If a concurrent thread unlinks old from the list, mpath_next_dev() will never
reach mpath_device == old to break the loop. Since old is unlinked, traversal
might wrap around the circular list infinitely, causing a hard lockup within
the SRCU read-side critical section.
Unlike the NVMe equivalent nvme_round_robin_path(), which avoids this by
breaking early if it cycles back to a fallback node, this patch omits that
loop termination condition. If no other path is MPATH_STATE_OPTIMIZED, could
this run forever?
> +
> + if (mpath_path_is_disabled(mpath_head, mpath_device))
> + continue;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723093627.2327456-1-john.g.garry@oracle.com?part=3
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 06/28] libmultipath: Add delayed removal support
2026-07-23 9:36 ` [PATCH v4 06/28] libmultipath: Add delayed removal support John Garry
@ 2026-07-23 9:57 ` sashiko-bot
2026-07-23 10:33 ` John Garry
0 siblings, 1 reply; 74+ messages in thread
From: sashiko-bot @ 2026-07-23 9:57 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Concurrent path addition and delayed removal execution causes a use-after-free by destroying an active multipath head.
- [High] Missing synchronous cancellation of `remove_work` during `mpath_head` teardown leaks the delayed work.
- [Medium] Module reference count leak in `mpath_can_remove_head()` when called multiple times.
- [Medium] Passing a signed `int *` to `kstrtouint()` causes sign-extension and massively inflated timeouts.
--
--- Patch [6]: [PATCH v4 06/28] libmultipath: Add delayed removal support ---
commit e89d624119c3a1b6148c925347d05a0886a18e1d
Author: John Garry <john.garry@linux.dev>
libmultipath: Add delayed removal support
Add support for delayed removal, same as exists for NVMe. The purpose
of this feature is to keep the multipath disk and cdev present for
intermittent periods of no available path. A removal work callback is
supplied by the driver.
> diff --git a/include/linux/multipath.h b/include/linux/multipath.h
> index 6915c2a979642..44e26c16b3dc4 100644
> --- a/include/linux/multipath.h
> +++ b/include/linux/multipath.h
[ ... ]
> diff --git a/lib/multipath.c b/lib/multipath.c
> index 20c633ad7e1df..1fa4bd6677051 100644
> --- a/lib/multipath.c
> +++ b/lib/multipath.c
> @@ -65,6 +65,9 @@ int mpath_add_device(struct mpath_device *mpath_device,
> list_add_tail_rcu(&mpath_device->siblings, &mpath_head->dev_list);
> mutex_unlock(&mpath_head->lock);
>
> + if (cancel_delayed_work(&mpath_head->remove_work))
> + module_put(mpath_head->drv_module);
> +
[Severity: High]
Could this lead to a use-after-free if the delayed work is already executing?
When mpath_add_device() calls cancel_delayed_work():
lib/multipath.c:mpath_add_device() {
...
if (cancel_delayed_work(&mpath_head->remove_work))
module_put(mpath_head->drv_module);
return 0;
}
If the timer has already expired and mpath_remove_head_work() is actively
running, cancel_delayed_work() returns false and this function proceeds to
add the device and return success.
Concurrently, mpath_remove_head_work() executes mpdt->remove_head(mpath_head)
without taking mpath_head->lock, which destroys the multipath head while we
are adding a new path to it.
> return 0;
> }
[ ... ]
> @@ -480,6 +493,39 @@ static void mpath_requeue_work(struct work_struct *work)
> }
> }
>
> +bool mpath_can_remove_head(struct mpath_head *mpath_head)
> +{
> + bool remove = false;
> +
> + mutex_lock(&mpath_head->lock);
> + /*
> + * Ensure that no one could remove this module while the head
> + * remove work is pending.
> + */
> + if (mpath_head_queue_if_no_path(mpath_head) &&
> + try_module_get(mpath_head->drv_module)) {
> +
> + mod_delayed_work(mpath_wq, &mpath_head->remove_work,
> + mpath_head->delayed_removal_secs * HZ);
[Severity: Medium]
Is there a module reference leak if this is called multiple times while the
timer is already pending?
If mpath_can_remove_head() is called multiple times in succession,
try_module_get() acquires a new reference each time. However, if the work
is already queued, mod_delayed_work() returns true but does not queue a
duplicate work item.
When the timer expires, mpath_remove_head_work() executes only once and calls
module_put() only once, leaking the additional references acquired during
the subsequent calls.
> + } else {
> + remove = true;
> + }
[ ... ]
> @@ -524,6 +570,9 @@ int mpath_alloc_head_disk(struct mpath_head *mpath_head,
> mpath_head->disk->private_data = mpath_head;
> mpath_head->disk->fops = &mpath_ops;
>
> + INIT_DELAYED_WORK(&mpath_head->remove_work, mpath_remove_head_work);
> + mpath_head->delayed_removal_secs = 0;
> +
[Severity: High]
Should there be a matching cancellation of remove_work during the teardown
path?
The delayed work is initialized here, but mpath_put_disk() only flushes
requeue_work and partition_scan_work before putting the disk. If the
multipath device is torn down while the delayed removal timeout is still
pending, does this leak the active timer and result in a use-after-free
when mpath_remove_head_work() eventually executes on the freed memory?
> set_bit(GD_SUPPRESS_PART_SCAN, &mpath_head->disk->state);
>
> return 0;
[ ... ]
> @@ -567,6 +616,47 @@ void mpath_device_set_live(struct mpath_device *mpath_device)
> }
> EXPORT_SYMBOL_GPL(mpath_device_set_live);
[ ... ]
> +ssize_t mpath_delayed_removal_secs_store(struct mpath_head *mpath_head,
> + const char *buf, size_t count)
> +{
> + ssize_t ret;
> + int sec;
> +
> + ret = kstrtouint(buf, 0, &sec);
[Severity: Medium]
Does passing a signed integer to kstrtouint() cause sign-extension issues
for large values?
The variable sec is declared as a signed int, but kstrtouint() expects an
unsigned int pointer. If a user writes a value greater than or equal to
2147483648, kstrtouint() will parse it correctly, but it will be stored as
a negative value in the signed int.
When sec is later assigned to mpath_head->delayed_removal_secs (which is
an unsigned long), it will undergo sign extension on 64-bit platforms,
resulting in a massive positive timeout value.
> + if (ret < 0)
> + return ret;
> +
> + mutex_lock(&mpath_head->lock);
> + mpath_head->delayed_removal_secs = sec;
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723093627.2327456-1-john.g.garry@oracle.com?part=6
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 02/28] libmultipath: Add basic gendisk support
2026-07-23 9:36 ` [PATCH v4 02/28] libmultipath: Add basic gendisk support John Garry
@ 2026-07-23 9:57 ` sashiko-bot
2026-07-23 10:34 ` John Garry
0 siblings, 1 reply; 74+ messages in thread
From: sashiko-bot @ 2026-07-23 9:57 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] mpath_alloc_head_disk() leaves an ERR_PTR in mpath_head->disk on allocation failure, causing a kernel panic during cleanup.
- [High] mpath_head_init() fails to initialize multiple work items (remove_work, requeue_work), leading to kernel warnings and crashes.
- [High] The mpath_ops block_device_operations struct lacks a .submit_bio callback, causing device_add_disk() to fail unconditionally.
- [High] mpath_head_uninit() uses a synchronous wait for userspace file descriptors to close, deadlocking kernel unplug paths.
- [Medium] mpath_put_disk() claims to clean up pending bios but fails to schedule or flush requeue_work, leaking bios.
--
--- Patch [2]: [PATCH v4 02/28] libmultipath: Add basic gendisk support ---
commit 8b695b95ebd074db05fc7130622e9ffd48786c4d
Author: John Garry <john.g.garry@oracle.com>
libmultipath: Add basic gendisk support
This patch adds support to allocate and free a multipath gendisk. It
introduces the mpath_head_template to provide custom multipath functionality.
NVMe-like functions are added to manage multipath structures.
> diff --git a/lib/multipath.c b/lib/multipath.c
> index 9cc398d266adf..916d01a285284 100644
> --- a/lib/multipath.c
> +++ b/lib/multipath.c
> @@ -35,6 +35,102 @@ void mpath_head_uninit(struct mpath_head *mpath_head)
> }
> EXPORT_SYMBOL_GPL(mpath_head_uninit);
[Severity: High]
Does mpath_head_uninit() use a synchronous wait for userspace file
descriptors to close?
It appears this function contains:
wait_var_event(&mpath_head->refcount,
refcount_read(&mpath_head->refcount) == 1);
If userspace holds the block device open (for example via udev or
monitoring tools), this regression might block the kernel thread executing
the device removal forever in TASK_UNINTERRUPTIBLE, leading to a deadlock.
[ ... ]
> +const struct block_device_operations mpath_ops = {
> + .owner = THIS_MODULE,
> + .open = mpath_bdev_open,
> + .release = mpath_bdev_release,
> +};
[Severity: High]
Is it intentional to omit the .submit_bio callback here?
When mpath_device_set_live() is called, it triggers device_add_disk(),
which uses the block layer's __add_disk() function. This function verifies
that bio-based disks have a valid ->submit_bio pointer.
Since it is missing, this regression might unconditionally fail with
-EINVAL, preventing the multipath disk from being added to the system.
[ ... ]
> +void mpath_put_disk(struct mpath_head *mpath_head)
> +{
> + if (!mpath_head->disk)
> + return;
> +
> + /* make sure all pending bios are cleaned up */
> + flush_work(&mpath_head->partition_scan_work);
> + put_disk(mpath_head->disk);
> +}
[Severity: Medium]
Will pending bios actually be cleaned up without flushing requeue_work?
The comment indicates pending bios are handled, but drivers using the
requeue functionality might have bios stranded in mpath_head->requeue_list
unless mpath_head->requeue_work is also scheduled and flushed here.
[ ... ]
> +int mpath_alloc_head_disk(struct mpath_head *mpath_head,
> + struct queue_limits *lim, int numa_node)
> +{
> + if (!mpath_head->disk_groups || !mpath_head->parent)
> + return -EINVAL;
> +
> + mpath_head->disk = blk_alloc_disk(lim, numa_node);
> + if (IS_ERR(mpath_head->disk))
> + return PTR_ERR(mpath_head->disk);
[Severity: High]
Can this assignment leave an error pointer in mpath_head->disk on failure?
If blk_alloc_disk() fails, it returns an error pointer which is stored here
and not cleared before returning. Later, when standard teardown paths call
mpath_put_disk():
void mpath_put_disk(struct mpath_head *mpath_head)
{
if (!mpath_head->disk)
return;
The check will fail to catch the error pointer, which is then passed to
put_disk(). Could this regression result in a kernel panic when dereferenced?
[ ... ]
> +int mpath_head_init(struct mpath_head *mpath_head)
> +{
> + memset(mpath_head, 0, sizeof(*mpath_head));
> + mutex_init(&mpath_head->lock);
> + refcount_set(&mpath_head->refcount, 1);
> +
> + INIT_WORK(&mpath_head->partition_scan_work,
> + multipath_partition_scan_work);
[Severity: High]
Are there missing work initializations in this function?
While partition_scan_work is initialized, remove_work and requeue_work
from the mpath_head structure appear to be omitted.
When mpath_add_device() is called, it executes:
cancel_delayed_work(&mpath_head->remove_work)
Passing an uninitialized, zero-filled delayed_work structure into the
workqueue APIs can violate their contracts. Could this regression trigger
debug warnings or crashes in timer management code?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723093627.2327456-1-john.g.garry@oracle.com?part=2
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 05/28] libmultipath: Add support for mpath_device management
2026-07-23 9:36 ` [PATCH v4 05/28] libmultipath: Add support for mpath_device management John Garry
@ 2026-07-23 9:58 ` sashiko-bot
2026-07-23 10:36 ` John Garry
0 siblings, 1 reply; 74+ messages in thread
From: sashiko-bot @ 2026-07-23 9:58 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] mpath_delete_device fails to clear the device from the current_path array before calling mpath_synchronize, guaranteeing an RCU Use-After-Free.
- [Low] Duplicate function declaration of `mpath_synchronize`.
--
--- Patch [5]: [PATCH v4 05/28] libmultipath: Add support for mpath_device management ---
commit 729c9713b67dacb1c3c3c943a84db42ae105702d
Author: John Garry <john.g.garry@oracle.com>
libmultipath: Add support for mpath_device management
Add support to add or remove a mpath_device as a path. This implements
functionality similar to NVMe equivalents for path management.
> diff --git a/include/linux/multipath.h b/include/linux/multipath.h
> index 57d13eb1450cd..6915c2a979642 100644
> --- a/include/linux/multipath.h
> +++ b/include/linux/multipath.h
[ ... ]
> @@ -89,6 +92,17 @@ static inline enum mpath_iopolicy_e mpath_read_iopolicy(
> void mpath_synchronize(struct mpath_head *mpath_head);
> int mpath_set_iopolicy(const char *str, enum mpath_iopolicy_e *iopolicy);
> int mpath_get_iopolicy(char *buf, int iopolicy);
> +bool mpath_clear_current_path(struct mpath_device *mpath_device);
> +void mpath_synchronize(struct mpath_head *mpath_head);
[Severity: Low]
This isn't a bug, but should we remove this duplicate declaration of
mpath_synchronize() since it is already declared just a few lines above?
> +int mpath_add_device(struct mpath_device *mpath_device,
[ ... ]
> diff --git a/lib/multipath.c b/lib/multipath.c
> index 448d879abb354..20c633ad7e1df 100644
> --- a/lib/multipath.c
> +++ b/lib/multipath.c
[ ... ]
> @@ -50,6 +50,87 @@ void mpath_synchronize(struct mpath_head *mpath_head)
[ ... ]
> +bool mpath_delete_device(struct mpath_device *mpath_device)
> +{
> + bool empty;
> +
> + mutex_lock(&mpath_device->mpath_head->lock);
> + list_del_rcu(&mpath_device->siblings);
> + empty = list_empty(&mpath_device->mpath_head->dev_list);
> + mutex_unlock(&mpath_device->mpath_head->lock);
> +
> + mpath_synchronize(mpath_device->mpath_head);
[Severity: High]
Does this leave a dangling pointer in the current_path array that could
lead to a use-after-free?
Since the deleted device isn't cleared from mpath_head->current_path[],
new I/O submissions starting after mpath_synchronize() returns could still
read the device from current_path and use it after it's been freed.
If a caller tries to avoid this by calling mpath_clear_current_path()
before mpath_delete_device(), concurrent I/O could potentially re-select
the device from dev_list and place it back into current_path before the
deletion acquires the list lock.
Should mpath_clear_current_path() be called here, exactly between
list_del_rcu() and mpath_synchronize()?
> +
> + return empty;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723093627.2327456-1-john.g.garry@oracle.com?part=5
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 07/28] libmultipath: Add sysfs helpers
2026-07-23 9:36 ` [PATCH v4 07/28] libmultipath: Add sysfs helpers John Garry
@ 2026-07-23 10:05 ` sashiko-bot
2026-07-23 10:37 ` John Garry
0 siblings, 1 reply; 74+ messages in thread
From: sashiko-bot @ 2026-07-23 10:05 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Calling `kstrtouint()` with an `int *` argument causes a compiler warning and leads to sign-extension overflow when parsed.
--
--- Patch [7]: [PATCH v4 07/28] libmultipath: Add sysfs helpers ---
commit ebebf59df39aef065973319479be0a19b5308cb0
Author: John Garry <john.g.garry@oracle.com>
libmultipath: Add sysfs helpers
This adds helpers for driver sysfs code to get/set iopolicy, show device paths
per NUMA node, and manage multipath attribute groups.
> diff --git a/lib/multipath.c b/lib/multipath.c
> new file mode 100644
> index 0000000000000..3ff4f42a096c2
> --- /dev/null
> +++ b/lib/multipath.c
[ ... ]
> +ssize_t mpath_delayed_removal_secs_store(struct mpath_head *mpath_head,
> + const char *buf, size_t count)
> +{
> + ssize_t ret;
> + int sec;
> +
> + ret = kstrtouint(buf, 0, &sec);
[Severity: High]
Does kstrtouint() properly handle being passed a pointer to a signed int?
If a user writes a value >= 2147483648 to this sysfs attribute, could the
parsed value be interpreted as negative?
> + if (ret < 0)
> + return ret;
> +
> + mutex_lock(&mpath_head->lock);
> + mpath_head->delayed_removal_secs = sec;
[Severity: High]
If sec is evaluated as a negative number here, does this assignment
sign-extend the value into the unsigned long delayed_removal_secs field?
This could result in an extremely large 64-bit value, which might cause a
permanent resource leak of the multipath disk structures when
mod_delayed_work() delays the removal indefinitely.
> + if (sec)
> + set_bit(MPATH_HEAD_QUEUE_IF_NO_PATH, &mpath_head->flags);
> + else
> + clear_bit(MPATH_HEAD_QUEUE_IF_NO_PATH, &mpath_head->flags);
> + mutex_unlock(&mpath_head->lock);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723093627.2327456-1-john.g.garry@oracle.com?part=7
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 09/28] libmultipath: Add support for block device IOCTL
2026-07-23 9:36 ` [PATCH v4 09/28] libmultipath: Add support for block device IOCTL John Garry
@ 2026-07-23 10:09 ` sashiko-bot
2026-07-23 10:38 ` John Garry
0 siblings, 1 reply; 74+ messages in thread
From: sashiko-bot @ 2026-07-23 10:09 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] Bypass of partition-based security restrictions for block ioctls by passing the underlying whole-disk block device (part0) to the lower driver's ioctl.
- [High] Holding the SRCU read lock around the underlying block device IOCTL causes an ABBA deadlock if the IOCTL triggers or blocks on path removal.
--
--- Patch [9]: [PATCH v4 09/28] libmultipath: Add support for block device IOCTL ---
commit 8ca4b41fee0b6082cd169ad8577365e7f205461d
Author: John Garry <john.garry@linux.dev>
Subject: libmultipath: Add support for block device IOCTL
This commit adds mpath_bdev_ioctl() as a multipath block device ioctl
handler. It delegates the ioctls to the underlying path's block device
operations.
> diff --git a/lib/multipath.c b/lib/multipath.c
> index 0232f51f64742..fc8ba8fc501f1 100644
> --- a/lib/multipath.c
> +++ b/lib/multipath.c
[ ... ]
> @@ -478,11 +478,44 @@ static void mpath_bdev_release(struct gendisk *disk)
> mpath_put_head(mpath_head);
> }
>
> +static int mpath_bdev_ioctl(struct block_device *bdev, blk_mode_t mode,
> + unsigned int cmd, unsigned long arg)
> +{
> + struct gendisk *disk = bdev->bd_disk;
> + struct mpath_head *mpath_head = mpath_gendisk_to_head(disk);
> + struct mpath_device *mpath_device;
> + int srcu_idx, err;
> +
> + srcu_idx = srcu_read_lock(&mpath_head->srcu);
> + mpath_device = mpath_find_path(mpath_head);
> + if (!mpath_device) {
> + err = -EWOULDBLOCK;
> + goto out_unlock;
> + }
> +
> + if (!mpath_device->disk->fops->ioctl) {
> + err = -EOPNOTSUPP;
> + goto out_unlock;
> + }
> +
> + err = mpath_device->disk->fops->ioctl(
> + mpath_device->disk->part0, mode, cmd, arg);
[Severity: Critical]
Does passing part0 bypass partition-based security restrictions in the
underlying driver?
When an unprivileged user issues an ioctl on a multipath partition, this
code delegates it but passes the whole-disk block device (part0) instead of
the partition's block device.
Underlying drivers, such as NVMe via nvme_ioctl(), rely on
bdev_is_partition() to detect partition-level access and deny unprivileged
passthrough commands. Since part0 is passed, bdev_is_partition() evaluates
to false, tricking the lower driver into skipping containment checks.
Could this allow unprivileged users with access to a multipath partition to
execute passthrough commands on the entire underlying physical device?
[Severity: High]
Can holding the SRCU read lock across the underlying ioctl cause a deadlock?
If the underlying ioctl initiates a path removal, such as a passthrough
command that deletes an NVMe namespace or resets a controller, the teardown
code path will synchronously wait for SRCU readers to exit via
synchronize_srcu().
Since the ioctl thread still holds the SRCU read lock while waiting for the
command or removal to complete, synchronize_srcu() will block forever.
> +out_unlock:
> + srcu_read_unlock(&mpath_head->srcu, srcu_idx);
> + return err;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723093627.2327456-1-john.g.garry@oracle.com?part=9
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 12/28] scsi-multipath: introduce basic SCSI device support
2026-07-23 9:36 ` [PATCH v4 12/28] scsi-multipath: introduce basic SCSI device support John Garry
@ 2026-07-23 10:14 ` sashiko-bot
0 siblings, 0 replies; 74+ messages in thread
From: sashiko-bot @ 2026-07-23 10:14 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Typo in the SPDX License Identifier comment.
--
--- Patch [12]: [PATCH v4 12/28] scsi-multipath: introduce basic SCSI device support ---
commit c8c9718e45aaf5b29f96266700711505a59de724
Author: John Garry <john.g.garry@oracle.com>
scsi-multipath: introduce basic SCSI device support
This commit introduces basic native SCSI multipath support by adding the
scsi_mpath_device structure and a module parameter to control the feature.
It integrates multipath device allocation and release hooks into the SCSI
scanning and sysfs device release paths.
> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
> new file mode 100644
> index 0000000000000..b9b58a3641aa2
> --- /dev/null
> +++ b/drivers/scsi/scsi_multipath.c
> @@ -0,0 +1,146 @@
> +// SPDX-License-Indentifier: GPL-2.0
[Severity: Low]
This isn't a bug, but there is a typo in the SPDX license identifier
(Indentifier instead of Identifier). Will this cause automated SPDX
compliance tools like scripts/spdxcheck.py to fail to recognize the license
tag?
> +/*
> + * Copyright (c) 2026 Oracle Corp
> + *
> + */
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723093627.2327456-1-john.g.garry@oracle.com?part=12
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 08/28] libmultipath: Add mpath_bdev_report_zones()
2026-07-23 9:36 ` [PATCH v4 08/28] libmultipath: Add mpath_bdev_report_zones() John Garry
@ 2026-07-23 10:15 ` sashiko-bot
2026-07-23 10:39 ` John Garry
0 siblings, 1 reply; 74+ messages in thread
From: sashiko-bot @ 2026-07-23 10:15 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The multipath head disk fails to initialize `nr_zones`, `zone_capacity`, and `last_zone_capacity`, leaving them as 0.
--
--- Patch [8]: [PATCH v4 08/28] libmultipath: Add mpath_bdev_report_zones() ---
commit 62d62f772c0199a5085c139be78e6b1dcd14b410
Author: John Garry <john.garry@linux.dev>
libmultipath: Add mpath_bdev_report_zones()
This commit adds a multipath handler for block_device_operations.report_zones
to libmultipath.
> diff --git a/lib/multipath.c b/lib/multipath.c
> index 3ff4f42a096c2..0232f51f64742 100644
> --- a/lib/multipath.c
> +++ b/lib/multipath.c
> @@ -44,6 +44,30 @@ int mpath_get_iopolicy(char *buf, int iopolicy)
> }
> EXPORT_SYMBOL_GPL(mpath_get_iopolicy);
>
> +#ifdef CONFIG_BLK_DEV_ZONED
> +static int mpath_bdev_report_zones(struct gendisk *disk, sector_t sector,
> + unsigned int nr_zones, struct blk_report_zones_args *args)
[Severity: High]
With this function advertising report_zones capability to the block layer, are
the zoned attributes for the multipath head disk properly initialized?
If a zoned block device driver uses the libmultipath framework to create a
multipath head, path disks are added via mpath_add_device(). However, it
doesn't appear to propagate the zoned attributes (nr_zones, zone_capacity,
last_zone_capacity) from the underlying path to the head disk.
Since the head disk is bio-based without zone append emulation, the block
layer doesn't automatically trigger blk_revalidate_disk_zones() on it. Will
this leave these attributes at 0, potentially causing zoned filesystems or
userspace tools relying on bdev_nr_zones() and bdev_zone_capacity() to fail
or trigger division-by-zero errors when querying the multipath device?
> +{
> + struct mpath_head *mpath_head = mpath_gendisk_to_head(disk);
> + struct mpath_device *mpath_device;
> + int srcu_idx, ret = -EWOULDBLOCK;
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723093627.2327456-1-john.g.garry@oracle.com?part=8
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 13/28] scsi-multipath: introduce scsi_device head structure
2026-07-23 9:36 ` [PATCH v4 13/28] scsi-multipath: introduce scsi_device head structure John Garry
@ 2026-07-23 10:16 ` sashiko-bot
2026-07-23 10:47 ` John Garry
0 siblings, 1 reply; 74+ messages in thread
From: sashiko-bot @ 2026-07-23 10:16 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] `scsi_mpath_find_head()` prematurely aborts list search on dying heads, leading to split multipath topologies.
- [High] Double removal in `scsi_mpath_remove_device()` triggers a NULL pointer dereference.
- [High] Premature teardown of `scsi_mpath_head` causes a Use-After-Free on inflight bios.
- [Medium] The sysfs attribute `vpd_id` is exposed to userspace before it is initialized.
--
--- Patch [13]: [PATCH v4 13/28] scsi-multipath: introduce scsi_device head structure ---
commit 11c848a95f46ee71e21d42eee402d581f96c8055
Author: John Garry <john.garry@linux.dev>
scsi-multipath: introduce scsi_device head structure
Introduce a scsi_device head structure - scsi_mpath_head - to manage
multipathing for a scsi_device. This is similar to nvme_ns_head structure.
> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
> index b9b58a3641aa2..609f8009e4ac3 100644
> --- a/drivers/scsi/scsi_multipath.c
> +++ b/drivers/scsi/scsi_multipath.c
[ ... ]
> @@ -89,6 +141,71 @@ static int scsi_multipath_sdev_init(struct scsi_device *sdev)
> +static struct scsi_mpath_head *scsi_mpath_alloc_head(void)
> +{
[ ... ]
> + ret = device_add(&scsi_mpath_head->dev);
> + if (ret) {
> + put_device(&scsi_mpath_head->dev);
> + return NULL;
> + }
> +
[Severity: Medium]
Will calling device_add() here expose an uninitialized vpd_id string to
userspace when the uevent is fired?
The vpd_id is only populated later in scsi_mpath_dev_alloc() via strscpy().
Could a concurrently executing udev rule read an empty string before the
string has been properly set?
> + return scsi_mpath_head;
> +
> +out_uninit_head:
> + mpath_head_uninit(&scsi_mpath_head->mpath_head);
> +out_free:
> + kfree(scsi_mpath_head);
> + return NULL;
> +}
> +
> +static struct scsi_mpath_head *scsi_mpath_find_head(
> + struct scsi_mpath_device *scsi_mpath_dev)
> +{
> + struct scsi_mpath_head *scsi_mpath_head;
> +
> + list_for_each_entry(scsi_mpath_head, &scsi_mpath_heads_list, entry) {
> + if (strncmp(scsi_mpath_head->vpd_id,
> + scsi_mpath_dev->device_id_str,
> + SCSI_MPATH_DEVICE_ID_LEN) == 0) {
> + if (scsi_mpath_get_head(scsi_mpath_head))
> + return NULL;
[Severity: High]
If scsi_mpath_get_head() fails (for example, if the head is dying), does
returning NULL here prematurely abort the list search? Should this be a
continue statement so we don't skip over a valid head further down the
list?
> + return scsi_mpath_head;
> + }
> + }
> +
> + return NULL;
> +}
[ ... ]
> @@ -133,13 +290,47 @@ void scsi_mpath_dev_release(struct scsi_device *sdev)
> +void scsi_mpath_remove_device(struct scsi_mpath_device *scsi_mpath_dev)
> +{
> + struct scsi_mpath_head *scsi_mpath_head = scsi_mpath_dev->scsi_mpath_head;
> +
> + ida_free(&scsi_mpath_head->ida, scsi_mpath_dev->index);
> +
> + scsi_mpath_remove_head(scsi_mpath_dev);
> +}
> +
> void scsi_mpath_dev_release(struct scsi_device *sdev)
> {
> struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
>
> + if (scsi_mpath_dev)
> + scsi_mpath_remove_device(scsi_mpath_dev);
[Severity: High]
Is it possible for this to trigger a NULL pointer dereference if the
multipath device has already been detached?
In __scsi_remove_device(), we call scsi_mpath_remove_device() which sets
scsi_mpath_dev->scsi_mpath_head = NULL:
__scsi_remove_device()
scsi_mpath_remove_device(sdev->scsi_mpath_dev)
scsi_mpath_remove_head()
scsi_mpath_dev->scsi_mpath_head = NULL;
When the device release callback runs later, it will call
scsi_mpath_remove_device() again because scsi_mpath_dev is still non-NULL,
leading to a crash when dereferencing scsi_mpath_head.
> scsi_multipath_sdev_uninit(sdev);
> }
> diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
> index 2f80d703ce640..d6bbaf424bd4a 100644
> --- a/drivers/scsi/scsi_sysfs.c
> +++ b/drivers/scsi/scsi_sysfs.c
> @@ -1501,6 +1501,9 @@ void __scsi_remove_device(struct scsi_device *sdev)
> } else
> put_device(&sdev->sdev_dev);
>
> + if (sdev->scsi_mpath_dev)
> + scsi_mpath_remove_device(sdev->scsi_mpath_dev);
> +
[Severity: High]
Could tearing down the multipath head here trigger a use-after-free for
inflight bios?
If this is the last path, the scsi_mpath_head could be unregistered and
freed before the request queue is drained in blk_mq_destroy_queue() later
in this function. When an inflight bio completes, its end_io callback might
try to access the freed head.
Should the multipath head removal happen after the queue is drained?
> /*
> * Stop accepting new requests and wait until all queuecommand() and
> * scsi_run_queue() invocations have finished before tearing down the
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723093627.2327456-1-john.g.garry@oracle.com?part=13
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 01/28] libmultipath: Add initial framework
2026-07-23 9:50 ` sashiko-bot
@ 2026-07-23 10:19 ` John Garry
0 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 10:19 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 23/07/2026 10:50, sashiko-bot@kernel.org wrote:
>> +static int __init mpath_init(void)
>> +{
>> + mpath_wq = alloc_workqueue("mpath-wq",
>> + WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_SYSFS, 0);
> [Severity: Low]
> Is this workqueue meant to be used in this patch?
>
> The workqueue mpath_wq is allocated in mpath_init() and destroyed in
> mpath_exit(), but no work is ever queued to it. Does this allocate an unused
> resource, or is it intended to be used by subsequent patches?
Yes, it is used later..
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 15/28] scsi-multipath: support iopolicy
2026-07-23 9:36 ` [PATCH v4 15/28] scsi-multipath: support iopolicy John Garry
@ 2026-07-23 10:20 ` sashiko-bot
2026-07-23 10:51 ` John Garry
0 siblings, 1 reply; 74+ messages in thread
From: sashiko-bot @ 2026-07-23 10:20 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
New issues:
- [High] Use-after-free on requeue_work during device teardown after sysfs store.
- [Low] Data race on global iopolicy module parameter.
Pre-existing issues:
- [High] TOCTOU race exposing uninitialized sysfs attributes to userspace.
--
--- Patch [15]: [PATCH v4 15/28] scsi-multipath: support iopolicy ---
commit d1b32244eafe032033fe9c1b3cb04bb7a990a08a
Author: John Garry <john.g.garry@oracle.com>
scsi-multipath: support iopolicy
This commit adds support for configuring the multipath I/O policy. It
introduces a new iopolicy member to the scsi_mpath_head structure and adds a
module parameter to set the default policy.
> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
> index 59d1c0b2d3441..6228ff4be4ff7 100644
> --- a/drivers/scsi/scsi_multipath.c
> +++ b/drivers/scsi/scsi_multipath.c
> @@ -61,6 +61,23 @@ static const struct kernel_param_ops multipath_param_ops = {
> module_param_cb(multipath, &multipath_param_ops, &scsi_multipath, 0444);
> MODULE_PARM_DESC(multipath, "turn on native multipath support, options: on, off, always");
>
> +static enum mpath_iopolicy_e iopolicy = MPATH_IOPOLICY_NUMA;
> +
> +static int scsi_mpath_set_iopolicy_param(const char *val, const struct kernel_param *kp)
> +{
> + return mpath_set_iopolicy(val, &iopolicy);
> +}
[Severity: Low]
Does this introduce a data race on the global module parameter?
The global iopolicy variable is updated locklessly here, and read locklessly
when assigning it later in scsi_mpath_alloc_head(). Should this use
READ_ONCE() and WRITE_ONCE() to prevent KCSAN warnings and ensure proper
concurrency hygiene?
> +
> +static int scsi_mpath_get_iopolicy_param(char *buf, const struct kernel_param *kp)
> +{
> + return mpath_get_iopolicy(buf, iopolicy);
> +}
> +
> +module_param_call(multipath_iopolicy, scsi_mpath_set_iopolicy_param,
> + scsi_mpath_get_iopolicy_param, &iopolicy, 0644);
> +MODULE_PARM_DESC(multipath_iopolicy,
> + "Default multipath I/O policy; 'numa' (default), 'round-robin' or 'queue-depth'");
> +
[ ... ]
> @@ -96,8 +113,36 @@ static ssize_t scsi_mpath_device_vpd_id_show(struct device *dev,
> }
> static DEVICE_ATTR(vpd_id, S_IRUGO, scsi_mpath_device_vpd_id_show, NULL);
>
> +static ssize_t scsi_mpath_device_iopolicy_store(struct device *dev,
> + struct device_attribute *attr, const char *buf, size_t count)
> +{
> + struct scsi_mpath_head *scsi_mpath_head =
> + container_of(dev, struct scsi_mpath_head, dev);
> + struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
> +
> + if (!mpath_iopolicy_store(&scsi_mpath_head->iopolicy, buf))
> + return -EINVAL;
> +
> + mpath_clear_paths(mpath_head);
> + mpath_schedule_requeue_work(mpath_head);
[Severity: High]
Can mpath_schedule_requeue_work() trigger a use-after-free during device
teardown?
If a sysfs write occurs concurrently with device removal, the asynchronous
requeue work is scheduled here. The teardown path in scsi_mpath_head_release()
frees the scsi_mpath_head structure without calling cancel_work_sync() to
cancel this pending work.
Could the workqueue access freed memory when it executes the queued function?
> + return count;
> +}
> +
> +static ssize_t scsi_mpath_device_iopolicy_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
[ ... ]
> @@ -201,6 +246,8 @@ static struct scsi_mpath_head *scsi_mpath_alloc_head(void)
> if (mpath_head_init(&scsi_mpath_head->mpath_head))
> goto out_free;
> scsi_mpath_head->mpath_head.mpdt = &smpdt;
> + scsi_mpath_head->iopolicy = iopolicy;
> + scsi_mpath_head->mpath_head.iopolicy = &scsi_mpath_head->iopolicy;
>
> scsi_mpath_head->index = ida_alloc(&scsi_multipath_dev_ida, GFP_KERNEL);
> if (scsi_mpath_head->index < 0)
[Severity: High]
This is a pre-existing issue, but does this allocation path contain a race
condition exposing uninitialized sysfs attributes to userspace?
Later in scsi_mpath_alloc_head(), device_add() is called which registers the
device and fires a KOBJ_ADD uevent. However, the caller scsi_mpath_dev_alloc()
only populates the scsi_mpath_head->vpd_id data after scsi_mpath_alloc_head()
returns.
Could userspace udev rules react to the new device and read an empty vpd_id
string before it is fully initialized?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723093627.2327456-1-john.g.garry@oracle.com?part=15
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 16/28] scsi-multipath: clone each bio
2026-07-23 9:36 ` [PATCH v4 16/28] scsi-multipath: clone each bio John Garry
@ 2026-07-23 10:27 ` sashiko-bot
2026-07-23 10:55 ` John Garry
0 siblings, 1 reply; 74+ messages in thread
From: sashiko-bot @ 2026-07-23 10:27 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Critical] NULL pointer dereference due to missing mandatory template callbacks
- [High] Missing failover handling in clone completion
- [High] Bio allocation deadlock due to missing BIOSET_NEED_RESCUER
- [Medium] Memory leak due to missing ida_destroy()
--
--- Patch [16]: [PATCH v4 16/28] scsi-multipath: clone each bio ---
commit a74e2254f4409451e96518ce4d539e122559fac1
Author: John Garry <john.garry@linux.dev>
scsi-multipath: clone each bio
For failover handling, we will take the approach to resubmit each
bio.
However, unlike NVMe, for SCSI there is no guarantee that any bio submitted
is either all or none completed.
As such, for SCSI, for failover handling we will take the approach to
just re-submit the original bio. For this, clone and submit each bio.
> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
> index 6228ff4be4ff7..9a4b35c597778 100644
> --- a/drivers/scsi/scsi_multipath.c
> +++ b/drivers/scsi/scsi_multipath.c
> @@ -97,6 +97,7 @@ static void scsi_mpath_head_release(struct device *dev)
> container_of(dev, struct scsi_mpath_head, dev);
> struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
>
> + bioset_exit(&scsi_mpath_head->bio_pool);
> ida_free(&scsi_multipath_dev_ida, scsi_mpath_head->index);
> mpath_head_uninit(mpath_head);
> kfree(scsi_mpath_head);
[Severity: Medium]
Will this leak memory if the embedded IDA isn't destroyed?
Even though empty IDAs free their bitmaps, the XArray API contract requires
calling ida_destroy() or xa_destroy() to ensure all internally allocated
memory is fully released. Can we add a call to
ida_destroy(&scsi_mpath_head->ida) before freeing the structure?
[ ... ]
> @@ -229,7 +230,34 @@ static int scsi_multipath_sdev_init(struct scsi_device *sdev)
> return 0;
> }
>
> +static void scsi_mpath_clone_end_io(struct bio *clone)
> +{
> + struct bio *master_bio = clone->bi_private;
> +
> + master_bio->bi_status = clone->bi_status;
> + bio_put(clone);
> + bio_endio(master_bio);
> +}
[Severity: High]
Does this skip all multipath failover logic on error?
When an underlying SCSI path fails, the error status is unconditionally
propagated to the master bio and completed. Should this be checking
blk_path_error() and requeueing the master bio to the requeue_list for a
retry on an alternate path instead?
[ ... ]
> +static struct mpath_head_template smpdt = {
> + .clone_bio = scsi_mpath_clone_bio,
> };
[Severity: Critical]
Will this trigger a NULL pointer dereference?
The template omits the .is_disabled, .is_optimized, .available_path, and
.remove_head callbacks. The libmultipath core does not check if these are
NULL before calling them. For example, submitting any bio to the device
triggers __mpath_find_path() which calls:
lib/multipath.c:mpath_path_is_disabled() {
return mpath_head->mpdt->is_disabled(mpath_device);
}
Should these mandatory callbacks be implemented in the template?
>
> static struct scsi_mpath_head *scsi_mpath_alloc_head(void)
> @@ -252,6 +280,9 @@ static struct scsi_mpath_head *scsi_mpath_alloc_head(void)
> scsi_mpath_head->index = ida_alloc(&scsi_multipath_dev_ida, GFP_KERNEL);
> if (scsi_mpath_head->index < 0)
> goto out_uninit_head;
> + if (bioset_init(&scsi_mpath_head->bio_pool, BIO_POOL_SIZE,
> + 0, BIOSET_PERCPU_CACHE))
[Severity: High]
Could this cause a system deadlock?
The bioset is initialized without the BIOSET_NEED_RESCUER flag. Later,
scsi_mpath_clone_bio() allocates from this pool via bio_alloc_clone(). Since
this allocation occurs inside submit_bio_noacct(), an exhausted mempool will
block waiting for completions.
Without BIOSET_NEED_RESCUER, the block layer's deadlock avoidance cannot
intervene. Should BIOSET_NEED_RESCUER be added to the flags here?
> + goto out_ida_free;
> kref_init(&scsi_mpath_head->ref);
>
> device_initialize(&scsi_mpath_head->dev);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723093627.2327456-1-john.g.garry@oracle.com?part=16
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 03/28] libmultipath: Add path selection support
2026-07-23 9:55 ` sashiko-bot
@ 2026-07-23 10:28 ` John Garry
0 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 10:28 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 23/07/2026 10:55, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
> - [High] The round-robin path selector can enter an infinite loop (SRCU lockup) if the currently selected path is removed concurrently.
> - [Medium] Calling `list_is_singular()` on an SRCU-protected list introduces data races and state inconsistencies.
> - [Low] Missing `WRITE_ONCE()` when updating `iopolicy` causes a data race with lockless readers.
> --
>
> --- Patch [3]: [PATCH v4 03/28] libmultipath: Add path selection support ---
> commit 84fab111a99eb088e0b8abed39b8163220a9318d
> Author: John Garry <john.g.garry@oracle.com>
>
> libmultipath: Add path selection support
>
> Add code for path selection. NVMe ANA is abstracted into enum
> mpath_access_state so that SCSI ALUA can be used. Path selection modes
> round-robin, NUMA, and queue-depth are added.
>
>> diff --git a/lib/multipath.c b/lib/multipath.c
>> index 916d01a285284..20bb8cb0ea956 100644
>> --- a/lib/multipath.c
>> +++ b/lib/multipath.c
>
> [ ... ]
>
>> +int mpath_set_iopolicy(const char *str, enum mpath_iopolicy_e *iopolicy)
>> +{
>> + int policy;
>> +
>> + if (!str)
>> + return -EINVAL;
>> + policy = mpath_iopolicy_parse(str);
>> + if (policy < 0)
>> + return policy;
>> + *iopolicy = policy;
>
> [Severity: Low]
> Does this code need a WRITE_ONCE() for the assignment?
>
> mpath_read_iopolicy() safely accesses this locklessly via READ_ONCE(), but
> this plain store could cause a data race violating LKMM guidelines and
> triggering KCSAN warnings.
No, that would not be a problem since this is just at init time.
>
>> +
>> + return 0;
>> +}
>
> [ ... ]
>
>> +static struct mpath_device *mpath_round_robin_path(
>> + struct mpath_head *mpath_head)
>> +{
>> + struct mpath_device *mpath_device, *found = NULL;
>> + int node = numa_node_id();
>> + enum mpath_access_state access_state_old;
>> + struct mpath_device *old =
>> + srcu_dereference(mpath_head->current_path[node],
>> + &mpath_head->srcu);
>> +
>> + if (unlikely(!old))
>> + return __mpath_find_path(mpath_head, node);
>> +
>> + if (list_is_singular(&mpath_head->dev_list)) {
>> + if (mpath_path_is_disabled(mpath_head, old))
>> + return NULL;
>> + return old;
>> + }
>
> [Severity: Medium]
> Is it safe to use list_is_singular() on an SRCU-protected list here?
>
> list_is_singular() reads head->prev, which is a writer-only pointer in
> RCU-protected lists, introducing a data race.
>
> Additionally, if old was just concurrently unlinked leaving exactly one other
> active device in the list, list_is_singular() evaluates to true. Could this
> cause the function to incorrectly return the unlinked device instead of the
> actual remaining device?
Yes, this function is protected
>
>> +
>> + for (mpath_device = mpath_next_dev(mpath_head, old);
>> + mpath_device && mpath_device != old;
>> + mpath_device = mpath_next_dev(mpath_head, mpath_device)) {
>
> [Severity: High]
> Can this loop become infinite if the currently selected path is removed
> concurrently?
>
> If a concurrent thread unlinks old from the list, mpath_next_dev() will never
> reach mpath_device == old to break the loop. Since old is unlinked, traversal
> might wrap around the circular list infinitely, causing a hard lockup within
> the SRCU read-side critical section.
>
> Unlike the NVMe equivalent nvme_round_robin_path(), which avoids this by
> breaking early if it cycles back to a fallback node, this patch omits that
> loop termination condition. If no other path is MPATH_STATE_OPTIMIZED, could
> this run forever?
This functionally is same as nvme_round_robin_path()
>
>> +
>> + if (mpath_path_is_disabled(mpath_head, mpath_device))
>> + continue;
>
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 21/28] scsi-multipath: add scsi_mpath_{start,end}_request()
2026-07-23 9:36 ` [PATCH v4 21/28] scsi-multipath: add scsi_mpath_{start,end}_request() John Garry
@ 2026-07-23 10:32 ` sashiko-bot
2026-07-23 10:57 ` John Garry
0 siblings, 1 reply; 74+ messages in thread
From: sashiko-bot @ 2026-07-23 10:32 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] Unconditional dereference of `req->bio` and `req->bio->bi_private` leads to a kernel panic for requests lacking a valid block layer bio setup (like passthrough or empty flushes).
- [High] Resource leaks and state corruption due to multipath flags not being preserved across request requeue operations.
- [High] Use-after-free of the master bio structure (`master->bi_bdev`) after request completion.
--
--- Patch [21]: [PATCH v4 21/28] scsi-multipath: add scsi_mpath_{start,end}_request() ---
commit b237b1f6dbfe052243ff199e6fb756310c17d07b
Author: John Garry <john.garry@linux.dev>
scsi-multipath: add scsi_mpath_{start,end}_request()
Add scsi_mpath_{start,end}_request() to handle updating private multipath
request data, like nvme_mpath_{start,end}_request().
> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
> index e566d47199fcc..6c50fdde70d65 100644
> --- a/drivers/scsi/scsi_multipath.c
> +++ b/drivers/scsi/scsi_multipath.c
> @@ -555,6 +555,59 @@ void scsi_mpath_put_head(struct scsi_mpath_head *scsi_mpath_head)
> }
> EXPORT_SYMBOL_GPL(scsi_mpath_put_head);
>
> +void scsi_mpath_start_request(struct request *req)
> +{
> + struct scsi_cmnd *scmd = blk_mq_rq_to_pdu(req);
> + struct scsi_device *sdev = scmd->device;
> + struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
> + struct scsi_mpath_head *scsi_mpath_head =
> + scsi_mpath_dev->scsi_mpath_head;
> + struct bio *clone = req->bio;
> + struct bio *master = clone->bi_private;
[Severity: Critical]
Are we dereferencing a potentially invalid bio here?
If a request lacks a bio (like an empty flush) or is a passthrough request,
req->bio could be NULL, which would cause a panic when dereferencing
clone->bi_private.
> +
> + if (mpath_qd_iopolicy(&scsi_mpath_head->iopolicy) &&
> + !(scmd->flags & SCMD_MPATH_CNT_ACTIVE)) {
> + struct Scsi_Host *shost = sdev->host;
> +
> + atomic_inc(&shost->mpath_nr_active);
> + scmd->flags |= SCMD_MPATH_CNT_ACTIVE;
> + }
> +
> + if (!blk_queue_io_stat(master->bi_bdev->bd_queue) || blk_rq_is_passthrough(req) ||
[Severity: Critical]
Is the safety check for passthrough requests too late?
The master->bi_bdev pointer is dereferenced in the blk_queue_io_stat() check
before the blk_rq_is_passthrough(req) condition is evaluated.
Could this trigger a NULL pointer dereference for passthrough commands where
the bio wasn't cloned by multipath?
> + (scmd->flags & SCMD_MPATH_IO_STATS))
> + return;
> +
> + scmd->flags |= SCMD_MPATH_IO_STATS;
> + scmd->start_time = bdev_start_io_acct(master->bi_bdev, req_op(req),
> + jiffies);
> + scmd->start_bytes = blk_rq_bytes(req);
> +}
> +
> +bool scsi_mpath_end_request(struct request *req, blk_status_t error,
> + unsigned int nr_bytes)
> +{
> + struct scsi_cmnd *scmd = blk_mq_rq_to_pdu(req);
> + struct scsi_device *sdev = scmd->device;
> + struct bio *clone = req->bio, *master = clone->bi_private;
> +
> + if (blk_update_request(req, error, nr_bytes))
> + return true;
> +
> + if (scmd->flags & SCMD_MPATH_CNT_ACTIVE) {
> + struct Scsi_Host *shost = sdev->host;
> +
> + atomic_dec_if_positive(&shost->mpath_nr_active);
> + }
> +
> + if (!(scmd->flags & SCMD_MPATH_IO_STATS))
> + return false;
> + bdev_end_io_acct(master->bi_bdev, req_op(req),
[Severity: High]
Does this code introduce a use-after-free of the master bio structure?
When blk_update_request() finishes a request, it returns false and invokes
bio_endio(). For a cloned bio, this completes the master bio, which the
upper layers (like the filesystem) may immediately free.
Could accessing master->bi_bdev here trigger memory corruption or kernel
panics if the bio structure has already been freed?
> + scmd->start_bytes >> SECTOR_SHIFT,
> + scmd->start_time);
> +
> + return false;
> +}
[ ... ]
> diff --git a/include/scsi/scsi_cmnd.h b/include/scsi/scsi_cmnd.h
> index 8ecfb94049db5..3ff425c5f1e56 100644
> --- a/include/scsi/scsi_cmnd.h
> +++ b/include/scsi/scsi_cmnd.h
> @@ -60,6 +60,8 @@ struct scsi_pointer {
> #define SCMD_FAIL_IF_RECOVERING (1 << 4)
> /* flags preserved across unprep / reprep */
> #define SCMD_PRESERVED_FLAGS (SCMD_INITIALIZED | SCMD_FAIL_IF_RECOVERING)
> +#define SCMD_MPATH_IO_STATS (1 << 5)
> +#define SCMD_MPATH_CNT_ACTIVE (1 << 6)
[Severity: High]
Should these new multipath state flags be included in SCMD_PRESERVED_FLAGS?
In scsi_queue_rq(), cmd->flags is masked with SCMD_PRESERVED_FLAGS before
dispatching:
drivers/scsi/scsi_lib.c:scsi_queue_rq() {
...
cmd->flags &= SCMD_PRESERVED_FLAGS;
...
if (is_mpath_request(req))
scsi_mpath_start_request(req);
...
}
If a command fails to dispatch and is requeued, the flags would be cleared.
Could this cause scsi_mpath_start_request() to erroneously increment the
host's active request counter (mpath_nr_active) multiple times, leading to a
permanent leak when the request finally completes and only decrements it once?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723093627.2327456-1-john.g.garry@oracle.com?part=21
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 17/28] scsi-multipath: clear path when device is blocked
2026-07-23 9:36 ` [PATCH v4 17/28] scsi-multipath: clear path when device is blocked John Garry
@ 2026-07-23 10:33 ` sashiko-bot
2026-07-23 11:01 ` John Garry
0 siblings, 1 reply; 74+ messages in thread
From: sashiko-bot @ 2026-07-23 10:33 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Calling mpath_synchronize() while the block device queue is quiesced and sdev->state_mutex is held causes a circular dependency deadlock under IO load.
- [High] scsi_mpath_dev_clear_path() triggers a guaranteed NULL pointer dereference because mpath_device->mpath_head is never initialized in the SCSI multipath framework.
- [High] Lifecycle races during device removal, initialization, or error paths cause scsi_mpath_dev_clear_path() to calculate and dereference an invalid pointer or freed memory.
--
--- Patch [17]: [PATCH v4 17/28] scsi-multipath: clear path when device is blocked ---
commit 5b8022a9b2f8e87d8f78b7424101ac5ed615cc6f
Author: John Garry <john.garry@linux.dev>
scsi-multipath: clear path when device is blocked
Add scsi_mpath_dev_clear_path() to clear a device path when it becomes
blocked.
> diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
> --- a/drivers/scsi/scsi_lib.c
> +++ b/drivers/scsi/scsi_lib.c
[ ... ]
> @@ -2999,13 +3000,16 @@ static void scsi_device_block(struct scsi_device *sdev, void *data)
> mutex_lock(&sdev->state_mutex);
> err = __scsi_internal_device_block_nowait(sdev);
> state = sdev->sdev_state;
> - if (err == 0)
> + if (err == 0) {
> /*
> * scsi_stop_queue() must be called with the state_mutex
> * held. Otherwise a simultaneous scsi_start_queue() call
> * might unquiesce the queue before we quiesce it.
> */
> scsi_stop_queue(sdev);
> + if (sdev->scsi_mpath_dev)
> + scsi_mpath_dev_clear_path(sdev->scsi_mpath_dev);
[Severity: High]
Could this lead to a circular dependency deadlock under IO load?
When scsi_device_block() stops the queue via scsi_stop_queue(),
no further requests are dispatched. If it then calls
scsi_mpath_dev_clear_path(), it ends up calling mpath_synchronize(),
which executes synchronize_srcu() while sdev->state_mutex is still held.
Concurrently, a thread inside mpath_bdev_submit_bio() could be holding
the SRCU read lock:
lib/multipath.c:mpath_bdev_submit_bio()
srcu_idx = srcu_read_lock(&mpath_head->srcu);
...
submit_bio_noacct(bio);
If this submit_bio_noacct() path blocks on tag exhaustion because the
queue is quiesced, it will never release the SRCU read lock.
mpath_synchronize() would then block indefinitely waiting for the SRCU
reader. Any subsequent attempt to unquiesce the queue would require
sdev->state_mutex, which is held by the thread blocked in
mpath_synchronize().
> + }
>
> mutex_unlock(&sdev->state_mutex);
> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
> --- a/drivers/scsi/scsi_multipath.c
> +++ b/drivers/scsi/scsi_multipath.c
[ ... ]
> @@ -230,6 +230,16 @@ static int scsi_multipath_sdev_init(struct scsi_device *sdev)
> return 0;
> }
>
> +void scsi_mpath_dev_clear_path(struct scsi_mpath_device *scsi_mpath_dev)
> +{
> + struct mpath_device *mpath_device = &scsi_mpath_dev->mpath_device;
> + struct scsi_mpath_head *scsi_mpath_head = scsi_mpath_dev->scsi_mpath_head;
> + struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
[Severity: High]
Are there lifecycle races when dereferencing scsi_mpath_dev here?
The scsi_device_block() function calls this while holding state_mutex,
but pointers like scsi_mpath_dev->scsi_mpath_head are mutated locklessly
relative to state_mutex. For example, during removal,
scsi_mpath_remove_head() sets scsi_mpath_dev->scsi_mpath_head to NULL
before state_mutex is acquired in __scsi_remove_device().
If a concurrent device block event occurs while the head is NULL, computing
&scsi_mpath_head->mpath_head will yield an invalid pointer. Similarly,
initialization error paths free sdev->scsi_mpath_dev without holding
state_mutex, which could lead to a use-after-free if scsi_device_block()
observes a non-NULL pointer before the memory is freed.
> +
> + if (mpath_clear_current_path(mpath_device))
[Severity: High]
Is mpath_device->mpath_head guaranteed to be initialized before calling this?
The scsi_mpath_dev is allocated using kzalloc() in
scsi_multipath_sdev_init(), meaning the embedded mpath_device is
zero-initialized. The mpath_device->mpath_head pointer doesn't appear to be
explicitly assigned anywhere in the SCSI multipath framework.
If mpath_clear_current_path() is called, it executes:
lib/multipath.c:mpath_clear_current_path()
struct mpath_head *mpath_head = mpath_device->mpath_head;
...
if (mpath_device == rcu_access_pointer(mpath_head->current_path[node]))
Since mpath_device->mpath_head is NULL, wouldn't this unconditional
dereference result in an immediate panic whenever a SCSI multipath
device is blocked?
> + mpath_synchronize(mpath_head);
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723093627.2327456-1-john.g.garry@oracle.com?part=17
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 06/28] libmultipath: Add delayed removal support
2026-07-23 9:57 ` sashiko-bot
@ 2026-07-23 10:33 ` John Garry
0 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 10:33 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 23/07/2026 10:57, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
> - [High] Concurrent path addition and delayed removal execution causes a use-after-free by destroying an active multipath head.
> - [High] Missing synchronous cancellation of `remove_work` during `mpath_head` teardown leaks the delayed work.
> - [Medium] Module reference count leak in `mpath_can_remove_head()` when called multiple times.
> - [Medium] Passing a signed `int *` to `kstrtouint()` causes sign-extension and massively inflated timeouts.
> --
>
> --- Patch [6]: [PATCH v4 06/28] libmultipath: Add delayed removal support ---
> commit e89d624119c3a1b6148c925347d05a0886a18e1d
> Author: John Garry <john.garry@linux.dev>
>
> libmultipath: Add delayed removal support
>
> Add support for delayed removal, same as exists for NVMe. The purpose
> of this feature is to keep the multipath disk and cdev present for
> intermittent periods of no available path. A removal work callback is
> supplied by the driver.
>
>> diff --git a/include/linux/multipath.h b/include/linux/multipath.h
>> index 6915c2a979642..44e26c16b3dc4 100644
>> --- a/include/linux/multipath.h
>> +++ b/include/linux/multipath.h
>
> [ ... ]
>
>> diff --git a/lib/multipath.c b/lib/multipath.c
>> index 20c633ad7e1df..1fa4bd6677051 100644
>> --- a/lib/multipath.c
>> +++ b/lib/multipath.c
>> @@ -65,6 +65,9 @@ int mpath_add_device(struct mpath_device *mpath_device,
>> list_add_tail_rcu(&mpath_device->siblings, &mpath_head->dev_list);
>> mutex_unlock(&mpath_head->lock);
>>
>> + if (cancel_delayed_work(&mpath_head->remove_work))
>> + module_put(mpath_head->drv_module);
>> +
>
> [Severity: High]
> Could this lead to a use-after-free if the delayed work is already executing?
>
> When mpath_add_device() calls cancel_delayed_work():
>
> lib/multipath.c:mpath_add_device() {
> ...
> if (cancel_delayed_work(&mpath_head->remove_work))
> module_put(mpath_head->drv_module);
> return 0;
> }
>
> If the timer has already expired and mpath_remove_head_work() is actively
> running, cancel_delayed_work() returns false and this function proceeds to
> add the device and return success.
>
> Concurrently, mpath_remove_head_work() executes mpdt->remove_head(mpath_head)
> without taking mpath_head->lock, which destroys the multipath head while we
> are adding a new path to it.
That won't happen since mpdt->remove_head callback will ensure that the
device list is empty
>
>> return 0;
>> }
>
> [ ... ]
>
>> @@ -480,6 +493,39 @@ static void mpath_requeue_work(struct work_struct *work)
>> }
>> }
>>
>> +bool mpath_can_remove_head(struct mpath_head *mpath_head)
>> +{
>> + bool remove = false;
>> +
>> + mutex_lock(&mpath_head->lock);
>> + /*
>> + * Ensure that no one could remove this module while the head
>> + * remove work is pending.
>> + */
>> + if (mpath_head_queue_if_no_path(mpath_head) &&
>> + try_module_get(mpath_head->drv_module)) {
>> +
>> + mod_delayed_work(mpath_wq, &mpath_head->remove_work,
>> + mpath_head->delayed_removal_secs * HZ);
>
> [Severity: Medium]
> Is there a module reference leak if this is called multiple times while the
> timer is already pending?
>
> If mpath_can_remove_head() is called multiple times in succession,
that's not possible since the caller must ensure that this is only
called once, see sd_mpath_remove()
> try_module_get() acquires a new reference each time. However, if the work
> is already queued, mod_delayed_work() returns true but does not queue a
> duplicate work item.
>
> When the timer expires, mpath_remove_head_work() executes only once and calls
> module_put() only once, leaking the additional references acquired during
> the subsequent calls.
>
>> + } else {
>> + remove = true;
>> + }
>
> [ ... ]
>
>> @@ -524,6 +570,9 @@ int mpath_alloc_head_disk(struct mpath_head *mpath_head,
>> mpath_head->disk->private_data = mpath_head;
>> mpath_head->disk->fops = &mpath_ops;
>>
>> + INIT_DELAYED_WORK(&mpath_head->remove_work, mpath_remove_head_work);
>> + mpath_head->delayed_removal_secs = 0;
>> +
>
> [Severity: High]
> Should there be a matching cancellation of remove_work during the teardown
> path?
>
> The delayed work is initialized here, but mpath_put_disk() only flushes
> requeue_work and partition_scan_work before putting the disk. If the
> multipath device is torn down while the delayed removal timeout is still
> pending, does this leak the active timer and result in a use-after-free
> when mpath_remove_head_work() eventually executes on the freed memory?
This can't happen
>
>> set_bit(GD_SUPPRESS_PART_SCAN, &mpath_head->disk->state);
>>
>> return 0;
>
> [ ... ]
>
>> @@ -567,6 +616,47 @@ void mpath_device_set_live(struct mpath_device *mpath_device)
>> }
>> EXPORT_SYMBOL_GPL(mpath_device_set_live);
>
> [ ... ]
>
>> +ssize_t mpath_delayed_removal_secs_store(struct mpath_head *mpath_head,
>> + const char *buf, size_t count)
>> +{
>> + ssize_t ret;
>> + int sec;
>> +
>> + ret = kstrtouint(buf, 0, &sec);
>
> [Severity: Medium]
> Does passing a signed integer to kstrtouint() cause sign-extension issues
> for large values?
>
> The variable sec is declared as a signed int, but kstrtouint() expects an
> unsigned int pointer. If a user writes a value greater than or equal to
> 2147483648, kstrtouint() will parse it correctly, but it will be stored as
> a negative value in the signed int.
>
> When sec is later assigned to mpath_head->delayed_removal_secs (which is
> an unsigned long), it will undergo sign extension on 64-bit platforms,
> resulting in a massive positive timeout value.
will fix
>
>> + if (ret < 0)
>> + return ret;
>> +
>> + mutex_lock(&mpath_head->lock);
>> + mpath_head->delayed_removal_secs = sec;
>
> [ ... ]
>
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 02/28] libmultipath: Add basic gendisk support
2026-07-23 9:57 ` sashiko-bot
@ 2026-07-23 10:34 ` John Garry
0 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 10:34 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 23/07/2026 10:57, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
> - [High] mpath_alloc_head_disk() leaves an ERR_PTR in mpath_head->disk on allocation failure, causing a kernel panic during cleanup.
> - [High] mpath_head_init() fails to initialize multiple work items (remove_work, requeue_work), leading to kernel warnings and crashes.
> - [High] The mpath_ops block_device_operations struct lacks a .submit_bio callback, causing device_add_disk() to fail unconditionally.
> - [High] mpath_head_uninit() uses a synchronous wait for userspace file descriptors to close, deadlocking kernel unplug paths.
> - [Medium] mpath_put_disk() claims to clean up pending bios but fails to schedule or flush requeue_work, leaking bios.
> --
>
> --- Patch [2]: [PATCH v4 02/28] libmultipath: Add basic gendisk support ---
> commit 8b695b95ebd074db05fc7130622e9ffd48786c4d
> Author: John Garry <john.g.garry@oracle.com>
>
> libmultipath: Add basic gendisk support
>
> This patch adds support to allocate and free a multipath gendisk. It
> introduces the mpath_head_template to provide custom multipath functionality.
> NVMe-like functions are added to manage multipath structures.
>
>> diff --git a/lib/multipath.c b/lib/multipath.c
>> index 9cc398d266adf..916d01a285284 100644
>> --- a/lib/multipath.c
>> +++ b/lib/multipath.c
>> @@ -35,6 +35,102 @@ void mpath_head_uninit(struct mpath_head *mpath_head)
>> }
>> EXPORT_SYMBOL_GPL(mpath_head_uninit);
>
> [Severity: High]
> Does mpath_head_uninit() use a synchronous wait for userspace file
> descriptors to close?
>
> It appears this function contains:
> wait_var_event(&mpath_head->refcount,
> refcount_read(&mpath_head->refcount) == 1);
>
> If userspace holds the block device open (for example via udev or
> monitoring tools), this regression might block the kernel thread executing
> the device removal forever in TASK_UNINTERRUPTIBLE, leading to a deadlock.
>
Yes, then userspace needs to drop the reference
> [ ... ]
>
>> +const struct block_device_operations mpath_ops = {
>> + .owner = THIS_MODULE,
>> + .open = mpath_bdev_open,
>> + .release = mpath_bdev_release,
>> +};
>
> [Severity: High]
> Is it intentional to omit the .submit_bio callback here?
check the later patches
>
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 05/28] libmultipath: Add support for mpath_device management
2026-07-23 9:58 ` sashiko-bot
@ 2026-07-23 10:36 ` John Garry
0 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 10:36 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 23/07/2026 10:58, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [High] mpath_delete_device fails to clear the device from the current_path array before calling mpath_synchronize, guaranteeing an RCU Use-After-Free.
> - [Low] Duplicate function declaration of `mpath_synchronize`.
> --
>
> --- Patch [5]: [PATCH v4 05/28] libmultipath: Add support for mpath_device management ---
> commit 729c9713b67dacb1c3c3c943a84db42ae105702d
> Author: John Garry <john.g.garry@oracle.com>
>
> libmultipath: Add support for mpath_device management
>
> Add support to add or remove a mpath_device as a path. This implements
> functionality similar to NVMe equivalents for path management.
>
>> diff --git a/include/linux/multipath.h b/include/linux/multipath.h
>> index 57d13eb1450cd..6915c2a979642 100644
>> --- a/include/linux/multipath.h
>> +++ b/include/linux/multipath.h
> [ ... ]
>> @@ -89,6 +92,17 @@ static inline enum mpath_iopolicy_e mpath_read_iopolicy(
>> void mpath_synchronize(struct mpath_head *mpath_head);
>> int mpath_set_iopolicy(const char *str, enum mpath_iopolicy_e *iopolicy);
>> int mpath_get_iopolicy(char *buf, int iopolicy);
>> +bool mpath_clear_current_path(struct mpath_device *mpath_device);
>> +void mpath_synchronize(struct mpath_head *mpath_head);
>
> [Severity: Low]
> This isn't a bug, but should we remove this duplicate declaration of
> mpath_synchronize() since it is already declared just a few lines above?
will fix
>
>> +int mpath_add_device(struct mpath_device *mpath_device,
> [ ... ]
>> diff --git a/lib/multipath.c b/lib/multipath.c
>> index 448d879abb354..20c633ad7e1df 100644
>> --- a/lib/multipath.c
>> +++ b/lib/multipath.c
> [ ... ]
>> @@ -50,6 +50,87 @@ void mpath_synchronize(struct mpath_head *mpath_head)
> [ ... ]
>> +bool mpath_delete_device(struct mpath_device *mpath_device)
>> +{
>> + bool empty;
>> +
>> + mutex_lock(&mpath_device->mpath_head->lock);
>> + list_del_rcu(&mpath_device->siblings);
>> + empty = list_empty(&mpath_device->mpath_head->dev_list);
>> + mutex_unlock(&mpath_device->mpath_head->lock);
>> +
>> + mpath_synchronize(mpath_device->mpath_head);
>
> [Severity: High]
> Does this leave a dangling pointer in the current_path array that could
> lead to a use-after-free?
>
> Since the deleted device isn't cleared from mpath_head->current_path[],
> new I/O submissions starting after mpath_synchronize() returns could still
> read the device from current_path and use it after it's been freed.
>
> If a caller tries to avoid this by calling mpath_clear_current_path()
> before mpath_delete_device(), concurrent I/O could potentially re-select
> the device from dev_list and place it back into current_path before the
> deletion acquires the list lock.
>
> Should mpath_clear_current_path() be called here, exactly between
> list_del_rcu() and mpath_synchronize()?
sd_mpath_remove() calls mpath_clear_current_path() and then
mpath_delete_device()
>
>> +
>> + return empty;
>> +}
>
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 19/28] scsi-multipath: failover handling
2026-07-23 9:36 ` [PATCH v4 19/28] scsi-multipath: failover handling John Garry
@ 2026-07-23 10:36 ` sashiko-bot
2026-07-23 11:03 ` John Garry
0 siblings, 1 reply; 74+ messages in thread
From: sashiko-bot @ 2026-07-23 10:36 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] Missing bi_next clearing in bio_list_add_clone() leads to corrupted bio requeue list.
- [High] Releasing the SCSI device via scsi_device_put() causes a module reference count underflow due to mismatched API usage.
- [High] Uninitialized mpath_device->mpath_head leads to a guaranteed NULL pointer dereference during failover.
- [High] The scsi_mpath_clone_end_io handler dereferences mpath_head after it has been freed during path device removal.
- [High] Missing cancel_work_sync() allows requeue_work to execute after scsi_mpath_head is freed.
--
--- Patch [19]: [PATCH v4 19/28] scsi-multipath: failover handling ---
commit e8daaaa500b47bda767344af0f467a56fa0a6095
Author: John Garry <john.garry@linux.dev>
scsi-multipath: failover handling
Failover occurs when the scsi_cmnd has failed and it is discovered that the
target scsi_device has transport down. For a scsi command which suffers
failover, requeue the master bio of each bio attached to its request.
> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
> index b896d28a7dd9a..a482b6ede21aa 100644
> --- a/drivers/scsi/scsi_multipath.c
> +++ b/drivers/scsi/scsi_multipath.c
> @@ -248,11 +248,53 @@ void scsi_mpath_dev_clear_path(struct scsi_mpath_device *scsi_mpath_dev)
> mpath_synchronize(mpath_head);
> }
>
> +static inline void bio_list_add_clone(struct bio_list *bl,
> + struct bio *clone)
> +{
> + struct bio *master_bio = clone->bi_private;
> +
> + if (bl->tail)
> + bl->tail->bi_next = master_bio;
> + else
> + bl->head = master_bio;
> + bl->tail = master_bio;
[Severity: High]
Since master_bio is appended to the tail of the bio_list, shouldn't its
bi_next pointer be explicitly cleared to null to properly terminate the
singly-linked list?
When mpath_requeue_work() later iterates through the list, couldn't this
cause it to follow a stale bi_next pointer past the end of the list?
> + bio_put(clone);
> +}
> +
> static void scsi_mpath_clone_end_io(struct bio *clone)
> {
> struct bio *master_bio = clone->bi_private;
>
> master_bio->bi_status = clone->bi_status;
> +
> + if (clone->bi_status && blk_path_error(clone->bi_status)) {
> + struct block_device *bi_bdev = clone->bi_bdev;
> + struct request_queue *q = bi_bdev->bd_queue;
> + struct scsi_device *sdev = scsi_device_from_queue(q);
> + struct scsi_mpath_device *scsi_mpath_dev;
> + struct mpath_device *mpath_device;
> + struct mpath_head *mpath_head;
> + unsigned long flags;
> +
> + if (!sdev) {
> + master_bio->bi_status = clone->bi_status;
> + goto end_bio;
> + }
> +
> + scsi_mpath_dev = sdev->scsi_mpath_dev;
> + mpath_device = &scsi_mpath_dev->mpath_device;
> + mpath_head = mpath_device->mpath_head;
[Severity: High]
Is mpath_device->mpath_head guaranteed to be initialized here?
If scsi_mpath_dev_alloc() initializes scsi_mpath_dev with kzalloc but never
assigns this pointer, wouldn't this cause a null pointer dereference when
accessing requeue_lock below?
[Severity: High]
Could an in-flight I/O completion racing with path removal extract a freed
mpath_head here?
If scsi_mpath_remove_device() drops the reference to scsi_mpath_head and
frees it, but sdev->scsi_mpath_dev remains valid, it seems this could lead
to a use-after-free when accessing its requeue_lock.
> +
> + spin_lock_irqsave(&mpath_head->requeue_lock, flags);
> + bio_list_add_clone(&mpath_head->requeue_list, clone);
> + spin_unlock_irqrestore(&mpath_head->requeue_lock, flags);
> +
> + mpath_schedule_requeue_work(mpath_head);
[Severity: High]
When we schedule this requeue work, is there a corresponding cancel_work_sync()
during cleanup?
Looking at scsi_mpath_head_release(), it frees scsi_mpath_head without
cancelling or flushing mpath_head->requeue_work. Could this allow a
use-after-free if the background worker runs after the head is freed?
> + scsi_device_put(sdev);
[Severity: High]
Will this cause a module reference count underflow?
scsi_device_from_queue() only acquires a device reference via get_device(),
but scsi_device_put() drops both the device and module reference. Should
this use put_device(&sdev->sdev_gendev) instead?
> + return;
> + }
> +
> +end_bio:
> bio_put(clone);
> bio_endio(master_bio);
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723093627.2327456-1-john.g.garry@oracle.com?part=19
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 20/28] scsi-multipath: provide callbacks for path state
2026-07-23 9:36 ` [PATCH v4 20/28] scsi-multipath: provide callbacks for path state John Garry
@ 2026-07-23 10:36 ` sashiko-bot
2026-07-23 11:05 ` John Garry
0 siblings, 1 reply; 74+ messages in thread
From: sashiko-bot @ 2026-07-23 10:36 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] ALUA access state is not synchronized with the core multipath library, causing path selection to incorrectly treat non-optimized paths as optimal and resulting in an infinite path re-evaluation loop.
- [High] Missing ALUA path activation and state change notifications cause indefinite I/O hangs when only STANDBY, TRANSITIONING, or LBA-dependent paths are available.
- [Medium] Missing READ_ONCE() on concurrent sdev->sdev_state access can cause TOCTOU vulnerabilities via compiler reloads.
--
--- Patch [20]: [PATCH v4 20/28] scsi-multipath: provide callbacks for path state ---
commit 0d49283b5a1edcf6983ec33d9d54dad0023fe90c
Author: John Garry <john.g.garry@oracle.com>
scsi-multipath: provide callbacks for path state
This commit provides callbacks for .is_disabled, .is_optimized, and
.available_path. These all use scsi_device.sdev_state and
scsi_device.access_state, which will be driven by ALUA.
> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
> index a482b6ede21aa..e566d47199fcc 100644
> --- a/drivers/scsi/scsi_multipath.c
> +++ b/drivers/scsi/scsi_multipath.c
> @@ -316,7 +316,56 @@ static struct bio *scsi_mpath_clone_bio(struct bio *bio)
> return clone;
> }
>
> +static bool scsi_mpath_is_disabled(struct mpath_device *mpath_device)
> +{
> + struct scsi_mpath_device *scsi_mpath_dev =
> + to_scsi_mpath_device(mpath_device);
> + struct scsi_device *sdev = scsi_mpath_dev->sdev;
> + unsigned char access_state = READ_ONCE(sdev->access_state) &
> + SCSI_ACCESS_STATE_MASK;
> +
> + if (sdev->sdev_state != SDEV_RUNNING)
> + return true;
[Severity: Medium]
Should sdev->sdev_state be read with READ_ONCE() here to avoid compiler
reloads, similar to how access_state is read above?
> +
> + if (access_state == SCSI_ACCESS_STATE_OPTIMAL ||
> + access_state == SCSI_ACCESS_STATE_ACTIVE)
> + return false;
> +
> + return true;
[Severity: High]
If a path is in ALUA STANDBY or TRANSITIONING state, it is treated as disabled
and skipped during path selection.
When all paths are in these states, scsi_mpath_available_path() will return
true since the state is SDEV_RUNNING, causing the block layer to requeue the
I/O.
Does this logic ever call scsi_dh_activate() or trigger an implicit ALUA
activation to transition these standby paths to optimal? Could this result in
an indefinite I/O hang with the requeue list looping forever?
> +}
> +
> +static bool scsi_mpath_is_optimized(struct mpath_device *mpath_device)
> +{
> + struct scsi_mpath_device *scsi_mpath_dev =
> + to_scsi_mpath_device(mpath_device);
> + struct scsi_device *sdev = scsi_mpath_dev->sdev;
> + unsigned char access_state = READ_ONCE(sdev->access_state) &
> + SCSI_ACCESS_STATE_MASK;
> +
> + if (sdev->sdev_state != SDEV_RUNNING)
> + return false;
[Severity: Medium]
Should this unlocked read of sdev->sdev_state also use READ_ONCE()?
> +
> + return access_state == SCSI_ACCESS_STATE_OPTIMAL;
[Severity: High]
Does this dynamic check of the ALUA state conflict with the statically
initialized access state in the core multipath library?
If an I/O is submitted to an ALUA Active/Non-Optimized path, won't the core
reject the cached path, fall back to __mpath_find_path() which uses the
static state, and continuously re-cache the exact same path? Could this
result in a perpetual cache miss and an infinite path re-evaluation loop on
every submission?
> +}
> +
> +static bool scsi_mpath_available_path(struct mpath_device *mpath_device)
> +{
> + struct scsi_mpath_device *scsi_mpath_dev =
> + to_scsi_mpath_device(mpath_device);
> + struct scsi_device *sdev = scsi_mpath_dev->sdev;
> + enum scsi_device_state sdev_state = sdev->sdev_state;
> +
> + if (sdev_state == SDEV_RUNNING || sdev_state == SDEV_QUIESCE ||
> + sdev_state == SDEV_BLOCK || sdev_state == SDEV_CREATED_BLOCK)
> + return true;
[Severity: Medium]
Since sdev_state is a local variable assigned via a plain read, can the
compiler reload it from memory for each condition in the if statement?
If the state changes concurrently between SDEV_BLOCK and SDEV_RUNNING, could
all checks evaluate to false even if the state is always one of the valid
values? Should this use READ_ONCE()?
> +
> + return false;
> +}
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723093627.2327456-1-john.g.garry@oracle.com?part=20
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 07/28] libmultipath: Add sysfs helpers
2026-07-23 10:05 ` sashiko-bot
@ 2026-07-23 10:37 ` John Garry
0 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 10:37 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 23/07/2026 11:05, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [High] Calling `kstrtouint()` with an `int *` argument causes a compiler warning and leads to sign-extension overflow when parsed.
> --
>
> --- Patch [7]: [PATCH v4 07/28] libmultipath: Add sysfs helpers ---
> commit ebebf59df39aef065973319479be0a19b5308cb0
> Author: John Garry <john.g.garry@oracle.com>
>
> libmultipath: Add sysfs helpers
>
> This adds helpers for driver sysfs code to get/set iopolicy, show device paths
> per NUMA node, and manage multipath attribute groups.
>
>> diff --git a/lib/multipath.c b/lib/multipath.c
>> new file mode 100644
>> index 0000000000000..3ff4f42a096c2
>> --- /dev/null
>> +++ b/lib/multipath.c
>
> [ ... ]
>
>> +ssize_t mpath_delayed_removal_secs_store(struct mpath_head *mpath_head,
>> + const char *buf, size_t count)
>> +{
>> + ssize_t ret;
>> + int sec;
>> +
>> + ret = kstrtouint(buf, 0, &sec);
>
> [Severity: High]
> Does kstrtouint() properly handle being passed a pointer to a signed int?
> If a user writes a value >= 2147483648 to this sysfs attribute, could the
> parsed value be interpreted as negative?
already commented on
>
>> + if (ret < 0)
>> + return ret;
>> +
>> + mutex_lock(&mpath_head->lock);
>> + mpath_head->delayed_removal_secs = sec;
>
> [Severity: High]
> If sec is evaluated as a negative number here, does this assignment
> sign-extend the value into the unsigned long delayed_removal_secs field?
>
> This could result in an extremely large 64-bit value, which might cause a
> permanent resource leak of the multipath disk structures when
> mod_delayed_work() delays the removal indefinitely.
>
>> + if (sec)
>> + set_bit(MPATH_HEAD_QUEUE_IF_NO_PATH, &mpath_head->flags);
>> + else
>> + clear_bit(MPATH_HEAD_QUEUE_IF_NO_PATH, &mpath_head->flags);
>> + mutex_unlock(&mpath_head->lock);
>
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 09/28] libmultipath: Add support for block device IOCTL
2026-07-23 10:09 ` sashiko-bot
@ 2026-07-23 10:38 ` John Garry
0 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 10:38 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 23/07/2026 11:09, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [Critical] Bypass of partition-based security restrictions for block ioctls by passing the underlying whole-disk block device (part0) to the lower driver's ioctl.
> - [High] Holding the SRCU read lock around the underlying block device IOCTL causes an ABBA deadlock if the IOCTL triggers or blocks on path removal.
> --
>
> --- Patch [9]: [PATCH v4 09/28] libmultipath: Add support for block device IOCTL ---
> commit 8ca4b41fee0b6082cd169ad8577365e7f205461d
> Author: John Garry <john.garry@linux.dev>
> Subject: libmultipath: Add support for block device IOCTL
>
> This commit adds mpath_bdev_ioctl() as a multipath block device ioctl
> handler. It delegates the ioctls to the underlying path's block device
> operations.
>
>> diff --git a/lib/multipath.c b/lib/multipath.c
>> index 0232f51f64742..fc8ba8fc501f1 100644
>> --- a/lib/multipath.c
>> +++ b/lib/multipath.c
>
> [ ... ]
>
>> @@ -478,11 +478,44 @@ static void mpath_bdev_release(struct gendisk *disk)
>> mpath_put_head(mpath_head);
>> }
>>
>> +static int mpath_bdev_ioctl(struct block_device *bdev, blk_mode_t mode,
>> + unsigned int cmd, unsigned long arg)
>> +{
>> + struct gendisk *disk = bdev->bd_disk;
>> + struct mpath_head *mpath_head = mpath_gendisk_to_head(disk);
>> + struct mpath_device *mpath_device;
>> + int srcu_idx, err;
>> +
>> + srcu_idx = srcu_read_lock(&mpath_head->srcu);
>> + mpath_device = mpath_find_path(mpath_head);
>> + if (!mpath_device) {
>> + err = -EWOULDBLOCK;
>> + goto out_unlock;
>> + }
>> +
>> + if (!mpath_device->disk->fops->ioctl) {
>> + err = -EOPNOTSUPP;
>> + goto out_unlock;
>> + }
>> +
>> + err = mpath_device->disk->fops->ioctl(
>> + mpath_device->disk->part0, mode, cmd, arg);
>
> [Severity: Critical]
will check
> Does passing part0 bypass partition-based security restrictions in the
> underlying driver?
>
> When an unprivileged user issues an ioctl on a multipath partition, this
> code delegates it but passes the whole-disk block device (part0) instead of
> the partition's block device.
>
> Underlying drivers, such as NVMe via nvme_ioctl(), rely on
> bdev_is_partition() to detect partition-level access and deny unprivileged
> passthrough commands. Since part0 is passed, bdev_is_partition() evaluates
> to false, tricking the lower driver into skipping containment checks.
>
> Could this allow unprivileged users with access to a multipath partition to
> execute passthrough commands on the entire underlying physical device?
>
> [Severity: High]
> Can holding the SRCU read lock across the underlying ioctl cause a deadlock?
>
that's not an issue for SCSI, and will solve later for NVMe
> If the underlying ioctl initiates a path removal, such as a passthrough
> command that deletes an NVMe namespace or resets a controller, the teardown
> code path will synchronously wait for SRCU readers to exit via
> synchronize_srcu().
>
> Since the ioctl thread still holds the SRCU read lock while waiting for the
> command or removal to complete, synchronize_srcu() will block forever.
>
>> +out_unlock:
>> + srcu_read_unlock(&mpath_head->srcu, srcu_idx);
>> + return err;
>> +}
>
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 18/28] scsi-multipath: revalidate paths upon device unblock
2026-07-23 9:36 ` [PATCH v4 18/28] scsi-multipath: revalidate paths upon device unblock John Garry
@ 2026-07-23 10:39 ` sashiko-bot
2026-07-23 11:15 ` John Garry
0 siblings, 1 reply; 74+ messages in thread
From: sashiko-bot @ 2026-07-23 10:39 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] The newly added call to `scsi_mpath_revalidate_paths()` in `scsi_internal_device_unblock()` races with device initialization and teardown, leading to a NULL pointer dereference or Use-After-Free.
- [High] Missing cancellation of `mpath_head->requeue_work` causes use-after-free when `scsi_mpath_head` is freed.
- [Medium] Path revalidation is skipped for devices unblocked via `scsi_internal_device_unblock_nowait()`.
--
--- Patch [18]: [PATCH v4 18/28] scsi-multipath: revalidate paths upon device unblock ---
commit b434ea17223b4b11754a84ac3b62a1af5b823144
Author: John Garry <john.garry@linux.dev>
scsi-multipath: revalidate paths upon device unblock
This patch adds functionality to revalidate paths when a scsi_device is
unblocked. It introduces scsi_mpath_revalidate_paths() to clear paths and
requeue bios, and hooks it into scsi_internal_device_unblock().
> diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
> index f2729c0969801..d98c3239c13ad 100644
> --- a/drivers/scsi/scsi_lib.c
> +++ b/drivers/scsi/scsi_lib.c
> @@ -3092,6 +3092,8 @@ static int scsi_internal_device_unblock(struct scsi_device *sdev,
>
> mutex_lock(&sdev->state_mutex);
> ret = scsi_internal_device_unblock_nowait(sdev, new_state);
> + if ((ret == 0) && sdev->scsi_mpath_dev)
> + scsi_mpath_revalidate_paths(sdev->scsi_mpath_dev);
[Severity: High]
Can this lead to a use-after-free or NULL pointer dereference during device
removal?
If __scsi_remove_device() transitions the device to SDEV_CANCEL and unlocks
state_mutex, it then locklessly calls scsi_mpath_remove_device() which sets
scsi_mpath_head to NULL and frees it.
If scsi_internal_device_unblock() runs concurrently,
scsi_internal_device_unblock_nowait() will return 0 (success) because the
device state is SDEV_CANCEL.
Does this cause the unblock path to mistakenly proceed and call
scsi_mpath_revalidate_paths(), dereferencing the freed or NULL
scsi_mpath_head?
[Severity: Medium]
Are there callers that bypass scsi_internal_device_unblock() and call
scsi_internal_device_unblock_nowait() directly?
If drivers directly call the nowait variant to unblock a device, it seems
they will successfully transition the device to SDEV_RUNNING but skip this
new path revalidation. Will this leave their queued bios stuck?
> mutex_unlock(&sdev->state_mutex);
>
> return ret;
> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
> index 6a2b06c148c1a..b896d28a7dd9a 100644
> --- a/drivers/scsi/scsi_multipath.c
> +++ b/drivers/scsi/scsi_multipath.c
> [ ... ]
> @@ -230,6 +230,14 @@ static int scsi_multipath_sdev_init(struct scsi_device *sdev)
> return 0;
> }
>
> +void scsi_mpath_revalidate_paths(struct scsi_mpath_device *scsi_mpath_dev)
> +{
> + struct scsi_mpath_head *scsi_mpath_head = scsi_mpath_dev->scsi_mpath_head;
> + struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
> +
> + mpath_revalidate_paths(mpath_head);
> +}
[Severity: High]
Does this introduce a race condition with mpath_head->requeue_work?
Calling mpath_revalidate_paths() schedules requeue_work locklessly. If this
revalidation happens right as the device is being removed, is the work item
properly canceled?
If scsi_mpath_head_release() frees the scsi_mpath_head structure without
calling cancel_work_sync() or flush_work() on the embedded requeue_work,
could the mpath_requeue_work() callback execute against freed memory?
> +
> void scsi_mpath_dev_clear_path(struct scsi_mpath_device *scsi_mpath_dev)
> {
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723093627.2327456-1-john.g.garry@oracle.com?part=18
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 23/28] scsi: sd: add multipath disk class
2026-07-23 9:36 ` [PATCH v4 23/28] scsi: sd: add multipath disk class John Garry
@ 2026-07-23 10:39 ` sashiko-bot
2026-07-23 11:21 ` John Garry
0 siblings, 1 reply; 74+ messages in thread
From: sashiko-bot @ 2026-07-23 10:39 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Low] Empty device release callback `sd_mpath_disk_release` bypasses driver core checks and is a known anti-pattern.
- [Low] Indentation of `#ifdef` preprocessor directives inside a struct definition.
--
--- Patch [23]: [PATCH v4 23/28] scsi: sd: add multipath disk class ---
commit 99b2103695f69e8e73bd9a9db23c590d7c822e26
Author: John Garry <john.g.garry@oracle.com>
scsi: sd: add multipath disk class
This commit introduces sd_mpath_disk_class, a new multipath version of the
scsi_disk class, and the sd_mpath_disk structure to manage multipath
gendisks.
> diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
> index 599e75f333343..242a15bc2c5bb 100644
> --- a/drivers/scsi/sd.c
> +++ b/drivers/scsi/sd.c
[ ... ]
> @@ -113,6 +114,39 @@ static mempool_t *sd_page_pool;
> static mempool_t *sd_large_page_pool;
> static atomic_t sd_large_page_pool_users = ATOMIC_INIT(0);
> static struct lock_class_key sd_bio_compl_lkclass;
> +#ifdef CONFIG_SCSI_MULTIPATH
> +struct sd_mpath_disk {
> + struct scsi_mpath_head *scsi_mpath_head;
> +};
> +
> +static void sd_mpath_disk_release(struct device *dev)
> +{
> +}
[Severity: Low]
Does this empty release callback intentionally bypass the driver core checks?
While this is currently unreachable as no devices are registered to this
class yet, providing an empty release function just to silence the driver
core's missing-release warning is a known anti-pattern. This will cause a
permanent memory leak when devices are eventually assigned to this class and
need to be freed.
> +
> +static const struct class sd_mpath_disk_class = {
> + .name = "scsi_mpath_disk",
> + .dev_release = sd_mpath_disk_release,
> +};
[ ... ]
> diff --git a/drivers/scsi/sd.h b/drivers/scsi/sd.h
> index 574af82430169..304b24644d942 100644
> --- a/drivers/scsi/sd.h
> +++ b/drivers/scsi/sd.h
> @@ -83,6 +83,9 @@ struct zoned_disk_info {
>
> struct scsi_disk {
> struct scsi_device *device;
> + #ifdef CONFIG_SCSI_MULTIPATH
> + struct sd_mpath_disk *sd_mpath_disk;
> + #endif
[Severity: Low]
This isn't a bug, but should these preprocessor directives start at column 0?
Indenting preprocessor directives inside the struct definition doesn't align
with the standard kernel coding style guidelines.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723093627.2327456-1-john.g.garry@oracle.com?part=23
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 22/28] scsi-multipath: add delayed disk removal support
2026-07-23 9:36 ` [PATCH v4 22/28] scsi-multipath: add delayed disk removal support John Garry
@ 2026-07-23 10:39 ` sashiko-bot
2026-07-23 11:21 ` John Garry
0 siblings, 1 reply; 74+ messages in thread
From: sashiko-bot @ 2026-07-23 10:39 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Broadcasting `scsi_mpath_head` to all SCSI drivers without halting iteration causes a Use-After-Free and Type Confusion risk if the callback drops the final reference and frees the structure.
- [High] Missing module reference pinning allows `scsi_mod` to be unloaded while delayed removal work is pending, leading to code-execution UAF.
--
--- Patch [22]: [PATCH v4 22/28] scsi-multipath: add delayed disk removal support ---
commit e75160848c3c93c7fdb328d5ebd94850baa881c8
Author: John Garry <john.g.garry@oracle.com>
scsi-multipath: add delayed disk removal support
This commit adds delayed disk removal support to the SCSI multipath core.
It introduces a new mpath_remove_head callback in the scsi_driver structure
and implements scsi_mpath_remove_head_work to execute this callback by
iterating over available SCSI drivers.
> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
> index 6c50fdde70d65..056b9c35d1144 100644
> --- a/drivers/scsi/scsi_multipath.c
> +++ b/drivers/scsi/scsi_multipath.c
[ ... ]
> @@ -362,7 +362,25 @@ static bool scsi_mpath_available_path(struct mpath_device *mpath_device)
> return false;
> }
>
> +static int scsi_mpath_remove_head_drv(struct device_driver *drv, void *data)
> +{
> + struct scsi_mpath_head *scsi_mpath_head = data;
> + struct scsi_driver *scsi_driver = to_scsi_driver(drv);
> +
> + if (scsi_driver->mpath_remove_head)
> + scsi_driver->mpath_remove_head(scsi_mpath_head);
> +
> + return 0;
> +}
> +
> +static void scsi_mpath_remove_head_work(struct mpath_head *mpath_head)
> +{
> + bus_for_each_drv(&scsi_bus_type, NULL, to_scsi_mpath_head(mpath_head),
> + scsi_mpath_remove_head_drv);
> +}
[Severity: High]
Does this code risk a use-after-free and type confusion regression during bus
iteration?
Looking at scsi_mpath_remove_head_work() in drivers/scsi/scsi_multipath.c, it
unconditionally iterates over all SCSI drivers via bus_for_each_drv(). If the
target driver implements mpath_remove_head and drops the final reference to
scsi_mpath_head freeing the structure, scsi_mpath_remove_head_drv() still
returns 0.
Could the iteration continue and pass the freed scsi_mpath_head pointer to
subsequent drivers on the bus, causing other drivers that implement
mpath_remove_head to dereference a dangling pointer?
Should the iteration halt by returning a non-zero value once the callback has
been successfully executed?
> static struct mpath_head_template smpdt = {
> + .remove_head = scsi_mpath_remove_head_work,
[Severity: High]
Does this delayed work introduction expose a use-after-free regression due to
a missing module reference?
When delayed removal is queued, it relies on mpath_head->drv_module being set
to pin the module. However, looking at scsi_mpath_alloc_head() where smpdt is
assigned:
drivers/scsi/scsi_multipath.c:scsi_mpath_alloc_head() {
...
scsi_mpath_head = kzalloc(sizeof(*scsi_mpath_head), GFP_KERNEL);
if (!scsi_mpath_head)
return NULL;
...
if (mpath_head_init(&scsi_mpath_head->mpath_head))
goto out_free;
scsi_mpath_head->mpath_head.mpdt = &smpdt;
...
}
Since mpath_head->drv_module is never initialized to THIS_MODULE,
try_module_get(mpath_head->drv_module) will return true without taking a
reference.
Could scsi_mod be unloaded while the delayed remove_work is still pending in
the workqueue, causing the remove_head callback execution to jump to unmapped
memory?
> .is_disabled = scsi_mpath_is_disabled,
> .is_optimized = scsi_mpath_is_optimized,
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723093627.2327456-1-john.g.garry@oracle.com?part=22
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 08/28] libmultipath: Add mpath_bdev_report_zones()
2026-07-23 10:15 ` sashiko-bot
@ 2026-07-23 10:39 ` John Garry
0 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 10:39 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 23/07/2026 11:15, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [High] The multipath head disk fails to initialize `nr_zones`, `zone_capacity`, and `last_zone_capacity`, leaving them as 0.
> --
>
> --- Patch [8]: [PATCH v4 08/28] libmultipath: Add mpath_bdev_report_zones() ---
> commit 62d62f772c0199a5085c139be78e6b1dcd14b410
> Author: John Garry <john.garry@linux.dev>
>
> libmultipath: Add mpath_bdev_report_zones()
>
> This commit adds a multipath handler for block_device_operations.report_zones
> to libmultipath.
>
>> diff --git a/lib/multipath.c b/lib/multipath.c
>> index 3ff4f42a096c2..0232f51f64742 100644
>> --- a/lib/multipath.c
>> +++ b/lib/multipath.c
>> @@ -44,6 +44,30 @@ int mpath_get_iopolicy(char *buf, int iopolicy)
>> }
>> EXPORT_SYMBOL_GPL(mpath_get_iopolicy);
>>
>> +#ifdef CONFIG_BLK_DEV_ZONED
>> +static int mpath_bdev_report_zones(struct gendisk *disk, sector_t sector,
>> + unsigned int nr_zones, struct blk_report_zones_args *args)
>
I may just drop this support for now
> [Severity: High]
> With this function advertising report_zones capability to the block layer, are
> the zoned attributes for the multipath head disk properly initialized?
>
> If a zoned block device driver uses the libmultipath framework to create a
> multipath head, path disks are added via mpath_add_device(). However, it
> doesn't appear to propagate the zoned attributes (nr_zones, zone_capacity,
> last_zone_capacity) from the underlying path to the head disk.
>
> Since the head disk is bio-based without zone append emulation, the block
> layer doesn't automatically trigger blk_revalidate_disk_zones() on it. Will
> this leave these attributes at 0, potentially causing zoned filesystems or
> userspace tools relying on bdev_nr_zones() and bdev_zone_capacity() to fail
> or trigger division-by-zero errors when querying the multipath device?
>
>> +{
>> + struct mpath_head *mpath_head = mpath_gendisk_to_head(disk);
>> + struct mpath_device *mpath_device;
>> + int srcu_idx, ret = -EWOULDBLOCK;
> [ ... ]
>
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 24/28] scsi: sd: add multipath disk attr groups
2026-07-23 9:36 ` [PATCH v4 24/28] scsi: sd: add multipath disk attr groups John Garry
@ 2026-07-23 10:47 ` sashiko-bot
2026-07-23 11:22 ` John Garry
0 siblings, 1 reply; 74+ messages in thread
From: sashiko-bot @ 2026-07-23 10:47 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The patch defines the `sd_mpath_disk_attr_groups` sysfs attribute group and its associated callback functions but never assigns it to any device or class (e.g., `sd_mpath_disk_class`). This leaves the multipath sysfs attributes entirely unexposed and the new code dead.
--
--- Patch [24]: [PATCH v4 24/28] scsi: sd: add multipath disk attr groups ---
commit d9075e7051637dd60fcce05f493768de652548d9
Author: John Garry <john.garry@linux.dev>
scsi: sd: add multipath disk attr groups
This commit adds sysfs attribute groups for multipath disks, including
delayed disk removal and the existing mpath attribute group.
> diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
> index 242a15bc2c5bb..36c745cbb2619 100644
> --- a/drivers/scsi/sd.c
> +++ b/drivers/scsi/sd.c
[ ... ]
> +static const struct attribute_group sd_mpath_disk_attr_group = {
> + .attrs = sd_mpath_disk_attrs,
> +};
> +
> +__maybe_unused
> +static const struct attribute_group *sd_mpath_disk_attr_groups[] = {
> + &sd_mpath_disk_attr_group,
> + &mpath_attr_group,
> + NULL
> +};
[Severity: Medium]
Are these attribute groups actually registered anywhere? It looks like
sd_mpath_disk_attr_groups is defined and marked __maybe_unused, but never
assigned to a device or class to expose the sysfs attributes to userspace,
leaving this code essentially unreachable.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723093627.2327456-1-john.g.garry@oracle.com?part=24
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 25/28] scsi: sd: support multipath disk
2026-07-23 9:36 ` [PATCH v4 25/28] scsi: sd: support multipath disk John Garry
@ 2026-07-23 10:47 ` sashiko-bot
2026-07-23 11:27 ` John Garry
0 siblings, 1 reply; 74+ messages in thread
From: sashiko-bot @ 2026-07-23 10:47 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
New issues:
- [High] Unconditional sd_mpath_put_disk() in sd_mpath_remove() destroys the multipath disk structure while delayed removal is pending, causing a use-after-free.
- [High] Uncanceled delayed work accesses freed mpath_head after sd_mpath_fail_probe unconditionally deletes the device.
- [High] Concurrent execution of sd_mpath_remove_head and sd_mpath_probe creates a race condition that incorrectly deletes a newly allocated gendisk.
- [Medium] Missing put_device() in sd_probe() error path for sd_mpath_add_disk() failure leaks sdkp and its parent's reference.
- [Medium] Error paths in sd_mpath_probe() call kfree() on a device object after device_initialize(), leaking kobject internals.
Pre-existing issues:
- [High] If sd_large_pool_create() fails, sd_probe() jumps to out_free_index bypassing device unregistration and freeing active sysfs device memory (sdkp).
--
--- Patch [25]: [PATCH v4 25/28] scsi: sd: support multipath disk ---
commit 7776d470d97b7568b27daa7f4e53e7184172c0bc
Author: John Garry <john.garry@linux.dev>
scsi: sd: support multipath disk
This commit adds support to attach a multipath disk. It allocates a
gendisk per path and marks them as hidden while keeping a global list of
sd_mpath_disks for matching scsi_devices.
> diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
> index 36c745cbb2619..d5a37b1b312c3 100644
> --- a/drivers/scsi/sd.c
> +++ b/drivers/scsi/sd.c
> @@ -4035,13 +4139,258 @@ static const struct attribute_group sd_mpath_disk_attr_group = {
[ ... ]
> +static int sd_mpath_probe(struct scsi_disk *sdkp)
> +{
[ ... ]
> + sd_mpath_disk->scsi_mpath_head = scsi_mpath_head;
> + device_initialize(&sd_mpath_disk->dev);
> + sd_mpath_disk->dev.class = &sd_mpath_disk_class;
[ ... ]
> + error = mpath_alloc_head_disk(mpath_head, &lim,
> + dev_to_node(dma_dev));
> + if (error)
> + goto out_free_disk;
[ ... ]
> +out_free_disk:
> + kfree(sd_mpath_disk);
[Severity: Medium]
Is it safe to call kfree() directly on the device object here?
Since device_initialize() was already called on sd_mpath_disk->dev,
shouldn't we use put_device() to prevent leaking resources allocated by
the kobject core?
[ ... ]
> +static void sd_mpath_remove(struct scsi_disk *sdkp)
> +{
[ ... ]
> + mutex_lock(&sd_mpath_disks_lock);
> + sd_mpath_disk->disk_count--;
> + if (!sd_mpath_disk->disk_count && mpath_can_remove_head(mpath_head)) {
> + list_del_init(&sd_mpath_disk->entry);
> + remove = true;
> + }
> + mutex_unlock(&sd_mpath_disks_lock);
> + mpath_remove_sysfs_link(mpath_device);
> + mpath_device->disk = NULL;
> +
> + if (remove) {
> + device_del(&sd_mpath_disk->dev);
> + mpath_remove_disk(mpath_head);
> + }
> + sd_mpath_put_disk(sd_mpath_disk);
> +}
[Severity: High]
Does this sequence risk a use-after-free if delayed removal is pending?
If mpath_can_remove_head() returns false, the object remains on the
global list and is not fully removed, but we unconditionally drop the
reference via sd_mpath_put_disk() at the end. This could free the object
while it is still globally accessible.
[ ... ]
> +static void sd_mpath_remove_head(struct scsi_mpath_head *scsi_mpath_head)
> +{
[ ... ]
> + list_del_init(&sd_mpath_disk->entry);
> + mutex_unlock(&sd_mpath_disks_lock);
> +
> + device_del(&sd_mpath_disk->dev);
> + mpath_remove_disk(mpath_head);
> + sd_mpath_put_disk(sd_mpath_disk);
> +}
[Severity: High]
Could dropping the mutex here race with a concurrent sd_mpath_probe()?
We drop sd_mpath_disks_lock before calling mpath_remove_disk(mpath_head).
If a new probe allocates a new disk and overwrites mpath_head->disk
in that window, mpath_remove_disk() might delete the newly allocated
gendisk instead of the old one being removed.
[ ... ]
> +static void sd_mpath_fail_probe(struct scsi_disk *sdkp)
> +{
[ ... ]
> + mutex_lock(&sd_mpath_disks_lock);
> + sd_mpath_disk->disk_count--;
> + if (!sd_mpath_disk->disk_count) {
> + list_del_init(&sd_mpath_disk->entry);
> + remove = true;
> + }
> + mutex_unlock(&sd_mpath_disks_lock);
> + mpath_device->disk = NULL;
> +
> + if (remove) {
> + device_del(&sd_mpath_disk->dev);
> + mpath_remove_disk(mpath_head);
> + }
> + sd_mpath_put_disk(sd_mpath_disk);
> +}
[Severity: High]
What happens if there is pending delayed work on mpath_head->remove_work
when we hit this error path? If a previous removal queued the work,
and a subsequent failed probe triggers this code, we delete and free the
head without canceling the delayed work. Will the workqueue then trigger
a use-after-free on the freed mpath_head?
[ ... ]
> @@ -4158,11 +4515,18 @@ static int sd_probe(struct scsi_device *sdp)
> sd_revalidate_disk(gd);
> if (sdp->sector_size > PAGE_SIZE) {
> if (sd_large_pool_create()) {
> + sd_mpath_fail_probe(sdkp);
> error = -ENOMEM;
> goto out_free_index;
> }
> }
[Severity: High]
This is a pre-existing issue, but can this lead to a use-after-free in
sysfs? At this point, device_add() has already succeeded. Bypassing
device_del() or device_unregister() and jumping directly to
out_free_index (which frees sdkp) leaves the device linked in the sysfs
hierarchy.
[ ... ]
> @@ -4184,6 +4549,19 @@ static int sd_probe(struct scsi_device *sdp)
> goto out;
> }
>
> + if (sdp->scsi_mpath_dev) {
> + error = sd_mpath_add_disk(sdkp);
> + if (error) {
> + sd_mpath_fail_probe(sdkp);
> + device_del(&sdkp->disk_dev);
> + del_gendisk(sdkp->disk);
> +
> + put_disk(sdkp->disk);
> + if (sdp->sector_size > PAGE_SIZE)
> + sd_large_pool_destroy();
> + goto out;
> + }
> + }
[Severity: Medium]
Are we leaking the scsi_disk structure and its parent reference here?
We call device_del(&sdkp->disk_dev), but we don't call put_device()
to drop the base reference acquired during device_initialize().
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723093627.2327456-1-john.g.garry@oracle.com?part=25
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 13/28] scsi-multipath: introduce scsi_device head structure
2026-07-23 10:16 ` sashiko-bot
@ 2026-07-23 10:47 ` John Garry
0 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 10:47 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 23/07/2026 11:16, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
> - [High] `scsi_mpath_find_head()` prematurely aborts list search on dying heads, leading to split multipath topologies.
> - [High] Double removal in `scsi_mpath_remove_device()` triggers a NULL pointer dereference.
> - [High] Premature teardown of `scsi_mpath_head` causes a Use-After-Free on inflight bios.
> - [Medium] The sysfs attribute `vpd_id` is exposed to userspace before it is initialized.
> --
>
> --- Patch [13]: [PATCH v4 13/28] scsi-multipath: introduce scsi_device head structure ---
> commit 11c848a95f46ee71e21d42eee402d581f96c8055
> Author: John Garry <john.garry@linux.dev>
>
> scsi-multipath: introduce scsi_device head structure
>
> Introduce a scsi_device head structure - scsi_mpath_head - to manage
> multipathing for a scsi_device. This is similar to nvme_ns_head structure.
>
>> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
>> index b9b58a3641aa2..609f8009e4ac3 100644
>> --- a/drivers/scsi/scsi_multipath.c
>> +++ b/drivers/scsi/scsi_multipath.c
>
> [ ... ]
>
>> @@ -89,6 +141,71 @@ static int scsi_multipath_sdev_init(struct scsi_device *sdev)
>> +static struct scsi_mpath_head *scsi_mpath_alloc_head(void)
>> +{
>
> [ ... ]
>
>> + ret = device_add(&scsi_mpath_head->dev);
>> + if (ret) {
>> + put_device(&scsi_mpath_head->dev);
>> + return NULL;
>> + }
>> +
>
> [Severity: Medium]
> Will calling device_add() here expose an uninitialized vpd_id string to
> userspace when the uevent is fired?
>
> The vpd_id is only populated later in scsi_mpath_dev_alloc() via strscpy().
> Could a concurrently executing udev rule read an empty string before the
> string has been properly set?
will fix
>
>> + return scsi_mpath_head;
>> +
>> +out_uninit_head:
>> + mpath_head_uninit(&scsi_mpath_head->mpath_head);
>> +out_free:
>> + kfree(scsi_mpath_head);
>> + return NULL;
>> +}
>> +
>> +static struct scsi_mpath_head *scsi_mpath_find_head(
>> + struct scsi_mpath_device *scsi_mpath_dev)
>> +{
>> + struct scsi_mpath_head *scsi_mpath_head;
>> +
>> + list_for_each_entry(scsi_mpath_head, &scsi_mpath_heads_list, entry) {
>> + if (strncmp(scsi_mpath_head->vpd_id,
>> + scsi_mpath_dev->device_id_str,
>> + SCSI_MPATH_DEVICE_ID_LEN) == 0) {
>> + if (scsi_mpath_get_head(scsi_mpath_head))
>> + return NULL;
>
> [Severity: High]
> If scsi_mpath_get_head() fails (for example, if the head is dying), does
> returning NULL here prematurely abort the list search? Should this be a
> continue statement so we don't skip over a valid head further down the
> list?
will fix
>
>> + return scsi_mpath_head;
>> + }
>> + }
>> +
>> + return NULL;
>> +}
>
> [ ... ]
>
>> @@ -133,13 +290,47 @@ void scsi_mpath_dev_release(struct scsi_device *sdev)
>> +void scsi_mpath_remove_device(struct scsi_mpath_device *scsi_mpath_dev)
>> +{
>> + struct scsi_mpath_head *scsi_mpath_head = scsi_mpath_dev->scsi_mpath_head;
>> +
>> + ida_free(&scsi_mpath_head->ida, scsi_mpath_dev->index);
>> +
>> + scsi_mpath_remove_head(scsi_mpath_dev);
>> +}
>> +
>> void scsi_mpath_dev_release(struct scsi_device *sdev)
>> {
>> struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
>>
>> + if (scsi_mpath_dev)
>> + scsi_mpath_remove_device(scsi_mpath_dev);
>
> [Severity: High]
> Is it possible for this to trigger a NULL pointer dereference if the
> multipath device has already been detached?
>
> In __scsi_remove_device(), we call scsi_mpath_remove_device() which sets
> scsi_mpath_dev->scsi_mpath_head = NULL:
>
> __scsi_remove_device()
> scsi_mpath_remove_device(sdev->scsi_mpath_dev)
> scsi_mpath_remove_head()
> scsi_mpath_dev->scsi_mpath_head = NULL;
>
> When the device release callback runs later, it will call
> scsi_mpath_remove_device() again because scsi_mpath_dev is still non-NULL,
> leading to a crash when dereferencing scsi_mpath_head.
no, it's does not.
scsi_device_dev_release() calls scsi_mpath_dev_release()
>
>> scsi_multipath_sdev_uninit(sdev);
>> }
>
>> diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
>> index 2f80d703ce640..d6bbaf424bd4a 100644
>> --- a/drivers/scsi/scsi_sysfs.c
>> +++ b/drivers/scsi/scsi_sysfs.c
>> @@ -1501,6 +1501,9 @@ void __scsi_remove_device(struct scsi_device *sdev)
>> } else
>> put_device(&sdev->sdev_dev);
>>
>> + if (sdev->scsi_mpath_dev)
>> + scsi_mpath_remove_device(sdev->scsi_mpath_dev);
>> +
>
> [Severity: High]
> Could tearing down the multipath head here trigger a use-after-free for
> inflight bios?
>
> If this is the last path, the scsi_mpath_head could be unregistered and
> freed before the request queue is drained in blk_mq_destroy_queue() later
> in this function. When an inflight bio completes, its end_io callback might
> try to access the freed head.
>
> Should the multipath head removal happen after the queue is drained?
it does as we drain the mpath disk queue first
>
>> /*
>> * Stop accepting new requests and wait until all queuecommand() and
>> * scsi_run_queue() invocations have finished before tearing down the
>
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 15/28] scsi-multipath: support iopolicy
2026-07-23 10:20 ` sashiko-bot
@ 2026-07-23 10:51 ` John Garry
0 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 10:51 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 23/07/2026 11:20, sashiko-bot@kernel.org wrote:
>> +static ssize_t scsi_mpath_device_iopolicy_store(struct device *dev,
>> + struct device_attribute *attr, const char *buf, size_t count)
>> +{
>> + struct scsi_mpath_head *scsi_mpath_head =
>> + container_of(dev, struct scsi_mpath_head, dev);
>> + struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
>> +
>> + if (!mpath_iopolicy_store(&scsi_mpath_head->iopolicy, buf))
>> + return -EINVAL;
>> +
>> + mpath_clear_paths(mpath_head);
>> + mpath_schedule_requeue_work(mpath_head);
> [Severity: High]
> Can mpath_schedule_requeue_work() trigger a use-after-free during device
> teardown?
>
> If a sysfs write occurs concurrently with device removal, the asynchronous
> requeue work is scheduled here. The teardown path in scsi_mpath_head_release()
> frees the scsi_mpath_head structure without calling cancel_work_sync() to
> cancel this pending work.
>
> Could the workqueue access freed memory when it executes the queued function?
we flush the queue when removing the disk, so should not be an issue
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 27/28] scsi: sd: add mpath_numa_nodes dev attribute
2026-07-23 9:36 ` [PATCH v4 27/28] scsi: sd: add mpath_numa_nodes dev attribute John Garry
@ 2026-07-23 10:52 ` sashiko-bot
2026-07-23 11:30 ` John Garry
0 siblings, 1 reply; 74+ messages in thread
From: sashiko-bot @ 2026-07-23 10:52 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Reading the `mpath_numa_nodes` sysfs attribute before the device is fully initialized causes a guaranteed NULL pointer dereference on `mpath_device->mpath_head`.
--
--- Patch [27]: [PATCH v4 27/28] scsi: sd: add mpath_numa_nodes dev attribute ---
commit cc5c94ceb8cbeeccd7a5678ef260c6bd21a0fd6b
Author: John Garry <john.g.garry@oracle.com>
scsi: sd: add mpath_numa_nodes dev attribute
Add an attribute to show multipath NUMA node per-path (scsi_disk).
> diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
> index f8c9096e6c43c..7255df6e3e971 100644
> --- a/drivers/scsi/sd.c
> +++ b/drivers/scsi/sd.c
> @@ -4076,8 +4076,22 @@ static ssize_t sd_mpath_dev_show(struct device *dev,
> }
> static DEVICE_ATTR(mpath_dev, 0444, sd_mpath_dev_show, NULL);
>
> +static ssize_t sd_mpath_numa_nodes_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct gendisk *gd = dev_to_disk(dev);
> + struct scsi_disk *sdkp = gd->private_data;
> + struct scsi_device *sdev = sdkp->device;
> + struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
> + struct mpath_device *mpath_device = &scsi_mpath_dev->mpath_device;
> +
> + return mpath_numa_nodes_show(mpath_device, buf);
[Severity: High]
Does this code risk a NULL pointer dereference if the attribute is read early?
When sd_probe() calls device_add_disk(), it exposes the mpath_numa_nodes
sysfs attribute to userspace and fires a uevent. If userspace (like udev)
reads this file before sd_mpath_add_disk() runs, mpath_device->mpath_head
will still be NULL.
When mpath_numa_nodes_show() receives this mpath_device, it passes the NULL
mpath_head to mpath_read_iopolicy(), which will dereference it:
lib/multipath.c:mpath_numa_nodes_show() {
struct mpath_head *mpath_head = mpath_device->mpath_head;
...
if (mpath_read_iopolicy(mpath_head) != MPATH_IOPOLICY_NUMA)
...
}
include/linux/multipath.h:mpath_read_iopolicy() {
return READ_ONCE(*mpath_head->iopolicy);
}
Should we check if mpath_device->mpath_head is valid before attempting to
dereference it?
> +}
> +static DEVICE_ATTR(mpath_numa_nodes, 0444, sd_mpath_numa_nodes_show, NULL);
> +
> static struct attribute *sd_mpath_dev_attrs[] = {
> &dev_attr_mpath_dev.attr,
> + &dev_attr_mpath_numa_nodes.attr,
> NULL
> };
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723093627.2327456-1-john.g.garry@oracle.com?part=27
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 16/28] scsi-multipath: clone each bio
2026-07-23 10:27 ` sashiko-bot
@ 2026-07-23 10:55 ` John Garry
0 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 10:55 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 23/07/2026 11:27, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
> - [Critical] NULL pointer dereference due to missing mandatory template callbacks
> - [High] Missing failover handling in clone completion
> - [High] Bio allocation deadlock due to missing BIOSET_NEED_RESCUER
> - [Medium] Memory leak due to missing ida_destroy()
> --
>
> --- Patch [16]: [PATCH v4 16/28] scsi-multipath: clone each bio ---
> commit a74e2254f4409451e96518ce4d539e122559fac1
> Author: John Garry <john.garry@linux.dev>
>
> scsi-multipath: clone each bio
>
> For failover handling, we will take the approach to resubmit each
> bio.
>
> However, unlike NVMe, for SCSI there is no guarantee that any bio submitted
> is either all or none completed.
>
> As such, for SCSI, for failover handling we will take the approach to
> just re-submit the original bio. For this, clone and submit each bio.
>
>> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
>> index 6228ff4be4ff7..9a4b35c597778 100644
>> --- a/drivers/scsi/scsi_multipath.c
>> +++ b/drivers/scsi/scsi_multipath.c
>> @@ -97,6 +97,7 @@ static void scsi_mpath_head_release(struct device *dev)
>> container_of(dev, struct scsi_mpath_head, dev);
>> struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
>>
>> + bioset_exit(&scsi_mpath_head->bio_pool);
>> ida_free(&scsi_multipath_dev_ida, scsi_mpath_head->index);
>> mpath_head_uninit(mpath_head);
>> kfree(scsi_mpath_head);
>
> [Severity: Medium]
> Will this leak memory if the embedded IDA isn't destroyed?
>
> Even though empty IDAs free their bitmaps, the XArray API contract requires
> calling ida_destroy() or xa_destroy() to ensure all internally allocated
> memory is fully released. Can we add a call to
> ida_destroy(&scsi_mpath_head->ida) before freeing the structure?
will fix
>
> [ ... ]
>> @@ -229,7 +230,34 @@ static int scsi_multipath_sdev_init(struct scsi_device *sdev)
>> return 0;
>> }
>>
>> +static void scsi_mpath_clone_end_io(struct bio *clone)
>> +{
>> + struct bio *master_bio = clone->bi_private;
>> +
>> + master_bio->bi_status = clone->bi_status;
>> + bio_put(clone);
>> + bio_endio(master_bio);
>> +}
>
> [Severity: High]
> Does this skip all multipath failover logic on error?
>
> When an underlying SCSI path fails, the error status is unconditionally
> propagated to the master bio and completed. Should this be checking
> blk_path_error() and requeueing the master bio to the requeue_list for a
> retry on an alternate path instead?
we do it later
>
> [ ... ]
>> +static struct mpath_head_template smpdt = {
>> + .clone_bio = scsi_mpath_clone_bio,
>> };
>
> [Severity: Critical]
> Will this trigger a NULL pointer dereference?
>
> The template omits the .is_disabled, .is_optimized, .available_path, and
> .remove_head callbacks. The libmultipath core does not check if these are
> NULL before calling them. For example, submitting any bio to the device
> triggers __mpath_find_path() which calls:
check later patches
>
> lib/multipath.c:mpath_path_is_disabled() {
> return mpath_head->mpdt->is_disabled(mpath_device);
> }
>
> Should these mandatory callbacks be implemented in the template?
>
>>
>> static struct scsi_mpath_head *scsi_mpath_alloc_head(void)
>> @@ -252,6 +280,9 @@ static struct scsi_mpath_head *scsi_mpath_alloc_head(void)
>> scsi_mpath_head->index = ida_alloc(&scsi_multipath_dev_ida, GFP_KERNEL);
>> if (scsi_mpath_head->index < 0)
>> goto out_uninit_head;
>> + if (bioset_init(&scsi_mpath_head->bio_pool, BIO_POOL_SIZE,
>> + 0, BIOSET_PERCPU_CACHE))
>
> [Severity: High]
> Could this cause a system deadlock?
>
> The bioset is initialized without the BIOSET_NEED_RESCUER flag. Later,
> scsi_mpath_clone_bio() allocates from this pool via bio_alloc_clone(). Since
> this allocation occurs inside submit_bio_noacct(), an exhausted mempool will
> block waiting for completions.
>
> Without BIOSET_NEED_RESCUER, the block layer's deadlock avoidance cannot
> intervene. Should BIOSET_NEED_RESCUER be added to the flags here?
will check it
>
>> + goto out_ida_free;
>> kref_init(&scsi_mpath_head->ref);
>>
>> device_initialize(&scsi_mpath_head->dev);
>
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 21/28] scsi-multipath: add scsi_mpath_{start,end}_request()
2026-07-23 10:32 ` sashiko-bot
@ 2026-07-23 10:57 ` John Garry
0 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 10:57 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 23/07/2026 11:32, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
> - [Critical] Unconditional dereference of `req->bio` and `req->bio->bi_private` leads to a kernel panic for requests lacking a valid block layer bio setup (like passthrough or empty flushes).
> - [High] Resource leaks and state corruption due to multipath flags not being preserved across request requeue operations.
> - [High] Use-after-free of the master bio structure (`master->bi_bdev`) after request completion.
> --
>
> --- Patch [21]: [PATCH v4 21/28] scsi-multipath: add scsi_mpath_{start,end}_request() ---
> commit b237b1f6dbfe052243ff199e6fb756310c17d07b
> Author: John Garry <john.garry@linux.dev>
>
> scsi-multipath: add scsi_mpath_{start,end}_request()
>
> Add scsi_mpath_{start,end}_request() to handle updating private multipath
> request data, like nvme_mpath_{start,end}_request().
>
>> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
>> index e566d47199fcc..6c50fdde70d65 100644
>> --- a/drivers/scsi/scsi_multipath.c
>> +++ b/drivers/scsi/scsi_multipath.c
>> @@ -555,6 +555,59 @@ void scsi_mpath_put_head(struct scsi_mpath_head *scsi_mpath_head)
>> }
>> EXPORT_SYMBOL_GPL(scsi_mpath_put_head);
>>
>> +void scsi_mpath_start_request(struct request *req)
>> +{
>> + struct scsi_cmnd *scmd = blk_mq_rq_to_pdu(req);
>> + struct scsi_device *sdev = scmd->device;
>> + struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
>> + struct scsi_mpath_head *scsi_mpath_head =
>> + scsi_mpath_dev->scsi_mpath_head;
>> + struct bio *clone = req->bio;
>> + struct bio *master = clone->bi_private;
>
> [Severity: Critical]
> Are we dereferencing a potentially invalid bio here?
will check
>
> If a request lacks a bio (like an empty flush) or is a passthrough request,
> req->bio could be NULL, which would cause a panic when dereferencing
> clone->bi_private.
>
>> +
>> + if (mpath_qd_iopolicy(&scsi_mpath_head->iopolicy) &&
>> + !(scmd->flags & SCMD_MPATH_CNT_ACTIVE)) {
>> + struct Scsi_Host *shost = sdev->host;
>> +
>> + atomic_inc(&shost->mpath_nr_active);
>> + scmd->flags |= SCMD_MPATH_CNT_ACTIVE;
>> + }
>> +
>> + if (!blk_queue_io_stat(master->bi_bdev->bd_queue) || blk_rq_is_passthrough(req) ||
>
> [Severity: Critical]
> Is the safety check for passthrough requests too late?
>
> The master->bi_bdev pointer is dereferenced in the blk_queue_io_stat() check
> before the blk_rq_is_passthrough(req) condition is evaluated.
>
> Could this trigger a NULL pointer dereference for passthrough commands where
> the bio wasn't cloned by multipath?
>
>> + (scmd->flags & SCMD_MPATH_IO_STATS))
>> + return;
>> +
>> + scmd->flags |= SCMD_MPATH_IO_STATS;
>> + scmd->start_time = bdev_start_io_acct(master->bi_bdev, req_op(req),
>> + jiffies);
>> + scmd->start_bytes = blk_rq_bytes(req);
>> +}
>> +
>> +bool scsi_mpath_end_request(struct request *req, blk_status_t error,
>> + unsigned int nr_bytes)
>> +{
>> + struct scsi_cmnd *scmd = blk_mq_rq_to_pdu(req);
>> + struct scsi_device *sdev = scmd->device;
>> + struct bio *clone = req->bio, *master = clone->bi_private;
>> +
>> + if (blk_update_request(req, error, nr_bytes))
>> + return true;
>> +
>> + if (scmd->flags & SCMD_MPATH_CNT_ACTIVE) {
>> + struct Scsi_Host *shost = sdev->host;
>> +
>> + atomic_dec_if_positive(&shost->mpath_nr_active);
>> + }
>> +
>> + if (!(scmd->flags & SCMD_MPATH_IO_STATS))
>> + return false;
>> + bdev_end_io_acct(master->bi_bdev, req_op(req),
>
> [Severity: High]
> Does this code introduce a use-after-free of the master bio structure?
>
> When blk_update_request() finishes a request, it returns false and invokes
> bio_endio(). For a cloned bio, this completes the master bio, which the
> upper layers (like the filesystem) may immediately free.
will fix
>
> Could accessing master->bi_bdev here trigger memory corruption or kernel
> panics if the bio structure has already been freed?
>
>> + scmd->start_bytes >> SECTOR_SHIFT,
>> + scmd->start_time);
>> +
>> + return false;
>> +}
>
> [ ... ]
>
>> diff --git a/include/scsi/scsi_cmnd.h b/include/scsi/scsi_cmnd.h
>> index 8ecfb94049db5..3ff425c5f1e56 100644
>> --- a/include/scsi/scsi_cmnd.h
>> +++ b/include/scsi/scsi_cmnd.h
>> @@ -60,6 +60,8 @@ struct scsi_pointer {
>> #define SCMD_FAIL_IF_RECOVERING (1 << 4)
>> /* flags preserved across unprep / reprep */
>> #define SCMD_PRESERVED_FLAGS (SCMD_INITIALIZED | SCMD_FAIL_IF_RECOVERING)
>> +#define SCMD_MPATH_IO_STATS (1 << 5)
>> +#define SCMD_MPATH_CNT_ACTIVE (1 << 6)
>
> [Severity: High]
> Should these new multipath state flags be included in SCMD_PRESERVED_FLAGS?
>
> In scsi_queue_rq(), cmd->flags is masked with SCMD_PRESERVED_FLAGS before
> dispatching:
>
> drivers/scsi/scsi_lib.c:scsi_queue_rq() {
> ...
> cmd->flags &= SCMD_PRESERVED_FLAGS;
> ...
> if (is_mpath_request(req))
> scsi_mpath_start_request(req);
> ...
> }
will fix
>
> If a command fails to dispatch and is requeued, the flags would be cleared.
>
> Could this cause scsi_mpath_start_request() to erroneously increment the
> host's active request counter (mpath_nr_active) multiple times, leading to a
> permanent leak when the request finally completes and only decrements it once?
>
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 17/28] scsi-multipath: clear path when device is blocked
2026-07-23 10:33 ` sashiko-bot
@ 2026-07-23 11:01 ` John Garry
0 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 11:01 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 23/07/2026 11:33, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
> - [High] Calling mpath_synchronize() while the block device queue is quiesced and sdev->state_mutex is held causes a circular dependency deadlock under IO load.
> - [High] scsi_mpath_dev_clear_path() triggers a guaranteed NULL pointer dereference because mpath_device->mpath_head is never initialized in the SCSI multipath framework.
> - [High] Lifecycle races during device removal, initialization, or error paths cause scsi_mpath_dev_clear_path() to calculate and dereference an invalid pointer or freed memory.
> --
>
> --- Patch [17]: [PATCH v4 17/28] scsi-multipath: clear path when device is blocked ---
> commit 5b8022a9b2f8e87d8f78b7424101ac5ed615cc6f
> Author: John Garry <john.garry@linux.dev>
>
> scsi-multipath: clear path when device is blocked
>
> Add scsi_mpath_dev_clear_path() to clear a device path when it becomes
> blocked.
>
>> diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
>> --- a/drivers/scsi/scsi_lib.c
>> +++ b/drivers/scsi/scsi_lib.c
>
> [ ... ]
>
>> @@ -2999,13 +3000,16 @@ static void scsi_device_block(struct scsi_device *sdev, void *data)
>> mutex_lock(&sdev->state_mutex);
>> err = __scsi_internal_device_block_nowait(sdev);
>> state = sdev->sdev_state;
>> - if (err == 0)
>> + if (err == 0) {
>> /*
>> * scsi_stop_queue() must be called with the state_mutex
>> * held. Otherwise a simultaneous scsi_start_queue() call
>> * might unquiesce the queue before we quiesce it.
>> */
>> scsi_stop_queue(sdev);
>> + if (sdev->scsi_mpath_dev)
>> + scsi_mpath_dev_clear_path(sdev->scsi_mpath_dev);
>
> [Severity: High]
> Could this lead to a circular dependency deadlock under IO load?
>
> When scsi_device_block() stops the queue via scsi_stop_queue(),
> no further requests are dispatched. If it then calls
> scsi_mpath_dev_clear_path(), it ends up calling mpath_synchronize(),
> which executes synchronize_srcu() while sdev->state_mutex is still held.
>
> Concurrently, a thread inside mpath_bdev_submit_bio() could be holding
> the SRCU read lock:
>
> lib/multipath.c:mpath_bdev_submit_bio()
> srcu_idx = srcu_read_lock(&mpath_head->srcu);
> ...
> submit_bio_noacct(bio);
>
> If this submit_bio_noacct() path blocks on tag exhaustion because the
> queue is quiesced, it will never release the SRCU read lock.
> mpath_synchronize() would then block indefinitely waiting for the SRCU
> reader. Any subsequent attempt to unquiesce the queue would require
> sdev->state_mutex, which is held by the thread blocked in
> mpath_synchronize().
will fix
>
>> + }
>>
>> mutex_unlock(&sdev->state_mutex);
>
>> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
>> --- a/drivers/scsi/scsi_multipath.c
>> +++ b/drivers/scsi/scsi_multipath.c
>
> [ ... ]
>
>> @@ -230,6 +230,16 @@ static int scsi_multipath_sdev_init(struct scsi_device *sdev)
>> return 0;
>> }
>>
>> +void scsi_mpath_dev_clear_path(struct scsi_mpath_device *scsi_mpath_dev)
>> +{
>> + struct mpath_device *mpath_device = &scsi_mpath_dev->mpath_device;
>> + struct scsi_mpath_head *scsi_mpath_head = scsi_mpath_dev->scsi_mpath_head;
>> + struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
>
> [Severity: High]
> Are there lifecycle races when dereferencing scsi_mpath_dev here?
>
will fix
> The scsi_device_block() function calls this while holding state_mutex,
> but pointers like scsi_mpath_dev->scsi_mpath_head are mutated locklessly
> relative to state_mutex. For example, during removal,
> scsi_mpath_remove_head() sets scsi_mpath_dev->scsi_mpath_head to NULL
> before state_mutex is acquired in __scsi_remove_device().
>
> If a concurrent device block event occurs while the head is NULL, computing
> &scsi_mpath_head->mpath_head will yield an invalid pointer. Similarly,
> initialization error paths free sdev->scsi_mpath_dev without holding
> state_mutex, which could lead to a use-after-free if scsi_device_block()
> observes a non-NULL pointer before the memory is freed.
>
>> +
>> + if (mpath_clear_current_path(mpath_device))
>
> [Severity: High]
> Is mpath_device->mpath_head guaranteed to be initialized before calling this?
>
> The scsi_mpath_dev is allocated using kzalloc() in
> scsi_multipath_sdev_init(), meaning the embedded mpath_device is
> zero-initialized. The mpath_device->mpath_head pointer doesn't appear to be
> explicitly assigned anywhere in the SCSI multipath framework.
it's done in sd.c -> mpath_add_device(), but I can move it to scsi
multipath code
>
> If mpath_clear_current_path() is called, it executes:
>
> lib/multipath.c:mpath_clear_current_path()
> struct mpath_head *mpath_head = mpath_device->mpath_head;
> ...
> if (mpath_device == rcu_access_pointer(mpath_head->current_path[node]))
>
> Since mpath_device->mpath_head is NULL, wouldn't this unconditional
> dereference result in an immediate panic whenever a SCSI multipath
> device is blocked?
>
>> + mpath_synchronize(mpath_head);
>> +}
>
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 19/28] scsi-multipath: failover handling
2026-07-23 10:36 ` sashiko-bot
@ 2026-07-23 11:03 ` John Garry
0 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 11:03 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 23/07/2026 11:36, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
> - [High] Missing bi_next clearing in bio_list_add_clone() leads to corrupted bio requeue list.
> - [High] Releasing the SCSI device via scsi_device_put() causes a module reference count underflow due to mismatched API usage.
> - [High] Uninitialized mpath_device->mpath_head leads to a guaranteed NULL pointer dereference during failover.
> - [High] The scsi_mpath_clone_end_io handler dereferences mpath_head after it has been freed during path device removal.
> - [High] Missing cancel_work_sync() allows requeue_work to execute after scsi_mpath_head is freed.
> --
>
> --- Patch [19]: [PATCH v4 19/28] scsi-multipath: failover handling ---
> commit e8daaaa500b47bda767344af0f467a56fa0a6095
> Author: John Garry <john.garry@linux.dev>
>
> scsi-multipath: failover handling
>
> Failover occurs when the scsi_cmnd has failed and it is discovered that the
> target scsi_device has transport down. For a scsi command which suffers
> failover, requeue the master bio of each bio attached to its request.
>
>> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
>> index b896d28a7dd9a..a482b6ede21aa 100644
>> --- a/drivers/scsi/scsi_multipath.c
>> +++ b/drivers/scsi/scsi_multipath.c
>> @@ -248,11 +248,53 @@ void scsi_mpath_dev_clear_path(struct scsi_mpath_device *scsi_mpath_dev)
>> mpath_synchronize(mpath_head);
>> }
>>
>> +static inline void bio_list_add_clone(struct bio_list *bl,
>> + struct bio *clone)
>> +{
>> + struct bio *master_bio = clone->bi_private;
>> +
>> + if (bl->tail)
>> + bl->tail->bi_next = master_bio;
>> + else
>> + bl->head = master_bio;
>> + bl->tail = master_bio;
>
> [Severity: High]
> Since master_bio is appended to the tail of the bio_list, shouldn't its
> bi_next pointer be explicitly cleared to null to properly terminate the
> singly-linked list?
>
> When mpath_requeue_work() later iterates through the list, couldn't this
> cause it to follow a stale bi_next pointer past the end of the list?
will fix
>
>> + bio_put(clone);
>> +}
>> +
>> static void scsi_mpath_clone_end_io(struct bio *clone)
>> {
>> struct bio *master_bio = clone->bi_private;
>>
>> master_bio->bi_status = clone->bi_status;
>> +
>> + if (clone->bi_status && blk_path_error(clone->bi_status)) {
>> + struct block_device *bi_bdev = clone->bi_bdev;
>> + struct request_queue *q = bi_bdev->bd_queue;
>> + struct scsi_device *sdev = scsi_device_from_queue(q);
>> + struct scsi_mpath_device *scsi_mpath_dev;
>> + struct mpath_device *mpath_device;
>> + struct mpath_head *mpath_head;
>> + unsigned long flags;
>> +
>> + if (!sdev) {
>> + master_bio->bi_status = clone->bi_status;
>> + goto end_bio;
>> + }
>> +
>> + scsi_mpath_dev = sdev->scsi_mpath_dev;
>> + mpath_device = &scsi_mpath_dev->mpath_device;
>> + mpath_head = mpath_device->mpath_head;
>
> [Severity: High]
> Is mpath_device->mpath_head guaranteed to be initialized here?
same as 17/28
>
> If scsi_mpath_dev_alloc() initializes scsi_mpath_dev with kzalloc but never
> assigns this pointer, wouldn't this cause a null pointer dereference when
> accessing requeue_lock below?
>
> [Severity: High]
> Could an in-flight I/O completion racing with path removal extract a freed
> mpath_head here?
>
> If scsi_mpath_remove_device() drops the reference to scsi_mpath_head and
> frees it, but sdev->scsi_mpath_dev remains valid, it seems this could lead
> to a use-after-free when accessing its requeue_lock.
>
>> +
>> + spin_lock_irqsave(&mpath_head->requeue_lock, flags);
>> + bio_list_add_clone(&mpath_head->requeue_list, clone);
>> + spin_unlock_irqrestore(&mpath_head->requeue_lock, flags);
>> +
>> + mpath_schedule_requeue_work(mpath_head);
>
> [Severity: High]
> When we schedule this requeue work, is there a corresponding cancel_work_sync()
> during cleanup?
there is a flush in disk removal path
>
> Looking at scsi_mpath_head_release(), it frees scsi_mpath_head without
> cancelling or flushing mpath_head->requeue_work. Could this allow a
> use-after-free if the background worker runs after the head is freed?
>
>> + scsi_device_put(sdev);
>
> [Severity: High]
> Will this cause a module reference count underflow?
>
> scsi_device_from_queue() only acquires a device reference via get_device(),
> but scsi_device_put() drops both the device and module reference. Should
> this use put_device(&sdev->sdev_gendev) instead?
will fix
>
>> + return;
>> + }
>> +
>> +end_bio:
>> bio_put(clone);
>> bio_endio(master_bio);
>> }
>
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 20/28] scsi-multipath: provide callbacks for path state
2026-07-23 10:36 ` sashiko-bot
@ 2026-07-23 11:05 ` John Garry
0 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 11:05 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 23/07/2026 11:36, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
> - [High] ALUA access state is not synchronized with the core multipath library, causing path selection to incorrectly treat non-optimized paths as optimal and resulting in an infinite path re-evaluation loop.
> - [High] Missing ALUA path activation and state change notifications cause indefinite I/O hangs when only STANDBY, TRANSITIONING, or LBA-dependent paths are available.
> - [Medium] Missing READ_ONCE() on concurrent sdev->sdev_state access can cause TOCTOU vulnerabilities via compiler reloads.
> --
>
> --- Patch [20]: [PATCH v4 20/28] scsi-multipath: provide callbacks for path state ---
> commit 0d49283b5a1edcf6983ec33d9d54dad0023fe90c
> Author: John Garry <john.g.garry@oracle.com>
>
> scsi-multipath: provide callbacks for path state
>
> This commit provides callbacks for .is_disabled, .is_optimized, and
> .available_path. These all use scsi_device.sdev_state and
> scsi_device.access_state, which will be driven by ALUA.
>
>> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
>> index a482b6ede21aa..e566d47199fcc 100644
>> --- a/drivers/scsi/scsi_multipath.c
>> +++ b/drivers/scsi/scsi_multipath.c
>> @@ -316,7 +316,56 @@ static struct bio *scsi_mpath_clone_bio(struct bio *bio)
>> return clone;
>> }
>>
>> +static bool scsi_mpath_is_disabled(struct mpath_device *mpath_device)
>> +{
>> + struct scsi_mpath_device *scsi_mpath_dev =
>> + to_scsi_mpath_device(mpath_device);
>> + struct scsi_device *sdev = scsi_mpath_dev->sdev;
>> + unsigned char access_state = READ_ONCE(sdev->access_state) &
>> + SCSI_ACCESS_STATE_MASK;
>> +
>> + if (sdev->sdev_state != SDEV_RUNNING)
>> + return true;
>
> [Severity: Medium]
> Should sdev->sdev_state be read with READ_ONCE() here to avoid compiler
> reloads, similar to how access_state is read above
will fix
>
>> +
>> + if (access_state == SCSI_ACCESS_STATE_OPTIMAL ||
>> + access_state == SCSI_ACCESS_STATE_ACTIVE)
>> + return false;
>> +
>> + return true;
>
> [Severity: High]
> If a path is in ALUA STANDBY or TRANSITIONING state, it is treated as disabled
> and skipped during path selection.
alua support needs to be improved later
>
> When all paths are in these states, scsi_mpath_available_path() will return
> true since the state is SDEV_RUNNING, causing the block layer to requeue the
> I/O.
>
> Does this logic ever call scsi_dh_activate() or trigger an implicit ALUA
> activation to transition these standby paths to optimal? Could this result in
> an indefinite I/O hang with the requeue list looping forever?
>
>> +}
>> +
>> +static bool scsi_mpath_is_optimized(struct mpath_device *mpath_device)
>> +{
>> + struct scsi_mpath_device *scsi_mpath_dev =
>> + to_scsi_mpath_device(mpath_device);
>> + struct scsi_device *sdev = scsi_mpath_dev->sdev;
>> + unsigned char access_state = READ_ONCE(sdev->access_state) &
>> + SCSI_ACCESS_STATE_MASK;
>> +
>> + if (sdev->sdev_state != SDEV_RUNNING)
>> + return false;
>
> [Severity: Medium]
> Should this unlocked read of sdev->sdev_state also use READ_ONCE()?
as above
>
>> +
>> + return access_state == SCSI_ACCESS_STATE_OPTIMAL;
>
> [Severity: High]
> Does this dynamic check of the ALUA state conflict with the statically
> initialized access state in the core multipath library?
>
> If an I/O is submitted to an ALUA Active/Non-Optimized path, won't the core
> reject the cached path, fall back to __mpath_find_path() which uses the
> static state, and continuously re-cache the exact same path? Could this
> result in a perpetual cache miss and an infinite path re-evaluation loop on
> every submission?
as above for ALUA
>
>> +}
>> +
>> +static bool scsi_mpath_available_path(struct mpath_device *mpath_device)
>> +{
>> + struct scsi_mpath_device *scsi_mpath_dev =
>> + to_scsi_mpath_device(mpath_device);
>> + struct scsi_device *sdev = scsi_mpath_dev->sdev;
>> + enum scsi_device_state sdev_state = sdev->sdev_state;
>> +
>> + if (sdev_state == SDEV_RUNNING || sdev_state == SDEV_QUIESCE ||
>> + sdev_state == SDEV_BLOCK || sdev_state == SDEV_CREATED_BLOCK)
>> + return true;
>
> [Severity: Medium]
> Since sdev_state is a local variable assigned via a plain read, can the
> compiler reload it from memory for each condition in the if statement?
>
> If the state changes concurrently between SDEV_BLOCK and SDEV_RUNNING, could
> all checks evaluate to false even if the state is always one of the valid
> values? Should this use READ_ONCE()?
as above
>
>> +
>> + return false;
>> +}
>
> [ ... ]
>
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 26/28] scsi: sd: add mpath_dev file
2026-07-23 9:36 ` [PATCH v4 26/28] scsi: sd: add mpath_dev file John Garry
@ 2026-07-23 11:07 ` sashiko-bot
2026-07-23 11:30 ` John Garry
0 siblings, 1 reply; 74+ messages in thread
From: sashiko-bot @ 2026-07-23 11:07 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Reading the `mpath_dev` sysfs attribute can cause a use-after-free of the multipath `gendisk` and `scsi_mpath_head` objects during SCSI path removal.
--
--- Patch [26]: [PATCH v4 26/28] scsi: sd: add mpath_dev file ---
commit 4a0e7acbc6d2b78e6b014dfe6924983c812cc80e
Author: John Garry <john.g.garry@oracle.com>
scsi: sd: add mpath_dev file
This commit adds a mpath_dev file so the multipath disk can be looked up
from the per-path gendisk directory. This allows utilities like lsscsi to
find the multipath disk even when the gendisk for the per-path scsi_device
is missing.
> diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
> index d5a37b1b312c3..f8c9096e6c43c 100644
> --- a/drivers/scsi/sd.c
> +++ b/drivers/scsi/sd.c
[ ... ]
> @@ -4059,6 +4059,53 @@ static int sd_mpath_revalidate_head(struct scsi_disk *sdkp)
>
> return ret;
> }
> +
> +static ssize_t sd_mpath_dev_show(struct device *dev,
> + struct device_attribute *attr, char *page)
> +{
> + struct gendisk *gd = dev_to_disk(dev);
> + struct scsi_disk *sdkp = gd->private_data;
> + struct scsi_device *sdev = sdkp->device;
> + struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
> + struct scsi_mpath_head *scsi_mpath_head = scsi_mpath_dev->scsi_mpath_head;
> + struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
> + struct gendisk *disk = mpath_head->disk;
[Severity: High]
Can a concurrent read of this sysfs file cause a use-after-free during device
removal?
Looking at sd_remove() in drivers/scsi/sd.c, it tears down the multipath
structures before removing the sysfs interfaces:
sd_remove() {
if (sdp->scsi_mpath_dev)
sd_mpath_remove(sdkp);
...
device_del(&sdkp->disk_dev);
del_gendisk(sdkp->disk);
}
Since del_gendisk() happens after sd_mpath_remove(), there is a window where
the mpath_dev sysfs file is still accessible but the underlying multipath
structures have been freed.
Could an unprivileged user read the mpath_dev file during this window and
dereference freed memory for scsi_mpath_head and mpath_head->disk?
> + struct device *disk_dev = disk_to_dev(disk);
> +
> + return print_dev_t(page, disk_dev->devt);
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723093627.2327456-1-john.g.garry@oracle.com?part=26
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 18/28] scsi-multipath: revalidate paths upon device unblock
2026-07-23 10:39 ` sashiko-bot
@ 2026-07-23 11:15 ` John Garry
0 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 11:15 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 23/07/2026 11:39, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
> - [High] The newly added call to `scsi_mpath_revalidate_paths()` in `scsi_internal_device_unblock()` races with device initialization and teardown, leading to a NULL pointer dereference or Use-After-Free.
> - [High] Missing cancellation of `mpath_head->requeue_work` causes use-after-free when `scsi_mpath_head` is freed.
> - [Medium] Path revalidation is skipped for devices unblocked via `scsi_internal_device_unblock_nowait()`.
> --
>
> --- Patch [18]: [PATCH v4 18/28] scsi-multipath: revalidate paths upon device unblock ---
> commit b434ea17223b4b11754a84ac3b62a1af5b823144
> Author: John Garry <john.garry@linux.dev>
>
> scsi-multipath: revalidate paths upon device unblock
>
> This patch adds functionality to revalidate paths when a scsi_device is
> unblocked. It introduces scsi_mpath_revalidate_paths() to clear paths and
> requeue bios, and hooks it into scsi_internal_device_unblock().
>
>> diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
>> index f2729c0969801..d98c3239c13ad 100644
>> --- a/drivers/scsi/scsi_lib.c
>> +++ b/drivers/scsi/scsi_lib.c
>> @@ -3092,6 +3092,8 @@ static int scsi_internal_device_unblock(struct scsi_device *sdev,
>>
>> mutex_lock(&sdev->state_mutex);
>> ret = scsi_internal_device_unblock_nowait(sdev, new_state);
>> + if ((ret == 0) && sdev->scsi_mpath_dev)
>> + scsi_mpath_revalidate_paths(sdev->scsi_mpath_dev);
>
> [Severity: High]
> Can this lead to a use-after-free or NULL pointer dereference during device
> removal?
>
> If __scsi_remove_device() transitions the device to SDEV_CANCEL and unlocks
> state_mutex, it then locklessly calls scsi_mpath_remove_device() which sets
> scsi_mpath_head to NULL and frees it.
>
> If scsi_internal_device_unblock() runs concurrently,
> scsi_internal_device_unblock_nowait() will return 0 (success) because the
> device state is SDEV_CANCEL.
>
> Does this cause the unblock path to mistakenly proceed and call
> scsi_mpath_revalidate_paths(), dereferencing the freed or NULL
> scsi_mpath_head?
will check
>
> [Severity: Medium]
> Are there callers that bypass scsi_internal_device_unblock() and call
> scsi_internal_device_unblock_nowait() directly?
will check
>
> If drivers directly call the nowait variant to unblock a device, it seems
> they will successfully transition the device to SDEV_RUNNING but skip this
> new path revalidation. Will this leave their queued bios stuck?
>
>> mutex_unlock(&sdev->state_mutex);
>>
>> return ret;
>> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
>> index 6a2b06c148c1a..b896d28a7dd9a 100644
>> --- a/drivers/scsi/scsi_multipath.c
>> +++ b/drivers/scsi/scsi_multipath.c
>> [ ... ]
>> @@ -230,6 +230,14 @@ static int scsi_multipath_sdev_init(struct scsi_device *sdev)
>> return 0;
>> }
>>
>> +void scsi_mpath_revalidate_paths(struct scsi_mpath_device *scsi_mpath_dev)
>> +{
>> + struct scsi_mpath_head *scsi_mpath_head = scsi_mpath_dev->scsi_mpath_head;
>> + struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
>> +
>> + mpath_revalidate_paths(mpath_head);
>> +}
>
> [Severity: High]
> Does this introduce a race condition with mpath_head->requeue_work?
>
> Calling mpath_revalidate_paths() schedules requeue_work locklessly. If this
> revalidation happens right as the device is being removed, is the work item
> properly canceled?
>
> If scsi_mpath_head_release() frees the scsi_mpath_head structure without
> calling cancel_work_sync() or flush_work() on the embedded requeue_work,
> could the mpath_requeue_work() callback execute against freed memory?
will check
>
>> +
>> void scsi_mpath_dev_clear_path(struct scsi_mpath_device *scsi_mpath_dev)
>> {
>
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 22/28] scsi-multipath: add delayed disk removal support
2026-07-23 10:39 ` sashiko-bot
@ 2026-07-23 11:21 ` John Garry
0 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 11:21 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 23/07/2026 11:39, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [High] Broadcasting `scsi_mpath_head` to all SCSI drivers without halting iteration causes a Use-After-Free and Type Confusion risk if the callback drops the final reference and frees the structure.
> - [High] Missing module reference pinning allows `scsi_mod` to be unloaded while delayed removal work is pending, leading to code-execution UAF.
> --
>
> --- Patch [22]: [PATCH v4 22/28] scsi-multipath: add delayed disk removal support ---
> commit e75160848c3c93c7fdb328d5ebd94850baa881c8
> Author: John Garry <john.g.garry@oracle.com>
>
> scsi-multipath: add delayed disk removal support
>
> This commit adds delayed disk removal support to the SCSI multipath core.
> It introduces a new mpath_remove_head callback in the scsi_driver structure
> and implements scsi_mpath_remove_head_work to execute this callback by
> iterating over available SCSI drivers.
>
>> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
>> index 6c50fdde70d65..056b9c35d1144 100644
>> --- a/drivers/scsi/scsi_multipath.c
>> +++ b/drivers/scsi/scsi_multipath.c
> [ ... ]
>> @@ -362,7 +362,25 @@ static bool scsi_mpath_available_path(struct mpath_device *mpath_device)
>> return false;
>> }
>>
>> +static int scsi_mpath_remove_head_drv(struct device_driver *drv, void *data)
>> +{
>> + struct scsi_mpath_head *scsi_mpath_head = data;
>> + struct scsi_driver *scsi_driver = to_scsi_driver(drv);
>> +
>> + if (scsi_driver->mpath_remove_head)
>> + scsi_driver->mpath_remove_head(scsi_mpath_head);
>> +
>> + return 0;
>> +}
>> +
>> +static void scsi_mpath_remove_head_work(struct mpath_head *mpath_head)
>> +{
>> + bus_for_each_drv(&scsi_bus_type, NULL, to_scsi_mpath_head(mpath_head),
>> + scsi_mpath_remove_head_drv);
>> +}
>
> [Severity: High]
> Does this code risk a use-after-free and type confusion regression during bus
> iteration?
>
> Looking at scsi_mpath_remove_head_work() in drivers/scsi/scsi_multipath.c, it
> unconditionally iterates over all SCSI drivers via bus_for_each_drv(). If the
> target driver implements mpath_remove_head and drops the final reference to
> scsi_mpath_head freeing the structure, scsi_mpath_remove_head_drv() still
> returns 0.
>
> Could the iteration continue and pass the freed scsi_mpath_head pointer to
> subsequent drivers on the bus, causing other drivers that implement
> mpath_remove_head to dereference a dangling pointer?
any other driver should have a reference if necessary and they should
not reference scsi_mpath_head if they don't
>
> Should the iteration halt by returning a non-zero value once the callback has
> been successfully executed?
>
>> static struct mpath_head_template smpdt = {
>> + .remove_head = scsi_mpath_remove_head_work,
>
> [Severity: High]
> Does this delayed work introduction expose a use-after-free regression due to
> a missing module reference?
>
> When delayed removal is queued, it relies on mpath_head->drv_module being set
> to pin the module. However, looking at scsi_mpath_alloc_head() where smpdt is
> assigned:
>
> drivers/scsi/scsi_multipath.c:scsi_mpath_alloc_head() {
> ...
> scsi_mpath_head = kzalloc(sizeof(*scsi_mpath_head), GFP_KERNEL);
> if (!scsi_mpath_head)
> return NULL;
> ...
> if (mpath_head_init(&scsi_mpath_head->mpath_head))
> goto out_free;
> scsi_mpath_head->mpath_head.mpdt = &smpdt;
> ...
> }
>
> Since mpath_head->drv_module is never initialized to THIS_MODULE,
> try_module_get(mpath_head->drv_module) will return true without taking a
> reference.
>
it's done by sd.c
> Could scsi_mod be unloaded while the delayed remove_work is still pending in
> the workqueue, causing the remove_head callback execution to jump to unmapped
> memory?
>
>> .is_disabled = scsi_mpath_is_disabled,
>> .is_optimized = scsi_mpath_is_optimized,
> [ ... ]
>
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 23/28] scsi: sd: add multipath disk class
2026-07-23 10:39 ` sashiko-bot
@ 2026-07-23 11:21 ` John Garry
0 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 11:21 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 23/07/2026 11:39, sashiko-bot@kernel.org wrote:
>> rs/scsi/sd.h
>> +++ b/drivers/scsi/sd.h
>> @@ -83,6 +83,9 @@ struct zoned_disk_info {
>>
>> struct scsi_disk {
>> struct scsi_device *device;
>> + #ifdef CONFIG_SCSI_MULTIPATH
>> + struct sd_mpath_disk *sd_mpath_disk;
>> + #endif
> [Severity: Low]
> This isn't a bug, but should these preprocessor directives start at column 0?
>
> Indenting preprocessor directives inside the struct definition doesn't align
> with the standard kernel coding style guidelines.
will fix
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 24/28] scsi: sd: add multipath disk attr groups
2026-07-23 10:47 ` sashiko-bot
@ 2026-07-23 11:22 ` John Garry
0 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 11:22 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 23/07/2026 11:47, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] The patch defines the `sd_mpath_disk_attr_groups` sysfs attribute group and its associated callback functions but never assigns it to any device or class (e.g., `sd_mpath_disk_class`). This leaves the multipath sysfs attributes entirely unexposed and the new code dead.
> --
>
> --- Patch [24]: [PATCH v4 24/28] scsi: sd: add multipath disk attr groups ---
> commit d9075e7051637dd60fcce05f493768de652548d9
> Author: John Garry <john.garry@linux.dev>
>
> scsi: sd: add multipath disk attr groups
>
> This commit adds sysfs attribute groups for multipath disks, including
> delayed disk removal and the existing mpath attribute group.
>
>> diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
>> index 242a15bc2c5bb..36c745cbb2619 100644
>> --- a/drivers/scsi/sd.c
>> +++ b/drivers/scsi/sd.c
> [ ... ]
>> +static const struct attribute_group sd_mpath_disk_attr_group = {
>> + .attrs = sd_mpath_disk_attrs,
>> +};
>> +
>> +__maybe_unused
>> +static const struct attribute_group *sd_mpath_disk_attr_groups[] = {
>> + &sd_mpath_disk_attr_group,
>> + &mpath_attr_group,
>> + NULL
>> +};
>
> [Severity: Medium]
> Are these attribute groups actually registered anywhere? It looks like
> sd_mpath_disk_attr_groups is defined and marked __maybe_unused, but never
> assigned to a device or class to expose the sysfs attributes to userspace,
> leaving this code essentially unreachable.
>
yes, there are referenced later
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 25/28] scsi: sd: support multipath disk
2026-07-23 10:47 ` sashiko-bot
@ 2026-07-23 11:27 ` John Garry
0 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 11:27 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 23/07/2026 11:47, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
>
> New issues:
> - [High] Unconditional sd_mpath_put_disk() in sd_mpath_remove() destroys the multipath disk structure while delayed removal is pending, causing a use-after-free.
> - [High] Uncanceled delayed work accesses freed mpath_head after sd_mpath_fail_probe unconditionally deletes the device.
> - [High] Concurrent execution of sd_mpath_remove_head and sd_mpath_probe creates a race condition that incorrectly deletes a newly allocated gendisk.
> - [Medium] Missing put_device() in sd_probe() error path for sd_mpath_add_disk() failure leaks sdkp and its parent's reference.
> - [Medium] Error paths in sd_mpath_probe() call kfree() on a device object after device_initialize(), leaking kobject internals.
>
> Pre-existing issues:
> - [High] If sd_large_pool_create() fails, sd_probe() jumps to out_free_index bypassing device unregistration and freeing active sysfs device memory (sdkp).
> --
>
> --- Patch [25]: [PATCH v4 25/28] scsi: sd: support multipath disk ---
> commit 7776d470d97b7568b27daa7f4e53e7184172c0bc
> Author: John Garry <john.garry@linux.dev>
>
> scsi: sd: support multipath disk
>
> This commit adds support to attach a multipath disk. It allocates a
> gendisk per path and marks them as hidden while keeping a global list of
> sd_mpath_disks for matching scsi_devices.
>
>> diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
>> index 36c745cbb2619..d5a37b1b312c3 100644
>> --- a/drivers/scsi/sd.c
>> +++ b/drivers/scsi/sd.c
>> @@ -4035,13 +4139,258 @@ static const struct attribute_group sd_mpath_disk_attr_group = {
> [ ... ]
>> +static int sd_mpath_probe(struct scsi_disk *sdkp)
>> +{
> [ ... ]
>> + sd_mpath_disk->scsi_mpath_head = scsi_mpath_head;
>> + device_initialize(&sd_mpath_disk->dev);
>> + sd_mpath_disk->dev.class = &sd_mpath_disk_class;
> [ ... ]
>> + error = mpath_alloc_head_disk(mpath_head, &lim,
>> + dev_to_node(dma_dev));
>> + if (error)
>> + goto out_free_disk;
> [ ... ]
>> +out_free_disk:
>> + kfree(sd_mpath_disk);
>
> [Severity: Medium]
> Is it safe to call kfree() directly on the device object here?
> Since device_initialize() was already called on sd_mpath_disk->dev,
> shouldn't we use put_device() to prevent leaking resources allocated by
> the kobject core?
will fix
>
> [ ... ]
>> +static void sd_mpath_remove(struct scsi_disk *sdkp)
>> +{
> [ ... ]
>> + mutex_lock(&sd_mpath_disks_lock);
>> + sd_mpath_disk->disk_count--;
>> + if (!sd_mpath_disk->disk_count && mpath_can_remove_head(mpath_head)) {
>> + list_del_init(&sd_mpath_disk->entry);
>> + remove = true;
>> + }
>> + mutex_unlock(&sd_mpath_disks_lock);
>> + mpath_remove_sysfs_link(mpath_device);
>> + mpath_device->disk = NULL;
>> +
>> + if (remove) {
>> + device_del(&sd_mpath_disk->dev);
>> + mpath_remove_disk(mpath_head);
>> + }
>> + sd_mpath_put_disk(sd_mpath_disk);
>> +}
>
> [Severity: High]
> Does this sequence risk a use-after-free if delayed removal is pending?
> If mpath_can_remove_head() returns false, the object remains on the
> global list and is not fully removed, but we unconditionally drop the
> reference via sd_mpath_put_disk() at the end. This could free the object
> while it is still globally accessible.
not an issue
>
> [ ... ]
>> +static void sd_mpath_remove_head(struct scsi_mpath_head *scsi_mpath_head)
>> +{
> [ ... ]
>> + list_del_init(&sd_mpath_disk->entry);
>> + mutex_unlock(&sd_mpath_disks_lock);
>> +
>> + device_del(&sd_mpath_disk->dev);
>> + mpath_remove_disk(mpath_head);
>> + sd_mpath_put_disk(sd_mpath_disk);
>> +}
>
> [Severity: High]
> Could dropping the mutex here race with a concurrent sd_mpath_probe()?
> We drop sd_mpath_disks_lock before calling mpath_remove_disk(mpath_head).
> If a new probe allocates a new disk and overwrites mpath_head->disk
> in that window, mpath_remove_disk() might delete the newly allocated
> gendisk instead of the old one being removed.
will check
>
> [ ... ]
>> +static void sd_mpath_fail_probe(struct scsi_disk *sdkp)
>> +{
> [ ... ]
>> + mutex_lock(&sd_mpath_disks_lock);
>> + sd_mpath_disk->disk_count--;
>> + if (!sd_mpath_disk->disk_count) {
>> + list_del_init(&sd_mpath_disk->entry);
>> + remove = true;
>> + }
>> + mutex_unlock(&sd_mpath_disks_lock);
>> + mpath_device->disk = NULL;
>> +
>> + if (remove) {
>> + device_del(&sd_mpath_disk->dev);
>> + mpath_remove_disk(mpath_head);
>> + }
>> + sd_mpath_put_disk(sd_mpath_disk);
>> +}
>
> [Severity: High]
> What happens if there is pending delayed work on mpath_head->remove_work
> when we hit this error path? If a previous removal queued the work,
> and a subsequent failed probe triggers this code, we delete and free the
> head without canceling the delayed work. Will the workqueue then trigger
> a use-after-free on the freed mpath_head?
will check
>
> [ ... ]
>> @@ -4158,11 +4515,18 @@ static int sd_probe(struct scsi_device *sdp)
>> sd_revalidate_disk(gd);
>> if (sdp->sector_size > PAGE_SIZE) {
>> if (sd_large_pool_create()) {
>> + sd_mpath_fail_probe(sdkp);
>> error = -ENOMEM;
>> goto out_free_index;
>> }
>> }
>
> [Severity: High]
> This is a pre-existing issue,
don't tell me pre-existing issues
but can this lead to a use-after-free in
> sysfs? At this point, device_add() has already succeeded. Bypassing
> device_del() or device_unregister() and jumping directly to
> out_free_index (which frees sdkp) leaves the device linked in the sysfs
> hierarchy.
>
> [ ... ]
>> @@ -4184,6 +4549,19 @@ static int sd_probe(struct scsi_device *sdp)
>> goto out;
>> }
>>
>> + if (sdp->scsi_mpath_dev) {
>> + error = sd_mpath_add_disk(sdkp);
>> + if (error) {
>> + sd_mpath_fail_probe(sdkp);
>> + device_del(&sdkp->disk_dev);
>> + del_gendisk(sdkp->disk);
>> +
>> + put_disk(sdkp->disk);
>> + if (sdp->sector_size > PAGE_SIZE)
>> + sd_large_pool_destroy();
>> + goto out;
>> + }
>> + }
>
> [Severity: Medium]
> Are we leaking the scsi_disk structure and its parent reference here?
> We call device_del(&sdkp->disk_dev), but we don't call put_device()
> to drop the base reference acquired during device_initialize().
>
not an issue as I think thAT del_gendisk handles it
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 27/28] scsi: sd: add mpath_numa_nodes dev attribute
2026-07-23 10:52 ` sashiko-bot
@ 2026-07-23 11:30 ` John Garry
0 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 11:30 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 23/07/2026 11:52, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [High] Reading the `mpath_numa_nodes` sysfs attribute before the device is fully initialized causes a guaranteed NULL pointer dereference on `mpath_device->mpath_head`.
> --
>
> --- Patch [27]: [PATCH v4 27/28] scsi: sd: add mpath_numa_nodes dev attribute ---
> commit cc5c94ceb8cbeeccd7a5678ef260c6bd21a0fd6b
> Author: John Garry <john.g.garry@oracle.com>
>
> scsi: sd: add mpath_numa_nodes dev attribute
>
> Add an attribute to show multipath NUMA node per-path (scsi_disk).
>
>> diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
>> index f8c9096e6c43c..7255df6e3e971 100644
>> --- a/drivers/scsi/sd.c
>> +++ b/drivers/scsi/sd.c
>> @@ -4076,8 +4076,22 @@ static ssize_t sd_mpath_dev_show(struct device *dev,
>> }
>> static DEVICE_ATTR(mpath_dev, 0444, sd_mpath_dev_show, NULL);
>>
>> +static ssize_t sd_mpath_numa_nodes_show(struct device *dev,
>> + struct device_attribute *attr, char *buf)
>> +{
>> + struct gendisk *gd = dev_to_disk(dev);
>> + struct scsi_disk *sdkp = gd->private_data;
>> + struct scsi_device *sdev = sdkp->device;
>> + struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
>> + struct mpath_device *mpath_device = &scsi_mpath_dev->mpath_device;
>> +
>> + return mpath_numa_nodes_show(mpath_device, buf);
>
> [Severity: High]
> Does this code risk a NULL pointer dereference if the attribute is read early?
>
> When sd_probe() calls device_add_disk(), it exposes the mpath_numa_nodes
> sysfs attribute to userspace and fires a uevent. If userspace (like udev)
> reads this file before sd_mpath_add_disk() runs, mpath_device->mpath_head
> will still be NULL.
will fix elsewhere
>
> When mpath_numa_nodes_show() receives this mpath_device, it passes the NULL
> mpath_head to mpath_read_iopolicy(), which will dereference it:
>
> lib/multipath.c:mpath_numa_nodes_show() {
> struct mpath_head *mpath_head = mpath_device->mpath_head;
> ...
> if (mpath_read_iopolicy(mpath_head) != MPATH_IOPOLICY_NUMA)
> ...
> }
>
> include/linux/multipath.h:mpath_read_iopolicy() {
> return READ_ONCE(*mpath_head->iopolicy);
> }
>
> Should we check if mpath_device->mpath_head is valid before attempting to
> dereference it?
>
>> +}
>> +static DEVICE_ATTR(mpath_numa_nodes, 0444, sd_mpath_numa_nodes_show, NULL);
>> +
>> static struct attribute *sd_mpath_dev_attrs[] = {
>> &dev_attr_mpath_dev.attr,
>> + &dev_attr_mpath_numa_nodes.attr,
>> NULL
>> };
>
^ permalink raw reply [flat|nested] 74+ messages in thread
* Re: [PATCH v4 26/28] scsi: sd: add mpath_dev file
2026-07-23 11:07 ` sashiko-bot
@ 2026-07-23 11:30 ` John Garry
0 siblings, 0 replies; 74+ messages in thread
From: John Garry @ 2026-07-23 11:30 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 23/07/2026 12:07, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [High] Reading the `mpath_dev` sysfs attribute can cause a use-after-free of the multipath `gendisk` and `scsi_mpath_head` objects during SCSI path removal.
> --
>
> --- Patch [26]: [PATCH v4 26/28] scsi: sd: add mpath_dev file ---
> commit 4a0e7acbc6d2b78e6b014dfe6924983c812cc80e
> Author: John Garry <john.g.garry@oracle.com>
> scsi: sd: add mpath_dev file
>
> This commit adds a mpath_dev file so the multipath disk can be looked up
> from the per-path gendisk directory. This allows utilities like lsscsi to
> find the multipath disk even when the gendisk for the per-path scsi_device
> is missing.
>
>> diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
>> index d5a37b1b312c3..f8c9096e6c43c 100644
>> --- a/drivers/scsi/sd.c
>> +++ b/drivers/scsi/sd.c
> [ ... ]
>> @@ -4059,6 +4059,53 @@ static int sd_mpath_revalidate_head(struct scsi_disk *sdkp)
>>
>> return ret;
>> }
>> +
>> +static ssize_t sd_mpath_dev_show(struct device *dev,
>> + struct device_attribute *attr, char *page)
>> +{
>> + struct gendisk *gd = dev_to_disk(dev);
>> + struct scsi_disk *sdkp = gd->private_data;
>> + struct scsi_device *sdev = sdkp->device;
>> + struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
>> + struct scsi_mpath_head *scsi_mpath_head = scsi_mpath_dev->scsi_mpath_head;
>> + struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
>> + struct gendisk *disk = mpath_head->disk;
>
> [Severity: High]
> Can a concurrent read of this sysfs file cause a use-after-free during device
> removal?
>
> Looking at sd_remove() in drivers/scsi/sd.c, it tears down the multipath
> structures before removing the sysfs interfaces:
>
> sd_remove() {
> if (sdp->scsi_mpath_dev)
> sd_mpath_remove(sdkp);
> ...
> device_del(&sdkp->disk_dev);
> del_gendisk(sdkp->disk);
> }
>
> Since del_gendisk() happens after sd_mpath_remove(), there is a window where
> the mpath_dev sysfs file is still accessible but the underlying multipath
> structures have been freed.
>
> Could an unprivileged user read the mpath_dev file during this window and
> dereference freed memory for scsi_mpath_head and mpath_head->disk?
will check
>
>> + struct device *disk_dev = disk_to_dev(disk);
>> +
>> + return print_dev_t(page, disk_dev->devt);
>> +}
>
^ permalink raw reply [flat|nested] 74+ messages in thread
end of thread, other threads:[~2026-07-23 11:31 UTC | newest]
Thread overview: 74+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-23 9:35 [PATCH v4 00/28] Native SCSI Multipath support John Garry
2026-07-23 9:35 ` [PATCH v4 01/28] libmultipath: Add initial framework John Garry
2026-07-23 9:50 ` sashiko-bot
2026-07-23 10:19 ` John Garry
2026-07-23 9:36 ` [PATCH v4 02/28] libmultipath: Add basic gendisk support John Garry
2026-07-23 9:57 ` sashiko-bot
2026-07-23 10:34 ` John Garry
2026-07-23 9:36 ` [PATCH v4 03/28] libmultipath: Add path selection support John Garry
2026-07-23 9:55 ` sashiko-bot
2026-07-23 10:28 ` John Garry
2026-07-23 9:36 ` [PATCH v4 04/28] libmultipath: Add bio handling John Garry
2026-07-23 9:36 ` [PATCH v4 05/28] libmultipath: Add support for mpath_device management John Garry
2026-07-23 9:58 ` sashiko-bot
2026-07-23 10:36 ` John Garry
2026-07-23 9:36 ` [PATCH v4 06/28] libmultipath: Add delayed removal support John Garry
2026-07-23 9:57 ` sashiko-bot
2026-07-23 10:33 ` John Garry
2026-07-23 9:36 ` [PATCH v4 07/28] libmultipath: Add sysfs helpers John Garry
2026-07-23 10:05 ` sashiko-bot
2026-07-23 10:37 ` John Garry
2026-07-23 9:36 ` [PATCH v4 08/28] libmultipath: Add mpath_bdev_report_zones() John Garry
2026-07-23 10:15 ` sashiko-bot
2026-07-23 10:39 ` John Garry
2026-07-23 9:36 ` [PATCH v4 09/28] libmultipath: Add support for block device IOCTL John Garry
2026-07-23 10:09 ` sashiko-bot
2026-07-23 10:38 ` John Garry
2026-07-23 9:36 ` [PATCH v4 10/28] libmultipath: Add mpath_bdev_getgeo() John Garry
2026-07-23 9:36 ` [PATCH v4 11/28] libmultipath: Add mpath_bdev_get_unique_id() John Garry
2026-07-23 9:36 ` [PATCH v4 12/28] scsi-multipath: introduce basic SCSI device support John Garry
2026-07-23 10:14 ` sashiko-bot
2026-07-23 9:36 ` [PATCH v4 13/28] scsi-multipath: introduce scsi_device head structure John Garry
2026-07-23 10:16 ` sashiko-bot
2026-07-23 10:47 ` John Garry
2026-07-23 9:36 ` [PATCH v4 14/28] scsi-multipath: provide sysfs link from to scsi_device John Garry
2026-07-23 9:36 ` [PATCH v4 15/28] scsi-multipath: support iopolicy John Garry
2026-07-23 10:20 ` sashiko-bot
2026-07-23 10:51 ` John Garry
2026-07-23 9:36 ` [PATCH v4 16/28] scsi-multipath: clone each bio John Garry
2026-07-23 10:27 ` sashiko-bot
2026-07-23 10:55 ` John Garry
2026-07-23 9:36 ` [PATCH v4 17/28] scsi-multipath: clear path when device is blocked John Garry
2026-07-23 10:33 ` sashiko-bot
2026-07-23 11:01 ` John Garry
2026-07-23 9:36 ` [PATCH v4 18/28] scsi-multipath: revalidate paths upon device unblock John Garry
2026-07-23 10:39 ` sashiko-bot
2026-07-23 11:15 ` John Garry
2026-07-23 9:36 ` [PATCH v4 19/28] scsi-multipath: failover handling John Garry
2026-07-23 10:36 ` sashiko-bot
2026-07-23 11:03 ` John Garry
2026-07-23 9:36 ` [PATCH v4 20/28] scsi-multipath: provide callbacks for path state John Garry
2026-07-23 10:36 ` sashiko-bot
2026-07-23 11:05 ` John Garry
2026-07-23 9:36 ` [PATCH v4 21/28] scsi-multipath: add scsi_mpath_{start,end}_request() John Garry
2026-07-23 10:32 ` sashiko-bot
2026-07-23 10:57 ` John Garry
2026-07-23 9:36 ` [PATCH v4 22/28] scsi-multipath: add delayed disk removal support John Garry
2026-07-23 10:39 ` sashiko-bot
2026-07-23 11:21 ` John Garry
2026-07-23 9:36 ` [PATCH v4 23/28] scsi: sd: add multipath disk class John Garry
2026-07-23 10:39 ` sashiko-bot
2026-07-23 11:21 ` John Garry
2026-07-23 9:36 ` [PATCH v4 24/28] scsi: sd: add multipath disk attr groups John Garry
2026-07-23 10:47 ` sashiko-bot
2026-07-23 11:22 ` John Garry
2026-07-23 9:36 ` [PATCH v4 25/28] scsi: sd: support multipath disk John Garry
2026-07-23 10:47 ` sashiko-bot
2026-07-23 11:27 ` John Garry
2026-07-23 9:36 ` [PATCH v4 26/28] scsi: sd: add mpath_dev file John Garry
2026-07-23 11:07 ` sashiko-bot
2026-07-23 11:30 ` John Garry
2026-07-23 9:36 ` [PATCH v4 27/28] scsi: sd: add mpath_numa_nodes dev attribute John Garry
2026-07-23 10:52 ` sashiko-bot
2026-07-23 11:30 ` John Garry
2026-07-23 9:36 ` [PATCH v4 28/28] scsi: sd: add mpath_queue_depth " John Garry
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.