linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 0/2] scsi: ufs: Enable bInitPowerMode of sleep
@ 2018-09-28 23:02 Evan Green
  2018-09-28 23:02 ` [PATCH v2 1/2] scsi: ufs: Allow SCSI commands early during init Evan Green
  2018-09-28 23:02 ` [PATCH v2 2/2] scsi: ufs: Execute START_STOP_UNIT " Evan Green
  0 siblings, 2 replies; 5+ messages in thread
From: Evan Green @ 2018-09-28 23:02 UTC (permalink / raw)
  To: Vinayak Holikatti, James E.J. Bottomley, Martin K. Petersen,
	Adrian Hunter, Stanislav Nijnikov, linux-scsi, linux-kernel
  Cc: Evan Green, Douglas Anderson, Gwendal Grignou

This change enables Linux to enumerate UFS devices that have been configured
to start in an initial power mode (bInitPowerMode) of sleep. The UFS spec
indicates that for devices in sleep mode, a START_STOP_UNIT SCSI command
must be sent to them to wake them up before doing other initialization tasks
like getting the device or geometry descriptors. This is not trivial to do
in the UFS driver, since it assumes that SCSI commands are only needed once
low level initialization of the device is complete.

These patches 1) Enable sending SCSI commands before the actual SCSI targets
are created, and 2) Sends the needed commands for devices in sleep mode.

This can be tested locally by setting byte 5 of the Configuration Descriptor
(bInitPowerMode) to zero, and rebooting. I discovered this bug by accidentally
zeroing out too many fields while I was testing provisioning functionality.

Note: This series is unrelated to (and independent of) the competing UFS
provisioning patches that have been floating around the list lately.

Changes since v1:
	* Split into two patches for easier comprehension. Can recombine to one
if this split is too contrived.
	* Fixed a leftover raw printk.

Evan Green (2):
  scsi: ufs: Allow SCSI commands early during init
  scsi: ufs: Execute START_STOP_UNIT during init

 drivers/scsi/ufs/ufshcd.c | 208 +++++++++++++++++++++++++++++++++++++++++++---
 drivers/scsi/ufs/ufshcd.h |  11 +++
 2 files changed, 206 insertions(+), 13 deletions(-)

-- 
2.16.4


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v2 1/2] scsi: ufs: Allow SCSI commands early during init
  2018-09-28 23:02 [PATCH v2 0/2] scsi: ufs: Enable bInitPowerMode of sleep Evan Green
@ 2018-09-28 23:02 ` Evan Green
  2018-09-28 23:02 ` [PATCH v2 2/2] scsi: ufs: Execute START_STOP_UNIT " Evan Green
  1 sibling, 0 replies; 5+ messages in thread
From: Evan Green @ 2018-09-28 23:02 UTC (permalink / raw)
  To: Vinayak Holikatti, James E.J. Bottomley, Martin K. Petersen,
	Adrian Hunter, Stanislav Nijnikov, linux-scsi, linux-kernel
  Cc: Evan Green, Douglas Anderson, Gwendal Grignou

The UFS driver is currently organized in such a way that sending SCSI
commands is not possible before SCSI targets have been created. This
presents a problem in that sending SCSI commands is necessary as
part of low-level initialization. The obvious solution would be to just
create the SCSI devices a little earlier during initialization, however
that kicks off the SCSI state machine to start poking at those targets,
which requires low-level initialzation to have already been completed.

This change adds a couple functions that enable sending SCSI requests
without actually having a SCSI target. The functions follow the same form
as the send_dev_cmd functions. It then wires that capability into a few
UFS functions that will be called during low-level initialization.

Signed-off-by: Evan Green <evgreen@chromium.org>
---
Changes since v1:
	* Refactored into two patches.

 drivers/scsi/ufs/ufshcd.c | 164 ++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 152 insertions(+), 12 deletions(-)

diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c
index c55f38ec391c..d5c9ca581905 100644
--- a/drivers/scsi/ufs/ufshcd.c
+++ b/drivers/scsi/ufs/ufshcd.c
@@ -234,6 +234,9 @@ static struct ufs_dev_fix ufs_fixups[] = {
 	END_FIX
 };
 
+static inline int
+ufshcd_scsi_cmd_status(struct ufshcd_lrb *lrbp, int scsi_status);
+
 static void ufshcd_tmc_handler(struct ufs_hba *hba);
 static void ufshcd_async_scan(void *data, async_cookie_t cookie);
 static int ufshcd_reset_and_restore(struct ufs_hba *hba);
@@ -2537,6 +2540,8 @@ static int
 ufshcd_dev_cmd_completion(struct ufs_hba *hba, struct ufshcd_lrb *lrbp)
 {
 	int resp;
+	int result;
+	int scsi_status;
 	int err = 0;
 
 	hba->ufs_stats.last_hibern8_exit_tstamp = ktime_set(0, 0);
@@ -2561,6 +2566,31 @@ ufshcd_dev_cmd_completion(struct ufs_hba *hba, struct ufshcd_lrb *lrbp)
 		dev_err(hba->dev, "%s: Reject UPIU not fully implemented\n",
 				__func__);
 		break;
+
+	case UPIU_TRANSACTION_RESPONSE:
+		/*
+		 * Get the response UPIU result to extract the SCSI command
+		 * status.
+		 */
+		result = ufshcd_get_rsp_upiu_result(lrbp->ucd_rsp_ptr);
+		scsi_status = result & MASK_SCSI_STATUS;
+		result = ufshcd_scsi_cmd_status(lrbp, scsi_status);
+		if ((result & MASK_SCSI_STATUS) != SAM_STAT_GOOD) {
+			dev_err(hba->dev,
+				"%s: Failed SCSI device management command: %x\n",
+				__func__, result);
+
+			print_hex_dump(KERN_ERR, "UFS Sense Data ",
+				       DUMP_PREFIX_OFFSET, 16, 1,
+				       lrbp->sense_buffer, lrbp->sense_bufflen,
+				       false);
+
+			ufshcd_print_trs(hba, 1 << lrbp->task_tag, true);
+			err = -EIO;
+		}
+
+		break;
+
 	default:
 		err = -EINVAL;
 		dev_err(hba->dev, "%s: Invalid device management cmd response: %x\n",
@@ -2704,6 +2734,84 @@ static int ufshcd_exec_dev_cmd(struct ufs_hba *hba,
 	return err;
 }
 
+static void ufshcd_scsi_dev_cmd_done(struct scsi_cmnd *cmd)
+{
+	struct ufs_hba *hba = (struct ufs_hba *)cmd->host_scribble;
+
+	if (hba->dev_cmd.complete) {
+		ufshcd_add_command_trace(hba, cmd->tag,
+				"dev_complete");
+		complete(hba->dev_cmd.complete);
+	}
+}
+
+/**
+ * ufshcd_exec_dev_scsi_cmd - API for sending device management SCSI requests
+ * @hba: UFS hba
+ * @cmd: specifies the SCSI command to send
+ * @timeout: time in seconds
+ *
+ * NOTE: Since there is only one available tag for device management commands,
+ * it is expected you hold the hba->dev_cmd.lock mutex.
+ */
+static int ufshcd_exec_dev_scsi_cmd(struct ufs_hba *hba,
+		struct scsi_cmnd *cmd, int timeout)
+{
+	struct ufshcd_lrb *lrbp;
+	int err;
+	int tag;
+	struct completion wait;
+	unsigned long flags;
+	u8 sense_data[18];
+
+	down_read(&hba->clk_scaling_lock);
+
+	/*
+	 * Get free slot, sleep if slots are unavailable.
+	 * Even though we use wait_event() which sleeps indefinitely,
+	 * the maximum wait time is bounded by SCSI request timeout.
+	 */
+	wait_event(hba->dev_cmd.tag_wq, ufshcd_get_dev_cmd_tag(hba, &tag));
+
+	/* Borrow the host_scribble to store a pointer back to the host */
+	cmd->scsi_done = ufshcd_scsi_dev_cmd_done;
+	cmd->host_scribble = (unsigned char *)hba;
+	cmd->tag = tag;
+	memset(&sense_data, 0, sizeof(sense_data));
+	init_completion(&wait);
+	lrbp = &hba->lrb[tag];
+	lrbp->cmd = cmd;
+	lrbp->task_tag = tag;
+	lrbp->lun = UFS_UPIU_UFS_DEVICE_WLUN;
+	lrbp->sense_buffer = sense_data;
+	lrbp->sense_bufflen = sizeof(sense_data);
+	err = ufshcd_comp_scsi_upiu(hba, lrbp);
+	if (unlikely(err))
+		goto out_put_tag;
+
+	hba->dev_cmd.complete = &wait;
+
+	ufshcd_add_query_upiu_trace(hba, tag, "query_send");
+	/* Make sure descriptors are ready before ringing the doorbell */
+	wmb();
+	spin_lock_irqsave(hba->host->host_lock, flags);
+	ufshcd_vops_setup_xfer_req(hba, tag, (lrbp->cmd ? true : false));
+	ufshcd_send_command(hba, tag);
+	spin_unlock_irqrestore(hba->host->host_lock, flags);
+
+	err = ufshcd_wait_for_dev_cmd(hba, lrbp, timeout);
+	ufshcd_add_query_upiu_trace(hba, tag,
+			err ? "query_complete_err" : "query_complete");
+
+out_put_tag:
+	lrbp->sense_buffer = NULL;
+	lrbp->sense_bufflen = 0;
+	ufshcd_put_dev_cmd_tag(hba, tag);
+	wake_up(&hba->dev_cmd.tag_wq);
+	up_read(&hba->clk_scaling_lock);
+	return err;
+}
+
 /**
  * ufshcd_init_query() - init the query response and request parameters
  * @hba: per-adapter instance
@@ -7216,6 +7324,7 @@ static void ufshcd_hba_exit(struct ufs_hba *hba)
 static int
 ufshcd_send_request_sense(struct ufs_hba *hba, struct scsi_device *sdp)
 {
+	struct scsi_cmnd scmd;
 	unsigned char cmd[6] = {REQUEST_SENSE,
 				0,
 				0,
@@ -7231,9 +7340,24 @@ ufshcd_send_request_sense(struct ufs_hba *hba, struct scsi_device *sdp)
 		goto out;
 	}
 
-	ret = scsi_execute(sdp, cmd, DMA_FROM_DEVICE, buffer,
-			UFSHCD_REQ_SENSE_SIZE, NULL, NULL,
-			msecs_to_jiffies(1000), 3, 0, RQF_PM, NULL);
+	if (sdp) {
+		ret = scsi_execute(sdp, cmd, DMA_FROM_DEVICE, buffer,
+				UFSHCD_REQ_SENSE_SIZE, NULL, NULL,
+				msecs_to_jiffies(1000), 3, 0, RQF_PM, NULL);
+	} else {
+		memset(&scmd, 0, sizeof(scmd));
+		scmd.sc_data_direction = DMA_NONE;
+		scmd.cmnd = cmd;
+		scmd.cmd_len = sizeof(cmd);
+		/* No data transfer is performed during early transfers. */
+		cmd[4] = 0;
+		mutex_lock(&hba->dev_cmd.lock);
+		ret = ufshcd_exec_dev_scsi_cmd(hba,
+			&scmd, msecs_to_jiffies(1000));
+
+		mutex_unlock(&hba->dev_cmd.lock);
+	}
+
 	if (ret)
 		pr_err("%s: failed with err %d\n", __func__, ret);
 
@@ -7252,30 +7376,27 @@ ufshcd_send_request_sense(struct ufs_hba *hba, struct scsi_device *sdp)
  * Returns non-zero if failed to set the requested power mode
  */
 static int ufshcd_set_dev_pwr_mode(struct ufs_hba *hba,
-				     enum ufs_dev_pwr_mode pwr_mode)
+				   enum ufs_dev_pwr_mode pwr_mode)
 {
+	struct scsi_cmnd scmd;
 	unsigned char cmd[6] = { START_STOP };
 	struct scsi_sense_hdr sshdr;
 	struct scsi_device *sdp;
 	unsigned long flags;
 	int ret;
 
+	cmd[4] = pwr_mode << 4;
 	spin_lock_irqsave(hba->host->host_lock, flags);
 	sdp = hba->sdev_ufs_device;
 	if (sdp) {
 		ret = scsi_device_get(sdp);
 		if (!ret && !scsi_device_online(sdp)) {
-			ret = -ENODEV;
 			scsi_device_put(sdp);
+			sdp = NULL;
 		}
-	} else {
-		ret = -ENODEV;
 	}
 	spin_unlock_irqrestore(hba->host->host_lock, flags);
 
-	if (ret)
-		return ret;
-
 	/*
 	 * If scsi commands fail, the scsi mid-layer schedules scsi error-
 	 * handling, which would wait for host to be resumed. Since we know
@@ -7291,7 +7412,24 @@ static int ufshcd_set_dev_pwr_mode(struct ufs_hba *hba,
 		hba->wlun_dev_clr_ua = false;
 	}
 
-	cmd[4] = pwr_mode << 4;
+	/*
+	 * If SCSI is not yet alive, try sending this command manually.
+	 * This is needed to avoid a circular dependency where SCSI
+	 * needs low level initialization to happen, but sending a
+	 * SCSI command (like START STOP UNIT) is part of low level
+	 * initialization.
+	 */
+	if (!sdp) {
+		memset(&scmd, 0, sizeof(scmd));
+		scmd.sc_data_direction = DMA_TO_DEVICE;
+		scmd.cmnd = cmd;
+		scmd.cmd_len = sizeof(cmd);
+		mutex_lock(&hba->dev_cmd.lock);
+		ret = ufshcd_exec_dev_scsi_cmd(hba, &scmd, START_STOP_TIMEOUT);
+		mutex_unlock(&hba->dev_cmd.lock);
+		hba->host->eh_noresume = 0;
+		return ret;
+	}
 
 	/*
 	 * Current function would be generally called from the power management
@@ -7311,7 +7449,9 @@ static int ufshcd_set_dev_pwr_mode(struct ufs_hba *hba,
 	if (!ret)
 		hba->curr_dev_pwr_mode = pwr_mode;
 out:
-	scsi_device_put(sdp);
+	if (sdp)
+		scsi_device_put(sdp);
+
 	hba->host->eh_noresume = 0;
 	return ret;
 }
-- 
2.16.4


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v2 2/2] scsi: ufs: Execute START_STOP_UNIT during init
  2018-09-28 23:02 [PATCH v2 0/2] scsi: ufs: Enable bInitPowerMode of sleep Evan Green
  2018-09-28 23:02 ` [PATCH v2 1/2] scsi: ufs: Allow SCSI commands early during init Evan Green
@ 2018-09-28 23:02 ` Evan Green
  2018-09-28 23:04   ` Christoph Hellwig
  1 sibling, 1 reply; 5+ messages in thread
From: Evan Green @ 2018-09-28 23:02 UTC (permalink / raw)
  To: Vinayak Holikatti, James E.J. Bottomley, Martin K. Petersen,
	Adrian Hunter, Stanislav Nijnikov, linux-scsi, linux-kernel
  Cc: Evan Green, Douglas Anderson, Gwendal Grignou

For UFS devices that are provisioned to have an initial power mode
(bInitPowerMode) of "sleep", Linux will currently fail to enumerate
the device. This is because the UFS specification says that the
device must get a START_STOP_UNIT SCSI command to wake the unit
up before other common initialization features like the device
descriptor will be available to be read.

This script executes a TEST UNIT READY and a START STOP UNIT SCSI
command to bring the device out of sleep mode before performing
additional low-level initialization.

Signed-off-by: Evan Green <evgreen@chromium.org>
---
Changes since v1:
	* Refactored into two patches.
	* Converted a leftover raw printk into dev_err.

 drivers/scsi/ufs/ufshcd.c | 44 +++++++++++++++++++++++++++++++++++++++++++-
 drivers/scsi/ufs/ufshcd.h | 11 +++++++++++
 2 files changed, 54 insertions(+), 1 deletion(-)

diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c
index d5c9ca581905..f19c44c7c2e5 100644
--- a/drivers/scsi/ufs/ufshcd.c
+++ b/drivers/scsi/ufs/ufshcd.c
@@ -243,6 +243,9 @@ static int ufshcd_reset_and_restore(struct ufs_hba *hba);
 static int ufshcd_eh_host_reset_handler(struct scsi_cmnd *cmd);
 static int ufshcd_clear_tm_cmd(struct ufs_hba *hba, int tag);
 static void ufshcd_hba_exit(struct ufs_hba *hba);
+static int ufshcd_set_dev_pwr_mode(struct ufs_hba *hba,
+				   enum ufs_dev_pwr_mode pwr_mode);
+
 static int ufshcd_probe_hba(struct ufs_hba *hba);
 static int __ufshcd_setup_clocks(struct ufs_hba *hba, bool on,
 				 bool skip_ref_clk);
@@ -4221,6 +4224,35 @@ static int ufshcd_complete_dev_init(struct ufs_hba *hba)
 	return err;
 }
 
+/**
+ * ufshcd_power_on() - checks device power state, and sends START STOP UNIT
+ * if needed to bring the device out of sleep mode.
+ * @hba: per-adapter instance
+ *
+ */
+static int ufshcd_power_on(struct ufs_hba *hba)
+{
+	uint32_t current_pwr_mode;
+	int rc;
+
+	rc = ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_READ_ATTR,
+		QUERY_ATTR_IDN_POWER_MODE, 0, 0,
+		&current_pwr_mode);
+
+	if (rc) {
+		dev_err(hba->dev, "Failed to get bCurrentPowerMode: %d\n", rc);
+		return rc;
+	}
+
+	if (current_pwr_mode != UFS_PWR_SLEEP)
+		return 0;
+
+	rc = ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE);
+	if (rc)
+		dev_err(hba->dev, "Failed to set power mode: %d\n", rc);
+
+	return rc;
+}
 /**
  * ufshcd_make_hba_operational - Make UFS controller operational
  * @hba: per adapter instance
@@ -6687,6 +6719,17 @@ static int ufshcd_probe_hba(struct ufs_hba *hba)
 	if (ret)
 		goto out;
 
+	/*
+	 * Unit Attention will need to be cleared after a reset and before
+	 * the device can be told to come out of sleep mode.
+	 */
+	hba->wlun_dev_clr_ua = true;
+	ret = ufshcd_power_on(hba);
+	if (ret) {
+		dev_err(hba->dev, "Failed to start unit. err = %d\n", ret);
+		goto out;
+	}
+
 	/* Init check for device descriptor sizes */
 	ufshcd_init_desc_sizes(hba);
 
@@ -6708,7 +6751,6 @@ static int ufshcd_probe_hba(struct ufs_hba *hba)
 	/* UFS device is also active now */
 	ufshcd_set_ufs_dev_active(hba);
 	ufshcd_force_reset_auto_bkops(hba);
-	hba->wlun_dev_clr_ua = true;
 
 	if (ufshcd_get_max_pwr_mode(hba)) {
 		dev_err(hba->dev,
diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h
index 33fdd3f281ae..7c340a4b6873 100644
--- a/drivers/scsi/ufs/ufshcd.h
+++ b/drivers/scsi/ufs/ufshcd.h
@@ -128,6 +128,17 @@ enum uic_link_state {
 #define ufshcd_set_link_hibern8(hba) ((hba)->uic_link_state = \
 				    UIC_LINK_HIBERN8_STATE)
 
+/* Values for the bCurrentPowerMode attribute */
+enum ufs_pwr_mode {
+	UFS_PWR_IDLE = 0x0,
+	UFS_PWR_PRE_ACTIVE = 0x1,
+	UFS_PWR_ACTIVE = 0x11,
+	UFS_PWR_PRE_SLEEP = 0x20,
+	UFS_PWR_SLEEP = 0x22,
+	UFS_PWR_PRE_PWR_DOWN = 0x30,
+	UFS_PWR_DOWN = 0x33,
+};
+
 /*
  * UFS Power management levels.
  * Each level is in increasing order of power savings.
-- 
2.16.4


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH v2 2/2] scsi: ufs: Execute START_STOP_UNIT during init
  2018-09-28 23:02 ` [PATCH v2 2/2] scsi: ufs: Execute START_STOP_UNIT " Evan Green
@ 2018-09-28 23:04   ` Christoph Hellwig
  2018-09-28 23:09     ` Evan Green
  0 siblings, 1 reply; 5+ messages in thread
From: Christoph Hellwig @ 2018-09-28 23:04 UTC (permalink / raw)
  To: Evan Green
  Cc: Vinayak Holikatti, James E.J. Bottomley, Martin K. Petersen,
	Adrian Hunter, Stanislav Nijnikov, linux-scsi, linux-kernel,
	Douglas Anderson, Gwendal Grignou

On Fri, Sep 28, 2018 at 04:02:03PM -0700, Evan Green wrote:
> For UFS devices that are provisioned to have an initial power mode
> (bInitPowerMode) of "sleep", Linux will currently fail to enumerate
> the device. This is because the UFS specification says that the
> device must get a START_STOP_UNIT SCSI command to wake the unit
> up before other common initialization features like the device
> descriptor will be available to be read.

Yikes, this is just completely broken in terms of scsi compliance.

I think we should simply not support such devices.

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH v2 2/2] scsi: ufs: Execute START_STOP_UNIT during init
  2018-09-28 23:04   ` Christoph Hellwig
@ 2018-09-28 23:09     ` Evan Green
  0 siblings, 0 replies; 5+ messages in thread
From: Evan Green @ 2018-09-28 23:09 UTC (permalink / raw)
  To: hch
  Cc: Vinayak Holikatti, jejb, martin.petersen, adrian.hunter,
	stanislav.nijnikov, linux-scsi, linux-kernel, Doug Anderson,
	Gwendal Grignou

On Fri, Sep 28, 2018 at 4:04 PM Christoph Hellwig <hch@infradead.org> wrote:
>
> On Fri, Sep 28, 2018 at 04:02:03PM -0700, Evan Green wrote:
> > For UFS devices that are provisioned to have an initial power mode
> > (bInitPowerMode) of "sleep", Linux will currently fail to enumerate
> > the device. This is because the UFS specification says that the
> > device must get a START_STOP_UNIT SCSI command to wake the unit
> > up before other common initialization features like the device
> > descriptor will be available to be read.
>
> Yikes, this is just completely broken in terms of scsi compliance.
>
> I think we should simply not support such devices.

Really? It's part of the UFS spec. The document I have specifies that
I shouldn't reproduce without permission, but it's in JESD220A section
7.2.4 UFS-Sleep Power Mode.
-Evan

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2018-09-28 23:10 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-09-28 23:02 [PATCH v2 0/2] scsi: ufs: Enable bInitPowerMode of sleep Evan Green
2018-09-28 23:02 ` [PATCH v2 1/2] scsi: ufs: Allow SCSI commands early during init Evan Green
2018-09-28 23:02 ` [PATCH v2 2/2] scsi: ufs: Execute START_STOP_UNIT " Evan Green
2018-09-28 23:04   ` Christoph Hellwig
2018-09-28 23:09     ` Evan Green

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).