In preparation for supporting TX Equalization refreshing, introduce
helper functions to safely pause and resume command processing.
ufshcd_pause_command_processing() ensures the host is in a quiescent
state by stopping the block layer tagset, acquiring the necessary
locks (scan_mutex and clk_scaling_lock), and waiting for any
in-flight commands to complete within a specified timeout.
ufshcd_resume_command_processing() restores the host to its previous
operational state by reversing these steps in the correct order.
Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
---
drivers/ufs/core/ufshcd-priv.h | 2 ++
drivers/ufs/core/ufshcd.c | 42 ++++++++++++++++++++++++++++++++++
2 files changed, 44 insertions(+)
diff --git a/drivers/ufs/core/ufshcd-priv.h b/drivers/ufs/core/ufshcd-priv.h
index 20ec8d8ac0a4..2303d57bf874 100644
--- a/drivers/ufs/core/ufshcd-priv.h
+++ b/drivers/ufs/core/ufshcd-priv.h
@@ -78,6 +78,8 @@ int ufshcd_mcq_sq_cleanup(struct ufs_hba *hba, int task_tag);
int ufshcd_mcq_abort(struct scsi_cmnd *cmd);
int ufshcd_try_to_abort_task(struct ufs_hba *hba, int tag);
void ufshcd_release_scsi_cmd(struct ufs_hba *hba, struct scsi_cmnd *cmd);
+int ufshcd_pause_command_processing(struct ufs_hba *hba, u64 timeout_us);
+void ufshcd_resume_command_processing(struct ufs_hba *hba);
/**
* enum ufs_descr_fmt - UFS string descriptor format
diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c
index 15543ad03181..6fef24612be1 100644
--- a/drivers/ufs/core/ufshcd.c
+++ b/drivers/ufs/core/ufshcd.c
@@ -1362,6 +1362,48 @@ static int ufshcd_wait_for_pending_cmds(struct ufs_hba *hba,
return ret;
}
+/**
+ * ufshcd_pause_command_processing - Pause command processing
+ * @hba: per-adapter instance
+ * @timeout_us: timeout in microseconds to wait for pending commands to finish
+ *
+ * This function stops new command submissions and waits for existing commands
+ * to complete.
+ *
+ * Return: 0 on success, %-EBUSY if commands did not finish within @timeout_us.
+ * On failure, all acquired locks are released and the tagset is unquiesced.
+ */
+int ufshcd_pause_command_processing(struct ufs_hba *hba, u64 timeout_us)
+{
+ int ret = 0;
+
+ mutex_lock(&hba->host->scan_mutex);
+ blk_mq_quiesce_tagset(&hba->host->tag_set);
+ down_write(&hba->clk_scaling_lock);
+
+ if (ufshcd_wait_for_pending_cmds(hba, timeout_us)) {
+ ret = -EBUSY;
+ up_write(&hba->clk_scaling_lock);
+ blk_mq_unquiesce_tagset(&hba->host->tag_set);
+ mutex_unlock(&hba->host->scan_mutex);
+ }
+
+ return ret;
+}
+
+/**
+ * ufshcd_resume_command_processing - Resume command processing
+ * @hba: per-adapter instance
+ *
+ * This function resumes command submissions.
+ */
+void ufshcd_resume_command_processing(struct ufs_hba *hba)
+{
+ up_write(&hba->clk_scaling_lock);
+ blk_mq_unquiesce_tagset(&hba->host->tag_set);
+ mutex_unlock(&hba->host->scan_mutex);
+}
+
/**
* ufshcd_scale_gear - scale up/down UFS gear
* @hba: per adapter instance
--
2.34.1
On 3/8/26 8:14 AM, Can Guo wrote:
> +/**
> + * ufshcd_pause_command_processing - Pause command processing
> + * @hba: per-adapter instance
> + * @timeout_us: timeout in microseconds to wait for pending commands to finish
> + *
> + * This function stops new command submissions and waits for existing commands
> + * to complete.
> + *
> + * Return: 0 on success, %-EBUSY if commands did not finish within @timeout_us.
> + * On failure, all acquired locks are released and the tagset is unquiesced.
> + */
> +int ufshcd_pause_command_processing(struct ufs_hba *hba, u64 timeout_us)
> +{
> + int ret = 0;
> +
> + mutex_lock(&hba->host->scan_mutex);
> + blk_mq_quiesce_tagset(&hba->host->tag_set);
> + down_write(&hba->clk_scaling_lock);
> +
> + if (ufshcd_wait_for_pending_cmds(hba, timeout_us)) {
> + ret = -EBUSY;
> + up_write(&hba->clk_scaling_lock);
> + blk_mq_unquiesce_tagset(&hba->host->tag_set);
> + mutex_unlock(&hba->host->scan_mutex);
> + }
> +
> + return ret;
> +}
> +
> +/**
> + * ufshcd_resume_command_processing - Resume command processing
> + * @hba: per-adapter instance
> + *
> + * This function resumes command submissions.
> + */
> +void ufshcd_resume_command_processing(struct ufs_hba *hba)
> +{
> + up_write(&hba->clk_scaling_lock);
> + blk_mq_unquiesce_tagset(&hba->host->tag_set);
> + mutex_unlock(&hba->host->scan_mutex);
> +}
> +
This patch duplicates existing code. Please integrate the following
changes in this patch:
- ufshcd_clock_scaling_prepare() calls
ufshcd_pause_command_processing().
- ufshcd_clock_scaling_unprepare() calls
ufshcd_resume_command_processing().
Thanks,
Bart.
Hi Bart,
On 3/14/2026 6:26 AM, Bart Van Assche wrote:
> On 3/8/26 8:14 AM, Can Guo wrote:
>> +/**
>> + * ufshcd_pause_command_processing - Pause command processing
>> + * @hba: per-adapter instance
>> + * @timeout_us: timeout in microseconds to wait for pending commands
>> to finish
>> + *
>> + * This function stops new command submissions and waits for
>> existing commands
>> + * to complete.
>> + *
>> + * Return: 0 on success, %-EBUSY if commands did not finish within
>> @timeout_us.
>> + * On failure, all acquired locks are released and the tagset is
>> unquiesced.
>> + */
>> +int ufshcd_pause_command_processing(struct ufs_hba *hba, u64
>> timeout_us)
>> +{
>> + int ret = 0;
>> +
>> + mutex_lock(&hba->host->scan_mutex);
>> + blk_mq_quiesce_tagset(&hba->host->tag_set);
>> + down_write(&hba->clk_scaling_lock);
>> +
>> + if (ufshcd_wait_for_pending_cmds(hba, timeout_us)) {
>> + ret = -EBUSY;
>> + up_write(&hba->clk_scaling_lock);
>> + blk_mq_unquiesce_tagset(&hba->host->tag_set);
>> + mutex_unlock(&hba->host->scan_mutex);
>> + }
>> +
>> + return ret;
>> +}
>> +
>> +/**
>> + * ufshcd_resume_command_processing - Resume command processing
>> + * @hba: per-adapter instance
>> + *
>> + * This function resumes command submissions.
>> + */
>> +void ufshcd_resume_command_processing(struct ufs_hba *hba)
>> +{
>> + up_write(&hba->clk_scaling_lock);
>> + blk_mq_unquiesce_tagset(&hba->host->tag_set);
>> + mutex_unlock(&hba->host->scan_mutex);
>> +}
>> +
>
> This patch duplicates existing code. Please integrate the following
> changes in this patch:
> - ufshcd_clock_scaling_prepare() calls
> ufshcd_pause_command_processing().
> - ufshcd_clock_scaling_unprepare() calls
> ufshcd_resume_command_processing().
I looked into ufshcd_clock_scaling_prepare() and
ufshcd_clock_scaling_unprepare(),
I see they are coupled with Writebooster, wb_mutex and check on
scaling.is_allowed,
I don't see an easy way of calling ufshcd_pause_command_processing() and
ufshcd_resume_command_processing() from there.
I also checked the history of changes to ufshcd_clock_scaling_prepare() and
ufshcd_clock_scaling_unprepare(), I can see multiple issues, e.g., deadlock,
were reported and fixed. I am not confident at all to make intrusive changes
to the two functions without breaking clock scaling.
Can we do it later as a separate patch and with enough testing conducted?
Thanks,
Can Guo.
>
> Thanks,
>
> Bart.
On 3/14/26 3:38 AM, Can Guo wrote:
> I also checked the history of changes to
> ufshcd_clock_scaling_prepare() and ufshcd_clock_scaling_unprepare(),
> I can see multiple issues, e.g., deadlock, were reported and fixed.
This deadlock fix: ba81043753ff ("scsi: ufs: core: Fix devfreq
deadlocks")? My understanding is that the deadlock was related to
calling ufshcd_wb_toggle() synchronously from ufshcd_devfreq_scale().
Such deadlocks can be avoided by converting a synchronous call into
an asynchronous call (queuing a work item). However, I'm not sure that's
an option in the context of clock scaling?
Thanks,
Bart.
© 2016 - 2026 Red Hat, Inc.