* [PATCH v1] ufs: core: decouple CQE processing from spinlock critical section
@ 2026-05-14 8:26 peter.wang
2026-05-14 16:22 ` Bart Van Assche
0 siblings, 1 reply; 2+ messages in thread
From: peter.wang @ 2026-05-14 8:26 UTC (permalink / raw)
To: linux-scsi, martin.petersen, avri.altman, alim.akhtar, jejb
Cc: wsd_upstream, linux-mediatek, peter.wang, chun-hung.wu,
alice.chao, cc.chou, chaotian.jing, tun-yu.yu, eddie.huang,
naomi.chu, ed.tsai, bvanassche, quic_cang, quic_asutoshd,
light.hsieh
From: Peter Wang <peter.wang@mediatek.com>
Currently, ufshcd_mcq_process_cqe() is called while holding the CQ
spinlock, which can lead to unnecessary lock contention since CQE
processing may involve time-consuming operations like completing I/O
requests and invoking callbacks.
Refactor the CQE processing flow to separate the lock-protected queue
head/tail slot updates from the actual CQE processing:
1. Add a new 'cqe_last_addr' field to 'ufs_hw_queue' to cache the
address of the last CQE entry, precomputed during memory allocation
in ufshcd_mcq_memory_alloc(). This avoids repeated recalculation
during the hot path.
2. Introduce ufshcd_mcq_inc_cqe_addr() helper in ufshcd-priv.h to
increment a CQE pointer with wraparound, using 'cqe_last_addr' for
boundary checking.
3. Refactor ufshcd_mcq_process_cqe() to accept a 'struct cq_entry *'
directly instead of deriving it from the hardware queue, decoupling
it from queue state.
4. In both ufshcd_mcq_compl_all_cqes_lock() and
ufshcd_mcq_poll_cqe_lock(), snapshot the starting CQE pointer before
advancing the head slot under the spinlock, then process the collected
CQEs after releasing the lock using the new helper.
This reduces the time spent holding the CQ spinlock to only the
minimal queue slot management operations, improving concurrency and
reducing latency under heavy I/O workloads.
Signed-off-by: Peter Wang <peter.wang@mediatek.com>
---
drivers/ufs/core/ufs-mcq.c | 23 ++++++++++++++++++-----
drivers/ufs/core/ufshcd-priv.h | 20 ++++++++++++++++++++
include/ufs/ufshcd.h | 1 +
3 files changed, 39 insertions(+), 5 deletions(-)
diff --git a/drivers/ufs/core/ufs-mcq.c b/drivers/ufs/core/ufs-mcq.c
index c1b1d67a1ddc..74a6595f9bda 100644
--- a/drivers/ufs/core/ufs-mcq.c
+++ b/drivers/ufs/core/ufs-mcq.c
@@ -248,6 +248,7 @@ int ufshcd_mcq_memory_alloc(struct ufs_hba *hba)
dev_err(hba->dev, "CQE allocation failed\n");
return -ENOMEM;
}
+ hwq->cqe_last_addr = hwq->cqe_base_addr + hwq->max_entries - 1;
}
return 0;
@@ -307,10 +308,8 @@ static int ufshcd_mcq_get_tag(struct ufs_hba *hba, struct cq_entry *cqe)
}
static void ufshcd_mcq_process_cqe(struct ufs_hba *hba,
- struct ufs_hw_queue *hwq)
+ struct cq_entry *cqe)
{
- struct cq_entry *cqe = ufshcd_mcq_cur_cqe(hwq);
-
if (cqe->command_desc_base_addr) {
int tag = ufshcd_mcq_get_tag(hba, cqe);
@@ -335,10 +334,12 @@ void ufshcd_mcq_compl_all_cqes_lock(struct ufs_hba *hba,
{
unsigned long flags;
u32 entries = hwq->max_entries;
+ struct cq_entry *cqe;
+ int i;
spin_lock_irqsave(&hwq->cq_lock, flags);
+ cqe = ufshcd_mcq_cur_cqe(hwq);
while (entries > 0) {
- ufshcd_mcq_process_cqe(hba, hwq);
ufshcd_mcq_inc_cq_head_slot(hwq);
entries--;
}
@@ -346,6 +347,11 @@ void ufshcd_mcq_compl_all_cqes_lock(struct ufs_hba *hba,
ufshcd_mcq_update_cq_tail_slot(hwq);
hwq->cq_head_slot = hwq->cq_tail_slot;
spin_unlock_irqrestore(&hwq->cq_lock, flags);
+
+ for (i = 0; i < hwq->max_entries; i++) {
+ ufshcd_mcq_process_cqe(hba, cqe);
+ cqe = ufshcd_mcq_inc_cqe_addr(hwq, cqe);
+ }
}
unsigned long ufshcd_mcq_poll_cqe_lock(struct ufs_hba *hba,
@@ -353,11 +359,13 @@ unsigned long ufshcd_mcq_poll_cqe_lock(struct ufs_hba *hba,
{
unsigned long completed_reqs = 0;
unsigned long flags;
+ struct cq_entry *cqe;
+ int i;
spin_lock_irqsave(&hwq->cq_lock, flags);
+ cqe = ufshcd_mcq_cur_cqe(hwq);
ufshcd_mcq_update_cq_tail_slot(hwq);
while (!ufshcd_mcq_is_cq_empty(hwq)) {
- ufshcd_mcq_process_cqe(hba, hwq);
ufshcd_mcq_inc_cq_head_slot(hwq);
completed_reqs++;
}
@@ -366,6 +374,11 @@ unsigned long ufshcd_mcq_poll_cqe_lock(struct ufs_hba *hba,
ufshcd_mcq_update_cq_head(hwq);
spin_unlock_irqrestore(&hwq->cq_lock, flags);
+ for (i = 0; i < completed_reqs; i++) {
+ ufshcd_mcq_process_cqe(hba, cqe);
+ cqe = ufshcd_mcq_inc_cqe_addr(hwq, cqe);
+ }
+
return completed_reqs;
}
EXPORT_SYMBOL_GPL(ufshcd_mcq_poll_cqe_lock);
diff --git a/drivers/ufs/core/ufshcd-priv.h b/drivers/ufs/core/ufshcd-priv.h
index 0a72148cb053..6d4d3e726a9a 100644
--- a/drivers/ufs/core/ufshcd-priv.h
+++ b/drivers/ufs/core/ufshcd-priv.h
@@ -440,6 +440,26 @@ static inline struct scsi_cmnd *ufshcd_tag_to_cmd(struct ufs_hba *hba, u32 tag)
return blk_mq_rq_to_pdu(rq);
}
+/**
+ * ufshcd_mcq_inc_cqe_addr - increment CQE pointer with wraparound
+ * @hwq: pointer to the hardware queue
+ * @cqe: current CQE pointer to increment
+ *
+ * Increments the CQE pointer to the next entry. If the pointer
+ * exceeds the last entry, it wraps around to the base address.
+ *
+ * Returns: pointer to the next cq_entry
+ */
+static inline struct cq_entry *ufshcd_mcq_inc_cqe_addr(struct ufs_hw_queue *q,
+ struct cq_entry *cqe)
+{
+ cqe++;
+ if (cqe > q->cqe_last_addr)
+ cqe = q->cqe_base_addr;
+
+ return cqe;
+}
+
static inline void ufshcd_inc_sq_tail(struct ufs_hw_queue *q)
__must_hold(&q->sq_lock)
{
diff --git a/include/ufs/ufshcd.h b/include/ufs/ufshcd.h
index cfbc75d8df83..1becb38e215e 100644
--- a/include/ufs/ufshcd.h
+++ b/include/ufs/ufshcd.h
@@ -1291,6 +1291,7 @@ struct ufs_hw_queue {
struct utp_transfer_req_desc *sqe_base_addr;
dma_addr_t sqe_dma_addr;
struct cq_entry *cqe_base_addr;
+ struct cq_entry *cqe_last_addr;
dma_addr_t cqe_dma_addr;
u32 max_entries;
u32 id;
--
2.45.2
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH v1] ufs: core: decouple CQE processing from spinlock critical section
2026-05-14 8:26 [PATCH v1] ufs: core: decouple CQE processing from spinlock critical section peter.wang
@ 2026-05-14 16:22 ` Bart Van Assche
0 siblings, 0 replies; 2+ messages in thread
From: Bart Van Assche @ 2026-05-14 16:22 UTC (permalink / raw)
To: peter.wang, linux-scsi, martin.petersen, avri.altman, alim.akhtar,
jejb
Cc: wsd_upstream, linux-mediatek, chun-hung.wu, alice.chao, cc.chou,
chaotian.jing, tun-yu.yu, eddie.huang, naomi.chu, ed.tsai,
quic_cang, quic_asutoshd, light.hsieh
On 5/14/26 1:26 AM, peter.wang@mediatek.com wrote:
> 4. In both ufshcd_mcq_compl_all_cqes_lock() and
> ufshcd_mcq_poll_cqe_lock(), snapshot the starting CQE pointer before
> advancing the head slot under the spinlock, then process the collected
> CQEs after releasing the lock using the new helper.
This can't work reliably. ufshcd_mcq_poll_cqe_lock() may be called
concurrently from different CPU cores, e.g. from a UFS completion
interrupt and from ufshcd_poll(). Processing CQEs without holding
hwq->cq_lock may lead to overwriting of CQEs before these have been
processed.
Thanks,
Bart.
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-05-14 16:23 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-14 8:26 [PATCH v1] ufs: core: decouple CQE processing from spinlock critical section peter.wang
2026-05-14 16:22 ` Bart Van Assche
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox