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;