Commit cfd3210497 for qemu.org
commit cfd3210497f95347c2262cf5b5bcf476a832a4b6
Author: Stanley Jhu <stanleyjhu@google.com>
Date: Mon Oct 5 19:03:13 2026 +0800
hw/ufs: Support MCQ runtime interrupt and queue status registers
Implement Multi-Circular Queue (MCQ) runtime operational registers
(SQnRTC, SQnRTS, SQnCTI, SQnIS/IE, CQnIS/IE, CQnIACR) per JEDEC UFSHCI:
- Handle SQRTC.STOP transitions (0 -> 1 stop and 1 -> 0 resume) and
SQRTC.ICU cleanup using Submission Queue Cleanup Task Information
(SQCTI). Immediately clear SQRTS.CUS and SQRTS.RTC on ICU = 1, post
a CQE with OCS = UFS_OCS_ABORTED on an active matching task, and set
SQRTS.CUS = 1 with SQRTS.RTC = 0 (success), 1 (task not found),
2 (SQ not stopped), or 3 (SQ disabled).
- Implement write-1-to-clear per-queue interrupt status (SQnIS, CQnIS)
and synchronize the global IS.CQES bit.
Signed-off-by: Stanley Jhu <stanleyjhu@google.com>
Reviewed-by: Jeuk Kim <jeuk20.kim@samsung.com>
Signed-off-by: Jeuk Kim <jeuk20.kim@samsung.com>
diff --git a/hw/ufs/trace-events b/hw/ufs/trace-events
index 922293355b..d8173b12b6 100644
--- a/hw/ufs/trace-events
+++ b/hw/ufs/trace-events
@@ -14,6 +14,7 @@ ufs_process_uiccmd(uint32_t uiccmd, uint32_t ucmdarg1, uint32_t ucmdarg2, uint32
ufs_mcq_complete_req(uint8_t qid) "sqid %"PRIu8""
ufs_mcq_create_sq(uint8_t sqid, uint8_t cqid, uint64_t addr, uint16_t size) "mcq create sq sqid %"PRIu8", cqid %"PRIu8", addr 0x%"PRIx64", size %"PRIu16""
ufs_mcq_create_cq(uint8_t cqid, uint64_t addr, uint16_t size) "mcq create cq cqid %"PRIu8", addr 0x%"PRIx64", size %"PRIu16""
+ufs_write_mcq_op_reg(uint8_t qid, uint32_t offset, uint32_t data) "qid %"PRIu8", offset 0x%"PRIx32", data 0x%"PRIx32""
ufs_hce_reset(void) "HCE 1 -> 0 reset: cancelling BHs, resetting MCQ and request lists"
# error condition
diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c
index adae6639e1..956ef9c878 100644
--- a/hw/ufs/ufs.c
+++ b/hw/ufs/ufs.c
@@ -446,13 +446,14 @@ static void ufs_mcq_process_sq(void *opaque)
{
UfsSq *sq = opaque;
UfsHc *u = sq->u;
+ UfsMcqOpReg *opr = &u->mcq_op_reg[sq->sqid];
UfsSqEntry sqe;
UfsRequest *req;
hwaddr addr;
uint16_t head = ufs_mcq_sq_head(u, sq->sqid);
int err;
- if (u->resetting) {
+ if (u->resetting || FIELD_EX32(opr->sq.rts, SQRTS, STS)) {
return;
}
@@ -536,6 +537,7 @@ static void ufs_mcq_process_cq(void *opaque)
}
ufs_clear_req(req);
+ req->state = UFS_REQUEST_IDLE;
QTAILQ_INSERT_TAIL(&req->sq->req_list, req, entry);
}
@@ -970,6 +972,56 @@ static void ufs_mcq_process_db(UfsHc *u, uint8_t qid, uint32_t db)
qemu_bh_schedule(sq->bh);
}
+static void ufs_mcq_sq_cleanup(UfsHc *u, uint8_t qid)
+{
+ UfsMcqOpReg *opr = &u->mcq_op_reg[qid];
+ UfsSq *sq = u->sq[qid];
+ uint8_t task_tag = FIELD_EX32(opr->sq.cti, SQCTI, TASK_TAG);
+ uint8_t lun = FIELD_EX32(opr->sq.cti, SQCTI, LUN);
+ UfsRequest *req = NULL;
+ uint8_t rtc;
+
+ opr->sq.rts = FIELD_DP32(opr->sq.rts, SQRTS, CUS, 0);
+ opr->sq.rts = FIELD_DP32(opr->sq.rts, SQRTS, RTC, 0);
+
+ if (!sq) {
+ rtc = 3;
+ } else if (!FIELD_EX32(opr->sq.rts, SQRTS, STS)) {
+ rtc = 2;
+ } else {
+ for (int i = 0; i < sq->size; i++) {
+ UfsRequest *candidate = &sq->req[i];
+ if ((candidate->state == UFS_REQUEST_RUNNING ||
+ candidate->state == UFS_REQUEST_ERROR) &&
+ candidate->req_upiu.header.task_tag == task_tag &&
+ candidate->req_upiu.header.lun == lun) {
+ req = candidate;
+ break;
+ }
+ }
+
+ if (req) {
+ if (req->sreq != NULL) {
+ SCSIRequest *sreq = req->sreq;
+ req->sreq = NULL;
+ scsi_req_cancel(sreq);
+ }
+ req->utrd.header.dword_2 = cpu_to_le32(
+ (le32_to_cpu(req->utrd.header.dword_2) & ~UFS_MASK_OCS) |
+ UFS_OCS_ABORTED);
+ req->state = UFS_REQUEST_COMPLETE;
+ QTAILQ_INSERT_TAIL(&sq->cq->req_list, req, entry);
+ ufs_mcq_process_cq(sq->cq);
+ rtc = 0;
+ } else {
+ rtc = 1;
+ }
+ }
+
+ opr->sq.rts = FIELD_DP32(opr->sq.rts, SQRTS, CUS, 1);
+ opr->sq.rts = FIELD_DP32(opr->sq.rts, SQRTS, RTC, rtc);
+}
+
static void ufs_write_mcq_op_reg(UfsHc *u, hwaddr offset, uint32_t data,
unsigned size)
{
@@ -983,6 +1035,9 @@ static void ufs_write_mcq_op_reg(UfsHc *u, hwaddr offset, uint32_t data,
opr = &u->mcq_op_reg[qid];
+ trace_ufs_write_mcq_op_reg(qid, (uint32_t)(offset % sizeof(UfsMcqOpReg)),
+ data);
+
switch (offset % sizeof(UfsMcqOpReg)) {
case offsetof(UfsMcqOpReg, sq.tp):
if (opr->sq.tp != data) {
@@ -990,6 +1045,40 @@ static void ufs_write_mcq_op_reg(UfsHc *u, hwaddr offset, uint32_t data,
}
opr->sq.tp = data;
break;
+ case offsetof(UfsMcqOpReg, sq.rtc): {
+ bool prev_stop = FIELD_EX32(opr->sq.rtc, SQRTC, STOP);
+ bool new_stop = FIELD_EX32(data, SQRTC, STOP);
+
+ if (!prev_stop && new_stop) {
+ /* SQ_STOP (0 -> 1): Stop queue */
+ opr->sq.rts = FIELD_DP32(opr->sq.rts, SQRTS, STS, 1);
+ if (u->sq[qid] && u->sq[qid]->bh) {
+ qemu_bh_cancel(u->sq[qid]->bh);
+ }
+ } else if (prev_stop && !new_stop) {
+ /* SQ_START (1 -> 0): Resume queue */
+ opr->sq.rts = FIELD_DP32(opr->sq.rts, SQRTS, STS, 0);
+ if (u->sq[qid] && u->sq[qid]->bh) {
+ qemu_bh_schedule(u->sq[qid]->bh);
+ }
+ }
+ if (FIELD_EX32(data, SQRTC, ICU)) {
+ ufs_mcq_sq_cleanup(u, qid);
+ data = FIELD_DP32(data, SQRTC, ICU, 0);
+ }
+ opr->sq.rtc = data;
+ break;
+ }
+ case offsetof(UfsMcqOpReg, sq.cti):
+ opr->sq.cti = data;
+ break;
+ case offsetof(UfsMcqOpReg, sq_int.is):
+ opr->sq_int.is &= ~data;
+ ufs_irq_check(u);
+ break;
+ case offsetof(UfsMcqOpReg, sq_int.ie):
+ opr->sq_int.ie = data;
+ break;
case offsetof(UfsMcqOpReg, cq.hp): {
UfsCq *cq = u->cq[qid];
@@ -1006,8 +1095,27 @@ static void ufs_write_mcq_op_reg(UfsHc *u, hwaddr offset, uint32_t data,
ufs_mcq_update_cq_head(u, qid, data);
break;
}
- case offsetof(UfsMcqOpReg, cq_int.is):
+ case offsetof(UfsMcqOpReg, cq_int.is): {
+ bool pending = false;
+
opr->cq_int.is &= ~data;
+ for (int i = 0; i < ARRAY_SIZE(u->mcq_op_reg); i++) {
+ if (u->mcq_op_reg[i].cq_int.is) {
+ pending = true;
+ break;
+ }
+ }
+ if (!pending) {
+ u->reg.is = FIELD_DP32(u->reg.is, IS, CQES, 0);
+ }
+ ufs_irq_check(u);
+ break;
+ }
+ case offsetof(UfsMcqOpReg, cq_int.ie):
+ opr->cq_int.ie = data;
+ break;
+ case offsetof(UfsMcqOpReg, cq_int.iacr):
+ opr->cq_int.iacr = data;
break;
default:
trace_ufs_err_invalid_register_offset(offset);
diff --git a/include/block/ufs.h b/include/block/ufs.h
index d19b3c65ef..b72403c478 100644
--- a/include/block/ufs.h
+++ b/include/block/ufs.h
@@ -224,6 +224,21 @@ typedef struct QEMU_PACKED UfsMcqSqReg {
uint32_t rts;
} UfsMcqSqReg;
+REG32(SQRTC, offsetof(UfsMcqSqReg, rtc))
+ FIELD(SQRTC, STOP, 0, 1)
+ FIELD(SQRTC, ICU, 1, 1)
+
+REG32(SQCTI, offsetof(UfsMcqSqReg, cti))
+ FIELD(SQCTI, TASK_TAG, 0, 8)
+ FIELD(SQCTI, LUN, 8, 8)
+ FIELD(SQCTI, IID, 16, 4)
+ FIELD(SQCTI, EXT_IID, 20, 4)
+
+REG32(SQRTS, offsetof(UfsMcqSqReg, rts))
+ FIELD(SQRTS, STS, 0, 1)
+ FIELD(SQRTS, CUS, 1, 1)
+ FIELD(SQRTS, RTC, 4, 4)
+
typedef struct QEMU_PACKED UfsMcqCqReg {
uint32_t hp;
uint32_t tp;