According to JEDEC UFSHCI 5.2.1 and 5.6, Multi-Circular Queue (MCQ) architecture provides per-queue runtime control and interrupt registers. When Linux initializes MCQ via ufshcd_mcq_make_queues_operational(), it accesses SQnRTC, SQnCTI, SQnIS, SQnIE, CQnIS, CQnIE, and CQnIACR. Currently, QEMU logs "invalid register offset" warnings for these registers.
Implement handling for these operational registers: - Define REG32 and FIELD macros for SQRTC (STOP, ICU) and SQRTS (STS, CUS, RTC). - SQnRTC: Handle SQSTART (bit 0=0) and SQSTOP (bit 0=1) commands to update SQnRTS (Run-Time Status), scheduling or cancelling the SQ bottom half. Enforce SQSTOP in ufs_mcq_process_sq() to halt processing when stopped. - SQnRTC / SQnRTS: Handle SQ_ICU (bit 1=1) queue cleanup command, reporting SQ_CUS (bit 1=1) and RTC completion code 0 in SQnRTS. - SQnCTI: Store Completion Timeout Interval configuration. - SQnIS: Handle Write-1-to-Clear interrupt status and invoke ufs_irq_check(). - CQnIS: Handle Write-1-to-Clear interrupt status. Recalculate whether any CQ has pending interrupts, and clear the global CQES (CQ Event Status) bit in IS before calling ufs_irq_check() to prevent IRQ storms. - SQnIE / CQnIE: Store Interrupt Enable configuration. - CQnIACR: Store Interrupt Aggregation Control Register configuration. - In ufs_hce_reset(), reset operational registers (utriacr, utrlclr, ie, utrlba/utrlbau) while preserving the Max Active Channels (MAC) capability field in MCQCONFIG according to params.mcq. - Add trace_ufs_write_mcq_op_reg trace event with explicit offset cast. Signed-off-by: Stanley Jhu <[email protected]> --- hw/ufs/trace-events | 1 + hw/ufs/ufs.c | 69 +++++++++++++++++++++++++++++++++++++++++++-- include/block/ufs.h | 9 ++++++ 3 files changed, 77 insertions(+), 2 deletions(-) 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 3c4d7424da..5064878028 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; } @@ -770,7 +771,17 @@ static void ufs_hce_reset(UfsHc *u) u->reg.utmrldbr = 0; u->reg.utrlcnr = 0; u->reg.utrlrsr = 0; + u->reg.utriacr = 0; + u->reg.utrlclr = 0; + if (u->params.mcq) { + u->reg.mcqconfig = FIELD_DP32(0, MCQCONFIG, MAC, 0x1f); + } else { + u->reg.mcqconfig = 0; + } + u->reg.ie = 0; u->reg.is = 0; + u->reg.utrlba = 0; + u->reg.utrlbau = 0; /* 4. Free MCQ Queues and reset MCQ dynamic registers */ if (u->params.mcq) { @@ -983,6 +994,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 +1004,38 @@ static void ufs_write_mcq_op_reg(UfsHc *u, hwaddr offset, uint32_t data, } opr->sq.tp = data; break; + case offsetof(UfsMcqOpReg, sq.rtc): + opr->sq.rtc = data; + if (FIELD_EX32(data, SQRTC, ICU)) { + /* SQ_ICU: Initiate Cleanup (SQ_CUS = 1, RTC = 0) */ + opr->sq.rts = FIELD_DP32(opr->sq.rts, SQRTS, CUS, 1); + opr->sq.rts = FIELD_DP32(opr->sq.rts, SQRTS, RTC, 0); + } + if (FIELD_EX32(data, SQRTC, STOP)) { + /* SQ_STOP: 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 { + /* SQ_START: Start queue */ + opr->sq.rts = FIELD_DP32(opr->sq.rts, SQRTS, STS, 0); + opr->sq.rts = FIELD_DP32(opr->sq.rts, SQRTS, CUS, 0); + if (u->sq[qid] && u->sq[qid]->bh) { + qemu_bh_schedule(u->sq[qid]->bh); + } + } + 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 +1052,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..00591aa755 100644 --- a/include/block/ufs.h +++ b/include/block/ufs.h @@ -224,6 +224,15 @@ 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(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; -- 2.55.0.979.g7e5102b832-goog
