[PATCH v2] hw/ufs: Reset controller and MCQ state on HCE transition to 0

Stanley Jhu posted 1 patch 3 weeks, 3 days ago
Failed in applying to current master (apply log)
There is a newer version of this series
hw/ufs/trace-events |  1 +
hw/ufs/ufs.h        |  2 ++
hw/ufs/lu.c         |  7 +++++
hw/ufs/ufs.c        | 94 +++++++++++++++++++++++++++++++++++++++++++++++------
4 files changed, 98 insertions(+), 6 deletions(-)
[PATCH v2] hw/ufs: Reset controller and MCQ state on HCE transition to 0
Posted by Stanley Jhu 3 weeks, 3 days ago
According to the JEDEC Universal Flash Storage Host Controller Interface
(UFSHCI) specification (Section 5.2.1 "Host Controller Enable"):
when Host Controller Enable (HCE) transitions from 1 to 0, a host
controller reset is initiated. The host controller shall abort all
active transfers, return internal state machines to idle, and de-assert
all interrupts.

Currently, QEMU's UFS emulator only clears HCS and HCE registers upon
HCE=0, leaving internal state active. Specifically:
- Outstanding SCSI requests in the block layer are not purged.
- Active bottom halves (doorbell_bh, complete_bh, and MCQ sq/cq BHs)
  remain scheduled.
- Allocated MCQ Submission and Completion Queues (sq and cq) are
  not freed.
- Dynamic MCQ queue registers, legacy UTRL request states, and status
  registers (UTRLCNR, UTRLRSR) remain stale.

Implement ufs_hce_reset() to:
1. Implement .cancel callback in ufs_scsi_info to properly unref
   scsi_req and prevent reference leaks when SCSI requests are purged.
2. Cancel active bottom halves (doorbell_bh, complete_bh, and MCQ sq/cq
   BHs) and guard them with a resetting flag before purging requests,
   ensuring blk_drain() cannot run or reschedule request-producing BHs.
3. Purge outstanding SCSI requests for all logical units via
   scsi_device_purge_requests().
4. Clear standard request list slots and release SGLs via
   ufs_clear_req(), and clear doorbells and status registers (UTRLCNR,
   UTRLRSR).
5. Free allocated MCQ queues and clear dynamic queue registers while
   preserving static capability offsets.
6. De-assert interrupts via ufs_irq_check().
7. Add a trace_ufs_hce_reset() trace event.

Signed-off-by: Stanley Jhu <stanleyjhu@google.com>
---
v1 -> v2:
- Added ufs_scsi_command_cancelled() as .cancel callback in ufs_scsi_info
  to drop scsi_req reference and avoid leaks upon purge (Jeuk Kim).
- Reordered sequence to cancel all active BHs before
  scsi_device_purge_requests() and introduced resetting flag to prevent
  CQ BH from rescheduling SQ BH during blk_drain() (Jeuk Kim).
- Cleared UTRLCNR and UTRLRSR registers upon reset (Jeuk Kim).
- Guarded ufs_process_req() and ufs_complete_req() against execution while
  resetting is in progress.
---
 hw/ufs/trace-events |  1 +
 hw/ufs/ufs.h        |  2 ++
 hw/ufs/lu.c         |  7 +++++
 hw/ufs/ufs.c        | 94 +++++++++++++++++++++++++++++++++++++++++++++++------
 4 files changed, 98 insertions(+), 6 deletions(-)

diff --git a/hw/ufs/trace-events b/hw/ufs/trace-events
index 00e263c7ba..1eb85c6978 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, uint
 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_hce_reset(void) "HCE 1 -> 0 reset: cancelling BHs, resetting MCQ
and request lists"

 # error condition
 ufs_err_dma_read_utrd(uint32_t slot, uint64_t addr) "failed to read
utrd. UTRLDBR slot %"PRIu32", UTRD dma addr %"PRIu64""
diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h
index a9cb8e9df6..42e7713837 100644
--- a/hw/ufs/ufs.h
+++ b/hw/ufs/ufs.h
@@ -147,6 +147,8 @@ typedef struct UfsHc {
     UfsSq *sq[UFS_MAX_MCQ_QNUM];
     UfsCq *cq[UFS_MAX_MCQ_QNUM];

+    bool resetting;
+
     uint8_t temperature;
 } UfsHc;

diff --git a/hw/ufs/lu.c b/hw/ufs/lu.c
index a792c342f5..4f9d0c2429 100644
--- a/hw/ufs/lu.c
+++ b/hw/ufs/lu.c
@@ -74,6 +74,12 @@ static void ufs_scsi_command_complete(SCSIRequest
*scsi_req, size_t resid)
     scsi_req_unref(scsi_req);
 }

+static void ufs_scsi_command_cancelled(SCSIRequest *scsi_req)
+{
+    scsi_req->hba_private = NULL;
+    scsi_req_unref(scsi_req);
+}
+
 static QEMUSGList *ufs_get_sg_list(SCSIRequest *scsi_req)
 {
     UfsRequest *req = scsi_req->hba_private;
@@ -88,6 +94,7 @@ static const struct SCSIBusInfo ufs_scsi_info = {

     .get_sg_list = ufs_get_sg_list,
     .complete = ufs_scsi_command_complete,
+    .cancel = ufs_scsi_command_cancelled,
 };

 static int ufs_emulate_report_luns(UfsRequest *req, uint8_t *outbuf,
diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c
index a8e2df8087..def9935105 100644
--- a/hw/ufs/ufs.c
+++ b/hw/ufs/ufs.c
@@ -415,6 +415,10 @@ static void ufs_mcq_process_sq(void *opaque)
     uint16_t head = ufs_mcq_sq_head(u, sq->sqid);
     int err;

+    if (u->resetting) {
+        return;
+    }
+
     while (!(ufs_mcq_sq_empty(u, sq->sqid) || QTAILQ_EMPTY(&sq->req_list))) {
         addr = sq->addr + head;
         err = ufs_addr_read(sq->u, addr, (void *)&sqe, sizeof(sqe));
@@ -482,7 +486,7 @@ static void ufs_mcq_process_cq(void *opaque)
         tail = (tail + sizeof(req->cqe)) % (cq->size * sizeof(req->cqe));
         ufs_mcq_update_cq_tail(u, cq->cqid, tail);

-        if (QTAILQ_EMPTY(&req->sq->req_list) &&
+        if (!u->resetting && QTAILQ_EMPTY(&req->sq->req_list) &&
             !ufs_mcq_sq_empty(u, req->sq->sqid)) {
             /* Dequeueing from SQ was blocked due to lack of free requests */
             qemu_bh_schedule(req->sq->bh);
@@ -679,6 +683,87 @@ static bool ufs_mcq_delete_cq(UfsHc *u, uint8_t qid)
     return true;
 }

+static void ufs_hce_reset(UfsHc *u)
+{
+    int i;
+
+    trace_ufs_hce_reset();
+
+    u->resetting = true;
+
+    /* 1. Cancel active Bottom Halves before purging requests */
+    if (u->doorbell_bh) {
+        qemu_bh_cancel(u->doorbell_bh);
+    }
+    if (u->complete_bh) {
+        qemu_bh_cancel(u->complete_bh);
+    }
+    if (u->params.mcq) {
+        for (i = 0; i < ARRAY_SIZE(u->sq); i++) {
+            if (u->sq[i] && u->sq[i]->bh) {
+                qemu_bh_cancel(u->sq[i]->bh);
+            }
+        }
+        for (i = 0; i < ARRAY_SIZE(u->cq); i++) {
+            if (u->cq[i] && u->cq[i]->bh) {
+                qemu_bh_cancel(u->cq[i]->bh);
+            }
+        }
+    }
+
+    /* 2. Purge outstanding SCSI requests for all logical units */
+    for (i = 0; i < UFS_MAX_LUS; i++) {
+        if (u->lus[i] && u->lus[i]->scsi_dev) {
+            scsi_device_purge_requests(u->lus[i]->scsi_dev, SENSE_CODE(RESET));
+        }
+    }
+
+    /* 3. Reset standard request list slots, doorbells, and status registers */
+    for (i = 0; i < u->params.nutrs; i++) {
+        ufs_clear_req(&u->req_list[i]);
+        u->req_list[i].state = UFS_REQUEST_IDLE;
+    }
+    u->reg.utrldbr = 0;
+    u->reg.utmrldbr = 0;
+    u->reg.utrlcnr = 0;
+    u->reg.utrlrsr = 0;
+    u->reg.is = 0;
+
+    /* 4. Free MCQ Queues and reset MCQ dynamic registers */
+    if (u->params.mcq) {
+        for (i = 0; i < ARRAY_SIZE(u->sq); i++) {
+            if (u->sq[i]) {
+                ufs_mcq_free_sq(u->sq[i]);
+                u->sq[i] = NULL;
+            }
+        }
+        for (i = 0; i < ARRAY_SIZE(u->cq); i++) {
+            if (u->cq[i]) {
+                ufs_mcq_free_cq(u->cq[i]);
+                u->cq[i] = NULL;
+            }
+        }
+
+        /* Clear dynamic queue configuration registers without
overwriting static capability offsets */
+        for (i = 0; i < ARRAY_SIZE(u->mcq_reg); i++) {
+            u->mcq_reg[i].sqattr = 0;
+            u->mcq_reg[i].sqlba = 0;
+            u->mcq_reg[i].squba = 0;
+            u->mcq_reg[i].sqcfg = 0;
+            u->mcq_reg[i].cqattr = 0;
+            u->mcq_reg[i].cqlba = 0;
+            u->mcq_reg[i].cquba = 0;
+            u->mcq_reg[i].cqcfg = 0;
+        }
+        memset(u->mcq_op_reg, 0, sizeof(u->mcq_op_reg));
+    }
+
+    u->resetting = false;
+
+    /* 5. De-assert IRQ */
+    ufs_irq_check(u);
+}
+
 static void ufs_write_reg(UfsHc *u, hwaddr offset, uint32_t data,
unsigned size)
 {
     switch (offset) {
@@ -696,6 +781,7 @@ static void ufs_write_reg(UfsHc *u, hwaddr offset,
uint32_t data, unsigned size)
             u->reg.hce = FIELD_DP32(u->reg.hce, HCE, HCE, 1);
         } else if (FIELD_EX32(u->reg.hce, HCE, HCE) &&
                    !FIELD_EX32(data, HCE, HCE)) {
+            ufs_hce_reset(u);
             u->reg.hcs = 0;
             u->reg.hce = FIELD_DP32(u->reg.hce, HCE, HCE, 0);
         }
@@ -1631,6 +1717,10 @@ static void ufs_process_req(void *opaque)
     UfsRequest *req;
     int slot;

+    if (u->resetting) {
+        return;
+    }
+
     for (slot = 0; slot < u->params.nutrs; slot++) {
         req = &u->req_list[slot];

@@ -1656,6 +1746,10 @@ void ufs_complete_req(UfsRequest *req,
UfsReqResult req_result)
     }

     req->state = UFS_REQUEST_COMPLETE;
+
+    if (u->resetting) {
+        return;
+    }

     if (ufs_mcq_req(req)) {
         trace_ufs_mcq_complete_req(req->sq->sqid);
--
2.43.0