2021-02-22 23:08:07

by Asutosh Das (asd)

[permalink] [raw]
Subject: [PATCH v4 1/2] scsi: ufs: Enable power management for wlun

During runtime-suspend of ufs host, the scsi devices are
already suspended and so are the queues associated with them.
But the ufs host sends SSU to wlun during its runtime-suspend.
During the process blk_queue_enter checks if the queue is not in
suspended state. If so, it waits for the queue to resume, and never
comes out of it.
The commit
(d55d15a33: scsi: block: Do not accept any requests while suspended)
adds the check if the queue is in suspended state in blk_queue_enter().

Call trace:
__switch_to+0x174/0x2c4
__schedule+0x478/0x764
schedule+0x9c/0xe0
blk_queue_enter+0x158/0x228
blk_mq_alloc_request+0x40/0xa4
blk_get_request+0x2c/0x70
__scsi_execute+0x60/0x1c4
ufshcd_set_dev_pwr_mode+0x124/0x1e4
ufshcd_suspend+0x208/0x83c
ufshcd_runtime_suspend+0x40/0x154
ufshcd_pltfrm_runtime_suspend+0x14/0x20
pm_generic_runtime_suspend+0x28/0x3c
__rpm_callback+0x80/0x2a4
rpm_suspend+0x308/0x614
rpm_idle+0x158/0x228
pm_runtime_work+0x84/0xac
process_one_work+0x1f0/0x470
worker_thread+0x26c/0x4c8
kthread+0x13c/0x320
ret_from_fork+0x10/0x18

Fix this by registering ufs device wlun as a scsi driver and
registering it for block runtime-pm. Also make this as a
supplier for all other luns. That way, this device wlun
suspends after all the consumers and resumes after
hba resumes.

Co-developed-by: Can Guo <[email protected]>
Signed-off-by: Can Guo <[email protected]>
Signed-off-by: Asutosh Das <[email protected]>
---
drivers/scsi/ufs/ufshcd.c | 455 +++++++++++++++++++++++++++++++++++----------
drivers/scsi/ufs/ufshcd.h | 4 +
include/trace/events/ufs.h | 20 ++
3 files changed, 382 insertions(+), 97 deletions(-)

diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c
index 45624c7..1d8044a 100644
--- a/drivers/scsi/ufs/ufshcd.c
+++ b/drivers/scsi/ufs/ufshcd.c
@@ -16,6 +16,7 @@
#include <linux/bitfield.h>
#include <linux/blk-pm.h>
#include <linux/blkdev.h>
+#include <scsi/scsi_driver.h>
#include "ufshcd.h"
#include "ufs_quirks.h"
#include "unipro.h"
@@ -251,6 +252,11 @@ static int ufshcd_wb_toggle_flush_during_h8(struct ufs_hba *hba, bool set);
static inline int ufshcd_wb_toggle_flush(struct ufs_hba *hba, bool enable);
static void ufshcd_hba_vreg_set_lpm(struct ufs_hba *hba);
static void ufshcd_hba_vreg_set_hpm(struct ufs_hba *hba);
+static int ufshcd_wl_runtime_suspend(struct device *dev);
+static int ufshcd_wl_runtime_resume(struct device *dev);
+static int ufshcd_wl_suspend(struct device *dev);
+static int ufshcd_wl_resume(struct device *dev);
+static void ufshcd_wl_shutdown(struct device *dev);

static inline bool ufshcd_valid_tag(struct ufs_hba *hba, int tag)
{
@@ -1556,7 +1562,7 @@ static ssize_t ufshcd_clkscale_enable_store(struct device *dev,
if (value == hba->clk_scaling.is_enabled)
goto out;

- pm_runtime_get_sync(hba->dev);
+ scsi_autopm_get_device(hba->sdev_ufs_device);
ufshcd_hold(hba, false);

hba->clk_scaling.is_enabled = value;
@@ -1572,7 +1578,7 @@ static ssize_t ufshcd_clkscale_enable_store(struct device *dev,
}

ufshcd_release(hba);
- pm_runtime_put_sync(hba->dev);
+ scsi_autopm_put_device(hba->sdev_ufs_device);
out:
up(&hba->host_sem);
return err ? err : count;
@@ -2572,6 +2578,17 @@ static inline u16 ufshcd_upiu_wlun_to_scsi_wlun(u8 upiu_wlun_id)
return (upiu_wlun_id & ~UFS_UPIU_WLUN_ID) | SCSI_W_LUN_BASE;
}

+static inline bool is_rpmb_wlun(struct scsi_device *sdev)
+{
+ return (sdev->lun == ufshcd_upiu_wlun_to_scsi_wlun(UFS_UPIU_RPMB_WLUN));
+}
+
+static inline bool is_device_wlun(struct scsi_device *sdev)
+{
+ return (sdev->lun ==
+ ufshcd_upiu_wlun_to_scsi_wlun(UFS_UPIU_UFS_DEVICE_WLUN));
+}
+
static void ufshcd_init_lrb(struct ufs_hba *hba, struct ufshcd_lrb *lrb, int i)
{
struct utp_transfer_cmd_desc *cmd_descp = hba->ucdl_base_addr;
@@ -4808,6 +4825,41 @@ static inline void ufshcd_get_lu_power_on_wp_status(struct ufs_hba *hba,
}

/**
+ * ufshcd_setup_links - associate link b/w device wlun and other luns
+ * @sdev: pointer to SCSI device
+ * @hba: pointer to ufs hba
+ */
+static void ufshcd_setup_links(struct ufs_hba *hba, struct scsi_device *sdev)
+{
+ struct device_link *link;
+
+ /*
+ * device wlun is the supplier & rest of the luns are consumers
+ * This ensures that device wlun suspends after all other luns.
+ */
+ if (hba->sdev_ufs_device) {
+ link = device_link_add(&sdev->sdev_gendev,
+ &hba->sdev_ufs_device->sdev_gendev,
+ DL_FLAG_PM_RUNTIME);
+ if (!link) {
+ dev_err(&sdev->sdev_gendev, "Failed establishing link - %s\n",
+ dev_name(&hba->sdev_ufs_device->sdev_gendev));
+ return;
+ }
+ hba->luns_avail--;
+ /* Ignore REPORT_LUN wlun probing */
+ if (hba->luns_avail != 1)
+ return;
+
+ pm_runtime_put_noidle(&hba->sdev_ufs_device->sdev_gendev);
+ pm_runtime_mark_last_busy(&hba->sdev_ufs_device->sdev_gendev);
+ } else {
+ /* device wlun is probed */
+ hba->luns_avail--;
+ }
+}
+
+/**
* ufshcd_slave_alloc - handle initial SCSI device configurations
* @sdev: pointer to SCSI device
*
@@ -4838,6 +4890,8 @@ static int ufshcd_slave_alloc(struct scsi_device *sdev)

ufshcd_get_lu_power_on_wp_status(hba, sdev);

+ ufshcd_setup_links(hba, sdev);
+
return 0;
}

@@ -4984,16 +5038,9 @@ ufshcd_transfer_rsp_status(struct ufs_hba *hba, struct ufshcd_lrb *lrbp)
* solution could be to abort the system suspend if
* UFS device needs urgent BKOPs.
*/
- if (!hba->pm_op_in_progress &&
- ufshcd_is_exception_event(lrbp->ucd_rsp_ptr) &&
- schedule_work(&hba->eeh_work)) {
- /*
- * Prevent suspend once eeh_work is scheduled
- * to avoid deadlock between ufshcd_suspend
- * and exception event handler.
- */
- pm_runtime_get_noresume(hba->dev);
- }
+ if (ufshcd_is_exception_event(lrbp->ucd_rsp_ptr))
+ /* Flushed in suspend */
+ schedule_work(&hba->eeh_work);
break;
case UPIU_TRANSACTION_REJECT_UPIU:
/* TODO: handle Reject UPIU Response */
@@ -5589,8 +5636,8 @@ static void ufshcd_rpm_dev_flush_recheck_work(struct work_struct *work)
* after a certain delay to recheck the threshold by next runtime
* suspend.
*/
- pm_runtime_get_sync(hba->dev);
- pm_runtime_put_sync(hba->dev);
+ scsi_autopm_get_device(hba->sdev_ufs_device);
+ scsi_autopm_put_device(hba->sdev_ufs_device);
}

/**
@@ -5607,7 +5654,6 @@ static void ufshcd_exception_event_handler(struct work_struct *work)
u32 status = 0;
hba = container_of(work, struct ufs_hba, eeh_work);

- pm_runtime_get_sync(hba->dev);
ufshcd_scsi_block_requests(hba);
err = ufshcd_get_ee_status(hba, &status);
if (err) {
@@ -5623,14 +5669,6 @@ static void ufshcd_exception_event_handler(struct work_struct *work)

out:
ufshcd_scsi_unblock_requests(hba);
- /*
- * pm_runtime_get_noresume is called while scheduling
- * eeh_work to avoid suspend racing with exception work.
- * Hence decrement usage counter using pm_runtime_put_noidle
- * to allow suspend on completion of exception event handler.
- */
- pm_runtime_put_noidle(hba->dev);
- pm_runtime_put(hba->dev);
return;
}

@@ -7254,6 +7292,15 @@ static int ufshcd_scsi_add_wlus(struct ufs_hba *hba)
goto out;
}
ufshcd_blk_pm_runtime_init(hba->sdev_ufs_device);
+ /*
+ * A pm_runtime_put_sync is invoked when this device enables blk_pm_runtime
+ * & would suspend the device-wlun upon timer expiry.
+ * But suspending device wlun _may_ put the ufs device in the pre-defined
+ * low power mode (SSU <rpm_lvl>). Probing of other luns may fail then.
+ * Don't allow this suspend until all the luns have been probed.
+ * See pm_runtime_mark_last_busy in ufshcd_setup_links.
+ */
+ pm_runtime_get_noresume(&hba->sdev_ufs_device->sdev_gendev);
scsi_device_put(hba->sdev_ufs_device);

hba->sdev_rpmb = __scsi_add_device(hba->host, 0, 0,
@@ -7417,6 +7464,9 @@ static int ufs_get_device_desc(struct ufs_hba *hba)
goto out;
}

+ hba->luns_avail = desc_buf[DEVICE_DESC_PARAM_NUM_LU] +
+ desc_buf[DEVICE_DESC_PARAM_NUM_WLU];
+
ufs_fixup_device_setup(hba);

ufshcd_wb_probe(hba, desc_buf);
@@ -7747,6 +7797,54 @@ static int ufshcd_device_params_init(struct ufs_hba *hba)
return ret;
}

+static int ufshcd_wl_probe(struct device *dev)
+{
+ struct scsi_device *sdev = to_scsi_device(dev);
+
+ /*
+ * Use this driver for both device wlun and rpmb lun
+ * It's used to send uac to rpmb wlun and ssu to device wlun
+ */
+ if (sdev && (is_rpmb_wlun(sdev) || is_device_wlun(sdev)))
+ return 0;
+ return -ENODEV;
+}
+
+static int ufshcd_wl_remove(struct device *dev)
+{
+ struct scsi_device *sdev = to_scsi_device(dev);
+
+ scsi_remove_device(sdev);
+ return 0;
+}
+
+static const struct dev_pm_ops ufshcd_wl_pm_ops = {
+ SET_RUNTIME_PM_OPS(ufshcd_wl_runtime_suspend, ufshcd_wl_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(ufshcd_wl_suspend, ufshcd_wl_resume)
+};
+
+/**
+ * ufs_wlun - describes 2 wluns:
+ * ufs-device wlun - used to send pm commands
+ * and rpmb wlun - for rpmb commands
+ * All luns including rpmb are consumers of ufs-device wlun.
+ *
+ * Currently, no sd driver is present for wluns.
+ * Hence the no specific pm operations are performed.
+ * With ufs design, SSU should be sent to ufs-device wlun.
+ * Hence register a scsi driver for ufs wluns only.
+ */
+static struct scsi_driver ufs_wlun = {
+ .gendrv = {
+ .name = "ufs_device_wlun",
+ .owner = THIS_MODULE,
+ .probe = ufshcd_wl_probe,
+ .remove = ufshcd_wl_remove,
+ .pm = &ufshcd_wl_pm_ops,
+ .shutdown = ufshcd_wl_shutdown,
+ },
+};
+
/**
* ufshcd_add_lus - probe and add UFS logical units
* @hba: per-adapter instance
@@ -7755,6 +7853,12 @@ static int ufshcd_add_lus(struct ufs_hba *hba)
{
int ret;

+ ret = scsi_register_driver(&ufs_wlun.gendrv);
+ if (ret) {
+ dev_err(hba->dev, "Register device wlun driver failed: %d\n",
+ ret);
+ return ret;
+ }
/* Add required well known logical units to scsi mid layer */
ret = ufshcd_scsi_add_wlus(hba);
if (ret)
@@ -7964,6 +8068,7 @@ static void ufshcd_async_scan(void *data, async_cookie_t cookie)
pm_runtime_put_sync(hba->dev);
ufshcd_hba_exit(hba);
}
+ hba->init_done = true;
}

static const struct attribute_group *ufshcd_driver_groups[] = {
@@ -8475,7 +8580,8 @@ static int ufshcd_set_dev_pwr_mode(struct ufs_hba *hba,
* handling context.
*/
hba->host->eh_noresume = 1;
- ufshcd_clear_ua_wluns(hba);
+ if (hba->wlun_dev_clr_ua)
+ ufshcd_clear_ua_wlun(hba, UFS_UPIU_UFS_DEVICE_WLUN);

cmd[4] = pwr_mode << 4;

@@ -8650,31 +8756,19 @@ static void ufshcd_hba_vreg_set_hpm(struct ufs_hba *hba)
ufshcd_setup_hba_vreg(hba, true);
}

-/**
- * ufshcd_suspend - helper function for suspend operations
- * @hba: per adapter instance
- * @pm_op: desired low power operation type
- *
- * This function will try to put the UFS device and link into low power
- * mode based on the "rpm_lvl" (Runtime PM level) or "spm_lvl"
- * (System PM level).
- *
- * If this function is called during shutdown, it will make sure that
- * both UFS device and UFS link is powered off.
- *
- * NOTE: UFS device & link must be active before we enter in this function.
- *
- * Returns 0 for success and non-zero for failure
- */
-static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
+static int __ufshcd_wl_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
{
int ret = 0;
- int check_for_bkops;
enum ufs_pm_level pm_lvl;
enum ufs_dev_pwr_mode req_dev_pwr_mode;
enum uic_link_state req_link_state;

- hba->pm_op_in_progress = 1;
+ /*
+ * Is invoked when the device wlun is added to sysfs
+ * But by then hba->sdev_ufs_device may not be initialized.
+ */
+ if (!hba->init_done)
+ return 0;
if (!ufshcd_is_shutdown_pm(pm_op)) {
pm_lvl = ufshcd_is_runtime_pm(pm_op) ?
hba->rpm_lvl : hba->spm_lvl;
@@ -8690,24 +8784,23 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
* just gate the clocks.
*/
ufshcd_hold(hba, false);
- hba->clk_gating.is_suspended = true;

if (ufshcd_is_clkscaling_supported(hba))
ufshcd_clk_scaling_suspend(hba, true);

if (req_dev_pwr_mode == UFS_ACTIVE_PWR_MODE &&
req_link_state == UIC_LINK_ACTIVE_STATE) {
- goto disable_clks;
+ goto enable_scaling;
}

if ((req_dev_pwr_mode == hba->curr_dev_pwr_mode) &&
(req_link_state == hba->uic_link_state))
- goto enable_gating;
+ goto enable_scaling;

/* UFS device & link must be active before we enter in this function */
if (!ufshcd_is_ufs_dev_active(hba) || !ufshcd_is_link_active(hba)) {
ret = -EINVAL;
- goto enable_gating;
+ goto enable_scaling;
}

if (ufshcd_is_runtime_pm(pm_op)) {
@@ -8719,7 +8812,7 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
*/
ret = ufshcd_urgent_bkops(hba);
if (ret)
- goto enable_gating;
+ goto enable_scaling;
} else {
/* make sure that auto bkops is disabled */
ufshcd_disable_auto_bkops(hba);
@@ -8746,10 +8839,224 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)

if (!hba->dev_info.b_rpm_dev_flush_capable) {
ret = ufshcd_set_dev_pwr_mode(hba, req_dev_pwr_mode);
- if (ret)
- goto enable_gating;
+ if (!ret)
+ goto out;
}
}
+enable_scaling:
+ if (ufshcd_is_clkscaling_supported(hba))
+ ufshcd_clk_scaling_suspend(hba, false);
+
+ hba->dev_info.b_rpm_dev_flush_capable = false;
+out:
+ if (hba->dev_info.b_rpm_dev_flush_capable) {
+ schedule_delayed_work(&hba->rpm_dev_flush_recheck_work,
+ msecs_to_jiffies(RPM_DEV_FLUSH_RECHECK_WORK_DELAY_MS));
+ }
+ if (ret)
+ ufshcd_update_evt_hist(hba, UFS_EVT_WL_SUSP_ERR, (u32)ret);
+ ufshcd_release(hba);
+ return ret;
+}
+
+static int __ufshcd_wl_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
+{
+ int ret = 0;
+
+ if (!hba->init_done)
+ return 0;
+
+ ufshcd_hold(hba, false);
+ if (ufshcd_is_ufs_dev_deepsleep(hba)) {
+ /* The device is in DeepSleep but hba was not suspended */
+ ret = ufshcd_reset_and_restore(hba);
+ if (ret)
+ goto out;
+ }
+
+ if (!ufshcd_is_ufs_dev_active(hba)) {
+ ret = ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE);
+ if (ret)
+ goto out;
+ }
+
+ if (ufshcd_keep_autobkops_enabled_except_suspend(hba))
+ ufshcd_enable_auto_bkops(hba);
+ else
+ /*
+ * If BKOPs operations are urgently needed at this moment then
+ * keep auto-bkops enabled or else disable it.
+ */
+ ufshcd_urgent_bkops(hba);
+
+ if (hba->clk_scaling.is_allowed)
+ ufshcd_resume_clkscaling(hba);
+
+ if (hba->dev_info.b_rpm_dev_flush_capable) {
+ hba->dev_info.b_rpm_dev_flush_capable = false;
+ cancel_delayed_work(&hba->rpm_dev_flush_recheck_work);
+ }
+
+out:
+ if (ret)
+ ufshcd_update_evt_hist(hba, UFS_EVT_WL_RES_ERR, (u32)ret);
+ ufshcd_release(hba);
+ return ret;
+}
+
+static int ufshcd_wl_runtime_suspend(struct device *dev)
+{
+ struct scsi_device *sdev = to_scsi_device(dev);
+ struct ufs_hba *hba;
+ int ret;
+ ktime_t start = ktime_get();
+
+ hba = shost_priv(sdev->host);
+
+ if (is_rpmb_wlun(sdev))
+ return 0;
+ ret = __ufshcd_wl_suspend(hba, UFS_RUNTIME_PM);
+ if (ret)
+ dev_err(&sdev->sdev_gendev, "%s failed: %d\n", __func__, ret);
+
+ trace_ufshcd_wl_runtime_suspend(dev_name(dev), ret,
+ ktime_to_us(ktime_sub(ktime_get(), start)),
+ hba->curr_dev_pwr_mode, hba->uic_link_state);
+
+ return ret;
+}
+
+static int ufshcd_wl_runtime_resume(struct device *dev)
+{
+ struct scsi_device *sdev = to_scsi_device(dev);
+ struct ufs_hba *hba;
+ int ret;
+ ktime_t start = ktime_get();
+
+ hba = shost_priv(sdev->host);
+
+ if (is_rpmb_wlun(sdev)) {
+ if (hba->sdev_rpmb)
+ return ufshcd_clear_ua_wlun(hba, UFS_UPIU_RPMB_WLUN);
+ return 0;
+ }
+
+ ret = __ufshcd_wl_resume(hba, UFS_RUNTIME_PM);
+ if (ret)
+ dev_err(&sdev->sdev_gendev, "%s failed: %d\n", __func__, ret);
+
+ trace_ufshcd_wl_runtime_resume(dev_name(dev), ret,
+ ktime_to_us(ktime_sub(ktime_get(), start)),
+ hba->curr_dev_pwr_mode, hba->uic_link_state);
+
+ return ret;
+}
+
+static int ufshcd_wl_suspend(struct device *dev)
+{
+ struct scsi_device *sdev = to_scsi_device(dev);
+ struct ufs_hba *hba;
+ int ret;
+ ktime_t start = ktime_get();
+
+ if (is_rpmb_wlun(sdev))
+ return 0;
+ hba = shost_priv(sdev->host);
+ ret = __ufshcd_wl_suspend(hba, UFS_SYSTEM_PM);
+ if (ret)
+ dev_err(&sdev->sdev_gendev, "%s failed: %d\n", __func__, ret);
+
+ trace_ufshcd_wl_suspend(dev_name(dev), ret,
+ ktime_to_us(ktime_sub(ktime_get(), start)),
+ hba->curr_dev_pwr_mode, hba->uic_link_state);
+
+ return ret;
+}
+
+static int ufshcd_wl_resume(struct device *dev)
+{
+ struct scsi_device *sdev = to_scsi_device(dev);
+ struct ufs_hba *hba;
+ int ret;
+ ktime_t start = ktime_get();
+
+ if (pm_runtime_suspended(dev))
+ return 0;
+ hba = shost_priv(sdev->host);
+
+ if (is_rpmb_wlun(sdev)) {
+ if (hba->sdev_rpmb)
+ return ufshcd_clear_ua_wlun(hba, UFS_UPIU_RPMB_WLUN);
+ return 0;
+ }
+
+ ret = __ufshcd_wl_resume(hba, UFS_SYSTEM_PM);
+ if (ret)
+ dev_err(&sdev->sdev_gendev, "%s failed: %d\n", __func__, ret);
+
+ trace_ufshcd_wl_resume(dev_name(dev), ret,
+ ktime_to_us(ktime_sub(ktime_get(), start)),
+ hba->curr_dev_pwr_mode, hba->uic_link_state);
+
+ return ret;
+}
+
+static void ufshcd_wl_shutdown(struct device *dev)
+{
+ struct scsi_device *sdev = to_scsi_device(dev);
+ struct ufs_hba *hba;
+
+ if (is_rpmb_wlun(sdev))
+ return;
+ hba = shost_priv(sdev->host);
+ scsi_device_quiesce(sdev);
+ shost_for_each_device(sdev, hba->host) {
+ if (sdev == hba->sdev_ufs_device)
+ continue;
+ scsi_remove_device(sdev);
+ }
+ __ufshcd_wl_suspend(hba, UFS_SHUTDOWN_PM);
+}
+
+/**
+ * ufshcd_suspend - helper function for suspend operations
+ * @hba: per adapter instance
+ * @pm_op: desired low power operation type
+ *
+ * This function will try to put the UFS link into low power
+ * mode based on the "rpm_lvl" (Runtime PM level) or "spm_lvl"
+ * (System PM level).
+ *
+ * If this function is called during shutdown, it will make sure that
+ * link is powered off.
+ *
+ * Returns 0 for success and non-zero for failure
+ */
+static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
+{
+ int ret = 0;
+ int check_for_bkops;
+ enum ufs_pm_level pm_lvl;
+ enum ufs_dev_pwr_mode req_dev_pwr_mode;
+ enum uic_link_state req_link_state;
+
+ hba->pm_op_in_progress = 1;
+ if (!ufshcd_is_shutdown_pm(pm_op)) {
+ pm_lvl = ufshcd_is_runtime_pm(pm_op) ?
+ hba->rpm_lvl : hba->spm_lvl;
+ req_dev_pwr_mode = ufs_get_pm_lvl_to_dev_pwr_mode(pm_lvl);
+ req_link_state = ufs_get_pm_lvl_to_link_pwr_state(pm_lvl);
+ } else {
+ req_dev_pwr_mode = UFS_POWERDOWN_PWR_MODE;
+ req_link_state = UIC_LINK_OFF_STATE;
+ }
+
+ /*
+ * If we can't transition into any of the low power modes
+ * just gate the clocks.
+ */
+ ufshcd_hold(hba, false);
+ hba->clk_gating.is_suspended = true;

/*
* In the case of DeepSleep, the device is expected to remain powered
@@ -8758,9 +9065,8 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
check_for_bkops = !ufshcd_is_ufs_dev_deepsleep(hba);
ret = ufshcd_link_state_transition(hba, req_link_state, check_for_bkops);
if (ret)
- goto set_dev_active;
+ goto enable_gating;

-disable_clks:
/*
* Call vendor specific suspend callback. As these callbacks may access
* vendor specific host controller register space call them before the
@@ -8790,7 +9096,6 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
goto out;

set_link_active:
- ufshcd_vreg_set_hpm(hba);
/*
* Device hardware reset is required to exit DeepSleep. Also, for
* DeepSleep, the link is off so host reset and restore will be done
@@ -8804,28 +9109,15 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
ufshcd_set_link_active(hba);
else if (ufshcd_is_link_off(hba))
ufshcd_host_reset_and_restore(hba);
-set_dev_active:
/* Can also get here needing to exit DeepSleep */
if (ufshcd_is_ufs_dev_deepsleep(hba)) {
ufshcd_device_reset(hba);
ufshcd_host_reset_and_restore(hba);
}
- if (!ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE))
- ufshcd_disable_auto_bkops(hba);
enable_gating:
- if (ufshcd_is_clkscaling_supported(hba))
- ufshcd_clk_scaling_suspend(hba, false);
-
hba->clk_gating.is_suspended = false;
- hba->dev_info.b_rpm_dev_flush_capable = false;
- ufshcd_clear_ua_wluns(hba);
ufshcd_release(hba);
out:
- if (hba->dev_info.b_rpm_dev_flush_capable) {
- schedule_delayed_work(&hba->rpm_dev_flush_recheck_work,
- msecs_to_jiffies(RPM_DEV_FLUSH_RECHECK_WORK_DELAY_MS));
- }
-
hba->pm_op_in_progress = 0;

if (ret)
@@ -8846,10 +9138,8 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
{
int ret;
- enum uic_link_state old_link_state;

hba->pm_op_in_progress = 1;
- old_link_state = hba->uic_link_state;

ufshcd_hba_vreg_set_hpm(hba);
ret = ufshcd_vreg_set_hpm(hba);
@@ -8901,43 +9191,14 @@ static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
goto vendor_suspend;
}

- if (!ufshcd_is_ufs_dev_active(hba)) {
- ret = ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE);
- if (ret)
- goto set_old_link_state;
- }
-
- if (ufshcd_keep_autobkops_enabled_except_suspend(hba))
- ufshcd_enable_auto_bkops(hba);
- else
- /*
- * If BKOPs operations are urgently needed at this moment then
- * keep auto-bkops enabled or else disable it.
- */
- ufshcd_urgent_bkops(hba);
-
- hba->clk_gating.is_suspended = false;
-
- if (ufshcd_is_clkscaling_supported(hba))
- ufshcd_clk_scaling_suspend(hba, false);
-
/* Enable Auto-Hibernate if configured */
ufshcd_auto_hibern8_enable(hba);

- if (hba->dev_info.b_rpm_dev_flush_capable) {
- hba->dev_info.b_rpm_dev_flush_capable = false;
- cancel_delayed_work(&hba->rpm_dev_flush_recheck_work);
- }
-
- ufshcd_clear_ua_wluns(hba);
-
/* Schedule clock gating in case of no access to UFS device yet */
ufshcd_release(hba);

goto out;

-set_old_link_state:
- ufshcd_link_state_transition(hba, old_link_state, 0);
vendor_suspend:
ufshcd_vops_suspend(hba, pm_op);
disable_irq_and_vops_clks:
diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h
index ee61f82..7662387 100644
--- a/drivers/scsi/ufs/ufshcd.h
+++ b/drivers/scsi/ufs/ufshcd.h
@@ -72,6 +72,8 @@ enum ufs_event_type {
UFS_EVT_LINK_STARTUP_FAIL,
UFS_EVT_RESUME_ERR,
UFS_EVT_SUSPEND_ERR,
+ UFS_EVT_WL_SUSP_ERR,
+ UFS_EVT_WL_RES_ERR,

/* abnormal events */
UFS_EVT_DEV_RESET,
@@ -841,6 +843,8 @@ struct ufs_hba {
#ifdef CONFIG_DEBUG_FS
struct dentry *debugfs_root;
#endif
+ bool init_done;
+ u32 luns_avail;
};

/* Returns true if clocks can be gated. Otherwise false */
diff --git a/include/trace/events/ufs.h b/include/trace/events/ufs.h
index e151477..d9d233b 100644
--- a/include/trace/events/ufs.h
+++ b/include/trace/events/ufs.h
@@ -246,6 +246,26 @@ DEFINE_EVENT(ufshcd_template, ufshcd_init,
int dev_state, int link_state),
TP_ARGS(dev_name, err, usecs, dev_state, link_state));

+DEFINE_EVENT(ufshcd_template, ufshcd_wl_suspend,
+ TP_PROTO(const char *dev_name, int err, s64 usecs,
+ int dev_state, int link_state),
+ TP_ARGS(dev_name, err, usecs, dev_state, link_state));
+
+DEFINE_EVENT(ufshcd_template, ufshcd_wl_resume,
+ TP_PROTO(const char *dev_name, int err, s64 usecs,
+ int dev_state, int link_state),
+ TP_ARGS(dev_name, err, usecs, dev_state, link_state));
+
+DEFINE_EVENT(ufshcd_template, ufshcd_wl_runtime_suspend,
+ TP_PROTO(const char *dev_name, int err, s64 usecs,
+ int dev_state, int link_state),
+ TP_ARGS(dev_name, err, usecs, dev_state, link_state));
+
+DEFINE_EVENT(ufshcd_template, ufshcd_wl_runtime_resume,
+ TP_PROTO(const char *dev_name, int err, s64 usecs,
+ int dev_state, int link_state),
+ TP_ARGS(dev_name, err, usecs, dev_state, link_state));
+
TRACE_EVENT(ufshcd_command,
TP_PROTO(const char *dev_name, enum ufs_trace_str_t str_t,
unsigned int tag, u32 doorbell, int transfer_len, u32 intr,
--
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project.


2021-02-23 23:57:57

by Adrian Hunter

[permalink] [raw]
Subject: Re: [PATCH v4 1/2] scsi: ufs: Enable power management for wlun

On 23/02/21 1:04 am, Asutosh Das wrote:
> During runtime-suspend of ufs host, the scsi devices are
> already suspended and so are the queues associated with them.
> But the ufs host sends SSU to wlun during its runtime-suspend.
> During the process blk_queue_enter checks if the queue is not in
> suspended state. If so, it waits for the queue to resume, and never
> comes out of it.
> The commit
> (d55d15a33: scsi: block: Do not accept any requests while suspended)
> adds the check if the queue is in suspended state in blk_queue_enter().
>
> Call trace:
> __switch_to+0x174/0x2c4
> __schedule+0x478/0x764
> schedule+0x9c/0xe0
> blk_queue_enter+0x158/0x228
> blk_mq_alloc_request+0x40/0xa4
> blk_get_request+0x2c/0x70
> __scsi_execute+0x60/0x1c4
> ufshcd_set_dev_pwr_mode+0x124/0x1e4
> ufshcd_suspend+0x208/0x83c
> ufshcd_runtime_suspend+0x40/0x154
> ufshcd_pltfrm_runtime_suspend+0x14/0x20
> pm_generic_runtime_suspend+0x28/0x3c
> __rpm_callback+0x80/0x2a4
> rpm_suspend+0x308/0x614
> rpm_idle+0x158/0x228
> pm_runtime_work+0x84/0xac
> process_one_work+0x1f0/0x470
> worker_thread+0x26c/0x4c8
> kthread+0x13c/0x320
> ret_from_fork+0x10/0x18
>
> Fix this by registering ufs device wlun as a scsi driver and
> registering it for block runtime-pm. Also make this as a
> supplier for all other luns. That way, this device wlun
> suspends after all the consumers and resumes after
> hba resumes.

Hi

I have a few more questions and comments.

At a glance it looked to me like the SCSI bus will leave a SCSI device
runtime suspended at system suspend, but UFS device spm_lvl and rpm_lvl need
not be the same. Do you know how SCSI handles that case?

>
> Co-developed-by: Can Guo <[email protected]>
> Signed-off-by: Can Guo <[email protected]>
> Signed-off-by: Asutosh Das <[email protected]>
> ---
> drivers/scsi/ufs/ufshcd.c | 455 +++++++++++++++++++++++++++++++++++----------
> drivers/scsi/ufs/ufshcd.h | 4 +
> include/trace/events/ufs.h | 20 ++
> 3 files changed, 382 insertions(+), 97 deletions(-)
>
> diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c
> index 45624c7..1d8044a 100644
> --- a/drivers/scsi/ufs/ufshcd.c
> +++ b/drivers/scsi/ufs/ufshcd.c
> @@ -16,6 +16,7 @@
> #include <linux/bitfield.h>
> #include <linux/blk-pm.h>
> #include <linux/blkdev.h>
> +#include <scsi/scsi_driver.h>
> #include "ufshcd.h"
> #include "ufs_quirks.h"
> #include "unipro.h"
> @@ -251,6 +252,11 @@ static int ufshcd_wb_toggle_flush_during_h8(struct ufs_hba *hba, bool set);
> static inline int ufshcd_wb_toggle_flush(struct ufs_hba *hba, bool enable);
> static void ufshcd_hba_vreg_set_lpm(struct ufs_hba *hba);
> static void ufshcd_hba_vreg_set_hpm(struct ufs_hba *hba);
> +static int ufshcd_wl_runtime_suspend(struct device *dev);
> +static int ufshcd_wl_runtime_resume(struct device *dev);
> +static int ufshcd_wl_suspend(struct device *dev);
> +static int ufshcd_wl_resume(struct device *dev);
> +static void ufshcd_wl_shutdown(struct device *dev);
>
> static inline bool ufshcd_valid_tag(struct ufs_hba *hba, int tag)
> {
> @@ -1556,7 +1562,7 @@ static ssize_t ufshcd_clkscale_enable_store(struct device *dev,
> if (value == hba->clk_scaling.is_enabled)
> goto out;
>
> - pm_runtime_get_sync(hba->dev);
> + scsi_autopm_get_device(hba->sdev_ufs_device);
> ufshcd_hold(hba, false);
>
> hba->clk_scaling.is_enabled = value;
> @@ -1572,7 +1578,7 @@ static ssize_t ufshcd_clkscale_enable_store(struct device *dev,
> }
>
> ufshcd_release(hba);
> - pm_runtime_put_sync(hba->dev);
> + scsi_autopm_put_device(hba->sdev_ufs_device);
> out:
> up(&hba->host_sem);
> return err ? err : count;
> @@ -2572,6 +2578,17 @@ static inline u16 ufshcd_upiu_wlun_to_scsi_wlun(u8 upiu_wlun_id)
> return (upiu_wlun_id & ~UFS_UPIU_WLUN_ID) | SCSI_W_LUN_BASE;
> }
>
> +static inline bool is_rpmb_wlun(struct scsi_device *sdev)
> +{
> + return (sdev->lun == ufshcd_upiu_wlun_to_scsi_wlun(UFS_UPIU_RPMB_WLUN));
> +}
> +
> +static inline bool is_device_wlun(struct scsi_device *sdev)
> +{
> + return (sdev->lun ==
> + ufshcd_upiu_wlun_to_scsi_wlun(UFS_UPIU_UFS_DEVICE_WLUN));
> +}
> +
> static void ufshcd_init_lrb(struct ufs_hba *hba, struct ufshcd_lrb *lrb, int i)
> {
> struct utp_transfer_cmd_desc *cmd_descp = hba->ucdl_base_addr;
> @@ -4808,6 +4825,41 @@ static inline void ufshcd_get_lu_power_on_wp_status(struct ufs_hba *hba,
> }
>
> /**
> + * ufshcd_setup_links - associate link b/w device wlun and other luns
> + * @sdev: pointer to SCSI device
> + * @hba: pointer to ufs hba
> + */
> +static void ufshcd_setup_links(struct ufs_hba *hba, struct scsi_device *sdev)
> +{
> + struct device_link *link;
> +
> + /*
> + * device wlun is the supplier & rest of the luns are consumers
> + * This ensures that device wlun suspends after all other luns.
> + */
> + if (hba->sdev_ufs_device) {
> + link = device_link_add(&sdev->sdev_gendev,
> + &hba->sdev_ufs_device->sdev_gendev,
> + DL_FLAG_PM_RUNTIME);
> + if (!link) {
> + dev_err(&sdev->sdev_gendev, "Failed establishing link - %s\n",
> + dev_name(&hba->sdev_ufs_device->sdev_gendev));
> + return;
> + }
> + hba->luns_avail--;
> + /* Ignore REPORT_LUN wlun probing */
> + if (hba->luns_avail != 1)
> + return;
> +
> + pm_runtime_put_noidle(&hba->sdev_ufs_device->sdev_gendev);
> + pm_runtime_mark_last_busy(&hba->sdev_ufs_device->sdev_gendev);
> + } else {
> + /* device wlun is probed */
> + hba->luns_avail--;
> + }
> +}
> +
> +/**
> * ufshcd_slave_alloc - handle initial SCSI device configurations
> * @sdev: pointer to SCSI device
> *
> @@ -4838,6 +4890,8 @@ static int ufshcd_slave_alloc(struct scsi_device *sdev)
>
> ufshcd_get_lu_power_on_wp_status(hba, sdev);
>
> + ufshcd_setup_links(hba, sdev);
> +
> return 0;
> }
>
> @@ -4984,16 +5038,9 @@ ufshcd_transfer_rsp_status(struct ufs_hba *hba, struct ufshcd_lrb *lrbp)
> * solution could be to abort the system suspend if
> * UFS device needs urgent BKOPs.
> */
> - if (!hba->pm_op_in_progress &&
> - ufshcd_is_exception_event(lrbp->ucd_rsp_ptr) &&
> - schedule_work(&hba->eeh_work)) {
> - /*
> - * Prevent suspend once eeh_work is scheduled
> - * to avoid deadlock between ufshcd_suspend
> - * and exception event handler.
> - */
> - pm_runtime_get_noresume(hba->dev);
> - }
> + if (ufshcd_is_exception_event(lrbp->ucd_rsp_ptr))
> + /* Flushed in suspend */
> + schedule_work(&hba->eeh_work);
> break;
> case UPIU_TRANSACTION_REJECT_UPIU:
> /* TODO: handle Reject UPIU Response */
> @@ -5589,8 +5636,8 @@ static void ufshcd_rpm_dev_flush_recheck_work(struct work_struct *work)
> * after a certain delay to recheck the threshold by next runtime
> * suspend.
> */
> - pm_runtime_get_sync(hba->dev);
> - pm_runtime_put_sync(hba->dev);
> + scsi_autopm_get_device(hba->sdev_ufs_device);
> + scsi_autopm_put_device(hba->sdev_ufs_device);
> }
>
> /**
> @@ -5607,7 +5654,6 @@ static void ufshcd_exception_event_handler(struct work_struct *work)
> u32 status = 0;
> hba = container_of(work, struct ufs_hba, eeh_work);
>
> - pm_runtime_get_sync(hba->dev);
> ufshcd_scsi_block_requests(hba);
> err = ufshcd_get_ee_status(hba, &status);
> if (err) {
> @@ -5623,14 +5669,6 @@ static void ufshcd_exception_event_handler(struct work_struct *work)
>
> out:
> ufshcd_scsi_unblock_requests(hba);
> - /*
> - * pm_runtime_get_noresume is called while scheduling
> - * eeh_work to avoid suspend racing with exception work.
> - * Hence decrement usage counter using pm_runtime_put_noidle
> - * to allow suspend on completion of exception event handler.
> - */
> - pm_runtime_put_noidle(hba->dev);
> - pm_runtime_put(hba->dev);
> return;
> }
>
> @@ -7254,6 +7292,15 @@ static int ufshcd_scsi_add_wlus(struct ufs_hba *hba)
> goto out;
> }
> ufshcd_blk_pm_runtime_init(hba->sdev_ufs_device);
> + /*
> + * A pm_runtime_put_sync is invoked when this device enables blk_pm_runtime
> + * & would suspend the device-wlun upon timer expiry.
> + * But suspending device wlun _may_ put the ufs device in the pre-defined
> + * low power mode (SSU <rpm_lvl>). Probing of other luns may fail then.
> + * Don't allow this suspend until all the luns have been probed.
> + * See pm_runtime_mark_last_busy in ufshcd_setup_links.
> + */
> + pm_runtime_get_noresume(&hba->sdev_ufs_device->sdev_gendev);
> scsi_device_put(hba->sdev_ufs_device);
>
> hba->sdev_rpmb = __scsi_add_device(hba->host, 0, 0,
> @@ -7417,6 +7464,9 @@ static int ufs_get_device_desc(struct ufs_hba *hba)
> goto out;
> }
>
> + hba->luns_avail = desc_buf[DEVICE_DESC_PARAM_NUM_LU] +
> + desc_buf[DEVICE_DESC_PARAM_NUM_WLU];
> +
> ufs_fixup_device_setup(hba);
>
> ufshcd_wb_probe(hba, desc_buf);
> @@ -7747,6 +7797,54 @@ static int ufshcd_device_params_init(struct ufs_hba *hba)
> return ret;
> }
>
> +static int ufshcd_wl_probe(struct device *dev)
> +{
> + struct scsi_device *sdev = to_scsi_device(dev);
> +
> + /*
> + * Use this driver for both device wlun and rpmb lun
> + * It's used to send uac to rpmb wlun and ssu to device wlun
> + */
> + if (sdev && (is_rpmb_wlun(sdev) || is_device_wlun(sdev)))
> + return 0;
> + return -ENODEV;
> +}
> +
> +static int ufshcd_wl_remove(struct device *dev)
> +{
> + struct scsi_device *sdev = to_scsi_device(dev);
> +
> + scsi_remove_device(sdev);

The 'probe' did not do the __scsi_add_device(), so does the 'remove' need to
do scsi_remove_device()?

> + return 0;
> +}
> +
> +static const struct dev_pm_ops ufshcd_wl_pm_ops = {
> + SET_RUNTIME_PM_OPS(ufshcd_wl_runtime_suspend, ufshcd_wl_runtime_resume, NULL)
> + SET_SYSTEM_SLEEP_PM_OPS(ufshcd_wl_suspend, ufshcd_wl_resume)

.poweroff needs to set the power mode to PowerDown irrespective of spm_lvl,
so not exactly the same as ufshcd_wl_suspend.

Also, ufshcd_pci_poweroff() in ufshcd-pci.c should be removed since it won't
work anymore.

> +};
> +
> +/**
> + * ufs_wlun - describes 2 wluns:
> + * ufs-device wlun - used to send pm commands
> + * and rpmb wlun - for rpmb commands
> + * All luns including rpmb are consumers of ufs-device wlun.
> + *
> + * Currently, no sd driver is present for wluns.
> + * Hence the no specific pm operations are performed.
> + * With ufs design, SSU should be sent to ufs-device wlun.
> + * Hence register a scsi driver for ufs wluns only.
> + */
> +static struct scsi_driver ufs_wlun = {
> + .gendrv = {
> + .name = "ufs_device_wlun",
> + .owner = THIS_MODULE,
> + .probe = ufshcd_wl_probe,
> + .remove = ufshcd_wl_remove,
> + .pm = &ufshcd_wl_pm_ops,
> + .shutdown = ufshcd_wl_shutdown,
> + },
> +};

WRT to RPMB, aren't all the callbacks optional, so that would leave only
something like below. Would that work?

#define wlun_dev_to_hba(dv) shost_priv(to_scsi_device(dv)->host)

static int ufs_rpmb_probe(struct device *dev)
{
return is_rpmb_wlun(to_scsi_device(dev)) ? 0 : -ENODEV;
}

static int ufs_rpmb_runtime_resume(struct device *dev)
{
struct ufs_hba *hba = wlun_dev_to_hba(dev);

if (hba->sdev_rpmb)
return ufshcd_clear_ua_wlun(hba, UFS_UPIU_RPMB_WLUN);
return 0;
}

static int ufs_rpmb_resume(struct device *dev)
{
struct ufs_hba *hba = wlun_dev_to_hba(dev);

if (hba->sdev_rpmb && !pm_runtime_suspended(dev))
return ufshcd_clear_ua_wlun(hba, UFS_UPIU_RPMB_WLUN);
return 0;
}

static const struct dev_pm_ops ufs_rpmb_pm_ops = {
SET_RUNTIME_PM_OPS(NULL, ufs_rpmb_runtime_resume, NULL)
SET_SYSTEM_SLEEP_PM_OPS(NULL, ufs_rpmb_resume)
};

static struct scsi_driver ufs_rpmb_template = {
.gendrv = {
.name = "ufs_rpmb_wlun",
.owner = THIS_MODULE,
.probe = ufs_rpmb_probe,
.pm = &ufs_rpmb_pm_ops,
},
};


> +
> /**
> * ufshcd_add_lus - probe and add UFS logical units
> * @hba: per-adapter instance
> @@ -7755,6 +7853,12 @@ static int ufshcd_add_lus(struct ufs_hba *hba)
> {
> int ret;
>
> + ret = scsi_register_driver(&ufs_wlun.gendrv);

That will attempt to register the driver every time a UFS host controller is
added (e.g. what happens if you unbind and re-bind the host controller).
Probably needs to be in ufshcd_core_init() with corresponding unregister in
ufshcd_core_exit()

> + if (ret) {
> + dev_err(hba->dev, "Register device wlun driver failed: %d\n",
> + ret);
> + return ret;
> + }
> /* Add required well known logical units to scsi mid layer */
> ret = ufshcd_scsi_add_wlus(hba);
> if (ret)
> @@ -7964,6 +8068,7 @@ static void ufshcd_async_scan(void *data, async_cookie_t cookie)
> pm_runtime_put_sync(hba->dev);
> ufshcd_hba_exit(hba);
> }
> + hba->init_done = true;
> }
>
> static const struct attribute_group *ufshcd_driver_groups[] = {
> @@ -8475,7 +8580,8 @@ static int ufshcd_set_dev_pwr_mode(struct ufs_hba *hba,
> * handling context.
> */
> hba->host->eh_noresume = 1;
> - ufshcd_clear_ua_wluns(hba);
> + if (hba->wlun_dev_clr_ua)
> + ufshcd_clear_ua_wlun(hba, UFS_UPIU_UFS_DEVICE_WLUN);
>
> cmd[4] = pwr_mode << 4;
>
> @@ -8650,31 +8756,19 @@ static void ufshcd_hba_vreg_set_hpm(struct ufs_hba *hba)
> ufshcd_setup_hba_vreg(hba, true);
> }
>
> -/**
> - * ufshcd_suspend - helper function for suspend operations
> - * @hba: per adapter instance
> - * @pm_op: desired low power operation type
> - *
> - * This function will try to put the UFS device and link into low power
> - * mode based on the "rpm_lvl" (Runtime PM level) or "spm_lvl"
> - * (System PM level).
> - *
> - * If this function is called during shutdown, it will make sure that
> - * both UFS device and UFS link is powered off.
> - *
> - * NOTE: UFS device & link must be active before we enter in this function.
> - *
> - * Returns 0 for success and non-zero for failure
> - */
> -static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
> +static int __ufshcd_wl_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
> {
> int ret = 0;
> - int check_for_bkops;
> enum ufs_pm_level pm_lvl;
> enum ufs_dev_pwr_mode req_dev_pwr_mode;
> enum uic_link_state req_link_state;
>
> - hba->pm_op_in_progress = 1;
> + /*
> + * Is invoked when the device wlun is added to sysfs
> + * But by then hba->sdev_ufs_device may not be initialized.
> + */
> + if (!hba->init_done)
> + return 0;
> if (!ufshcd_is_shutdown_pm(pm_op)) {
> pm_lvl = ufshcd_is_runtime_pm(pm_op) ?
> hba->rpm_lvl : hba->spm_lvl;
> @@ -8690,24 +8784,23 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
> * just gate the clocks.
> */
> ufshcd_hold(hba, false);
> - hba->clk_gating.is_suspended = true;
>
> if (ufshcd_is_clkscaling_supported(hba))
> ufshcd_clk_scaling_suspend(hba, true);
>
> if (req_dev_pwr_mode == UFS_ACTIVE_PWR_MODE &&
> req_link_state == UIC_LINK_ACTIVE_STATE) {
> - goto disable_clks;
> + goto enable_scaling;
> }
>
> if ((req_dev_pwr_mode == hba->curr_dev_pwr_mode) &&
> (req_link_state == hba->uic_link_state))
> - goto enable_gating;
> + goto enable_scaling;
>
> /* UFS device & link must be active before we enter in this function */
> if (!ufshcd_is_ufs_dev_active(hba) || !ufshcd_is_link_active(hba)) {
> ret = -EINVAL;
> - goto enable_gating;
> + goto enable_scaling;
> }
>
> if (ufshcd_is_runtime_pm(pm_op)) {
> @@ -8719,7 +8812,7 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
> */
> ret = ufshcd_urgent_bkops(hba);
> if (ret)
> - goto enable_gating;
> + goto enable_scaling;
> } else {
> /* make sure that auto bkops is disabled */
> ufshcd_disable_auto_bkops(hba);
> @@ -8746,10 +8839,224 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>
> if (!hba->dev_info.b_rpm_dev_flush_capable) {
> ret = ufshcd_set_dev_pwr_mode(hba, req_dev_pwr_mode);
> - if (ret)
> - goto enable_gating;
> + if (!ret)
> + goto out;
> }
> }
> +enable_scaling:
> + if (ufshcd_is_clkscaling_supported(hba))
> + ufshcd_clk_scaling_suspend(hba, false);
> +
> + hba->dev_info.b_rpm_dev_flush_capable = false;
> +out:
> + if (hba->dev_info.b_rpm_dev_flush_capable) {
> + schedule_delayed_work(&hba->rpm_dev_flush_recheck_work,
> + msecs_to_jiffies(RPM_DEV_FLUSH_RECHECK_WORK_DELAY_MS));
> + }
> + if (ret)
> + ufshcd_update_evt_hist(hba, UFS_EVT_WL_SUSP_ERR, (u32)ret);
> + ufshcd_release(hba);
> + return ret;
> +}
> +
> +static int __ufshcd_wl_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
> +{
> + int ret = 0;
> +
> + if (!hba->init_done)
> + return 0;
> +
> + ufshcd_hold(hba, false);
> + if (ufshcd_is_ufs_dev_deepsleep(hba)) {
> + /* The device is in DeepSleep but hba was not suspended */
> + ret = ufshcd_reset_and_restore(hba);
> + if (ret)
> + goto out;
> + }
> +
> + if (!ufshcd_is_ufs_dev_active(hba)) {
> + ret = ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE);
> + if (ret)
> + goto out;
> + }
> +
> + if (ufshcd_keep_autobkops_enabled_except_suspend(hba))
> + ufshcd_enable_auto_bkops(hba);
> + else
> + /*
> + * If BKOPs operations are urgently needed at this moment then
> + * keep auto-bkops enabled or else disable it.
> + */
> + ufshcd_urgent_bkops(hba);
> +
> + if (hba->clk_scaling.is_allowed)
> + ufshcd_resume_clkscaling(hba);
> +
> + if (hba->dev_info.b_rpm_dev_flush_capable) {
> + hba->dev_info.b_rpm_dev_flush_capable = false;
> + cancel_delayed_work(&hba->rpm_dev_flush_recheck_work);
> + }
> +
> +out:
> + if (ret)
> + ufshcd_update_evt_hist(hba, UFS_EVT_WL_RES_ERR, (u32)ret);
> + ufshcd_release(hba);
> + return ret;
> +}
> +
> +static int ufshcd_wl_runtime_suspend(struct device *dev)
> +{
> + struct scsi_device *sdev = to_scsi_device(dev);
> + struct ufs_hba *hba;
> + int ret;
> + ktime_t start = ktime_get();
> +
> + hba = shost_priv(sdev->host);
> +
> + if (is_rpmb_wlun(sdev))
> + return 0;
> + ret = __ufshcd_wl_suspend(hba, UFS_RUNTIME_PM);
> + if (ret)
> + dev_err(&sdev->sdev_gendev, "%s failed: %d\n", __func__, ret);
> +
> + trace_ufshcd_wl_runtime_suspend(dev_name(dev), ret,
> + ktime_to_us(ktime_sub(ktime_get(), start)),
> + hba->curr_dev_pwr_mode, hba->uic_link_state);
> +
> + return ret;
> +}
> +
> +static int ufshcd_wl_runtime_resume(struct device *dev)
> +{
> + struct scsi_device *sdev = to_scsi_device(dev);
> + struct ufs_hba *hba;
> + int ret;
> + ktime_t start = ktime_get();
> +
> + hba = shost_priv(sdev->host);
> +
> + if (is_rpmb_wlun(sdev)) {
> + if (hba->sdev_rpmb)
> + return ufshcd_clear_ua_wlun(hba, UFS_UPIU_RPMB_WLUN);
> + return 0;
> + }
> +
> + ret = __ufshcd_wl_resume(hba, UFS_RUNTIME_PM);
> + if (ret)
> + dev_err(&sdev->sdev_gendev, "%s failed: %d\n", __func__, ret);
> +
> + trace_ufshcd_wl_runtime_resume(dev_name(dev), ret,
> + ktime_to_us(ktime_sub(ktime_get(), start)),
> + hba->curr_dev_pwr_mode, hba->uic_link_state);
> +
> + return ret;
> +}
> +
> +static int ufshcd_wl_suspend(struct device *dev)
> +{
> + struct scsi_device *sdev = to_scsi_device(dev);
> + struct ufs_hba *hba;
> + int ret;
> + ktime_t start = ktime_get();
> +
> + if (is_rpmb_wlun(sdev))
> + return 0;
> + hba = shost_priv(sdev->host);
> + ret = __ufshcd_wl_suspend(hba, UFS_SYSTEM_PM);
> + if (ret)
> + dev_err(&sdev->sdev_gendev, "%s failed: %d\n", __func__, ret);
> +
> + trace_ufshcd_wl_suspend(dev_name(dev), ret,
> + ktime_to_us(ktime_sub(ktime_get(), start)),
> + hba->curr_dev_pwr_mode, hba->uic_link_state);
> +
> + return ret;
> +}
> +
> +static int ufshcd_wl_resume(struct device *dev)
> +{
> + struct scsi_device *sdev = to_scsi_device(dev);
> + struct ufs_hba *hba;
> + int ret;
> + ktime_t start = ktime_get();
> +
> + if (pm_runtime_suspended(dev))
> + return 0;
> + hba = shost_priv(sdev->host);
> +
> + if (is_rpmb_wlun(sdev)) {
> + if (hba->sdev_rpmb)
> + return ufshcd_clear_ua_wlun(hba, UFS_UPIU_RPMB_WLUN);
> + return 0;
> + }
> +
> + ret = __ufshcd_wl_resume(hba, UFS_SYSTEM_PM);
> + if (ret)
> + dev_err(&sdev->sdev_gendev, "%s failed: %d\n", __func__, ret);
> +
> + trace_ufshcd_wl_resume(dev_name(dev), ret,
> + ktime_to_us(ktime_sub(ktime_get(), start)),
> + hba->curr_dev_pwr_mode, hba->uic_link_state);
> +
> + return ret;
> +}
> +
> +static void ufshcd_wl_shutdown(struct device *dev)
> +{
> + struct scsi_device *sdev = to_scsi_device(dev);
> + struct ufs_hba *hba;
> +
> + if (is_rpmb_wlun(sdev))
> + return;
> + hba = shost_priv(sdev->host);
> + scsi_device_quiesce(sdev);
> + shost_for_each_device(sdev, hba->host) {
> + if (sdev == hba->sdev_ufs_device)
> + continue;
> + scsi_remove_device(sdev);

Shutdown needs to quiesce devices. Removing devices is not advisable
because the upper layers cannot respond at this point, causing deadlocks.
Is scsi_remove_device() really needed here?

> + }
> + __ufshcd_wl_suspend(hba, UFS_SHUTDOWN_PM);
> +}
> +
> +/**
> + * ufshcd_suspend - helper function for suspend operations
> + * @hba: per adapter instance
> + * @pm_op: desired low power operation type
> + *
> + * This function will try to put the UFS link into low power
> + * mode based on the "rpm_lvl" (Runtime PM level) or "spm_lvl"
> + * (System PM level).
> + *
> + * If this function is called during shutdown, it will make sure that
> + * link is powered off.
> + *
> + * Returns 0 for success and non-zero for failure
> + */
> +static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
> +{
> + int ret = 0;
> + int check_for_bkops;
> + enum ufs_pm_level pm_lvl;
> + enum ufs_dev_pwr_mode req_dev_pwr_mode;
> + enum uic_link_state req_link_state;
> +
> + hba->pm_op_in_progress = 1;
> + if (!ufshcd_is_shutdown_pm(pm_op)) {
> + pm_lvl = ufshcd_is_runtime_pm(pm_op) ?
> + hba->rpm_lvl : hba->spm_lvl;
> + req_dev_pwr_mode = ufs_get_pm_lvl_to_dev_pwr_mode(pm_lvl);
> + req_link_state = ufs_get_pm_lvl_to_link_pwr_state(pm_lvl);
> + } else {
> + req_dev_pwr_mode = UFS_POWERDOWN_PWR_MODE;
> + req_link_state = UIC_LINK_OFF_STATE;
> + }
> +
> + /*
> + * If we can't transition into any of the low power modes
> + * just gate the clocks.
> + */
> + ufshcd_hold(hba, false);
> + hba->clk_gating.is_suspended = true;
>
> /*
> * In the case of DeepSleep, the device is expected to remain powered
> @@ -8758,9 +9065,8 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
> check_for_bkops = !ufshcd_is_ufs_dev_deepsleep(hba);
> ret = ufshcd_link_state_transition(hba, req_link_state, check_for_bkops);
> if (ret)
> - goto set_dev_active;
> + goto enable_gating;
>
> -disable_clks:
> /*
> * Call vendor specific suspend callback. As these callbacks may access
> * vendor specific host controller register space call them before the
> @@ -8790,7 +9096,6 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
> goto out;
>
> set_link_active:
> - ufshcd_vreg_set_hpm(hba);
> /*
> * Device hardware reset is required to exit DeepSleep. Also, for
> * DeepSleep, the link is off so host reset and restore will be done
> @@ -8804,28 +9109,15 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
> ufshcd_set_link_active(hba);
> else if (ufshcd_is_link_off(hba))
> ufshcd_host_reset_and_restore(hba);
> -set_dev_active:
> /* Can also get here needing to exit DeepSleep */
> if (ufshcd_is_ufs_dev_deepsleep(hba)) {
> ufshcd_device_reset(hba);
> ufshcd_host_reset_and_restore(hba);
> }
> - if (!ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE))
> - ufshcd_disable_auto_bkops(hba);
> enable_gating:
> - if (ufshcd_is_clkscaling_supported(hba))
> - ufshcd_clk_scaling_suspend(hba, false);
> -
> hba->clk_gating.is_suspended = false;
> - hba->dev_info.b_rpm_dev_flush_capable = false;
> - ufshcd_clear_ua_wluns(hba);
> ufshcd_release(hba);
> out:
> - if (hba->dev_info.b_rpm_dev_flush_capable) {
> - schedule_delayed_work(&hba->rpm_dev_flush_recheck_work,
> - msecs_to_jiffies(RPM_DEV_FLUSH_RECHECK_WORK_DELAY_MS));
> - }
> -
> hba->pm_op_in_progress = 0;
>
> if (ret)
> @@ -8846,10 +9138,8 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
> static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
> {
> int ret;
> - enum uic_link_state old_link_state;
>
> hba->pm_op_in_progress = 1;
> - old_link_state = hba->uic_link_state;
>
> ufshcd_hba_vreg_set_hpm(hba);
> ret = ufshcd_vreg_set_hpm(hba);
> @@ -8901,43 +9191,14 @@ static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
> goto vendor_suspend;
> }
>
> - if (!ufshcd_is_ufs_dev_active(hba)) {
> - ret = ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE);
> - if (ret)
> - goto set_old_link_state;
> - }
> -
> - if (ufshcd_keep_autobkops_enabled_except_suspend(hba))
> - ufshcd_enable_auto_bkops(hba);
> - else
> - /*
> - * If BKOPs operations are urgently needed at this moment then
> - * keep auto-bkops enabled or else disable it.
> - */
> - ufshcd_urgent_bkops(hba);
> -
> - hba->clk_gating.is_suspended = false;
> -
> - if (ufshcd_is_clkscaling_supported(hba))
> - ufshcd_clk_scaling_suspend(hba, false);
> -
> /* Enable Auto-Hibernate if configured */
> ufshcd_auto_hibern8_enable(hba);
>
> - if (hba->dev_info.b_rpm_dev_flush_capable) {
> - hba->dev_info.b_rpm_dev_flush_capable = false;
> - cancel_delayed_work(&hba->rpm_dev_flush_recheck_work);
> - }
> -
> - ufshcd_clear_ua_wluns(hba);
> -
> /* Schedule clock gating in case of no access to UFS device yet */
> ufshcd_release(hba);
>
> goto out;
>
> -set_old_link_state:
> - ufshcd_link_state_transition(hba, old_link_state, 0);
> vendor_suspend:
> ufshcd_vops_suspend(hba, pm_op);
> disable_irq_and_vops_clks:
> diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h
> index ee61f82..7662387 100644
> --- a/drivers/scsi/ufs/ufshcd.h
> +++ b/drivers/scsi/ufs/ufshcd.h
> @@ -72,6 +72,8 @@ enum ufs_event_type {
> UFS_EVT_LINK_STARTUP_FAIL,
> UFS_EVT_RESUME_ERR,
> UFS_EVT_SUSPEND_ERR,
> + UFS_EVT_WL_SUSP_ERR,
> + UFS_EVT_WL_RES_ERR,
>
> /* abnormal events */
> UFS_EVT_DEV_RESET,
> @@ -841,6 +843,8 @@ struct ufs_hba {
> #ifdef CONFIG_DEBUG_FS
> struct dentry *debugfs_root;
> #endif
> + bool init_done;
> + u32 luns_avail;
> };
>
> /* Returns true if clocks can be gated. Otherwise false */
> diff --git a/include/trace/events/ufs.h b/include/trace/events/ufs.h
> index e151477..d9d233b 100644
> --- a/include/trace/events/ufs.h
> +++ b/include/trace/events/ufs.h
> @@ -246,6 +246,26 @@ DEFINE_EVENT(ufshcd_template, ufshcd_init,
> int dev_state, int link_state),
> TP_ARGS(dev_name, err, usecs, dev_state, link_state));
>
> +DEFINE_EVENT(ufshcd_template, ufshcd_wl_suspend,
> + TP_PROTO(const char *dev_name, int err, s64 usecs,
> + int dev_state, int link_state),
> + TP_ARGS(dev_name, err, usecs, dev_state, link_state));
> +
> +DEFINE_EVENT(ufshcd_template, ufshcd_wl_resume,
> + TP_PROTO(const char *dev_name, int err, s64 usecs,
> + int dev_state, int link_state),
> + TP_ARGS(dev_name, err, usecs, dev_state, link_state));
> +
> +DEFINE_EVENT(ufshcd_template, ufshcd_wl_runtime_suspend,
> + TP_PROTO(const char *dev_name, int err, s64 usecs,
> + int dev_state, int link_state),
> + TP_ARGS(dev_name, err, usecs, dev_state, link_state));
> +
> +DEFINE_EVENT(ufshcd_template, ufshcd_wl_runtime_resume,
> + TP_PROTO(const char *dev_name, int err, s64 usecs,
> + int dev_state, int link_state),
> + TP_ARGS(dev_name, err, usecs, dev_state, link_state));
> +
> TRACE_EVENT(ufshcd_command,
> TP_PROTO(const char *dev_name, enum ufs_trace_str_t str_t,
> unsigned int tag, u32 doorbell, int transfer_len, u32 intr,
>

2021-02-24 01:11:02

by Asutosh Das (asd)

[permalink] [raw]
Subject: Re: [PATCH v4 1/2] scsi: ufs: Enable power management for wlun

On Tue, Feb 23 2021 at 12:23 -0800, Adrian Hunter wrote:
>On 23/02/21 1:04 am, Asutosh Das wrote:
>> During runtime-suspend of ufs host, the scsi devices are
>> already suspended and so are the queues associated with them.
>> But the ufs host sends SSU to wlun during its runtime-suspend.
>> During the process blk_queue_enter checks if the queue is not in
>> suspended state. If so, it waits for the queue to resume, and never
>> comes out of it.
>> The commit
>> (d55d15a33: scsi: block: Do not accept any requests while suspended)
>> adds the check if the queue is in suspended state in blk_queue_enter().
>>
>> Call trace:
>> __switch_to+0x174/0x2c4
>> __schedule+0x478/0x764
>> schedule+0x9c/0xe0
>> blk_queue_enter+0x158/0x228
>> blk_mq_alloc_request+0x40/0xa4
>> blk_get_request+0x2c/0x70
>> __scsi_execute+0x60/0x1c4
>> ufshcd_set_dev_pwr_mode+0x124/0x1e4
>> ufshcd_suspend+0x208/0x83c
>> ufshcd_runtime_suspend+0x40/0x154
>> ufshcd_pltfrm_runtime_suspend+0x14/0x20
>> pm_generic_runtime_suspend+0x28/0x3c
>> __rpm_callback+0x80/0x2a4
>> rpm_suspend+0x308/0x614
>> rpm_idle+0x158/0x228
>> pm_runtime_work+0x84/0xac
>> process_one_work+0x1f0/0x470
>> worker_thread+0x26c/0x4c8
>> kthread+0x13c/0x320
>> ret_from_fork+0x10/0x18
>>
>> Fix this by registering ufs device wlun as a scsi driver and
>> registering it for block runtime-pm. Also make this as a
>> supplier for all other luns. That way, this device wlun
>> suspends after all the consumers and resumes after
>> hba resumes.
>
>Hi
>
Hi Adrian
>I have a few more questions and comments.
>
Thanks for the review.
I'll go through your comments and fix it in the next version.

>At a glance it looked to me like the SCSI bus will leave a SCSI device
>runtime suspended at system suspend, but UFS device spm_lvl and rpm_lvl need
>not be the same. Do you know how SCSI handles that case?
>

Good point.

Yes, it says that:
/*
* All the high-level SCSI drivers that implement runtime
* PM treat runtime suspend, system suspend, and system
* hibernate nearly identically. In all cases the requirements
* for runtime suspension are stricter.
*/

It's not true in case of ufs.

I can think of a couple of approaches:

Adding a couple of more cb() to ufs device wlun:
1. .prepare() -> runtime resume the ufs device -> pm_runtime_get_noresume()
2. .resume_early() -> pm_runtime_put_noidle()

Or,

Add a check to SCSI pm such that, should a device expose a capability saying
that it's runtime-pm differs from its system-pm, scsi-pm invokes the said device's
system-pm cb(). I'm not sure if this approach would violate any underlying assumptions.

Also, I'm not sure which would be a better approach or if there's another approach entirely.
Appreciate any insights.

>>
>> Co-developed-by: Can Guo <[email protected]>
>> Signed-off-by: Can Guo <[email protected]>
>> Signed-off-by: Asutosh Das <[email protected]>
>> ---
>> drivers/scsi/ufs/ufshcd.c | 455 +++++++++++++++++++++++++++++++++++----------
>> drivers/scsi/ufs/ufshcd.h | 4 +
>> include/trace/events/ufs.h | 20 ++
>> 3 files changed, 382 insertions(+), 97 deletions(-)
>>
>> diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c
>> index 45624c7..1d8044a 100644
>> --- a/drivers/scsi/ufs/ufshcd.c
>> +++ b/drivers/scsi/ufs/ufshcd.c
>> @@ -16,6 +16,7 @@
>> #include <linux/bitfield.h>
>> #include <linux/blk-pm.h>
>> #include <linux/blkdev.h>
>> +#include <scsi/scsi_driver.h>
>> #include "ufshcd.h"
>> #include "ufs_quirks.h"
>> #include "unipro.h"
>> @@ -251,6 +252,11 @@ static int ufshcd_wb_toggle_flush_during_h8(struct ufs_hba *hba, bool set);
>> static inline int ufshcd_wb_toggle_flush(struct ufs_hba *hba, bool enable);
>> static void ufshcd_hba_vreg_set_lpm(struct ufs_hba *hba);
>> static void ufshcd_hba_vreg_set_hpm(struct ufs_hba *hba);
>> +static int ufshcd_wl_runtime_suspend(struct device *dev);
>> +static int ufshcd_wl_runtime_resume(struct device *dev);
>> +static int ufshcd_wl_suspend(struct device *dev);
>> +static int ufshcd_wl_resume(struct device *dev);
>> +static void ufshcd_wl_shutdown(struct device *dev);
>>
>> static inline bool ufshcd_valid_tag(struct ufs_hba *hba, int tag)
>> {
>> @@ -1556,7 +1562,7 @@ static ssize_t ufshcd_clkscale_enable_store(struct device *dev,
>> if (value == hba->clk_scaling.is_enabled)
>> goto out;
>>
>> - pm_runtime_get_sync(hba->dev);
>> + scsi_autopm_get_device(hba->sdev_ufs_device);
>> ufshcd_hold(hba, false);
>>
>> hba->clk_scaling.is_enabled = value;
>> @@ -1572,7 +1578,7 @@ static ssize_t ufshcd_clkscale_enable_store(struct device *dev,
>> }
>>
>> ufshcd_release(hba);
>> - pm_runtime_put_sync(hba->dev);
>> + scsi_autopm_put_device(hba->sdev_ufs_device);
>> out:
>> up(&hba->host_sem);
>> return err ? err : count;
>> @@ -2572,6 +2578,17 @@ static inline u16 ufshcd_upiu_wlun_to_scsi_wlun(u8 upiu_wlun_id)
>> return (upiu_wlun_id & ~UFS_UPIU_WLUN_ID) | SCSI_W_LUN_BASE;
>> }
>>
>> +static inline bool is_rpmb_wlun(struct scsi_device *sdev)
>> +{
>> + return (sdev->lun == ufshcd_upiu_wlun_to_scsi_wlun(UFS_UPIU_RPMB_WLUN));
>> +}
>> +
>> +static inline bool is_device_wlun(struct scsi_device *sdev)
>> +{
>> + return (sdev->lun ==
>> + ufshcd_upiu_wlun_to_scsi_wlun(UFS_UPIU_UFS_DEVICE_WLUN));
>> +}
>> +
>> static void ufshcd_init_lrb(struct ufs_hba *hba, struct ufshcd_lrb *lrb, int i)
>> {
>> struct utp_transfer_cmd_desc *cmd_descp = hba->ucdl_base_addr;
>> @@ -4808,6 +4825,41 @@ static inline void ufshcd_get_lu_power_on_wp_status(struct ufs_hba *hba,
>> }
>>
>> /**
>> + * ufshcd_setup_links - associate link b/w device wlun and other luns
>> + * @sdev: pointer to SCSI device
>> + * @hba: pointer to ufs hba
>> + */
>> +static void ufshcd_setup_links(struct ufs_hba *hba, struct scsi_device *sdev)
>> +{
>> + struct device_link *link;
>> +
>> + /*
>> + * device wlun is the supplier & rest of the luns are consumers
>> + * This ensures that device wlun suspends after all other luns.
>> + */
>> + if (hba->sdev_ufs_device) {
>> + link = device_link_add(&sdev->sdev_gendev,
>> + &hba->sdev_ufs_device->sdev_gendev,
>> + DL_FLAG_PM_RUNTIME);
>> + if (!link) {
>> + dev_err(&sdev->sdev_gendev, "Failed establishing link - %s\n",
>> + dev_name(&hba->sdev_ufs_device->sdev_gendev));
>> + return;
>> + }
>> + hba->luns_avail--;
>> + /* Ignore REPORT_LUN wlun probing */
>> + if (hba->luns_avail != 1)
>> + return;
>> +
>> + pm_runtime_put_noidle(&hba->sdev_ufs_device->sdev_gendev);
>> + pm_runtime_mark_last_busy(&hba->sdev_ufs_device->sdev_gendev);
>> + } else {
>> + /* device wlun is probed */
>> + hba->luns_avail--;
>> + }
>> +}
>> +
>> +/**
>> * ufshcd_slave_alloc - handle initial SCSI device configurations
>> * @sdev: pointer to SCSI device
>> *
>> @@ -4838,6 +4890,8 @@ static int ufshcd_slave_alloc(struct scsi_device *sdev)
>>
>> ufshcd_get_lu_power_on_wp_status(hba, sdev);
>>
>> + ufshcd_setup_links(hba, sdev);
>> +
>> return 0;
>> }
>>
>> @@ -4984,16 +5038,9 @@ ufshcd_transfer_rsp_status(struct ufs_hba *hba, struct ufshcd_lrb *lrbp)
>> * solution could be to abort the system suspend if
>> * UFS device needs urgent BKOPs.
>> */
>> - if (!hba->pm_op_in_progress &&
>> - ufshcd_is_exception_event(lrbp->ucd_rsp_ptr) &&
>> - schedule_work(&hba->eeh_work)) {
>> - /*
>> - * Prevent suspend once eeh_work is scheduled
>> - * to avoid deadlock between ufshcd_suspend
>> - * and exception event handler.
>> - */
>> - pm_runtime_get_noresume(hba->dev);
>> - }
>> + if (ufshcd_is_exception_event(lrbp->ucd_rsp_ptr))
>> + /* Flushed in suspend */
>> + schedule_work(&hba->eeh_work);
>> break;
>> case UPIU_TRANSACTION_REJECT_UPIU:
>> /* TODO: handle Reject UPIU Response */
>> @@ -5589,8 +5636,8 @@ static void ufshcd_rpm_dev_flush_recheck_work(struct work_struct *work)
>> * after a certain delay to recheck the threshold by next runtime
>> * suspend.
>> */
>> - pm_runtime_get_sync(hba->dev);
>> - pm_runtime_put_sync(hba->dev);
>> + scsi_autopm_get_device(hba->sdev_ufs_device);
>> + scsi_autopm_put_device(hba->sdev_ufs_device);
>> }
>>
>> /**
>> @@ -5607,7 +5654,6 @@ static void ufshcd_exception_event_handler(struct work_struct *work)
>> u32 status = 0;
>> hba = container_of(work, struct ufs_hba, eeh_work);
>>
>> - pm_runtime_get_sync(hba->dev);
>> ufshcd_scsi_block_requests(hba);
>> err = ufshcd_get_ee_status(hba, &status);
>> if (err) {
>> @@ -5623,14 +5669,6 @@ static void ufshcd_exception_event_handler(struct work_struct *work)
>>
>> out:
>> ufshcd_scsi_unblock_requests(hba);
>> - /*
>> - * pm_runtime_get_noresume is called while scheduling
>> - * eeh_work to avoid suspend racing with exception work.
>> - * Hence decrement usage counter using pm_runtime_put_noidle
>> - * to allow suspend on completion of exception event handler.
>> - */
>> - pm_runtime_put_noidle(hba->dev);
>> - pm_runtime_put(hba->dev);
>> return;
>> }
>>
>> @@ -7254,6 +7292,15 @@ static int ufshcd_scsi_add_wlus(struct ufs_hba *hba)
>> goto out;
>> }
>> ufshcd_blk_pm_runtime_init(hba->sdev_ufs_device);
>> + /*
>> + * A pm_runtime_put_sync is invoked when this device enables blk_pm_runtime
>> + * & would suspend the device-wlun upon timer expiry.
>> + * But suspending device wlun _may_ put the ufs device in the pre-defined
>> + * low power mode (SSU <rpm_lvl>). Probing of other luns may fail then.
>> + * Don't allow this suspend until all the luns have been probed.
>> + * See pm_runtime_mark_last_busy in ufshcd_setup_links.
>> + */
>> + pm_runtime_get_noresume(&hba->sdev_ufs_device->sdev_gendev);
>> scsi_device_put(hba->sdev_ufs_device);
>>
>> hba->sdev_rpmb = __scsi_add_device(hba->host, 0, 0,
>> @@ -7417,6 +7464,9 @@ static int ufs_get_device_desc(struct ufs_hba *hba)
>> goto out;
>> }
>>
>> + hba->luns_avail = desc_buf[DEVICE_DESC_PARAM_NUM_LU] +
>> + desc_buf[DEVICE_DESC_PARAM_NUM_WLU];
>> +
>> ufs_fixup_device_setup(hba);
>>
>> ufshcd_wb_probe(hba, desc_buf);
>> @@ -7747,6 +7797,54 @@ static int ufshcd_device_params_init(struct ufs_hba *hba)
>> return ret;
>> }
>>
>> +static int ufshcd_wl_probe(struct device *dev)
>> +{
>> + struct scsi_device *sdev = to_scsi_device(dev);
>> +
>> + /*
>> + * Use this driver for both device wlun and rpmb lun
>> + * It's used to send uac to rpmb wlun and ssu to device wlun
>> + */
>> + if (sdev && (is_rpmb_wlun(sdev) || is_device_wlun(sdev)))
>> + return 0;
>> + return -ENODEV;
>> +}
>> +
>> +static int ufshcd_wl_remove(struct device *dev)
>> +{
>> + struct scsi_device *sdev = to_scsi_device(dev);
>> +
>> + scsi_remove_device(sdev);
>
>The 'probe' did not do the __scsi_add_device(), so does the 'remove' need to
>do scsi_remove_device()?
>
>> + return 0;
>> +}
>> +
>> +static const struct dev_pm_ops ufshcd_wl_pm_ops = {
>> + SET_RUNTIME_PM_OPS(ufshcd_wl_runtime_suspend, ufshcd_wl_runtime_resume, NULL)
>> + SET_SYSTEM_SLEEP_PM_OPS(ufshcd_wl_suspend, ufshcd_wl_resume)
>
>.poweroff needs to set the power mode to PowerDown irrespective of spm_lvl,
>so not exactly the same as ufshcd_wl_suspend.
>
>Also, ufshcd_pci_poweroff() in ufshcd-pci.c should be removed since it won't
>work anymore.
>
>> +};
>> +
>> +/**
>> + * ufs_wlun - describes 2 wluns:
>> + * ufs-device wlun - used to send pm commands
>> + * and rpmb wlun - for rpmb commands
>> + * All luns including rpmb are consumers of ufs-device wlun.
>> + *
>> + * Currently, no sd driver is present for wluns.
>> + * Hence the no specific pm operations are performed.
>> + * With ufs design, SSU should be sent to ufs-device wlun.
>> + * Hence register a scsi driver for ufs wluns only.
>> + */
>> +static struct scsi_driver ufs_wlun = {
>> + .gendrv = {
>> + .name = "ufs_device_wlun",
>> + .owner = THIS_MODULE,
>> + .probe = ufshcd_wl_probe,
>> + .remove = ufshcd_wl_remove,
>> + .pm = &ufshcd_wl_pm_ops,
>> + .shutdown = ufshcd_wl_shutdown,
>> + },
>> +};
>
>WRT to RPMB, aren't all the callbacks optional, so that would leave only
>something like below. Would that work?
>
>#define wlun_dev_to_hba(dv) shost_priv(to_scsi_device(dv)->host)
>
>static int ufs_rpmb_probe(struct device *dev)
>{
> return is_rpmb_wlun(to_scsi_device(dev)) ? 0 : -ENODEV;
>}
>
>static int ufs_rpmb_runtime_resume(struct device *dev)
>{
> struct ufs_hba *hba = wlun_dev_to_hba(dev);
>
> if (hba->sdev_rpmb)
> return ufshcd_clear_ua_wlun(hba, UFS_UPIU_RPMB_WLUN);
> return 0;
>}
>
>static int ufs_rpmb_resume(struct device *dev)
>{
> struct ufs_hba *hba = wlun_dev_to_hba(dev);
>
> if (hba->sdev_rpmb && !pm_runtime_suspended(dev))
> return ufshcd_clear_ua_wlun(hba, UFS_UPIU_RPMB_WLUN);
> return 0;
>}
>
>static const struct dev_pm_ops ufs_rpmb_pm_ops = {
> SET_RUNTIME_PM_OPS(NULL, ufs_rpmb_runtime_resume, NULL)
> SET_SYSTEM_SLEEP_PM_OPS(NULL, ufs_rpmb_resume)
>};
>
>static struct scsi_driver ufs_rpmb_template = {
> .gendrv = {
> .name = "ufs_rpmb_wlun",
> .owner = THIS_MODULE,
> .probe = ufs_rpmb_probe,
> .pm = &ufs_rpmb_pm_ops,
> },
>};
>
>
>> +
>> /**
>> * ufshcd_add_lus - probe and add UFS logical units
>> * @hba: per-adapter instance
>> @@ -7755,6 +7853,12 @@ static int ufshcd_add_lus(struct ufs_hba *hba)
>> {
>> int ret;
>>
>> + ret = scsi_register_driver(&ufs_wlun.gendrv);
>
>That will attempt to register the driver every time a UFS host controller is
>added (e.g. what happens if you unbind and re-bind the host controller).
>Probably needs to be in ufshcd_core_init() with corresponding unregister in
>ufshcd_core_exit()
>
>> + if (ret) {
>> + dev_err(hba->dev, "Register device wlun driver failed: %d\n",
>> + ret);
>> + return ret;
>> + }
>> /* Add required well known logical units to scsi mid layer */
>> ret = ufshcd_scsi_add_wlus(hba);
>> if (ret)
>> @@ -7964,6 +8068,7 @@ static void ufshcd_async_scan(void *data, async_cookie_t cookie)
>> pm_runtime_put_sync(hba->dev);
>> ufshcd_hba_exit(hba);
>> }
>> + hba->init_done = true;
>> }
>>
>> static const struct attribute_group *ufshcd_driver_groups[] = {
>> @@ -8475,7 +8580,8 @@ static int ufshcd_set_dev_pwr_mode(struct ufs_hba *hba,
>> * handling context.
>> */
>> hba->host->eh_noresume = 1;
>> - ufshcd_clear_ua_wluns(hba);
>> + if (hba->wlun_dev_clr_ua)
>> + ufshcd_clear_ua_wlun(hba, UFS_UPIU_UFS_DEVICE_WLUN);
>>
>> cmd[4] = pwr_mode << 4;
>>
>> @@ -8650,31 +8756,19 @@ static void ufshcd_hba_vreg_set_hpm(struct ufs_hba *hba)
>> ufshcd_setup_hba_vreg(hba, true);
>> }
>>
>> -/**
>> - * ufshcd_suspend - helper function for suspend operations
>> - * @hba: per adapter instance
>> - * @pm_op: desired low power operation type
>> - *
>> - * This function will try to put the UFS device and link into low power
>> - * mode based on the "rpm_lvl" (Runtime PM level) or "spm_lvl"
>> - * (System PM level).
>> - *
>> - * If this function is called during shutdown, it will make sure that
>> - * both UFS device and UFS link is powered off.
>> - *
>> - * NOTE: UFS device & link must be active before we enter in this function.
>> - *
>> - * Returns 0 for success and non-zero for failure
>> - */
>> -static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>> +static int __ufshcd_wl_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>> {
>> int ret = 0;
>> - int check_for_bkops;
>> enum ufs_pm_level pm_lvl;
>> enum ufs_dev_pwr_mode req_dev_pwr_mode;
>> enum uic_link_state req_link_state;
>>
>> - hba->pm_op_in_progress = 1;
>> + /*
>> + * Is invoked when the device wlun is added to sysfs
>> + * But by then hba->sdev_ufs_device may not be initialized.
>> + */
>> + if (!hba->init_done)
>> + return 0;
>> if (!ufshcd_is_shutdown_pm(pm_op)) {
>> pm_lvl = ufshcd_is_runtime_pm(pm_op) ?
>> hba->rpm_lvl : hba->spm_lvl;
>> @@ -8690,24 +8784,23 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>> * just gate the clocks.
>> */
>> ufshcd_hold(hba, false);
>> - hba->clk_gating.is_suspended = true;
>>
>> if (ufshcd_is_clkscaling_supported(hba))
>> ufshcd_clk_scaling_suspend(hba, true);
>>
>> if (req_dev_pwr_mode == UFS_ACTIVE_PWR_MODE &&
>> req_link_state == UIC_LINK_ACTIVE_STATE) {
>> - goto disable_clks;
>> + goto enable_scaling;
>> }
>>
>> if ((req_dev_pwr_mode == hba->curr_dev_pwr_mode) &&
>> (req_link_state == hba->uic_link_state))
>> - goto enable_gating;
>> + goto enable_scaling;
>>
>> /* UFS device & link must be active before we enter in this function */
>> if (!ufshcd_is_ufs_dev_active(hba) || !ufshcd_is_link_active(hba)) {
>> ret = -EINVAL;
>> - goto enable_gating;
>> + goto enable_scaling;
>> }
>>
>> if (ufshcd_is_runtime_pm(pm_op)) {
>> @@ -8719,7 +8812,7 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>> */
>> ret = ufshcd_urgent_bkops(hba);
>> if (ret)
>> - goto enable_gating;
>> + goto enable_scaling;
>> } else {
>> /* make sure that auto bkops is disabled */
>> ufshcd_disable_auto_bkops(hba);
>> @@ -8746,10 +8839,224 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>>
>> if (!hba->dev_info.b_rpm_dev_flush_capable) {
>> ret = ufshcd_set_dev_pwr_mode(hba, req_dev_pwr_mode);
>> - if (ret)
>> - goto enable_gating;
>> + if (!ret)
>> + goto out;
>> }
>> }
>> +enable_scaling:
>> + if (ufshcd_is_clkscaling_supported(hba))
>> + ufshcd_clk_scaling_suspend(hba, false);
>> +
>> + hba->dev_info.b_rpm_dev_flush_capable = false;
>> +out:
>> + if (hba->dev_info.b_rpm_dev_flush_capable) {
>> + schedule_delayed_work(&hba->rpm_dev_flush_recheck_work,
>> + msecs_to_jiffies(RPM_DEV_FLUSH_RECHECK_WORK_DELAY_MS));
>> + }
>> + if (ret)
>> + ufshcd_update_evt_hist(hba, UFS_EVT_WL_SUSP_ERR, (u32)ret);
>> + ufshcd_release(hba);
>> + return ret;
>> +}
>> +
>> +static int __ufshcd_wl_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>> +{
>> + int ret = 0;
>> +
>> + if (!hba->init_done)
>> + return 0;
>> +
>> + ufshcd_hold(hba, false);
>> + if (ufshcd_is_ufs_dev_deepsleep(hba)) {
>> + /* The device is in DeepSleep but hba was not suspended */
>> + ret = ufshcd_reset_and_restore(hba);
>> + if (ret)
>> + goto out;
>> + }
>> +
>> + if (!ufshcd_is_ufs_dev_active(hba)) {
>> + ret = ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE);
>> + if (ret)
>> + goto out;
>> + }
>> +
>> + if (ufshcd_keep_autobkops_enabled_except_suspend(hba))
>> + ufshcd_enable_auto_bkops(hba);
>> + else
>> + /*
>> + * If BKOPs operations are urgently needed at this moment then
>> + * keep auto-bkops enabled or else disable it.
>> + */
>> + ufshcd_urgent_bkops(hba);
>> +
>> + if (hba->clk_scaling.is_allowed)
>> + ufshcd_resume_clkscaling(hba);
>> +
>> + if (hba->dev_info.b_rpm_dev_flush_capable) {
>> + hba->dev_info.b_rpm_dev_flush_capable = false;
>> + cancel_delayed_work(&hba->rpm_dev_flush_recheck_work);
>> + }
>> +
>> +out:
>> + if (ret)
>> + ufshcd_update_evt_hist(hba, UFS_EVT_WL_RES_ERR, (u32)ret);
>> + ufshcd_release(hba);
>> + return ret;
>> +}
>> +
>> +static int ufshcd_wl_runtime_suspend(struct device *dev)
>> +{
>> + struct scsi_device *sdev = to_scsi_device(dev);
>> + struct ufs_hba *hba;
>> + int ret;
>> + ktime_t start = ktime_get();
>> +
>> + hba = shost_priv(sdev->host);
>> +
>> + if (is_rpmb_wlun(sdev))
>> + return 0;
>> + ret = __ufshcd_wl_suspend(hba, UFS_RUNTIME_PM);
>> + if (ret)
>> + dev_err(&sdev->sdev_gendev, "%s failed: %d\n", __func__, ret);
>> +
>> + trace_ufshcd_wl_runtime_suspend(dev_name(dev), ret,
>> + ktime_to_us(ktime_sub(ktime_get(), start)),
>> + hba->curr_dev_pwr_mode, hba->uic_link_state);
>> +
>> + return ret;
>> +}
>> +
>> +static int ufshcd_wl_runtime_resume(struct device *dev)
>> +{
>> + struct scsi_device *sdev = to_scsi_device(dev);
>> + struct ufs_hba *hba;
>> + int ret;
>> + ktime_t start = ktime_get();
>> +
>> + hba = shost_priv(sdev->host);
>> +
>> + if (is_rpmb_wlun(sdev)) {
>> + if (hba->sdev_rpmb)
>> + return ufshcd_clear_ua_wlun(hba, UFS_UPIU_RPMB_WLUN);
>> + return 0;
>> + }
>> +
>> + ret = __ufshcd_wl_resume(hba, UFS_RUNTIME_PM);
>> + if (ret)
>> + dev_err(&sdev->sdev_gendev, "%s failed: %d\n", __func__, ret);
>> +
>> + trace_ufshcd_wl_runtime_resume(dev_name(dev), ret,
>> + ktime_to_us(ktime_sub(ktime_get(), start)),
>> + hba->curr_dev_pwr_mode, hba->uic_link_state);
>> +
>> + return ret;
>> +}
>> +
>> +static int ufshcd_wl_suspend(struct device *dev)
>> +{
>> + struct scsi_device *sdev = to_scsi_device(dev);
>> + struct ufs_hba *hba;
>> + int ret;
>> + ktime_t start = ktime_get();
>> +
>> + if (is_rpmb_wlun(sdev))
>> + return 0;
>> + hba = shost_priv(sdev->host);
>> + ret = __ufshcd_wl_suspend(hba, UFS_SYSTEM_PM);
>> + if (ret)
>> + dev_err(&sdev->sdev_gendev, "%s failed: %d\n", __func__, ret);
>> +
>> + trace_ufshcd_wl_suspend(dev_name(dev), ret,
>> + ktime_to_us(ktime_sub(ktime_get(), start)),
>> + hba->curr_dev_pwr_mode, hba->uic_link_state);
>> +
>> + return ret;
>> +}
>> +
>> +static int ufshcd_wl_resume(struct device *dev)
>> +{
>> + struct scsi_device *sdev = to_scsi_device(dev);
>> + struct ufs_hba *hba;
>> + int ret;
>> + ktime_t start = ktime_get();
>> +
>> + if (pm_runtime_suspended(dev))
>> + return 0;
>> + hba = shost_priv(sdev->host);
>> +
>> + if (is_rpmb_wlun(sdev)) {
>> + if (hba->sdev_rpmb)
>> + return ufshcd_clear_ua_wlun(hba, UFS_UPIU_RPMB_WLUN);
>> + return 0;
>> + }
>> +
>> + ret = __ufshcd_wl_resume(hba, UFS_SYSTEM_PM);
>> + if (ret)
>> + dev_err(&sdev->sdev_gendev, "%s failed: %d\n", __func__, ret);
>> +
>> + trace_ufshcd_wl_resume(dev_name(dev), ret,
>> + ktime_to_us(ktime_sub(ktime_get(), start)),
>> + hba->curr_dev_pwr_mode, hba->uic_link_state);
>> +
>> + return ret;
>> +}
>> +
>> +static void ufshcd_wl_shutdown(struct device *dev)
>> +{
>> + struct scsi_device *sdev = to_scsi_device(dev);
>> + struct ufs_hba *hba;
>> +
>> + if (is_rpmb_wlun(sdev))
>> + return;
>> + hba = shost_priv(sdev->host);
>> + scsi_device_quiesce(sdev);
>> + shost_for_each_device(sdev, hba->host) {
>> + if (sdev == hba->sdev_ufs_device)
>> + continue;
>> + scsi_remove_device(sdev);
>
>Shutdown needs to quiesce devices. Removing devices is not advisable
>because the upper layers cannot respond at this point, causing deadlocks.
>Is scsi_remove_device() really needed here?
>
>> + }
>> + __ufshcd_wl_suspend(hba, UFS_SHUTDOWN_PM);
>> +}
>> +
>> +/**
>> + * ufshcd_suspend - helper function for suspend operations
>> + * @hba: per adapter instance
>> + * @pm_op: desired low power operation type
>> + *
>> + * This function will try to put the UFS link into low power
>> + * mode based on the "rpm_lvl" (Runtime PM level) or "spm_lvl"
>> + * (System PM level).
>> + *
>> + * If this function is called during shutdown, it will make sure that
>> + * link is powered off.
>> + *
>> + * Returns 0 for success and non-zero for failure
>> + */
>> +static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>> +{
>> + int ret = 0;
>> + int check_for_bkops;
>> + enum ufs_pm_level pm_lvl;
>> + enum ufs_dev_pwr_mode req_dev_pwr_mode;
>> + enum uic_link_state req_link_state;
>> +
>> + hba->pm_op_in_progress = 1;
>> + if (!ufshcd_is_shutdown_pm(pm_op)) {
>> + pm_lvl = ufshcd_is_runtime_pm(pm_op) ?
>> + hba->rpm_lvl : hba->spm_lvl;
>> + req_dev_pwr_mode = ufs_get_pm_lvl_to_dev_pwr_mode(pm_lvl);
>> + req_link_state = ufs_get_pm_lvl_to_link_pwr_state(pm_lvl);
>> + } else {
>> + req_dev_pwr_mode = UFS_POWERDOWN_PWR_MODE;
>> + req_link_state = UIC_LINK_OFF_STATE;
>> + }
>> +
>> + /*
>> + * If we can't transition into any of the low power modes
>> + * just gate the clocks.
>> + */
>> + ufshcd_hold(hba, false);
>> + hba->clk_gating.is_suspended = true;
>>
>> /*
>> * In the case of DeepSleep, the device is expected to remain powered
>> @@ -8758,9 +9065,8 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>> check_for_bkops = !ufshcd_is_ufs_dev_deepsleep(hba);
>> ret = ufshcd_link_state_transition(hba, req_link_state, check_for_bkops);
>> if (ret)
>> - goto set_dev_active;
>> + goto enable_gating;
>>
>> -disable_clks:
>> /*
>> * Call vendor specific suspend callback. As these callbacks may access
>> * vendor specific host controller register space call them before the
>> @@ -8790,7 +9096,6 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>> goto out;
>>
>> set_link_active:
>> - ufshcd_vreg_set_hpm(hba);
>> /*
>> * Device hardware reset is required to exit DeepSleep. Also, for
>> * DeepSleep, the link is off so host reset and restore will be done
>> @@ -8804,28 +9109,15 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>> ufshcd_set_link_active(hba);
>> else if (ufshcd_is_link_off(hba))
>> ufshcd_host_reset_and_restore(hba);
>> -set_dev_active:
>> /* Can also get here needing to exit DeepSleep */
>> if (ufshcd_is_ufs_dev_deepsleep(hba)) {
>> ufshcd_device_reset(hba);
>> ufshcd_host_reset_and_restore(hba);
>> }
>> - if (!ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE))
>> - ufshcd_disable_auto_bkops(hba);
>> enable_gating:
>> - if (ufshcd_is_clkscaling_supported(hba))
>> - ufshcd_clk_scaling_suspend(hba, false);
>> -
>> hba->clk_gating.is_suspended = false;
>> - hba->dev_info.b_rpm_dev_flush_capable = false;
>> - ufshcd_clear_ua_wluns(hba);
>> ufshcd_release(hba);
>> out:
>> - if (hba->dev_info.b_rpm_dev_flush_capable) {
>> - schedule_delayed_work(&hba->rpm_dev_flush_recheck_work,
>> - msecs_to_jiffies(RPM_DEV_FLUSH_RECHECK_WORK_DELAY_MS));
>> - }
>> -
>> hba->pm_op_in_progress = 0;
>>
>> if (ret)
>> @@ -8846,10 +9138,8 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>> static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>> {
>> int ret;
>> - enum uic_link_state old_link_state;
>>
>> hba->pm_op_in_progress = 1;
>> - old_link_state = hba->uic_link_state;
>>
>> ufshcd_hba_vreg_set_hpm(hba);
>> ret = ufshcd_vreg_set_hpm(hba);
>> @@ -8901,43 +9191,14 @@ static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>> goto vendor_suspend;
>> }
>>
>> - if (!ufshcd_is_ufs_dev_active(hba)) {
>> - ret = ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE);
>> - if (ret)
>> - goto set_old_link_state;
>> - }
>> -
>> - if (ufshcd_keep_autobkops_enabled_except_suspend(hba))
>> - ufshcd_enable_auto_bkops(hba);
>> - else
>> - /*
>> - * If BKOPs operations are urgently needed at this moment then
>> - * keep auto-bkops enabled or else disable it.
>> - */
>> - ufshcd_urgent_bkops(hba);
>> -
>> - hba->clk_gating.is_suspended = false;
>> -
>> - if (ufshcd_is_clkscaling_supported(hba))
>> - ufshcd_clk_scaling_suspend(hba, false);
>> -
>> /* Enable Auto-Hibernate if configured */
>> ufshcd_auto_hibern8_enable(hba);
>>
>> - if (hba->dev_info.b_rpm_dev_flush_capable) {
>> - hba->dev_info.b_rpm_dev_flush_capable = false;
>> - cancel_delayed_work(&hba->rpm_dev_flush_recheck_work);
>> - }
>> -
>> - ufshcd_clear_ua_wluns(hba);
>> -
>> /* Schedule clock gating in case of no access to UFS device yet */
>> ufshcd_release(hba);
>>
>> goto out;
>>
>> -set_old_link_state:
>> - ufshcd_link_state_transition(hba, old_link_state, 0);
>> vendor_suspend:
>> ufshcd_vops_suspend(hba, pm_op);
>> disable_irq_and_vops_clks:
>> diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h
>> index ee61f82..7662387 100644
>> --- a/drivers/scsi/ufs/ufshcd.h
>> +++ b/drivers/scsi/ufs/ufshcd.h
>> @@ -72,6 +72,8 @@ enum ufs_event_type {
>> UFS_EVT_LINK_STARTUP_FAIL,
>> UFS_EVT_RESUME_ERR,
>> UFS_EVT_SUSPEND_ERR,
>> + UFS_EVT_WL_SUSP_ERR,
>> + UFS_EVT_WL_RES_ERR,
>>
>> /* abnormal events */
>> UFS_EVT_DEV_RESET,
>> @@ -841,6 +843,8 @@ struct ufs_hba {
>> #ifdef CONFIG_DEBUG_FS
>> struct dentry *debugfs_root;
>> #endif
>> + bool init_done;
>> + u32 luns_avail;
>> };
>>
>> /* Returns true if clocks can be gated. Otherwise false */
>> diff --git a/include/trace/events/ufs.h b/include/trace/events/ufs.h
>> index e151477..d9d233b 100644
>> --- a/include/trace/events/ufs.h
>> +++ b/include/trace/events/ufs.h
>> @@ -246,6 +246,26 @@ DEFINE_EVENT(ufshcd_template, ufshcd_init,
>> int dev_state, int link_state),
>> TP_ARGS(dev_name, err, usecs, dev_state, link_state));
>>
>> +DEFINE_EVENT(ufshcd_template, ufshcd_wl_suspend,
>> + TP_PROTO(const char *dev_name, int err, s64 usecs,
>> + int dev_state, int link_state),
>> + TP_ARGS(dev_name, err, usecs, dev_state, link_state));
>> +
>> +DEFINE_EVENT(ufshcd_template, ufshcd_wl_resume,
>> + TP_PROTO(const char *dev_name, int err, s64 usecs,
>> + int dev_state, int link_state),
>> + TP_ARGS(dev_name, err, usecs, dev_state, link_state));
>> +
>> +DEFINE_EVENT(ufshcd_template, ufshcd_wl_runtime_suspend,
>> + TP_PROTO(const char *dev_name, int err, s64 usecs,
>> + int dev_state, int link_state),
>> + TP_ARGS(dev_name, err, usecs, dev_state, link_state));
>> +
>> +DEFINE_EVENT(ufshcd_template, ufshcd_wl_runtime_resume,
>> + TP_PROTO(const char *dev_name, int err, s64 usecs,
>> + int dev_state, int link_state),
>> + TP_ARGS(dev_name, err, usecs, dev_state, link_state));
>> +
>> TRACE_EVENT(ufshcd_command,
>> TP_PROTO(const char *dev_name, enum ufs_trace_str_t str_t,
>> unsigned int tag, u32 doorbell, int transfer_len, u32 intr,
>>
>

2021-02-24 12:44:58

by Asutosh Das (asd)

[permalink] [raw]
Subject: Re: [PATCH v4 1/2] scsi: ufs: Enable power management for wlun

On Tue, Feb 23 2021 at 12:23 -0800, Adrian Hunter wrote:
>On 23/02/21 1:04 am, Asutosh Das wrote:
>> During runtime-suspend of ufs host, the scsi devices are
>> already suspended and so are the queues associated with them.
>> But the ufs host sends SSU to wlun during its runtime-suspend.
>> During the process blk_queue_enter checks if the queue is not in
>> suspended state. If so, it waits for the queue to resume, and never
>> comes out of it.
>> The commit
>> (d55d15a33: scsi: block: Do not accept any requests while suspended)
>> adds the check if the queue is in suspended state in blk_queue_enter().
>>
>> Call trace:
>> __switch_to+0x174/0x2c4
>> __schedule+0x478/0x764
>> schedule+0x9c/0xe0
>> blk_queue_enter+0x158/0x228
>> blk_mq_alloc_request+0x40/0xa4
>> blk_get_request+0x2c/0x70
>> __scsi_execute+0x60/0x1c4
>> ufshcd_set_dev_pwr_mode+0x124/0x1e4
>> ufshcd_suspend+0x208/0x83c
>> ufshcd_runtime_suspend+0x40/0x154
>> ufshcd_pltfrm_runtime_suspend+0x14/0x20
>> pm_generic_runtime_suspend+0x28/0x3c
>> __rpm_callback+0x80/0x2a4
>> rpm_suspend+0x308/0x614
>> rpm_idle+0x158/0x228
>> pm_runtime_work+0x84/0xac
>> process_one_work+0x1f0/0x470
>> worker_thread+0x26c/0x4c8
>> kthread+0x13c/0x320
>> ret_from_fork+0x10/0x18
>>
>> Fix this by registering ufs device wlun as a scsi driver and
>> registering it for block runtime-pm. Also make this as a
>> supplier for all other luns. That way, this device wlun
>> suspends after all the consumers and resumes after
>> hba resumes.
>
>Hi
>
>I have a few more questions and comments.
>
>At a glance it looked to me like the SCSI bus will leave a SCSI device
>runtime suspended at system suspend, but UFS device spm_lvl and rpm_lvl need
>not be the same. Do you know how SCSI handles that case?
>
>>
>> Co-developed-by: Can Guo <[email protected]>
>> Signed-off-by: Can Guo <[email protected]>
>> Signed-off-by: Asutosh Das <[email protected]>
>> ---
>> drivers/scsi/ufs/ufshcd.c | 455 +++++++++++++++++++++++++++++++++++----------
>> drivers/scsi/ufs/ufshcd.h | 4 +
>> include/trace/events/ufs.h | 20 ++
>> 3 files changed, 382 insertions(+), 97 deletions(-)
>>
>> diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c
>> index 45624c7..1d8044a 100644
>> --- a/drivers/scsi/ufs/ufshcd.c
>> +++ b/drivers/scsi/ufs/ufshcd.c
>> @@ -16,6 +16,7 @@
>> #include <linux/bitfield.h>
>> #include <linux/blk-pm.h>
>> #include <linux/blkdev.h>
>> +#include <scsi/scsi_driver.h>
>> #include "ufshcd.h"
>> #include "ufs_quirks.h"
>> #include "unipro.h"
>> @@ -251,6 +252,11 @@ static int ufshcd_wb_toggle_flush_during_h8(struct ufs_hba *hba, bool set);
>> static inline int ufshcd_wb_toggle_flush(struct ufs_hba *hba, bool enable);
>> static void ufshcd_hba_vreg_set_lpm(struct ufs_hba *hba);
>> static void ufshcd_hba_vreg_set_hpm(struct ufs_hba *hba);
>> +static int ufshcd_wl_runtime_suspend(struct device *dev);
>> +static int ufshcd_wl_runtime_resume(struct device *dev);
>> +static int ufshcd_wl_suspend(struct device *dev);
>> +static int ufshcd_wl_resume(struct device *dev);
>> +static void ufshcd_wl_shutdown(struct device *dev);
>>
>> static inline bool ufshcd_valid_tag(struct ufs_hba *hba, int tag)
>> {
>> @@ -1556,7 +1562,7 @@ static ssize_t ufshcd_clkscale_enable_store(struct device *dev,
>> if (value == hba->clk_scaling.is_enabled)
>> goto out;
>>
>> - pm_runtime_get_sync(hba->dev);
>> + scsi_autopm_get_device(hba->sdev_ufs_device);
>> ufshcd_hold(hba, false);
>>
>> hba->clk_scaling.is_enabled = value;
>> @@ -1572,7 +1578,7 @@ static ssize_t ufshcd_clkscale_enable_store(struct device *dev,
>> }
>>
>> ufshcd_release(hba);
>> - pm_runtime_put_sync(hba->dev);
>> + scsi_autopm_put_device(hba->sdev_ufs_device);
>> out:
>> up(&hba->host_sem);
>> return err ? err : count;
>> @@ -2572,6 +2578,17 @@ static inline u16 ufshcd_upiu_wlun_to_scsi_wlun(u8 upiu_wlun_id)
>> return (upiu_wlun_id & ~UFS_UPIU_WLUN_ID) | SCSI_W_LUN_BASE;
>> }
>>
>> +static inline bool is_rpmb_wlun(struct scsi_device *sdev)
>> +{
>> + return (sdev->lun == ufshcd_upiu_wlun_to_scsi_wlun(UFS_UPIU_RPMB_WLUN));
>> +}
>> +
>> +static inline bool is_device_wlun(struct scsi_device *sdev)
>> +{
>> + return (sdev->lun ==
>> + ufshcd_upiu_wlun_to_scsi_wlun(UFS_UPIU_UFS_DEVICE_WLUN));
>> +}
>> +
>> static void ufshcd_init_lrb(struct ufs_hba *hba, struct ufshcd_lrb *lrb, int i)
>> {
>> struct utp_transfer_cmd_desc *cmd_descp = hba->ucdl_base_addr;
>> @@ -4808,6 +4825,41 @@ static inline void ufshcd_get_lu_power_on_wp_status(struct ufs_hba *hba,
>> }
>>
>> /**
>> + * ufshcd_setup_links - associate link b/w device wlun and other luns
>> + * @sdev: pointer to SCSI device
>> + * @hba: pointer to ufs hba
>> + */
>> +static void ufshcd_setup_links(struct ufs_hba *hba, struct scsi_device *sdev)
>> +{
>> + struct device_link *link;
>> +
>> + /*
>> + * device wlun is the supplier & rest of the luns are consumers
>> + * This ensures that device wlun suspends after all other luns.
>> + */
>> + if (hba->sdev_ufs_device) {
>> + link = device_link_add(&sdev->sdev_gendev,
>> + &hba->sdev_ufs_device->sdev_gendev,
>> + DL_FLAG_PM_RUNTIME);
>> + if (!link) {
>> + dev_err(&sdev->sdev_gendev, "Failed establishing link - %s\n",
>> + dev_name(&hba->sdev_ufs_device->sdev_gendev));
>> + return;
>> + }
>> + hba->luns_avail--;
>> + /* Ignore REPORT_LUN wlun probing */
>> + if (hba->luns_avail != 1)
>> + return;
>> +
>> + pm_runtime_put_noidle(&hba->sdev_ufs_device->sdev_gendev);
>> + pm_runtime_mark_last_busy(&hba->sdev_ufs_device->sdev_gendev);
>> + } else {
>> + /* device wlun is probed */
>> + hba->luns_avail--;
>> + }
>> +}
>> +
>> +/**
>> * ufshcd_slave_alloc - handle initial SCSI device configurations
>> * @sdev: pointer to SCSI device
>> *
>> @@ -4838,6 +4890,8 @@ static int ufshcd_slave_alloc(struct scsi_device *sdev)
>>
>> ufshcd_get_lu_power_on_wp_status(hba, sdev);
>>
>> + ufshcd_setup_links(hba, sdev);
>> +
>> return 0;
>> }
>>
>> @@ -4984,16 +5038,9 @@ ufshcd_transfer_rsp_status(struct ufs_hba *hba, struct ufshcd_lrb *lrbp)
>> * solution could be to abort the system suspend if
>> * UFS device needs urgent BKOPs.
>> */
>> - if (!hba->pm_op_in_progress &&
>> - ufshcd_is_exception_event(lrbp->ucd_rsp_ptr) &&
>> - schedule_work(&hba->eeh_work)) {
>> - /*
>> - * Prevent suspend once eeh_work is scheduled
>> - * to avoid deadlock between ufshcd_suspend
>> - * and exception event handler.
>> - */
>> - pm_runtime_get_noresume(hba->dev);
>> - }
>> + if (ufshcd_is_exception_event(lrbp->ucd_rsp_ptr))
>> + /* Flushed in suspend */
>> + schedule_work(&hba->eeh_work);
>> break;
>> case UPIU_TRANSACTION_REJECT_UPIU:
>> /* TODO: handle Reject UPIU Response */
>> @@ -5589,8 +5636,8 @@ static void ufshcd_rpm_dev_flush_recheck_work(struct work_struct *work)
>> * after a certain delay to recheck the threshold by next runtime
>> * suspend.
>> */
>> - pm_runtime_get_sync(hba->dev);
>> - pm_runtime_put_sync(hba->dev);
>> + scsi_autopm_get_device(hba->sdev_ufs_device);
>> + scsi_autopm_put_device(hba->sdev_ufs_device);
>> }
>>
>> /**
>> @@ -5607,7 +5654,6 @@ static void ufshcd_exception_event_handler(struct work_struct *work)
>> u32 status = 0;
>> hba = container_of(work, struct ufs_hba, eeh_work);
>>
>> - pm_runtime_get_sync(hba->dev);
>> ufshcd_scsi_block_requests(hba);
>> err = ufshcd_get_ee_status(hba, &status);
>> if (err) {
>> @@ -5623,14 +5669,6 @@ static void ufshcd_exception_event_handler(struct work_struct *work)
>>
>> out:
>> ufshcd_scsi_unblock_requests(hba);
>> - /*
>> - * pm_runtime_get_noresume is called while scheduling
>> - * eeh_work to avoid suspend racing with exception work.
>> - * Hence decrement usage counter using pm_runtime_put_noidle
>> - * to allow suspend on completion of exception event handler.
>> - */
>> - pm_runtime_put_noidle(hba->dev);
>> - pm_runtime_put(hba->dev);
>> return;
>> }
>>
>> @@ -7254,6 +7292,15 @@ static int ufshcd_scsi_add_wlus(struct ufs_hba *hba)
>> goto out;
>> }
>> ufshcd_blk_pm_runtime_init(hba->sdev_ufs_device);
>> + /*
>> + * A pm_runtime_put_sync is invoked when this device enables blk_pm_runtime
>> + * & would suspend the device-wlun upon timer expiry.
>> + * But suspending device wlun _may_ put the ufs device in the pre-defined
>> + * low power mode (SSU <rpm_lvl>). Probing of other luns may fail then.
>> + * Don't allow this suspend until all the luns have been probed.
>> + * See pm_runtime_mark_last_busy in ufshcd_setup_links.
>> + */
>> + pm_runtime_get_noresume(&hba->sdev_ufs_device->sdev_gendev);
>> scsi_device_put(hba->sdev_ufs_device);
>>
>> hba->sdev_rpmb = __scsi_add_device(hba->host, 0, 0,
>> @@ -7417,6 +7464,9 @@ static int ufs_get_device_desc(struct ufs_hba *hba)
>> goto out;
>> }
>>
>> + hba->luns_avail = desc_buf[DEVICE_DESC_PARAM_NUM_LU] +
>> + desc_buf[DEVICE_DESC_PARAM_NUM_WLU];
>> +
>> ufs_fixup_device_setup(hba);
>>
>> ufshcd_wb_probe(hba, desc_buf);
>> @@ -7747,6 +7797,54 @@ static int ufshcd_device_params_init(struct ufs_hba *hba)
>> return ret;
>> }
>>
>> +static int ufshcd_wl_probe(struct device *dev)
>> +{
>> + struct scsi_device *sdev = to_scsi_device(dev);
>> +
>> + /*
>> + * Use this driver for both device wlun and rpmb lun
>> + * It's used to send uac to rpmb wlun and ssu to device wlun
>> + */
>> + if (sdev && (is_rpmb_wlun(sdev) || is_device_wlun(sdev)))
>> + return 0;
>> + return -ENODEV;
>> +}
>> +
>> +static int ufshcd_wl_remove(struct device *dev)
>> +{
>> + struct scsi_device *sdev = to_scsi_device(dev);
>> +
>> + scsi_remove_device(sdev);
>
>The 'probe' did not do the __scsi_add_device(), so does the 'remove' need to
>do scsi_remove_device()?
>

No, I will remove 'scsi_remove_device()'.

>> + return 0;
>> +}
>> +
>> +static const struct dev_pm_ops ufshcd_wl_pm_ops = {
>> + SET_RUNTIME_PM_OPS(ufshcd_wl_runtime_suspend, ufshcd_wl_runtime_resume, NULL)
>> + SET_SYSTEM_SLEEP_PM_OPS(ufshcd_wl_suspend, ufshcd_wl_resume)
>
>.poweroff needs to set the power mode to PowerDown irrespective of spm_lvl,
>so not exactly the same as ufshcd_wl_suspend.
>
Not exactly same - yes. But it passes UFS_SHUTDOWN_PM as argument which would
put the ufs device in power-off mode.

>Also, ufshcd_pci_poweroff() in ufshcd-pci.c should be removed since it won't
>work anymore.
>
I see that this function doesn't use UFS_SHUTDOWN_PM.
I'll remove this function.

>> +};
>> +
>> +/**
>> + * ufs_wlun - describes 2 wluns:
>> + * ufs-device wlun - used to send pm commands
>> + * and rpmb wlun - for rpmb commands
>> + * All luns including rpmb are consumers of ufs-device wlun.
>> + *
>> + * Currently, no sd driver is present for wluns.
>> + * Hence the no specific pm operations are performed.
>> + * With ufs design, SSU should be sent to ufs-device wlun.
>> + * Hence register a scsi driver for ufs wluns only.
>> + */
>> +static struct scsi_driver ufs_wlun = {
>> + .gendrv = {
>> + .name = "ufs_device_wlun",
>> + .owner = THIS_MODULE,
>> + .probe = ufshcd_wl_probe,
>> + .remove = ufshcd_wl_remove,
>> + .pm = &ufshcd_wl_pm_ops,
>> + .shutdown = ufshcd_wl_shutdown,
>> + },
>> +};
>
>WRT to RPMB, aren't all the callbacks optional, so that would leave only
>something like below. Would that work?
>
>#define wlun_dev_to_hba(dv) shost_priv(to_scsi_device(dv)->host)
>
>static int ufs_rpmb_probe(struct device *dev)
>{
> return is_rpmb_wlun(to_scsi_device(dev)) ? 0 : -ENODEV;
>}
>
>static int ufs_rpmb_runtime_resume(struct device *dev)
>{
> struct ufs_hba *hba = wlun_dev_to_hba(dev);
>
> if (hba->sdev_rpmb)
> return ufshcd_clear_ua_wlun(hba, UFS_UPIU_RPMB_WLUN);
> return 0;
>}
>
>static int ufs_rpmb_resume(struct device *dev)
>{
> struct ufs_hba *hba = wlun_dev_to_hba(dev);
>
> if (hba->sdev_rpmb && !pm_runtime_suspended(dev))
> return ufshcd_clear_ua_wlun(hba, UFS_UPIU_RPMB_WLUN);
> return 0;
>}
>
>static const struct dev_pm_ops ufs_rpmb_pm_ops = {
> SET_RUNTIME_PM_OPS(NULL, ufs_rpmb_runtime_resume, NULL)
> SET_SYSTEM_SLEEP_PM_OPS(NULL, ufs_rpmb_resume)
>};
>
>static struct scsi_driver ufs_rpmb_template = {
> .gendrv = {
> .name = "ufs_rpmb_wlun",
> .owner = THIS_MODULE,
> .probe = ufs_rpmb_probe,
> .pm = &ufs_rpmb_pm_ops,
> },
>};
>
>
Thanks. I'll shamelessly copy this code :)

>> +
>> /**
>> * ufshcd_add_lus - probe and add UFS logical units
>> * @hba: per-adapter instance
>> @@ -7755,6 +7853,12 @@ static int ufshcd_add_lus(struct ufs_hba *hba)
>> {
>> int ret;
>>
>> + ret = scsi_register_driver(&ufs_wlun.gendrv);
>
>That will attempt to register the driver every time a UFS host controller is
>added (e.g. what happens if you unbind and re-bind the host controller).
>Probably needs to be in ufshcd_core_init() with corresponding unregister in
>ufshcd_core_exit()

I see that when I bind/unbind the driver, it invokes probe and all of the init
again. I'll add scsi_unregister_driver() in ufshcd_remove().

>
>> + if (ret) {
>> + dev_err(hba->dev, "Register device wlun driver failed: %d\n",
>> + ret);
>> + return ret;
>> + }
>> /* Add required well known logical units to scsi mid layer */
>> ret = ufshcd_scsi_add_wlus(hba);
>> if (ret)
>> @@ -7964,6 +8068,7 @@ static void ufshcd_async_scan(void *data, async_cookie_t cookie)
>> pm_runtime_put_sync(hba->dev);
>> ufshcd_hba_exit(hba);
>> }
>> + hba->init_done = true;
>> }
>>
>> static const struct attribute_group *ufshcd_driver_groups[] = {
>> @@ -8475,7 +8580,8 @@ static int ufshcd_set_dev_pwr_mode(struct ufs_hba *hba,
>> * handling context.
>> */
>> hba->host->eh_noresume = 1;
>> - ufshcd_clear_ua_wluns(hba);
>> + if (hba->wlun_dev_clr_ua)
>> + ufshcd_clear_ua_wlun(hba, UFS_UPIU_UFS_DEVICE_WLUN);
>>
>> cmd[4] = pwr_mode << 4;
>>
>> @@ -8650,31 +8756,19 @@ static void ufshcd_hba_vreg_set_hpm(struct ufs_hba *hba)
>> ufshcd_setup_hba_vreg(hba, true);
>> }
>>
>> -/**
>> - * ufshcd_suspend - helper function for suspend operations
>> - * @hba: per adapter instance
>> - * @pm_op: desired low power operation type
>> - *
>> - * This function will try to put the UFS device and link into low power
>> - * mode based on the "rpm_lvl" (Runtime PM level) or "spm_lvl"
>> - * (System PM level).
>> - *
>> - * If this function is called during shutdown, it will make sure that
>> - * both UFS device and UFS link is powered off.
>> - *
>> - * NOTE: UFS device & link must be active before we enter in this function.
>> - *
>> - * Returns 0 for success and non-zero for failure
>> - */
>> -static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>> +static int __ufshcd_wl_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>> {
>> int ret = 0;
>> - int check_for_bkops;
>> enum ufs_pm_level pm_lvl;
>> enum ufs_dev_pwr_mode req_dev_pwr_mode;
>> enum uic_link_state req_link_state;
>>
>> - hba->pm_op_in_progress = 1;
>> + /*
>> + * Is invoked when the device wlun is added to sysfs
>> + * But by then hba->sdev_ufs_device may not be initialized.
>> + */
>> + if (!hba->init_done)
>> + return 0;
>> if (!ufshcd_is_shutdown_pm(pm_op)) {
>> pm_lvl = ufshcd_is_runtime_pm(pm_op) ?
>> hba->rpm_lvl : hba->spm_lvl;
>> @@ -8690,24 +8784,23 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>> * just gate the clocks.
>> */
>> ufshcd_hold(hba, false);
>> - hba->clk_gating.is_suspended = true;
>>
>> if (ufshcd_is_clkscaling_supported(hba))
>> ufshcd_clk_scaling_suspend(hba, true);
>>
>> if (req_dev_pwr_mode == UFS_ACTIVE_PWR_MODE &&
>> req_link_state == UIC_LINK_ACTIVE_STATE) {
>> - goto disable_clks;
>> + goto enable_scaling;
>> }
>>
>> if ((req_dev_pwr_mode == hba->curr_dev_pwr_mode) &&
>> (req_link_state == hba->uic_link_state))
>> - goto enable_gating;
>> + goto enable_scaling;
>>
>> /* UFS device & link must be active before we enter in this function */
>> if (!ufshcd_is_ufs_dev_active(hba) || !ufshcd_is_link_active(hba)) {
>> ret = -EINVAL;
>> - goto enable_gating;
>> + goto enable_scaling;
>> }
>>
>> if (ufshcd_is_runtime_pm(pm_op)) {
>> @@ -8719,7 +8812,7 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>> */
>> ret = ufshcd_urgent_bkops(hba);
>> if (ret)
>> - goto enable_gating;
>> + goto enable_scaling;
>> } else {
>> /* make sure that auto bkops is disabled */
>> ufshcd_disable_auto_bkops(hba);
>> @@ -8746,10 +8839,224 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>>
>> if (!hba->dev_info.b_rpm_dev_flush_capable) {
>> ret = ufshcd_set_dev_pwr_mode(hba, req_dev_pwr_mode);
>> - if (ret)
>> - goto enable_gating;
>> + if (!ret)
>> + goto out;
>> }
>> }
>> +enable_scaling:
>> + if (ufshcd_is_clkscaling_supported(hba))
>> + ufshcd_clk_scaling_suspend(hba, false);
>> +
>> + hba->dev_info.b_rpm_dev_flush_capable = false;
>> +out:
>> + if (hba->dev_info.b_rpm_dev_flush_capable) {
>> + schedule_delayed_work(&hba->rpm_dev_flush_recheck_work,
>> + msecs_to_jiffies(RPM_DEV_FLUSH_RECHECK_WORK_DELAY_MS));
>> + }
>> + if (ret)
>> + ufshcd_update_evt_hist(hba, UFS_EVT_WL_SUSP_ERR, (u32)ret);
>> + ufshcd_release(hba);
>> + return ret;
>> +}
>> +
>> +static int __ufshcd_wl_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>> +{
>> + int ret = 0;
>> +
>> + if (!hba->init_done)
>> + return 0;
>> +
>> + ufshcd_hold(hba, false);
>> + if (ufshcd_is_ufs_dev_deepsleep(hba)) {
>> + /* The device is in DeepSleep but hba was not suspended */
>> + ret = ufshcd_reset_and_restore(hba);
>> + if (ret)
>> + goto out;
>> + }
>> +
>> + if (!ufshcd_is_ufs_dev_active(hba)) {
>> + ret = ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE);
>> + if (ret)
>> + goto out;
>> + }
>> +
>> + if (ufshcd_keep_autobkops_enabled_except_suspend(hba))
>> + ufshcd_enable_auto_bkops(hba);
>> + else
>> + /*
>> + * If BKOPs operations are urgently needed at this moment then
>> + * keep auto-bkops enabled or else disable it.
>> + */
>> + ufshcd_urgent_bkops(hba);
>> +
>> + if (hba->clk_scaling.is_allowed)
>> + ufshcd_resume_clkscaling(hba);
>> +
>> + if (hba->dev_info.b_rpm_dev_flush_capable) {
>> + hba->dev_info.b_rpm_dev_flush_capable = false;
>> + cancel_delayed_work(&hba->rpm_dev_flush_recheck_work);
>> + }
>> +
>> +out:
>> + if (ret)
>> + ufshcd_update_evt_hist(hba, UFS_EVT_WL_RES_ERR, (u32)ret);
>> + ufshcd_release(hba);
>> + return ret;
>> +}
>> +
>> +static int ufshcd_wl_runtime_suspend(struct device *dev)
>> +{
>> + struct scsi_device *sdev = to_scsi_device(dev);
>> + struct ufs_hba *hba;
>> + int ret;
>> + ktime_t start = ktime_get();
>> +
>> + hba = shost_priv(sdev->host);
>> +
>> + if (is_rpmb_wlun(sdev))
>> + return 0;
>> + ret = __ufshcd_wl_suspend(hba, UFS_RUNTIME_PM);
>> + if (ret)
>> + dev_err(&sdev->sdev_gendev, "%s failed: %d\n", __func__, ret);
>> +
>> + trace_ufshcd_wl_runtime_suspend(dev_name(dev), ret,
>> + ktime_to_us(ktime_sub(ktime_get(), start)),
>> + hba->curr_dev_pwr_mode, hba->uic_link_state);
>> +
>> + return ret;
>> +}
>> +
>> +static int ufshcd_wl_runtime_resume(struct device *dev)
>> +{
>> + struct scsi_device *sdev = to_scsi_device(dev);
>> + struct ufs_hba *hba;
>> + int ret;
>> + ktime_t start = ktime_get();
>> +
>> + hba = shost_priv(sdev->host);
>> +
>> + if (is_rpmb_wlun(sdev)) {
>> + if (hba->sdev_rpmb)
>> + return ufshcd_clear_ua_wlun(hba, UFS_UPIU_RPMB_WLUN);
>> + return 0;
>> + }
>> +
>> + ret = __ufshcd_wl_resume(hba, UFS_RUNTIME_PM);
>> + if (ret)
>> + dev_err(&sdev->sdev_gendev, "%s failed: %d\n", __func__, ret);
>> +
>> + trace_ufshcd_wl_runtime_resume(dev_name(dev), ret,
>> + ktime_to_us(ktime_sub(ktime_get(), start)),
>> + hba->curr_dev_pwr_mode, hba->uic_link_state);
>> +
>> + return ret;
>> +}
>> +
>> +static int ufshcd_wl_suspend(struct device *dev)
>> +{
>> + struct scsi_device *sdev = to_scsi_device(dev);
>> + struct ufs_hba *hba;
>> + int ret;
>> + ktime_t start = ktime_get();
>> +
>> + if (is_rpmb_wlun(sdev))
>> + return 0;
>> + hba = shost_priv(sdev->host);
>> + ret = __ufshcd_wl_suspend(hba, UFS_SYSTEM_PM);
>> + if (ret)
>> + dev_err(&sdev->sdev_gendev, "%s failed: %d\n", __func__, ret);
>> +
>> + trace_ufshcd_wl_suspend(dev_name(dev), ret,
>> + ktime_to_us(ktime_sub(ktime_get(), start)),
>> + hba->curr_dev_pwr_mode, hba->uic_link_state);
>> +
>> + return ret;
>> +}
>> +
>> +static int ufshcd_wl_resume(struct device *dev)
>> +{
>> + struct scsi_device *sdev = to_scsi_device(dev);
>> + struct ufs_hba *hba;
>> + int ret;
>> + ktime_t start = ktime_get();
>> +
>> + if (pm_runtime_suspended(dev))
>> + return 0;
>> + hba = shost_priv(sdev->host);
>> +
>> + if (is_rpmb_wlun(sdev)) {
>> + if (hba->sdev_rpmb)
>> + return ufshcd_clear_ua_wlun(hba, UFS_UPIU_RPMB_WLUN);
>> + return 0;
>> + }
>> +
>> + ret = __ufshcd_wl_resume(hba, UFS_SYSTEM_PM);
>> + if (ret)
>> + dev_err(&sdev->sdev_gendev, "%s failed: %d\n", __func__, ret);
>> +
>> + trace_ufshcd_wl_resume(dev_name(dev), ret,
>> + ktime_to_us(ktime_sub(ktime_get(), start)),
>> + hba->curr_dev_pwr_mode, hba->uic_link_state);
>> +
>> + return ret;
>> +}
>> +
>> +static void ufshcd_wl_shutdown(struct device *dev)
>> +{
>> + struct scsi_device *sdev = to_scsi_device(dev);
>> + struct ufs_hba *hba;
>> +
>> + if (is_rpmb_wlun(sdev))
>> + return;
>> + hba = shost_priv(sdev->host);
>> + scsi_device_quiesce(sdev);
>> + shost_for_each_device(sdev, hba->host) {
>> + if (sdev == hba->sdev_ufs_device)
>> + continue;
>> + scsi_remove_device(sdev);
>
>Shutdown needs to quiesce devices. Removing devices is not advisable
>because the upper layers cannot respond at this point, causing deadlocks.
>Is scsi_remove_device() really needed here?
>
Thanks. I will change this.

>
>> + }
>> + __ufshcd_wl_suspend(hba, UFS_SHUTDOWN_PM);
>> +}
>> +
>> +/**
>> + * ufshcd_suspend - helper function for suspend operations
>> + * @hba: per adapter instance
>> + * @pm_op: desired low power operation type
>> + *
>> + * This function will try to put the UFS link into low power
>> + * mode based on the "rpm_lvl" (Runtime PM level) or "spm_lvl"
>> + * (System PM level).
>> + *
>> + * If this function is called during shutdown, it will make sure that
>> + * link is powered off.
>> + *
>> + * Returns 0 for success and non-zero for failure
>> + */
>> +static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>> +{
>> + int ret = 0;
>> + int check_for_bkops;
>> + enum ufs_pm_level pm_lvl;
>> + enum ufs_dev_pwr_mode req_dev_pwr_mode;
>> + enum uic_link_state req_link_state;
>> +
>> + hba->pm_op_in_progress = 1;
>> + if (!ufshcd_is_shutdown_pm(pm_op)) {
>> + pm_lvl = ufshcd_is_runtime_pm(pm_op) ?
>> + hba->rpm_lvl : hba->spm_lvl;
>> + req_dev_pwr_mode = ufs_get_pm_lvl_to_dev_pwr_mode(pm_lvl);
>> + req_link_state = ufs_get_pm_lvl_to_link_pwr_state(pm_lvl);
>> + } else {
>> + req_dev_pwr_mode = UFS_POWERDOWN_PWR_MODE;
>> + req_link_state = UIC_LINK_OFF_STATE;
>> + }
>> +
>> + /*
>> + * If we can't transition into any of the low power modes
>> + * just gate the clocks.
>> + */
>> + ufshcd_hold(hba, false);
>> + hba->clk_gating.is_suspended = true;
>>
>> /*
>> * In the case of DeepSleep, the device is expected to remain powered
>> @@ -8758,9 +9065,8 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>> check_for_bkops = !ufshcd_is_ufs_dev_deepsleep(hba);
>> ret = ufshcd_link_state_transition(hba, req_link_state, check_for_bkops);
>> if (ret)
>> - goto set_dev_active;
>> + goto enable_gating;
>>
>> -disable_clks:
>> /*
>> * Call vendor specific suspend callback. As these callbacks may access
>> * vendor specific host controller register space call them before the
>> @@ -8790,7 +9096,6 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>> goto out;
>>
>> set_link_active:
>> - ufshcd_vreg_set_hpm(hba);
>> /*
>> * Device hardware reset is required to exit DeepSleep. Also, for
>> * DeepSleep, the link is off so host reset and restore will be done
>> @@ -8804,28 +9109,15 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>> ufshcd_set_link_active(hba);
>> else if (ufshcd_is_link_off(hba))
>> ufshcd_host_reset_and_restore(hba);
>> -set_dev_active:
>> /* Can also get here needing to exit DeepSleep */
>> if (ufshcd_is_ufs_dev_deepsleep(hba)) {
>> ufshcd_device_reset(hba);
>> ufshcd_host_reset_and_restore(hba);
>> }
>> - if (!ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE))
>> - ufshcd_disable_auto_bkops(hba);
>> enable_gating:
>> - if (ufshcd_is_clkscaling_supported(hba))
>> - ufshcd_clk_scaling_suspend(hba, false);
>> -
>> hba->clk_gating.is_suspended = false;
>> - hba->dev_info.b_rpm_dev_flush_capable = false;
>> - ufshcd_clear_ua_wluns(hba);
>> ufshcd_release(hba);
>> out:
>> - if (hba->dev_info.b_rpm_dev_flush_capable) {
>> - schedule_delayed_work(&hba->rpm_dev_flush_recheck_work,
>> - msecs_to_jiffies(RPM_DEV_FLUSH_RECHECK_WORK_DELAY_MS));
>> - }
>> -
>> hba->pm_op_in_progress = 0;
>>
>> if (ret)
>> @@ -8846,10 +9138,8 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>> static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>> {
>> int ret;
>> - enum uic_link_state old_link_state;
>>
>> hba->pm_op_in_progress = 1;
>> - old_link_state = hba->uic_link_state;
>>
>> ufshcd_hba_vreg_set_hpm(hba);
>> ret = ufshcd_vreg_set_hpm(hba);
>> @@ -8901,43 +9191,14 @@ static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>> goto vendor_suspend;
>> }
>>
>> - if (!ufshcd_is_ufs_dev_active(hba)) {
>> - ret = ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE);
>> - if (ret)
>> - goto set_old_link_state;
>> - }
>> -
>> - if (ufshcd_keep_autobkops_enabled_except_suspend(hba))
>> - ufshcd_enable_auto_bkops(hba);
>> - else
>> - /*
>> - * If BKOPs operations are urgently needed at this moment then
>> - * keep auto-bkops enabled or else disable it.
>> - */
>> - ufshcd_urgent_bkops(hba);
>> -
>> - hba->clk_gating.is_suspended = false;
>> -
>> - if (ufshcd_is_clkscaling_supported(hba))
>> - ufshcd_clk_scaling_suspend(hba, false);
>> -
>> /* Enable Auto-Hibernate if configured */
>> ufshcd_auto_hibern8_enable(hba);
>>
>> - if (hba->dev_info.b_rpm_dev_flush_capable) {
>> - hba->dev_info.b_rpm_dev_flush_capable = false;
>> - cancel_delayed_work(&hba->rpm_dev_flush_recheck_work);
>> - }
>> -
>> - ufshcd_clear_ua_wluns(hba);
>> -
>> /* Schedule clock gating in case of no access to UFS device yet */
>> ufshcd_release(hba);
>>
>> goto out;
>>
>> -set_old_link_state:
>> - ufshcd_link_state_transition(hba, old_link_state, 0);
>> vendor_suspend:
>> ufshcd_vops_suspend(hba, pm_op);
>> disable_irq_and_vops_clks:
>> diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h
>> index ee61f82..7662387 100644
>> --- a/drivers/scsi/ufs/ufshcd.h
>> +++ b/drivers/scsi/ufs/ufshcd.h
>> @@ -72,6 +72,8 @@ enum ufs_event_type {
>> UFS_EVT_LINK_STARTUP_FAIL,
>> UFS_EVT_RESUME_ERR,
>> UFS_EVT_SUSPEND_ERR,
>> + UFS_EVT_WL_SUSP_ERR,
>> + UFS_EVT_WL_RES_ERR,
>>
>> /* abnormal events */
>> UFS_EVT_DEV_RESET,
>> @@ -841,6 +843,8 @@ struct ufs_hba {
>> #ifdef CONFIG_DEBUG_FS
>> struct dentry *debugfs_root;
>> #endif
>> + bool init_done;
>> + u32 luns_avail;
>> };
>>
>> /* Returns true if clocks can be gated. Otherwise false */
>> diff --git a/include/trace/events/ufs.h b/include/trace/events/ufs.h
>> index e151477..d9d233b 100644
>> --- a/include/trace/events/ufs.h
>> +++ b/include/trace/events/ufs.h
>> @@ -246,6 +246,26 @@ DEFINE_EVENT(ufshcd_template, ufshcd_init,
>> int dev_state, int link_state),
>> TP_ARGS(dev_name, err, usecs, dev_state, link_state));
>>
>> +DEFINE_EVENT(ufshcd_template, ufshcd_wl_suspend,
>> + TP_PROTO(const char *dev_name, int err, s64 usecs,
>> + int dev_state, int link_state),
>> + TP_ARGS(dev_name, err, usecs, dev_state, link_state));
>> +
>> +DEFINE_EVENT(ufshcd_template, ufshcd_wl_resume,
>> + TP_PROTO(const char *dev_name, int err, s64 usecs,
>> + int dev_state, int link_state),
>> + TP_ARGS(dev_name, err, usecs, dev_state, link_state));
>> +
>> +DEFINE_EVENT(ufshcd_template, ufshcd_wl_runtime_suspend,
>> + TP_PROTO(const char *dev_name, int err, s64 usecs,
>> + int dev_state, int link_state),
>> + TP_ARGS(dev_name, err, usecs, dev_state, link_state));
>> +
>> +DEFINE_EVENT(ufshcd_template, ufshcd_wl_runtime_resume,
>> + TP_PROTO(const char *dev_name, int err, s64 usecs,
>> + int dev_state, int link_state),
>> + TP_ARGS(dev_name, err, usecs, dev_state, link_state));
>> +
>> TRACE_EVENT(ufshcd_command,
>> TP_PROTO(const char *dev_name, enum ufs_trace_str_t str_t,
>> unsigned int tag, u32 doorbell, int transfer_len, u32 intr,
>>
>