scsi: ufs: core: Avoid possible memory reclaim deadlock in TX EQTR context

TX EQTR may run while devfreq gear scaling has quiesced the UFS
tagset. In that context, functions ufshcd_tx_eqtr(), __ufshcd_tx_eqtr()
and ufs_qcom_get_rx_fom() allocate memory with GFP_KERNEL. If direct
reclaim is triggered, reclaim/writeback can depend on I/O to UFS
device. Because the queue is quiesced, this can cause deadlock.

Use memalloc_noio_save/restore() in ufshcd_tx_eqtr() to cover all
allocations in the TX EQTR call tree, including:

 - params->eqtr_record in ufshcd_tx_eqtr()

 - eqtr_data in __ufshcd_tx_eqtr()

 - params in ufs_qcom_get_rx_fom()

This is preferred over tagging individual call sites with GFP_NOIO, as it
automatically covers any future allocations added anywhere in the call tree
without requiring each caller to be aware of this constraint.

[mkp: fix label as suggested by Bart]

Fixes: 03e5d38e2f ("scsi: ufs: core: Add support for TX Equalization")
Closes: https://sashiko.dev/#/patchset/20260615132834.2985346-1-can.guo@oss.qualcomm.com?part=2
Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
Reviewed-by: Ziqi Chen <ziqi.chen@oss.qualcomm.com>
Reviewed-by: Manivannan Sadhasivam <mani@kernel.org>
Link: https://patch.msgid.link/20260618140941.902000-1-can.guo@oss.qualcomm.com
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
This commit is contained in:
Can Guo
2026-06-18 07:09:29 -07:00
committed by Martin K. Petersen
parent e700a7c94f
commit 760fc6f0e2

View File

@@ -10,6 +10,7 @@
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/sched/mm.h>
#include <ufs/ufshcd.h>
#include <ufs/unipro.h>
#include "ufshcd-priv.h"
@@ -1216,14 +1217,25 @@ static int ufshcd_tx_eqtr(struct ufs_hba *hba,
struct ufs_pa_layer_attr *pwr_mode)
{
struct ufs_pa_layer_attr old_pwr_info;
unsigned int noio_flag;
int ret;
/*
* ufshcd_tx_eqtr() is called from a power-mode-change context where
* I/O is suspended. Use memalloc_noio_save() to propagate GFP_NOIO
* to all allocations in the call tree instead of tagging each call
* site individually.
*/
noio_flag = memalloc_noio_save();
if (!params->eqtr_record) {
params->eqtr_record = devm_kzalloc(hba->dev,
sizeof(*params->eqtr_record),
GFP_KERNEL);
if (!params->eqtr_record)
return -ENOMEM;
if (!params->eqtr_record) {
ret = -ENOMEM;
goto out_noio_restore;
}
}
memcpy(&old_pwr_info, &hba->pwr_info, sizeof(struct ufs_pa_layer_attr));
@@ -1231,23 +1243,26 @@ static int ufshcd_tx_eqtr(struct ufs_hba *hba,
ret = ufshcd_tx_eqtr_prepare(hba, pwr_mode);
if (ret) {
dev_err(hba->dev, "Failed to prepare TX EQTR: %d\n", ret);
goto out;
goto out_unprepare;
}
ret = ufshcd_vops_tx_eqtr_notify(hba, PRE_CHANGE, pwr_mode);
if (ret)
goto out;
goto out_unprepare;
ret = __ufshcd_tx_eqtr(hba, params, pwr_mode);
if (ret)
goto out;
goto out_unprepare;
ret = ufshcd_vops_tx_eqtr_notify(hba, POST_CHANGE, pwr_mode);
out:
out_unprepare:
if (ret)
ufshcd_tx_eqtr_unprepare(hba, &old_pwr_info);
out_noio_restore:
memalloc_noio_restore(noio_flag);
return ret;
}