On 3/10/26 12:49, John Garry wrote:
Add basic support just to get the per-port group state.

This support does not account of state transitioning, sdev port group
reconfiguration, etc, required for full support.

libmultipath callbacks scsi_mpath_is_optimized() and
scsi_mpath_is_disabled() are updated to take account of the ALUA-provided
path information.

As before, for no ALUA support (and scsi_multipath_always on) we assume
that the paths are all optimized.

Much of this code in scsi_mpath_alua_init() is copied from scsi_dh_alua.c,
originally authored by Hannes Reinecke.

Signed-off-by: John Garry <[email protected]>
---
  drivers/scsi/scsi_multipath.c | 163 ++++++++++++++++++++++++++++++++--
  include/scsi/scsi_multipath.h |   3 +
  2 files changed, 160 insertions(+), 6 deletions(-)

diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
index 1489c7e979167..0a314080bf0a5 100644
--- a/drivers/scsi/scsi_multipath.c
+++ b/drivers/scsi/scsi_multipath.c
@@ -4,6 +4,7 @@
   *
   */
+#include <scsi/scsi_alua.h>
  #include <scsi/scsi_cmnd.h>
  #include <scsi/scsi_driver.h>
  #include <scsi/scsi_proto.h>
@@ -346,18 +347,29 @@ static bool scsi_mpath_is_disabled(struct mpath_device 
*mpath_device)
                                to_scsi_mpath_device(mpath_device);
        struct scsi_device *sdev = scsi_mpath_dev->sdev;
        enum scsi_device_state sdev_state = sdev->sdev_state;
+       int alua_state = scsi_mpath_dev->alua_state;
if (sdev_state == SDEV_RUNNING || sdev_state == SDEV_CANCEL)
                return false;
- return true;
+       if (alua_state == SCSI_ACCESS_STATE_OPTIMAL ||
+           alua_state == SCSI_ACCESS_STATE_ACTIVE)
+               return true;
+
+       return false;
  }
static bool scsi_mpath_is_optimized(struct mpath_device *mpath_device)
  {
+       struct scsi_mpath_device *scsi_mpath_dev =
+                               to_scsi_mpath_device(mpath_device);
+
        if (scsi_mpath_is_disabled(mpath_device))
                return false;
-       return true;
+       if (scsi_mpath_dev->alua_state == SCSI_ACCESS_STATE_OPTIMAL)
+               return true;
+       return false;
+
  }
/* Until we have ALUA support, we're always optimised */
@@ -366,7 +378,7 @@ static enum mpath_access_state scsi_mpath_get_access_state(
  {
        if (scsi_mpath_is_disabled(mpath_device))
                return MPATH_STATE_INVALID;
-       return MPATH_STATE_OPTIMIZED;
+       return scsi_mpath_is_optimized(mpath_device);
  }
static bool scsi_mpath_available_path(struct mpath_device *mpath_device, bool *available)
@@ -579,16 +591,147 @@ static void scsi_multipath_sdev_uninit(struct 
scsi_device *sdev)
        sdev->scsi_mpath_dev = NULL;
  }
+static int scsi_mpath_alua_init(struct scsi_device *sdev)
+{
+       struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
+       struct scsi_sense_hdr sense_hdr;
+       int len, k, off, bufflen = ALUA_RTPG_SIZE;
+       unsigned char *desc, *buff;
+       unsigned int tpg_desc_tbl_off;
+       int group_id, rel_port = -1;
+       bool ext_hdr_unsupp = false;
+       int ret;
+
+       group_id = scsi_vpd_tpg_id(sdev, &rel_port);
+       if (group_id < 0) {
+               /*
+                * Internal error; TPGS supported but required
+                * VPD identification descriptors not present.
+                * Disable ALUA support.
+                */
+               sdev_printk(KERN_INFO, sdev,
+                           "%s: No target port descriptors found\n",
+                           __func__);
+               return -EIO;
+       }
+
+       buff = kzalloc(bufflen, GFP_KERNEL);
+       if (!buff)
+               return -ENOMEM;
+ retry:
+       ret = submit_rtpg(sdev, buff, bufflen, &sense_hdr,
+                               ext_hdr_unsupp);
+
+       if (ret) {
+               if (ret < 0 || !scsi_sense_valid(&sense_hdr)) {
+                       sdev_printk(KERN_INFO, sdev,
+                                   "%s: rtpg failed, result %d\n",
+                                   __func__, ret);
+                       kfree(buff);
+                       if (ret < 0)
+                               return -EBUSY;
+                       if (host_byte(ret) == DID_NO_CONNECT)
+                               return -ENODEV;
+                       return -EIO;
+               }
+
+               /*
+                * submit_rtpg() has failed on existing arrays
+                * when requesting extended header info, and
+                * the array doesn't support extended headers,
+                * even though it shouldn't according to T10.
+                * The retry without rtpg_ext_hdr_req set
+                * handles this.
+                * Note:  some arrays return a sense key of ILLEGAL_REQUEST
+                * with ASC 00h if they don't support the extended header.
+                */
+               if (ext_hdr_unsupp &&
+                   sense_hdr.sense_key == ILLEGAL_REQUEST) {
+                       ext_hdr_unsupp = true;
+                       goto retry;
+               }
+               /*
+                * If the array returns with 'ALUA state transition'
+                * sense code here it cannot return RTPG data during
+                * transition. So set the state to 'transitioning' directly.
+                */
+               if (sense_hdr.sense_key == NOT_READY &&
+                   sense_hdr.asc == 0x04 && sense_hdr.ascq == 0x0a)
+                       goto out;
+
+               /*
+                * Retry on any other UNIT ATTENTION occurred.
+                */
+               if (sense_hdr.sense_key == UNIT_ATTENTION) {
+                       scsi_print_sense_hdr(sdev, __func__, &sense_hdr);
+                       kfree(buff);
+                       return -EAGAIN;
+               }
+               sdev_printk(KERN_ERR, sdev, "%s: rtpg failed\n",
+                           __func__);
+               scsi_print_sense_hdr(sdev, __func__, &sense_hdr);
+               kfree(buff);
+               return -EIO;
+       }
+
+       len = get_unaligned_be32(&buff[0]) + 4;
+
+       if (len > bufflen) {
+               /* Resubmit with the correct length */
+               kfree(buff);
+               bufflen = len;
+               buff = kmalloc(bufflen, GFP_KERNEL);
+               if (!buff) {
+                       /* Temporary failure, bypass */
+                       return -EBUSY;
+               }
+               goto retry;
+       }
+
+       if ((buff[4] & RTPG_FMT_MASK) == RTPG_FMT_EXT_HDR)
+               tpg_desc_tbl_off = 8;
+       else
+               tpg_desc_tbl_off = 4;
+
+       for (k = tpg_desc_tbl_off, desc = buff + tpg_desc_tbl_off;
+            k < len;
+            k += off, desc += off) {
+               u16 group_id_found = get_unaligned_be16(&desc[2]);
+
+               if (group_id_found == group_id) {
+                       int valid_states, state, pref;
+
+                       state = desc[0] & 0x0f;
+                       pref = desc[0] >> 7;
+                       valid_states = desc[1];
+
+                       alua_print_info(sdev, group_id, state, pref, 
valid_states);
+
+                       scsi_mpath_dev->alua_state = state;
+                       scsi_mpath_dev->alua_pref = pref;
+                       scsi_mpath_dev->alua_valid_states = valid_states;
+                       goto out;
+               }
+
+               off = 8 + (desc[7] * 4);
+       }
+
+out:
+       kfree(buff);
+       return 0;
+}
+
  int scsi_mpath_dev_alloc(struct scsi_device *sdev)
  {
        struct scsi_mpath_head *scsi_mpath_head;
-       int ret;
+       int ret, tpgs;
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");
+       tpgs = alua_check_tpgs(sdev);
+       if (!(tpgs & TPGS_MODE_IMPLICIT) && !scsi_multipath_always) {
+               sdev_printk(KERN_DEBUG, sdev, "IMPLICIT TPGS are required for 
multipath support\n");
                return 0;
        }
@@ -622,6 +765,14 @@ int scsi_mpath_dev_alloc(struct scsi_device *sdev)
        sdev->scsi_mpath_dev->scsi_mpath_head = scsi_mpath_head;
found:
+       if (tpgs & TPGS_MODE_IMPLICIT) {
+               ret = scsi_mpath_alua_init(sdev);
+               if (ret)
+                       goto out_put_head;
+       } else {
+               sdev->scsi_mpath_dev->alua_state = SCSI_ACCESS_STATE_OPTIMAL;
+       }
+
        sdev->scsi_mpath_dev->index = ida_alloc(&scsi_mpath_head->ida, 
GFP_KERNEL);
        if (sdev->scsi_mpath_dev->index < 0) {
                ret = sdev->scsi_mpath_dev->index;
diff --git a/include/scsi/scsi_multipath.h b/include/scsi/scsi_multipath.h
index 2011447f482d6..7c7ee2fb7def7 100644
--- a/include/scsi/scsi_multipath.h
+++ b/include/scsi/scsi_multipath.h
@@ -38,6 +38,9 @@ struct scsi_mpath_device {
        int                     index;
        atomic_t                nr_active;
        struct scsi_mpath_head  *scsi_mpath_head;
+       int                     alua_state;
+       int                     alua_pref;
+       int                     alua_valid_states;
char device_id_str[SCSI_MPATH_DEVICE_ID_LEN];
  };

Is there a specific reason why this cannot be in the generic code?
After all, if the device reports anything else than ALUA_STATE_OPTIMAL
or ALUA_STATE_ACTIVE I/O will fail, irrespective of multipath being
active.

I would love to see that in the generic SCSI code, independent on this patchset. It would allow us to simplify the device handler code, too,
as then device handler really would only be required for explicit
ALUA. (And could be ignored for scsi-multipathing).

Cheers,

Hannes
--
Dr. Hannes Reinecke                  Kernel Storage Architect
[email protected]                               +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich

Reply via email to