[PATCH 03/13] scsi: alua: Add scsi_alua_rtpg()

John Garry posted 13 patches 2 weeks, 6 days ago
[PATCH 03/13] scsi: alua: Add scsi_alua_rtpg()
Posted by John Garry 2 weeks, 6 days ago
Add scsi_alua_rtpg(), which does the same as alua_rtpg() from
scsi_dh_alua.c

Members of the per-sdev alua_data structure are updated from same in
alua_dh_data.

Signed-off-by: John Garry <john.g.garry@oracle.com>
---
 drivers/scsi/scsi_alua.c | 311 +++++++++++++++++++++++++++++++++++++++
 include/scsi/scsi_alua.h |   8 +
 2 files changed, 319 insertions(+)

diff --git a/drivers/scsi/scsi_alua.c b/drivers/scsi/scsi_alua.c
index a5a67c6deff17..50c1d17b52dc7 100644
--- a/drivers/scsi/scsi_alua.c
+++ b/drivers/scsi/scsi_alua.c
@@ -6,6 +6,8 @@
  * All rights reserved.
  */
 
+#include <linux/unaligned.h>
+
 #include <scsi/scsi.h>
 #include <scsi/scsi_proto.h>
 #include <scsi/scsi_dbg.h>
@@ -16,6 +18,314 @@
 
 static struct workqueue_struct *kalua_wq;
 
+#define TPGS_SUPPORT_NONE		0x00
+#define TPGS_SUPPORT_OPTIMIZED		0x01
+#define TPGS_SUPPORT_NONOPTIMIZED	0x02
+#define TPGS_SUPPORT_STANDBY		0x04
+#define TPGS_SUPPORT_UNAVAILABLE	0x08
+#define TPGS_SUPPORT_LBA_DEPENDENT	0x10
+#define TPGS_SUPPORT_OFFLINE		0x40
+#define TPGS_SUPPORT_TRANSITION		0x80
+#define TPGS_SUPPORT_ALL		0xdf
+
+#define RTPG_FMT_MASK			0x70
+#define RTPG_FMT_EXT_HDR		0x10
+
+#define ALUA_RTPG_SIZE			128
+#define ALUA_FAILOVER_TIMEOUT		60
+#define ALUA_FAILOVER_RETRIES		5
+#define ALUA_RTPG_DELAY_MSECS		5
+#define ALUA_RTPG_RETRY_DELAY		2
+
+/*
+ * submit_rtpg - Issue a REPORT TARGET GROUP STATES command
+ * @sdev: sdev the command should be sent to
+ */
+static int submit_rtpg(struct scsi_device *sdev, unsigned char *buff,
+		       int bufflen, struct scsi_sense_hdr *sshdr)
+{
+	u8 cdb[MAX_COMMAND_SIZE];
+	blk_opf_t opf = REQ_OP_DRV_IN | REQ_FAILFAST_DEV |
+				REQ_FAILFAST_TRANSPORT | REQ_FAILFAST_DRIVER;
+	const struct scsi_exec_args exec_args = {
+		.sshdr = sshdr,
+	};
+
+	/* Prepare the command. */
+	memset(cdb, 0x0, MAX_COMMAND_SIZE);
+	cdb[0] = MAINTENANCE_IN;
+	if (!sdev->alua->rtpg_ext_hdr_unsupp)
+		cdb[1] = MI_REPORT_TARGET_PGS | MI_EXT_HDR_PARAM_FMT;
+	else
+		cdb[1] = MI_REPORT_TARGET_PGS;
+	put_unaligned_be32(bufflen, &cdb[6]);
+
+	return scsi_execute_cmd(sdev, cdb, opf, buff, bufflen,
+				ALUA_FAILOVER_TIMEOUT * HZ,
+				ALUA_FAILOVER_RETRIES, &exec_args);
+}
+
+static char print_alua_state(unsigned char state)
+{
+	switch (state) {
+	case SCSI_ACCESS_STATE_OPTIMAL:
+		return 'A';
+	case SCSI_ACCESS_STATE_ACTIVE:
+		return 'N';
+	case SCSI_ACCESS_STATE_STANDBY:
+		return 'S';
+	case SCSI_ACCESS_STATE_UNAVAILABLE:
+		return 'U';
+	case SCSI_ACCESS_STATE_LBA:
+		return 'L';
+	case SCSI_ACCESS_STATE_OFFLINE:
+		return 'O';
+	case SCSI_ACCESS_STATE_TRANSITIONING:
+		return 'T';
+	default:
+		return 'X';
+	}
+}
+
+/*
+ * scsi_alua_rtpg - Evaluate REPORT TARGET GROUP STATES
+ * @sdev: the device to be evaluated.
+ *
+ * Evaluate the Target Port Group State.
+ * Returns -ENODEV if the path is
+ * found to be unusable.
+ */
+__maybe_unused
+static int scsi_alua_rtpg(struct scsi_device *sdev)
+{
+	struct alua_data *alua = sdev->alua;
+	struct scsi_sense_hdr sense_hdr;
+	int len, k, off, bufflen = ALUA_RTPG_SIZE;
+	int group_id_old, state_old, pref_old, valid_states_old;
+	unsigned char *desc, *buff;
+	unsigned err;
+	int retval;
+	unsigned int tpg_desc_tbl_off;
+	unsigned char orig_transition_tmo;
+	unsigned long flags;
+	bool transitioning_sense = false;
+	int rel_port, 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",
+			    DRV_NAME);
+		return -EOPNOTSUPP; //SCSI_DH_DEV_UNSUPP;
+	}
+
+	group_id_old = alua->group_id;
+	state_old = alua->state;
+	pref_old = alua->pref;
+	valid_states_old = alua->valid_states;
+
+	if (!alua->expiry) {
+		unsigned long transition_tmo = ALUA_FAILOVER_TIMEOUT * HZ;
+
+		if (alua->transition_tmo)
+			transition_tmo = alua->transition_tmo * HZ;
+
+		alua->expiry = round_jiffies_up(jiffies + transition_tmo);
+	}
+
+	buff = kzalloc(bufflen, GFP_KERNEL);
+	if (!buff)
+		return -ENOMEM;
+
+ retry:
+	err = 0;
+	retval = submit_rtpg(sdev, buff, bufflen, &sense_hdr);
+
+	if (retval) {
+		/*
+		 * Some (broken) implementations have a habit of returning
+		 * an error during things like firmware update etc.
+		 * But if the target only supports active/optimized there's
+		 * not much we can do; it's not that we can switch paths
+		 * or anything.
+		 * So ignore any errors to avoid spurious failures during
+		 * path failover.
+		 */
+		if ((alua->valid_states & ~TPGS_SUPPORT_OPTIMIZED) == 0) {
+			sdev_printk(KERN_INFO, sdev,
+				    "%s: ignoring rtpg result %d\n",
+				    DRV_NAME, retval);
+			kfree(buff);
+			return 0;//SCSI_DH_OK
+		}
+		if (retval < 0 || !scsi_sense_valid(&sense_hdr)) {
+			sdev_printk(KERN_INFO, sdev,
+				    "%s: rtpg failed, result %d\n",
+				    DRV_NAME, retval);
+			kfree(buff);
+			if (retval < 0)
+				return -EBUSY;//SCSI_DH_DEV_TEMP_BUSY;
+			if (host_byte(retval) == DID_NO_CONNECT)
+				return -ENOENT;//SCSI_DH_RES_TEMP_UNAVAIL;
+			return -EIO;//SCSI_DH_IO
+		}
+
+		/*
+		 * 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 (!alua->rtpg_ext_hdr_unsupp &&
+		    sense_hdr.sense_key == ILLEGAL_REQUEST) {
+			alua->rtpg_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) {
+			transitioning_sense = true;
+			goto skip_rtpg;
+		}
+		/*
+		 * Retry on any other UNIT ATTENTION occurred.
+		 */
+		if (sense_hdr.sense_key == UNIT_ATTENTION)
+			err = -EAGAIN;//SCSI_DH_RETRY
+		if (err == -EAGAIN &&
+		    alua->expiry != 0 && time_before(jiffies, alua->expiry)) {
+			sdev_printk(KERN_ERR, sdev, "%s: rtpg retry\n",
+				    DRV_NAME);
+			scsi_print_sense_hdr(sdev, DRV_NAME, &sense_hdr);
+			kfree(buff);
+			return err;
+		}
+		sdev_printk(KERN_ERR, sdev, "%s: rtpg failed\n",
+			    DRV_NAME);
+		scsi_print_sense_hdr(sdev, DRV_NAME, &sense_hdr);
+		kfree(buff);
+		alua->expiry = 0;
+		return -EIO;//SCSI_DH_IO
+	}
+
+	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) {
+			sdev_printk(KERN_WARNING, sdev,
+				    "%s: kmalloc buffer failed\n",__func__);
+			/* Temporary failure, bypass */
+			alua->expiry = 0;
+			return -EBUSY;//SCSI_DH_DEV_TEMP_BUSY;
+		}
+		goto retry;
+	}
+
+	orig_transition_tmo = alua->transition_tmo;
+	if ((buff[4] & RTPG_FMT_MASK) == RTPG_FMT_EXT_HDR && buff[5] != 0)
+		alua->transition_tmo = buff[5];
+	else
+		alua->transition_tmo = ALUA_FAILOVER_TIMEOUT;
+
+	if (orig_transition_tmo != alua->transition_tmo) {
+		sdev_printk(KERN_INFO, sdev,
+			    "%s: transition timeout set to %d seconds\n",
+			    DRV_NAME, alua->transition_tmo);
+		alua->expiry = jiffies + alua->transition_tmo * HZ;
+	}
+
+	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_desc = get_unaligned_be16(&desc[2]);
+
+		spin_lock_irqsave(&alua->lock, flags);
+		if (group_id_desc == group_id) {
+			alua->group_id = group_id;
+			WRITE_ONCE(alua->state, desc[0] & 0x0f);
+			alua->pref = desc[0] >> 7;
+			WRITE_ONCE(sdev->access_state, desc[0]);
+			alua->valid_states = desc[1];
+		}
+		spin_unlock_irqrestore(&alua->lock, flags);
+		off = 8 + (desc[7] * 4);
+	}
+
+ skip_rtpg:
+	spin_lock_irqsave(&alua->lock, flags);
+	if (transitioning_sense)
+		alua->state = SCSI_ACCESS_STATE_TRANSITIONING;
+
+	if (group_id_old != alua->group_id || state_old != alua->state ||
+		pref_old != alua->pref || valid_states_old != alua->valid_states)
+		sdev_printk(KERN_INFO, sdev,
+			"%s: port group %02x state %c %s supports %c%c%c%c%c%c%c\n",
+			DRV_NAME, alua->group_id, print_alua_state(alua->state),
+			alua->pref ? "preferred" : "non-preferred",
+			alua->valid_states&TPGS_SUPPORT_TRANSITION?'T':'t',
+			alua->valid_states&TPGS_SUPPORT_OFFLINE?'O':'o',
+			alua->valid_states&TPGS_SUPPORT_LBA_DEPENDENT?'L':'l',
+			alua->valid_states&TPGS_SUPPORT_UNAVAILABLE?'U':'u',
+			alua->valid_states&TPGS_SUPPORT_STANDBY?'S':'s',
+			alua->valid_states&TPGS_SUPPORT_NONOPTIMIZED?'N':'n',
+			alua->valid_states&TPGS_SUPPORT_OPTIMIZED?'A':'a');
+
+	switch (alua->state) {
+	case SCSI_ACCESS_STATE_TRANSITIONING:
+		if (time_before(jiffies, alua->expiry)) {
+			/* State transition, retry */
+			alua->interval = ALUA_RTPG_RETRY_DELAY;
+			err = -EAGAIN;//SCSI_DH_RETRY
+		} else {
+			unsigned char access_state;
+
+			/* Transitioning time exceeded, set port to standby */
+			err = -EIO;//SCSI_DH_IO;
+			alua->state = SCSI_ACCESS_STATE_STANDBY;
+			alua->expiry = 0;
+			access_state = alua->state & SCSI_ACCESS_STATE_MASK;
+			if (alua->pref)
+				access_state |= SCSI_ACCESS_STATE_PREFERRED;
+			WRITE_ONCE(sdev->access_state, access_state);
+		}
+		break;
+	case SCSI_ACCESS_STATE_OFFLINE:
+		/* Path unusable */
+		err = -ENODEV;//SCSI_DH_DEV_OFFLINED;
+		alua->expiry = 0;
+		break;
+	default:
+		/* Useable path if active */
+		err = 0;//SCSI_DH_OK
+		alua->expiry = 0;
+		break;
+	}
+	spin_unlock_irqrestore(&alua->lock, flags);
+	kfree(buff);
+	return err;
+}
+
 int scsi_alua_sdev_init(struct scsi_device *sdev)
 {
 	int rel_port, ret, tpgs;
@@ -47,6 +357,7 @@ int scsi_alua_sdev_init(struct scsi_device *sdev)
 
 	sdev->alua->sdev = sdev;
 	sdev->alua->tpgs = tpgs;
+	spin_lock_init(&sdev->alua->lock);
 
 	return 0;
 out_free_data:
diff --git a/include/scsi/scsi_alua.h b/include/scsi/scsi_alua.h
index 07cdcb4f5b518..068277261ed9d 100644
--- a/include/scsi/scsi_alua.h
+++ b/include/scsi/scsi_alua.h
@@ -16,7 +16,15 @@
 struct alua_data {
 	int			group_id;
 	int			tpgs;
+	int			state;
+	int			pref;
+	int			valid_states;
+	bool			rtpg_ext_hdr_unsupp;
+	unsigned char		transition_tmo;
+	unsigned long		expiry;
+	unsigned long		interval;
 	struct scsi_device	*sdev;
+	spinlock_t		lock;
 };
 
 int scsi_alua_sdev_init(struct scsi_device *sdev);
-- 
2.43.5
Re: [PATCH 03/13] scsi: alua: Add scsi_alua_rtpg()
Posted by Hannes Reinecke 2 weeks, 5 days ago
On 3/17/26 13:06, John Garry wrote:
> Add scsi_alua_rtpg(), which does the same as alua_rtpg() from
> scsi_dh_alua.c
> 
> Members of the per-sdev alua_data structure are updated from same in
> alua_dh_data.
> 
> Signed-off-by: John Garry <john.g.garry@oracle.com>
> ---
>   drivers/scsi/scsi_alua.c | 311 +++++++++++++++++++++++++++++++++++++++
>   include/scsi/scsi_alua.h |   8 +
>   2 files changed, 319 insertions(+)
> 
> diff --git a/drivers/scsi/scsi_alua.c b/drivers/scsi/scsi_alua.c
> index a5a67c6deff17..50c1d17b52dc7 100644
> --- a/drivers/scsi/scsi_alua.c
> +++ b/drivers/scsi/scsi_alua.c
> @@ -6,6 +6,8 @@
>    * All rights reserved.
>    */
>   
> +#include <linux/unaligned.h>
> +
>   #include <scsi/scsi.h>
>   #include <scsi/scsi_proto.h>
>   #include <scsi/scsi_dbg.h>
> @@ -16,6 +18,314 @@
>   
>   static struct workqueue_struct *kalua_wq;
>   
> +#define TPGS_SUPPORT_NONE		0x00
> +#define TPGS_SUPPORT_OPTIMIZED		0x01
> +#define TPGS_SUPPORT_NONOPTIMIZED	0x02
> +#define TPGS_SUPPORT_STANDBY		0x04
> +#define TPGS_SUPPORT_UNAVAILABLE	0x08
> +#define TPGS_SUPPORT_LBA_DEPENDENT	0x10
> +#define TPGS_SUPPORT_OFFLINE		0x40
> +#define TPGS_SUPPORT_TRANSITION		0x80
> +#define TPGS_SUPPORT_ALL		0xdf
> +
> +#define RTPG_FMT_MASK			0x70
> +#define RTPG_FMT_EXT_HDR		0x10
> +
> +#define ALUA_RTPG_SIZE			128
> +#define ALUA_FAILOVER_TIMEOUT		60
> +#define ALUA_FAILOVER_RETRIES		5
> +#define ALUA_RTPG_DELAY_MSECS		5
> +#define ALUA_RTPG_RETRY_DELAY		2
> +
> +/*
> + * submit_rtpg - Issue a REPORT TARGET GROUP STATES command
> + * @sdev: sdev the command should be sent to
> + */
> +static int submit_rtpg(struct scsi_device *sdev, unsigned char *buff,
> +		       int bufflen, struct scsi_sense_hdr *sshdr)
> +{
> +	u8 cdb[MAX_COMMAND_SIZE];
> +	blk_opf_t opf = REQ_OP_DRV_IN | REQ_FAILFAST_DEV |
> +				REQ_FAILFAST_TRANSPORT | REQ_FAILFAST_DRIVER;
> +	const struct scsi_exec_args exec_args = {
> +		.sshdr = sshdr,
> +	};
> +
> +	/* Prepare the command. */
> +	memset(cdb, 0x0, MAX_COMMAND_SIZE);
> +	cdb[0] = MAINTENANCE_IN;
> +	if (!sdev->alua->rtpg_ext_hdr_unsupp)
> +		cdb[1] = MI_REPORT_TARGET_PGS | MI_EXT_HDR_PARAM_FMT;
> +	else
> +		cdb[1] = MI_REPORT_TARGET_PGS;
> +	put_unaligned_be32(bufflen, &cdb[6]);
> +
> +	return scsi_execute_cmd(sdev, cdb, opf, buff, bufflen,
> +				ALUA_FAILOVER_TIMEOUT * HZ,
> +				ALUA_FAILOVER_RETRIES, &exec_args);
> +}
> +
> +static char print_alua_state(unsigned char state)
> +{
> +	switch (state) {
> +	case SCSI_ACCESS_STATE_OPTIMAL:
> +		return 'A';
> +	case SCSI_ACCESS_STATE_ACTIVE:
> +		return 'N';
> +	case SCSI_ACCESS_STATE_STANDBY:
> +		return 'S';
> +	case SCSI_ACCESS_STATE_UNAVAILABLE:
> +		return 'U';
> +	case SCSI_ACCESS_STATE_LBA:
> +		return 'L';
> +	case SCSI_ACCESS_STATE_OFFLINE:
> +		return 'O';
> +	case SCSI_ACCESS_STATE_TRANSITIONING:
> +		return 'T';
> +	default:
> +		return 'X';
> +	}
> +}
> +
> +/*
> + * scsi_alua_rtpg - Evaluate REPORT TARGET GROUP STATES
> + * @sdev: the device to be evaluated.
> + *
> + * Evaluate the Target Port Group State.
> + * Returns -ENODEV if the path is
> + * found to be unusable.
> + */
> +__maybe_unused
> +static int scsi_alua_rtpg(struct scsi_device *sdev)
> +{
> +	struct alua_data *alua = sdev->alua;
> +	struct scsi_sense_hdr sense_hdr;
> +	int len, k, off, bufflen = ALUA_RTPG_SIZE;
> +	int group_id_old, state_old, pref_old, valid_states_old;
> +	unsigned char *desc, *buff;
> +	unsigned err;
> +	int retval;
> +	unsigned int tpg_desc_tbl_off;
> +	unsigned char orig_transition_tmo;
> +	unsigned long flags;
> +	bool transitioning_sense = false;
> +	int rel_port, 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",
> +			    DRV_NAME);
> +		return -EOPNOTSUPP; //SCSI_DH_DEV_UNSUPP;
> +	}
> +
> +	group_id_old = alua->group_id;
> +	state_old = alua->state;
> +	pref_old = alua->pref;
> +	valid_states_old = alua->valid_states;
> +
> +	if (!alua->expiry) {
> +		unsigned long transition_tmo = ALUA_FAILOVER_TIMEOUT * HZ;
> +
> +		if (alua->transition_tmo)
> +			transition_tmo = alua->transition_tmo * HZ;
> +
> +		alua->expiry = round_jiffies_up(jiffies + transition_tmo);
> +	}
> +
> +	buff = kzalloc(bufflen, GFP_KERNEL);
> +	if (!buff)
> +		return -ENOMEM;
> +
> + retry:
> +	err = 0;
> +	retval = submit_rtpg(sdev, buff, bufflen, &sense_hdr);
> +
> +	if (retval) {
> +		/*
> +		 * Some (broken) implementations have a habit of returning
> +		 * an error during things like firmware update etc.
> +		 * But if the target only supports active/optimized there's
> +		 * not much we can do; it's not that we can switch paths
> +		 * or anything.
> +		 * So ignore any errors to avoid spurious failures during
> +		 * path failover.
> +		 */
> +		if ((alua->valid_states & ~TPGS_SUPPORT_OPTIMIZED) == 0) {
> +			sdev_printk(KERN_INFO, sdev,
> +				    "%s: ignoring rtpg result %d\n",
> +				    DRV_NAME, retval);
> +			kfree(buff);
> +			return 0;//SCSI_DH_OK
> +		}
> +		if (retval < 0 || !scsi_sense_valid(&sense_hdr)) {
> +			sdev_printk(KERN_INFO, sdev,
> +				    "%s: rtpg failed, result %d\n",
> +				    DRV_NAME, retval);
> +			kfree(buff);
> +			if (retval < 0)
> +				return -EBUSY;//SCSI_DH_DEV_TEMP_BUSY;
> +			if (host_byte(retval) == DID_NO_CONNECT)
> +				return -ENOENT;//SCSI_DH_RES_TEMP_UNAVAIL;
> +			return -EIO;//SCSI_DH_IO
> +		}
> +
> +		/*
> +		 * 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 (!alua->rtpg_ext_hdr_unsupp &&
> +		    sense_hdr.sense_key == ILLEGAL_REQUEST) {
> +			alua->rtpg_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) {
> +			transitioning_sense = true;
> +			goto skip_rtpg;
> +		}
> +		/*
> +		 * Retry on any other UNIT ATTENTION occurred.
> +		 */
> +		if (sense_hdr.sense_key == UNIT_ATTENTION)
> +			err = -EAGAIN;//SCSI_DH_RETRY
> +		if (err == -EAGAIN &&
> +		    alua->expiry != 0 && time_before(jiffies, alua->expiry)) {
> +			sdev_printk(KERN_ERR, sdev, "%s: rtpg retry\n",
> +				    DRV_NAME);
> +			scsi_print_sense_hdr(sdev, DRV_NAME, &sense_hdr);
> +			kfree(buff);
> +			return err;
> +		}
> +		sdev_printk(KERN_ERR, sdev, "%s: rtpg failed\n",
> +			    DRV_NAME);
> +		scsi_print_sense_hdr(sdev, DRV_NAME, &sense_hdr);
> +		kfree(buff);
> +		alua->expiry = 0;
> +		return -EIO;//SCSI_DH_IO
> +	}
> +
> +	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) {
> +			sdev_printk(KERN_WARNING, sdev,
> +				    "%s: kmalloc buffer failed\n",__func__);
> +			/* Temporary failure, bypass */
> +			alua->expiry = 0;
> +			return -EBUSY;//SCSI_DH_DEV_TEMP_BUSY;
> +		}
> +		goto retry;
> +	}
> +
> +	orig_transition_tmo = alua->transition_tmo;
> +	if ((buff[4] & RTPG_FMT_MASK) == RTPG_FMT_EXT_HDR && buff[5] != 0)
> +		alua->transition_tmo = buff[5];
> +	else
> +		alua->transition_tmo = ALUA_FAILOVER_TIMEOUT;
> +
> +	if (orig_transition_tmo != alua->transition_tmo) {
> +		sdev_printk(KERN_INFO, sdev,
> +			    "%s: transition timeout set to %d seconds\n",
> +			    DRV_NAME, alua->transition_tmo);
> +		alua->expiry = jiffies + alua->transition_tmo * HZ;
> +	}
> +
> +	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_desc = get_unaligned_be16(&desc[2]);
> +
> +		spin_lock_irqsave(&alua->lock, flags);
> +		if (group_id_desc == group_id) {
> +			alua->group_id = group_id;
> +			WRITE_ONCE(alua->state, desc[0] & 0x0f);
> +			alua->pref = desc[0] >> 7;
> +			WRITE_ONCE(sdev->access_state, desc[0]);
> +			alua->valid_states = desc[1];
> +		}
> +		spin_unlock_irqrestore(&alua->lock, flags);
> +		off = 8 + (desc[7] * 4);
> +	}
> +
> + skip_rtpg:
> +	spin_lock_irqsave(&alua->lock, flags);
> +	if (transitioning_sense)
> +		alua->state = SCSI_ACCESS_STATE_TRANSITIONING;
> +
> +	if (group_id_old != alua->group_id || state_old != alua->state ||
> +		pref_old != alua->pref || valid_states_old != alua->valid_states)
> +		sdev_printk(KERN_INFO, sdev,
> +			"%s: port group %02x state %c %s supports %c%c%c%c%c%c%c\n",
> +			DRV_NAME, alua->group_id, print_alua_state(alua->state),
> +			alua->pref ? "preferred" : "non-preferred",
> +			alua->valid_states&TPGS_SUPPORT_TRANSITION?'T':'t',
> +			alua->valid_states&TPGS_SUPPORT_OFFLINE?'O':'o',
> +			alua->valid_states&TPGS_SUPPORT_LBA_DEPENDENT?'L':'l',
> +			alua->valid_states&TPGS_SUPPORT_UNAVAILABLE?'U':'u',
> +			alua->valid_states&TPGS_SUPPORT_STANDBY?'S':'s',
> +			alua->valid_states&TPGS_SUPPORT_NONOPTIMIZED?'N':'n',
> +			alua->valid_states&TPGS_SUPPORT_OPTIMIZED?'A':'a');
> +
> +	switch (alua->state) {
> +	case SCSI_ACCESS_STATE_TRANSITIONING:
> +		if (time_before(jiffies, alua->expiry)) {
> +			/* State transition, retry */
> +			alua->interval = ALUA_RTPG_RETRY_DELAY;
> +			err = -EAGAIN;//SCSI_DH_RETRY
> +		} else {
> +			unsigned char access_state;
> +
> +			/* Transitioning time exceeded, set port to standby */
> +			err = -EIO;//SCSI_DH_IO;
> +			alua->state = SCSI_ACCESS_STATE_STANDBY;
> +			alua->expiry = 0;
> +			access_state = alua->state & SCSI_ACCESS_STATE_MASK;
> +			if (alua->pref)
> +				access_state |= SCSI_ACCESS_STATE_PREFERRED;
> +			WRITE_ONCE(sdev->access_state, access_state);
> +		}
> +		break;
> +	case SCSI_ACCESS_STATE_OFFLINE:
> +		/* Path unusable */
> +		err = -ENODEV;//SCSI_DH_DEV_OFFLINED;
> +		alua->expiry = 0;
> +		break;
> +	default:
> +		/* Useable path if active */
> +		err = 0;//SCSI_DH_OK
> +		alua->expiry = 0;
> +		break;
> +	}
> +	spin_unlock_irqrestore(&alua->lock, flags);
> +	kfree(buff);
> +	return err;
> +}
> +
>   int scsi_alua_sdev_init(struct scsi_device *sdev)
>   {
>   	int rel_port, ret, tpgs;
> @@ -47,6 +357,7 @@ int scsi_alua_sdev_init(struct scsi_device *sdev)
>   
>   	sdev->alua->sdev = sdev;
>   	sdev->alua->tpgs = tpgs;
> +	spin_lock_init(&sdev->alua->lock);
>   
>   	return 0;
>   out_free_data:
> diff --git a/include/scsi/scsi_alua.h b/include/scsi/scsi_alua.h
> index 07cdcb4f5b518..068277261ed9d 100644
> --- a/include/scsi/scsi_alua.h
> +++ b/include/scsi/scsi_alua.h
> @@ -16,7 +16,15 @@
>   struct alua_data {
>   	int			group_id;
>   	int			tpgs;
> +	int			state;
> +	int			pref;
> +	int			valid_states;
> +	bool			rtpg_ext_hdr_unsupp;
> +	unsigned char		transition_tmo;
> +	unsigned long		expiry;
> +	unsigned long		interval;
>   	struct scsi_device	*sdev;
> +	spinlock_t		lock;
>   };
>   
>   int scsi_alua_sdev_init(struct scsi_device *sdev);

Ah, right. Now I see where you want to go with the separate
structure. Still wonder why you need the 'sdev' back link in
there, though.

Other than that:

Reviewed-by: Hannes Reinecke <hare@suse.de>

Cheers,

Hannes
-- 
Dr. Hannes Reinecke                  Kernel Storage Architect
hare@suse.de                                +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
Re: [PATCH 03/13] scsi: alua: Add scsi_alua_rtpg()
Posted by John Garry 2 weeks ago
On 18/03/2026 07:50, Hannes Reinecke wrote:
>> diff --git a/include/scsi/scsi_alua.h b/include/scsi/scsi_alua.h
>> index 07cdcb4f5b518..068277261ed9d 100644
>> --- a/include/scsi/scsi_alua.h
>> +++ b/include/scsi/scsi_alua.h
>> @@ -16,7 +16,15 @@
>>   struct alua_data {
>>       int            group_id;
>>       int            tpgs;
>> +    int            state;
>> +    int            pref;
>> +    int            valid_states;
>> +    bool            rtpg_ext_hdr_unsupp;
>> +    unsigned char        transition_tmo;
>> +    unsigned long        expiry;
>> +    unsigned long        interval;
>>       struct scsi_device    *sdev;
>> +    spinlock_t        lock;
>>   };
>>   int scsi_alua_sdev_init(struct scsi_device *sdev);
> 
> Ah, right. Now I see where you want to go with the separate
> structure. Still wonder why you need the 'sdev' back link in
> there, though.

at some points we need to look up the sdev from alua data, like 
scsi_alua_rtpg_run()

> 
> Other than that:
> 
> Reviewed-by: Hannes Reinecke <hare@suse.de>

cheers