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.

Two module params are introduced to enable multipath:
- scsi_multipath
- scsi_multipath_always

SCSI multipath will only be available until the following conditions:
- scsi_multipath enabled and ALUA supported and unique ID available in
  VPD page 83.
- scsi_multipath_always enabled and unique ID available in VPD page 83

The scsi_device structure contains a pointer to scsi_mpath_device, which
means whether multipath is enabled or disabled for the scsi_device.

Signed-off-by: John Garry <[email protected]>
---
 drivers/scsi/Kconfig          |  10 +++
 drivers/scsi/Makefile         |   1 +
 drivers/scsi/scsi.c           |   8 +-
 drivers/scsi/scsi_multipath.c | 158 ++++++++++++++++++++++++++++++++++
 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, 239 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 19d0884479a24..cfab7ad1e3c2c 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"
+       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 16de3e41f94c4..64b7a82828b81 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 28c9bbf439db6..99920715a9896 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..04e0bad3d9204
--- /dev/null
+++ b/drivers/scsi/scsi_multipath.c
@@ -0,0 +1,158 @@
+// 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"
+
+bool scsi_multipath;
+static bool scsi_multipath_always;
+
+static int multipath_param_set(const char *val, const struct kernel_param *kp)
+{
+       int ret;
+       bool *arg = kp->arg;
+
+       ret = param_set_bool(val, kp);
+       if (ret)
+               return ret;
+
+       if (scsi_multipath_always && !*arg) {
+               pr_err("Can't disable multipath when multipath_always_on is 
configured.\n");
+               *arg = true;
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static const struct kernel_param_ops multipath_param_ops = {
+       .set = multipath_param_set,
+       .get = param_get_bool,
+};
+
+module_param_cb(scsi_multipath, &multipath_param_ops, &scsi_multipath, 0444);
+MODULE_PARM_DESC(scsi_multipath, "turn on native multipath support");
+
+static int multipath_always_on_set(const char *val,
+               const struct kernel_param *kp)
+{
+       int ret;
+       bool *arg = kp->arg;
+
+       ret = param_set_bool(val, kp);
+       if (ret < 0)
+               return ret;
+
+       if (*arg)
+               scsi_multipath = true;
+
+       return 0;
+}
+
+static const struct kernel_param_ops multipath_always_on_ops = {
+       .set = multipath_always_on_set,
+       .get = param_get_bool,
+};
+
+module_param_cb(scsi_multipath_always, &multipath_always_on_ops,
+               &scsi_multipath_always, 0444);
+MODULE_PARM_DESC(scsi_multipath_always,
+       "create multipath node always even for no ALUA support");
+
+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);
+
+       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)
+               return 0;
+
+       if (!scsi_device_tpgs(sdev) && !scsi_multipath_always) {
+               sdev_printk(KERN_NOTICE, sdev, "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) {
+               ret = 0;
+               goto out_uninit;
+       }
+
+       return 0;
+
+out_uninit:
+       scsi_multipath_sdev_uninit(sdev);
+       return ret;
+}
+
+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 7acbfcfc2172e..e22d3245d4b65 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"
@@ -1122,6 +1123,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 99eb0a30df615..0d69e27600a7a 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 d32f5841f4f85..52974dba0a724 100644
--- a/include/scsi/scsi_device.h
+++ b/include/scsi/scsi_device.h
@@ -279,6 +279,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..ca00ea10cd5db
--- /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 40
+
+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.5


Reply via email to