[PATCH v3 1/2] scsi: libsas: Add PHYE_NOTIFY_ENABLE_SPINUP phy event for ASC/ASCQ=0x04/0x11

Xingui Yang <[email protected]> Mon, 3 Aug 2026 10:05:35 +0800
Newsgroups org.kernel.vger.linux-scsi,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
When a SAS device is in the Active_Wait or Idle_Wait power state, it
returns NOT_READY with ASC/ASCQ = 0x04/0x11 (notify (enable spinup)
required), indicating that a NOTIFY(ENABLE SPINUP) primitive is needed to
trigger media spinup.

Add a PHYE_NOTIFY_ENABLE_SPINUP phy event and an optional
lldd_notify_enable_spinup callback to sas_domain_function_template. Sense
detection is done in sas_ssp_task_spinup_notify(), called from
sas_ssp_task_response() which is the common entry point for all SAS LLDDs.

Signed-off-by: Xingui Yang <[email protected]>
---
 drivers/scsi/libsas/sas_internal.h  |  2 ++
 drivers/scsi/libsas/sas_phy.c       | 12 ++++++++
 drivers/scsi/libsas/sas_scsi_host.c | 45 +++++++++++++++++++++++++++++
 drivers/scsi/libsas/sas_task.c      |  2 ++
 include/scsi/libsas.h               |  9 ++++++
 5 files changed, 70 insertions(+)

diff --git a/drivers/scsi/libsas/sas_internal.h b/drivers/scsi/libsas/sas_internal.h
index 7dce0f587149..fa06f50b0bf0 100644
--- a/drivers/scsi/libsas/sas_internal.h
+++ b/drivers/scsi/libsas/sas_internal.h
@@ -107,6 +107,8 @@ extern const work_func_t sas_port_event_fns[PORT_NUM_EVENTS];
 
 void sas_task_internal_done(struct sas_task *task);
 void sas_task_internal_timedout(struct timer_list *t);
+void sas_ssp_task_spinup_notify(struct sas_task *task,
+				struct ssp_response_iu *iu);
 int sas_execute_tmf(struct domain_device *device, void *parameter,
 		    int para_len, int force_phy_id,
 		    struct sas_tmf_task *tmf);
diff --git a/drivers/scsi/libsas/sas_phy.c b/drivers/scsi/libsas/sas_phy.c
index 58f08dc2c187..897a5b46db78 100644
--- a/drivers/scsi/libsas/sas_phy.c
+++ b/drivers/scsi/libsas/sas_phy.c
@@ -111,6 +111,17 @@ static void sas_phye_shutdown(struct work_struct *work)
 	phy->in_shutdown = 0;
 }
 
+static void sas_phye_notify_enable_spinup(struct work_struct *work)
+{
+	struct asd_sas_event *ev = to_asd_sas_event(work);
+	struct asd_sas_phy *phy = ev->phy;
+	struct sas_ha_struct *sas_ha = phy->ha;
+	struct sas_internal *i =
+		to_sas_internal(sas_ha->shost->transportt);
+
+	i->dft->lldd_notify_enable_spinup(phy);
+}
+
 /* ---------- Phy class registration ---------- */
 
 int sas_register_phys(struct sas_ha_struct *sas_ha)
@@ -186,4 +197,5 @@ const work_func_t sas_phy_event_fns[PHY_NUM_EVENTS] = {
 	[PHYE_SPINUP_HOLD] = sas_phye_spinup_hold,
 	[PHYE_RESUME_TIMEOUT] = sas_phye_resume_timeout,
 	[PHYE_SHUTDOWN] = sas_phye_shutdown,
+	[PHYE_NOTIFY_ENABLE_SPINUP] = sas_phye_notify_enable_spinup,
 };
diff --git a/drivers/scsi/libsas/sas_scsi_host.c b/drivers/scsi/libsas/sas_scsi_host.c
index c83282733ec4..bb1dfc16d8d1 100644
--- a/drivers/scsi/libsas/sas_scsi_host.c
+++ b/drivers/scsi/libsas/sas_scsi_host.c
@@ -34,6 +34,51 @@
 #include <linux/scatterlist.h>
 #include <linux/libata.h>
 
+/*
+ * If the SSP response carries NOT_READY sense with ASC/ASCQ = 0x04/0x11
+ * ("notify (enable spinup) required"), queue a PHYE_NOTIFY_ENABLE_SPINUP
+ * phy event so the LLDD can send a NOTIFY(ENABLE SPINUP) primitive.
+ */
+void sas_ssp_task_spinup_notify(struct sas_task *task,
+				struct ssp_response_iu *iu)
+{
+	struct domain_device *dev = task->dev;
+	struct sas_ha_struct *ha = dev->port->ha;
+	struct sas_internal *i = to_sas_internal(ha->shost->transportt);
+	struct scsi_sense_hdr sshdr;
+	struct sas_phy *local_phy;
+	struct asd_sas_phy *phy;
+	u32 sense_len;
+
+	/*
+	 * NOTIFY(ENABLE SPINUP) must be sent on the local phy directly
+	 * attached to the target. Skip expander-attached devices.
+	 */
+	if (dev->parent && dev_is_expander(dev->parent->dev_type))
+		return;
+
+	if (!i->dft->lldd_notify_enable_spinup)
+		return;
+
+	if (iu->status != SAM_STAT_CHECK_CONDITION)
+		return;
+
+	sense_len = min_t(u32, be32_to_cpu(iu->sense_data_len),
+			  SAS_STATUS_BUF_SIZE);
+	if (!scsi_normalize_sense(iu->sense_data, sense_len, &sshdr))
+		return;
+
+	if (sshdr.sense_key != NOT_READY ||
+	    sshdr.asc != 0x04 || sshdr.ascq != 0x11)
+		return;
+
+	local_phy = sas_get_local_phy(dev);
+	phy = ha->sas_phy[local_phy->number];
+	sas_put_local_phy(local_phy);
+
+	sas_notify_phy_event(phy, PHYE_NOTIFY_ENABLE_SPINUP, GFP_ATOMIC);
+}
+
 /* record final status and free the task */
 static void sas_end_task(struct scsi_cmnd *sc, struct sas_task *task)
 {
diff --git a/drivers/scsi/libsas/sas_task.c b/drivers/scsi/libsas/sas_task.c
index e9d291007817..d1eb6ce90626 100644
--- a/drivers/scsi/libsas/sas_task.c
+++ b/drivers/scsi/libsas/sas_task.c
@@ -29,6 +29,8 @@ void sas_ssp_task_response(struct device *dev, struct sas_task *task,
 			      be32_to_cpu(iu->sense_data_len));
 		memcpy(tstat->buf, iu->sense_data, tstat->buf_valid_size);
 
+		sas_ssp_task_spinup_notify(task, iu);
+
 		if (iu->status != SAM_STAT_CHECK_CONDITION)
 			dev_warn(dev, "dev %016llx sent sense data, but stat(0x%x) is not CHECK CONDITION\n",
 				 SAS_ADDR(task->dev->sas_addr), iu->status);
diff --git a/include/scsi/libsas.h b/include/scsi/libsas.h
index 163f23c92b41..945b7cfe0224 100644
--- a/include/scsi/libsas.h
+++ b/include/scsi/libsas.h
@@ -49,6 +49,7 @@ enum phy_event {
 	PHYE_SPINUP_HOLD,             /* hot plug SATA, no COMWAKE sent */
 	PHYE_RESUME_TIMEOUT,
 	PHYE_SHUTDOWN,
+	PHYE_NOTIFY_ENABLE_SPINUP,    /* NOTIFY(ENABLE SPINUP) primitive */
 	PHY_NUM_EVENTS,
 };
 
@@ -674,6 +675,14 @@ struct sas_domain_function_template {
 	/* GPIO support */
 	int (*lldd_write_gpio)(struct sas_ha_struct *, u8 reg_type,
 			       u8 reg_index, u8 reg_count, u8 *write_data);
+
+	/*
+	 * Optional callback invoked when an SSP target returns NOT_READY
+	 * with ASC/ASCQ = 0x04/0x11 ("notify (enable spinup) required"),
+	 * indicating the device is in Active_Wait/Idle_Wait state and
+	 * needs a NOTIFY(ENABLE SPINUP) primitive to proceed.
+	 */
+	void (*lldd_notify_enable_spinup)(struct asd_sas_phy *phy);
 };
 
 extern int sas_register_ha(struct sas_ha_struct *);
-- 
2.43.0