Since commit eef1d74bf88f ("PM: runtime: Only set runtime_error on suspend callback failures"), a failed W-LUN runtime resume leaves ufs_device_wlun in RPM_SUSPENDED with power.runtime_error == 0. If error recovery then powers up the host, ufshcd_vops_resume() is called twice in a row without an intervening ufshcd_vops_suspend(): 1. __ufshcd_wl_suspend() runs ufshcd_vops_suspend() at POST_CHANGE, and a failed W-LUN runtime resume rolls back through vendor_suspend: to keep the variant suspended while ufs_device_wlun stays RPM_SUSPENDED. 2. ufshcd_err_handling_prepare() sees ufs_device_wlun suspended and calls ufshcd_vops_resume(), while ufshcd_recover_pm_error() leaves ufs_device_wlun in RPM_SUSPENDED. 3. The next ufs_device_wlun runtime resume calls ufshcd_vops_resume() again with no ufshcd_vops_suspend() in between, leaking core_clk and iface_clk enable counts in ufs_qcom_resume() via qcom_ice_resume(). Track POST_CHANGE variant suspend state in hba->is_vops_suspended and skip ufshcd_vops_resume() when already resumed so variant suspend and resume callbacks remain paired 1:1 across error recovery. Fixes: eef1d74bf88f ("PM: runtime: Only set runtime_error on suspend callback failures") Cc: stable@vger.kernel.org # v7.3+ Signed-off-by: Stanley Jhu --- drivers/ufs/core/ufshcd-priv.h | 21 ++++++++++++++++----- include/ufs/ufshcd.h | 2 ++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/drivers/ufs/core/ufshcd-priv.h b/drivers/ufs/core/ufshcd-priv.h index e55c2a02c1f5..9e4d8174a0f0 100644 --- a/drivers/ufs/core/ufshcd-priv.h +++ b/drivers/ufs/core/ufshcd-priv.h @@ -236,20 +236,31 @@ static inline void ufshcd_vops_fixup_dev_quirks(struct ufs_hba *hba) } static inline int ufshcd_vops_suspend(struct ufs_hba *hba, enum ufs_pm_op op, - enum ufs_notify_change_status status) + enum ufs_notify_change_status status) { + int ret = 0; + if (hba->vops && hba->vops->suspend) - return hba->vops->suspend(hba, op, status); + ret = hba->vops->suspend(hba, op, status); + if (!ret && status == POST_CHANGE) + hba->is_vops_suspended = true; - return 0; + return ret; } static inline int ufshcd_vops_resume(struct ufs_hba *hba, enum ufs_pm_op op) { + int ret = 0; + + if (!hba->is_vops_suspended) + return 0; + if (hba->vops && hba->vops->resume) - return hba->vops->resume(hba, op); + ret = hba->vops->resume(hba, op); + if (!ret) + hba->is_vops_suspended = false; - return 0; + return ret; } static inline void ufshcd_vops_dbg_register_dump(struct ufs_hba *hba) diff --git a/include/ufs/ufshcd.h b/include/ufs/ufshcd.h index be62165b785e..cdcaccf9201b 100644 --- a/include/ufs/ufshcd.h +++ b/include/ufs/ufshcd.h @@ -1038,6 +1038,7 @@ enum ufshcd_mcq_opr { * @devfreq: frequency scaling information owned by the devfreq core * @clk_scaling: frequency scaling information owned by the UFS driver * @is_sys_suspended: UFS device has been suspended because of system suspend + * @is_vops_suspended: tracks whether variant POST_CHANGE suspend is active * @urgent_bkops_lvl: keeps track of urgent bkops level for device * @is_urgent_bkops_lvl_checked: keeps track if the urgent bkops level for * device is known or not. @@ -1226,6 +1227,7 @@ struct ufs_hba { struct devfreq *devfreq; struct ufs_clk_scaling clk_scaling; bool is_sys_suspended; + bool is_vops_suspended; enum bkops_status urgent_bkops_lvl; bool is_urgent_bkops_lvl_checked; -- 2.56.0.385.gac671c819a-goog