On 8/04/21 5:49 pm, 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 (START_STOP_UNIT) 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 <cang@xxxxxxxxxxxxxx> > Signed-off-by: Can Guo <cang@xxxxxxxxxxxxxx> > Signed-off-by: Asutosh Das <asutoshd@xxxxxxxxxxxxxx> > --- <SNIP> > @@ -5815,14 +5857,14 @@ static void ufshcd_recover_pm_error(struct ufs_hba *hba) > > hba->is_sys_suspended = false; > /* > - * Set RPM status of hba device to RPM_ACTIVE, > + * Set RPM status of wlun device to RPM_ACTIVE, > * this also clears its runtime error. > */ > - ret = pm_runtime_set_active(hba->dev); > + ret = pm_runtime_set_active(&hba->sdev_ufs_device->sdev_gendev); It may be that it was hba->dev with a runtime error. So, also need here: if (ret) ret = pm_runtime_set_active(hba->dev); > @@ -8671,7 +8701,7 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op) > enum ufs_dev_pwr_mode req_dev_pwr_mode; > enum uic_link_state req_link_state; > > - hba->pm_op_in_progress = 1; > + hba->pm_op_in_progress = true; > if (!ufshcd_is_shutdown_pm(pm_op)) { > pm_lvl = ufshcd_is_runtime_pm(pm_op) ? > hba->rpm_lvl : hba->spm_lvl; > @@ -8694,17 +8724,17 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op) > > if (req_dev_pwr_mode == UFS_ACTIVE_PWR_MODE && > req_link_state == UIC_LINK_ACTIVE_STATE) { > - goto disable_clks; > + goto enable_scaling; Previously "goto disable_clks" would go to the vendor callback ufshcd_vops_suspend(), but now it is skipped. > } > > 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; > } <SNIP> > +/** > + * ufshcd_suspend - helper function for suspend operations > + * @hba: per adapter instance > + * > + * This function will put disable irqs, turn off clocks > + * and set vreg and hba-vreg in lpm mode. > + * Also check the description of __ufshcd_wl_suspend(). > + */ > +static void ufshcd_suspend(struct ufs_hba *hba) > +{ > + hba->pm_op_in_progress = 1; Seems like pm_op_in_progress wouldn't be needed in this function, nor ufshcd_resume(). You could probably simplify the callers slighly by putting the "if (!hbs->is_powered) return" check here and in ufshcd_resume(). > + > + /* > + * Disable the host irq as host controller as there won't be any > + * host controller transaction expected till resume. > + */ > ufshcd_disable_irq(hba); > ufshcd_setup_clocks(hba, false); > if (ufshcd_is_clkgating_allowed(hba)) { > @@ -8948,6 +9054,43 @@ static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op) > trace_ufshcd_clk_gating(dev_name(hba->dev), > hba->clk_gating.state); > } > + > + ufshcd_vreg_set_lpm(hba); > + /* Put the host controller in low power mode if possible */ > + ufshcd_hba_vreg_set_lpm(hba); > + hba->pm_op_in_progress = 0; > +} > + > +/** > + * ufshcd_resume - helper function for resume operations > + * @hba: per adapter instance > + * > + * This function basically turns on the regulators, clocks and > + * irqs of the hba. > + * Also check the description of __ufshcd_wl_resume(). > + * > + * Returns 0 for success and non-zero for failure > + */ > +static int ufshcd_resume(struct ufs_hba *hba) > +{ > + int ret; > + > + hba->pm_op_in_progress = 1; > + > + ufshcd_hba_vreg_set_hpm(hba); > + ret = ufshcd_vreg_set_hpm(hba); > + if (ret) > + goto out; > + > + /* Make sure clocks are enabled before accessing controller */ > + ret = ufshcd_setup_clocks(hba, true); > + if (ret) > + goto disable_vreg; > + > + /* enable the host irq as host controller would be active soon */ > + ufshcd_enable_irq(hba); > + goto out; > + > disable_vreg: > ufshcd_vreg_set_lpm(hba); > out: > @@ -8962,6 +9105,7 @@ static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op) > * @hba: per adapter instance > * > * Check the description of ufshcd_suspend() function for more details. > + * Also check the description of __ufshcd_wl_suspend(). > * > * Returns 0 for success and non-zero for failure > */ > @@ -8982,21 +9126,7 @@ int ufshcd_system_suspend(struct ufs_hba *hba) > if ((ufs_get_pm_lvl_to_dev_pwr_mode(hba->spm_lvl) == > hba->curr_dev_pwr_mode) && > (ufs_get_pm_lvl_to_link_pwr_state(hba->spm_lvl) == > hba->uic_link_state) && > !hba->dev_info.b_rpm_dev_flush_capable) > goto out; The logic above can be removed. > > - if (pm_runtime_suspended(hba->dev)) { > - /* > - * UFS device and/or UFS link low power states during runtime > - * suspend seems to be different than what is expected during > - * system suspend. Hence runtime resume the devic & link and > - * let the system suspend low power states to take effect. > - * TODO: If resume takes longer time, we might have optimize > - * it in future by not resuming everything if possible. > - */ > - ret = ufshcd_runtime_resume(hba); > - if (ret) > - goto out; > - } System suspend/resume and runtime suspend/resume are identical for hba->dev, so the logic becomes: if (pm_runtime_suspended(hba->dev)) goto out; > - > - ret = ufshcd_suspend(hba, UFS_SYSTEM_PM); > + ufshcd_suspend(hba); > out: > trace_ufshcd_system_suspend(dev_name(hba->dev), ret, > ktime_to_us(ktime_sub(ktime_get(), start)), > @@ -9018,25 +9148,20 @@ EXPORT_SYMBOL(ufshcd_system_suspend); > > int ufshcd_system_resume(struct ufs_hba *hba) > { > - int ret = 0; > ktime_t start = ktime_get(); > > - if (!hba->is_powered || pm_runtime_suspended(hba->dev)) > - /* > - * Let the runtime resume take care of resuming > - * if runtime suspended. > - */ > + if (!hba->is_powered) System suspend/resume and runtime suspend/resume are identical for hba->dev, so the original logic is fine here. > goto out; > else > - ret = ufshcd_resume(hba, UFS_SYSTEM_PM); > + ufshcd_resume(hba); > out: > - trace_ufshcd_system_resume(dev_name(hba->dev), ret, > + trace_ufshcd_system_resume(dev_name(hba->dev), 0, > ktime_to_us(ktime_sub(ktime_get(), start)), > hba->curr_dev_pwr_mode, hba->uic_link_state); > - if (!ret) > - hba->is_sys_suspended = false; > + > + hba->is_sys_suspended = false; > up(&hba->host_sem); It seems likely that hba->host_sem down / up should be in ufshcd_wl_suspend() / ufshcd_wl_resume() respectively, instead of here. Ditto hba->is_sys_suspended > - return ret; > + return 0; > } > EXPORT_SYMBOL(ufshcd_system_resume); > > @@ -9045,23 +9170,23 @@ EXPORT_SYMBOL(ufshcd_system_resume); > * @hba: per adapter instance > * > * Check the description of ufshcd_suspend() function for more details. > + * Also check the description of __ufshcd_wl_suspend(). > * > * Returns 0 for success and non-zero for failure > */ > int ufshcd_runtime_suspend(struct ufs_hba *hba) > { > - int ret = 0; > ktime_t start = ktime_get(); > > if (!hba->is_powered) > goto out; > else > - ret = ufshcd_suspend(hba, UFS_RUNTIME_PM); > + ufshcd_suspend(hba); > out: > - trace_ufshcd_runtime_suspend(dev_name(hba->dev), ret, > + trace_ufshcd_runtime_suspend(dev_name(hba->dev), 0, > ktime_to_us(ktime_sub(ktime_get(), start)), > hba->curr_dev_pwr_mode, hba->uic_link_state); > - return ret; > + return 0; > } > EXPORT_SYMBOL(ufshcd_runtime_suspend); > > @@ -9069,37 +9194,25 @@ EXPORT_SYMBOL(ufshcd_runtime_suspend); > * ufshcd_runtime_resume - runtime resume routine > * @hba: per adapter instance > * > - * This function basically brings the UFS device, UniPro link and controller > + * This function basically brings controller > * to active state. Following operations are done in this function: > * > * 1. Turn on all the controller related clocks > - * 2. Bring the UniPro link out of Hibernate state > - * 3. If UFS device is in sleep state, turn ON VCC rail and bring the UFS device > - * to active state. > - * 4. If auto-bkops is enabled on the device, disable it. > - * > - * So following would be the possible power state after this function return > - * successfully: > - * S1: UFS device in Active state with VCC rail ON > - * UniPro link in Active state > - * All the UFS/UniPro controller clocks are ON > - * > - * Returns 0 for success and non-zero for failure > + * 2. Turn ON VCC rail > */ > int ufshcd_runtime_resume(struct ufs_hba *hba) > { > - int ret = 0; > ktime_t start = ktime_get(); > > if (!hba->is_powered) > goto out; > else > - ret = ufshcd_resume(hba, UFS_RUNTIME_PM); > + ufshcd_resume(hba); > out: > - trace_ufshcd_runtime_resume(dev_name(hba->dev), ret, > + trace_ufshcd_runtime_resume(dev_name(hba->dev), 0, > ktime_to_us(ktime_sub(ktime_get(), start)), > hba->curr_dev_pwr_mode, hba->uic_link_state); > - return ret; > + return 0; > } > EXPORT_SYMBOL(ufshcd_runtime_resume); > > @@ -9113,18 +9226,13 @@ EXPORT_SYMBOL(ufshcd_runtime_idle); > * ufshcd_shutdown - shutdown routine > * @hba: per adapter instance > * > - * This function would power off both UFS device and UFS link. > + * This function would turn off both UFS device and UFS hba > + * regulators. It would also disable clocks. > * > * Returns 0 always to allow force shutdown even in case of errors. > */ > int ufshcd_shutdown(struct ufs_hba *hba) > { > - int ret = 0; > - > - down(&hba->host_sem); > - hba->shutting_down = true; > - up(&hba->host_sem); > - > if (!hba->is_powered) > goto out; > > @@ -9133,10 +9241,8 @@ int ufshcd_shutdown(struct ufs_hba *hba) > > pm_runtime_get_sync(hba->dev); > > - ret = ufshcd_suspend(hba, UFS_SHUTDOWN_PM); > + ufshcd_suspend(hba); > out: > - if (ret) > - dev_err(hba->dev, "%s failed, err %d\n", __func__, ret); > hba->is_powered = false; > /* allow force shutdown even in case of errors */ > return 0; > @@ -9150,6 +9256,8 @@ EXPORT_SYMBOL(ufshcd_shutdown); > */ > void ufshcd_remove(struct ufs_hba *hba) > { > + if (hba->sdev_ufs_device) > + scsi_autopm_get_device(hba->sdev_ufs_device); > ufs_bsg_remove(hba); > ufs_sysfs_remove_nodes(hba->dev); > blk_cleanup_queue(hba->tmf_queue); > @@ -9453,15 +9561,175 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) > } > EXPORT_SYMBOL_GPL(ufshcd_init); > > +void ufshcd_resume_complete(struct device *dev) > +{ > + struct ufs_hba *hba = dev_get_drvdata(dev); > + > + pm_runtime_put_noidle(&hba->sdev_ufs_device->sdev_gendev); pm_runtime_put() instead of pm_runtime_put_noidle() would be more correct here. > +} > +EXPORT_SYMBOL_GPL(ufshcd_resume_complete); > + > +int ufshcd_suspend_prepare(struct device *dev) > +{ > + struct ufs_hba *hba = dev_get_drvdata(dev); > + > + /* > + * SCSI assumes that runtime-pm and system-pm for scsi drivers > + * are same. And it doesn't wake up the device for system-suspend > + * if it's runtime suspended. But ufs doesn't follow that. > + * The rpm-lvl and spm-lvl can be different in ufs. > + * Force it to honor system-suspend. > + */ > + scsi_autopm_get_device(hba->sdev_ufs_device); > + /* Refer ufshcd_resume_complete() */ > + pm_runtime_get_noresume(&hba->sdev_ufs_device->sdev_gendev); > + scsi_autopm_put_device(hba->sdev_ufs_device); This could simply be: pm_runtime_get_sync(&hba->sdev_ufs_device->sdev_gendev); or new wrapper. > + return 0; > +} > +EXPORT_SYMBOL_GPL(ufshcd_suspend_prepare); > + > +#ifdef CONFIG_PM_SLEEP > +static int ufshcd_wl_poweroff(struct device *dev) > +{ > + struct scsi_device *sdev = to_scsi_device(dev); > + struct ufs_hba *hba = shost_priv(sdev->host); > + > + __ufshcd_wl_suspend(hba, UFS_SHUTDOWN_PM); > + return 0; > +} > +#endif > + > +static int ufshcd_wl_probe(struct device *dev) > +{ > + struct scsi_device *sdev = to_scsi_device(dev); > + > + if (!is_device_wlun(sdev)) > + return -ENODEV; > + > + blk_pm_runtime_init(sdev->request_queue, dev); > + pm_runtime_set_autosuspend_delay(dev, 0); > + pm_runtime_allow(dev); > + > + return 0; > +} > + > +static int ufshcd_wl_remove(struct device *dev) > +{ > + pm_runtime_forbid(dev); > + return 0; > +} > + > +static const struct dev_pm_ops ufshcd_wl_pm_ops = { > +#ifdef CONFIG_PM_SLEEP > + .suspend = ufshcd_wl_suspend, > + .resume = ufshcd_wl_resume, > + .freeze = ufshcd_wl_suspend, > + .thaw = ufshcd_wl_resume, > + .poweroff = ufshcd_wl_poweroff, > + .restore = ufshcd_wl_resume, > +#endif > + SET_RUNTIME_PM_OPS(ufshcd_wl_runtime_suspend, ufshcd_wl_runtime_resume, NULL) > +}; > + > +/** > + * ufs_dev_wlun_template - describes ufs device wlun > + * ufs-device wlun - used to send pm commands > + * All luns 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_dev_wlun_template = { > + .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, > + }, > +}; > + > +static int ufshcd_rpmb_probe(struct device *dev) > +{ > + return is_rpmb_wlun(to_scsi_device(dev)) ? 0 : -ENODEV; > +} > + > +static inline int ufshcd_clear_rpmb_uac(struct ufs_hba *hba) > +{ > + int ret = 0; > + > + if (!hba->wlun_rpmb_clr_ua) > + return 0; > + ret = ufshcd_clear_ua_wlun(hba, UFS_UPIU_RPMB_WLUN); > + if (!ret) > + hba->wlun_rpmb_clr_ua = 0; > + return ret; > +} > + > +static int ufshcd_rpmb_runtime_resume(struct device *dev) > +{ > + struct ufs_hba *hba = wlun_dev_to_hba(dev); > + > + if (hba->sdev_rpmb) > + return ufshcd_clear_rpmb_uac(hba); > + return 0; > +} > + > +static int ufshcd_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_rpmb_uac(hba); > + return 0; > +} > + > +static const struct dev_pm_ops ufs_rpmb_pm_ops = { > + SET_RUNTIME_PM_OPS(NULL, ufshcd_rpmb_runtime_resume, NULL) > + SET_SYSTEM_SLEEP_PM_OPS(NULL, ufshcd_rpmb_resume) > +}; > + > +/** > + * Describes the ufs rpmb wlun. > + * Used only to send uac. > + */ > +static struct scsi_driver ufs_rpmb_wlun_template = { > + .gendrv = { > + .name = "ufs_rpmb_wlun", > + .owner = THIS_MODULE, > + .probe = ufshcd_rpmb_probe, > + .pm = &ufs_rpmb_pm_ops, > + }, > +}; > + > static int __init ufshcd_core_init(void) > { > + int ret; > + > ufs_debugfs_init(); > + > + ret = scsi_register_driver(&ufs_dev_wlun_template.gendrv); > + if (ret) { > + ufs_debugfs_exit(); > + return ret; > + } > + ret = scsi_register_driver(&ufs_rpmb_wlun_template.gendrv); > + if (ret) { > + ufs_debugfs_exit(); > + scsi_unregister_driver(&ufs_dev_wlun_template.gendrv); > + return ret; > + } > return 0; A separated error path is preferred i.e. ret = scsi_register_driver(&ufs_dev_wlun_template.gendrv); if (ret) goto debugfs_exit; ret = scsi_register_driver(&ufs_rpmb_wlun_template.gendrv); if (ret) goto unregister; return 0; unregister: scsi_unregister_driver(&ufs_dev_wlun_template.gendrv); debugfs_exit: ufs_debugfs_exit(); return ret; > } > > static void __exit ufshcd_core_exit(void) > { > ufs_debugfs_exit(); > + scsi_unregister_driver(&ufs_rpmb_wlun_template.gendrv); > + scsi_unregister_driver(&ufs_dev_wlun_template.gendrv); > } > > module_init(ufshcd_core_init); > diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h > index 5eb66a8..40436e3 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, > @@ -807,6 +809,7 @@ struct ufs_hba { > struct list_head clk_list_head; > > bool wlun_dev_clr_ua; > + bool wlun_rpmb_clr_ua; > > /* Number of requests aborts */ > int req_abort_count; > @@ -846,6 +849,7 @@ struct ufs_hba { > struct delayed_work debugfs_ee_work; > u32 debugfs_ee_rate_limit_ms; > #endif > + u32 luns_avail; > }; > > /* Returns true if clocks can be gated. Otherwise false */ > @@ -1105,6 +1109,8 @@ int ufshcd_exec_raw_upiu_cmd(struct ufs_hba *hba, > enum query_opcode desc_op); > > int ufshcd_wb_toggle(struct ufs_hba *hba, bool enable); > +int ufshcd_suspend_prepare(struct device *dev); > +void ufshcd_resume_complete(struct device *dev); > > /* Wrapper functions for safely calling variant operations */ > static inline const char *ufshcd_get_var_name(struct ufs_hba *hba) > diff --git a/include/trace/events/ufs.h b/include/trace/events/ufs.h > index 1cb6f1a..599739e 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, >