From 760fc6f0e25a72832c2fcf37ecf5f1b770ec8374 Mon Sep 17 00:00:00 2001 From: Can Guo Date: Thu, 18 Jun 2026 07:09:29 -0700 Subject: [PATCH] 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: 03e5d38e2f98 ("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 Reviewed-by: Ziqi Chen Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260618140941.902000-1-can.guo@oss.qualcomm.com Signed-off-by: Martin K. Petersen --- drivers/ufs/core/ufs-txeq.c | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/drivers/ufs/core/ufs-txeq.c b/drivers/ufs/core/ufs-txeq.c index f06dc3c3492d..a083094a0465 100644 --- a/drivers/ufs/core/ufs-txeq.c +++ b/drivers/ufs/core/ufs-txeq.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #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; }