[PATCH v3 3/5] hw/ufs: Add idle operation

Jaemyung Lee posted 5 patches 2 days, 11 hours ago
Maintainers: Jeuk Kim <jeuk20.kim@samsung.com>, Kevin Wolf <kwolf@redhat.com>, Hanna Reitz <hreitz@redhat.com>, Fabiano Rosas <farosas@suse.de>, Laurent Vivier <lvivier@redhat.com>, Paolo Bonzini <pbonzini@redhat.com>
There is a newer version of this series
[PATCH v3 3/5] hw/ufs: Add idle operation
Posted by Jaemyung Lee 2 days, 11 hours ago
When no I/O occurs, the UFS Device performs various internal operations.
To emulate this, adds a timer that periodically checks the current I/O
status of the device and call the ufs_process_idle() function when idle.

Signed-off-by: Jaemyung Lee <jaemyung.lee@samsung.com>
---
 hw/ufs/ufs.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/ufs/ufs.h |  2 ++
 2 files changed, 65 insertions(+)

diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c
index 69f82ab462d135fe6cda479891f9d8f26d19be9a..33887ac12a4bae647acb94b292f1d680bff9a007 100644
--- a/hw/ufs/ufs.c
+++ b/hw/ufs/ufs.c
@@ -1801,6 +1801,63 @@ static void ufs_sendback_req(void *opaque)
     ufs_irq_check(u);
 }
 
+static void ufs_process_idle(UfsHc *u)
+{
+    /* Currently do nothing */
+    return;
+}
+
+static inline bool ufs_check_idle(UfsHc *u)
+{
+    return !u->reg.utrldbr;
+}
+
+static inline bool ufs_mcq_check_idle(UfsHc *u)
+{
+    if (!u->params.mcq) {
+        return true;
+    }
+
+    for (int qid = 0; qid < ARRAY_SIZE(u->sq); qid++) {
+        if (!ufs_mcq_sq_empty(u, qid) || !u->sq[qid]) {
+            return false;
+        }
+
+        /* internal ongoing MCQ request check */
+        UfsSq *sq = u->sq[qid];
+        for (int i = 0; i < sq->size; i++) {
+            if (sq->req[i].state != UFS_REQUEST_IDLE) {
+                return false;
+            }
+        }
+    }
+
+    for (int qid = 0; qid < ARRAY_SIZE(u->cq); qid++) {
+        if (!ufs_mcq_cq_empty(u, qid) || !u->cq[qid]) {
+            return false;
+        }
+
+        if (!QTAILQ_EMPTY(&u->cq[qid]->req_list)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+#define UFS_IDLE_TIMER_TICK 100 /* 0.1s */
+static void ufs_idle_timer_cb(void *opaque)
+{
+    UfsHc *u = opaque;
+    int64_t now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL_RT);
+
+    if (ufs_check_idle(u) && ufs_mcq_check_idle(u)) {
+        ufs_process_idle(u);
+    }
+
+    timer_mod(&u->idle_timer, now + UFS_IDLE_TIMER_TICK);
+}
+
 static bool ufs_check_constraints(UfsHc *u, Error **errp)
 {
     if (u->params.nutrs > UFS_MAX_NUTRS) {
@@ -1863,6 +1920,7 @@ static void ufs_init_hc(UfsHc *u)
     uint32_t cap = 0;
     uint32_t mcqconfig = 0;
     uint32_t mcqcap = 0;
+    int64_t now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL_RT);
 
     u->reg_size = pow2ceil(ufs_reg_size(u));
 
@@ -1959,6 +2017,9 @@ static void ufs_init_hc(UfsHc *u)
      * dynamically
      */
     u->temperature = UFS_TEMPERATURE;
+
+    timer_init_ms(&u->idle_timer, QEMU_CLOCK_VIRTUAL_RT, ufs_idle_timer_cb, u);
+    timer_mod(&u->idle_timer, now + UFS_IDLE_TIMER_TICK);
 }
 
 static void ufs_realize(PCIDevice *pci_dev, Error **errp)
@@ -1986,6 +2047,8 @@ static void ufs_exit(PCIDevice *pci_dev)
 {
     UfsHc *u = UFS(pci_dev);
 
+    timer_del(&u->idle_timer);
+
     qemu_free_irq(u->irq);
 
     qemu_bh_delete(u->doorbell_bh);
diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h
index 13d964c5ae5ec430a98b2ef71987cb9279e9a317..b5f040302129f4d02732ddd20ef82eb33c41922a 100644
--- a/hw/ufs/ufs.h
+++ b/hw/ufs/ufs.h
@@ -148,6 +148,8 @@ typedef struct UfsHc {
     UfsCq *cq[UFS_MAX_MCQ_QNUM];
 
     uint8_t temperature;
+
+    QEMUTimer idle_timer;
 } UfsHc;
 
 static inline uint32_t ufs_mcq_sq_tail(UfsHc *u, uint32_t qid)

-- 
2.48.1
Re: [PATCH v3 3/5] hw/ufs: Add idle operation
Posted by Jeuk Kim 2 days, 8 hours ago
On 4/9/2026 2:37 PM, Jaemyung Lee wrote:
> When no I/O occurs, the UFS Device performs various internal operations.
> To emulate this, adds a timer that periodically checks the current I/O
> status of the device and call the ufs_process_idle() function when idle.
>
> Signed-off-by: Jaemyung Lee <jaemyung.lee@samsung.com>
> ---
>   hw/ufs/ufs.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>   hw/ufs/ufs.h |  2 ++
>   2 files changed, 65 insertions(+)
>
> diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c
> index 69f82ab462d135fe6cda479891f9d8f26d19be9a..33887ac12a4bae647acb94b292f1d680bff9a007 100644
> --- a/hw/ufs/ufs.c
> +++ b/hw/ufs/ufs.c
> @@ -1801,6 +1801,63 @@ static void ufs_sendback_req(void *opaque)
>       ufs_irq_check(u);
>   }
>   
> +static void ufs_process_idle(UfsHc *u)
> +{
> +    /* Currently do nothing */
> +    return;
> +}
> +
> +static inline bool ufs_check_idle(UfsHc *u)
> +{
> +    return !u->reg.utrldbr;
> +}
> +
> +static inline bool ufs_mcq_check_idle(UfsHc *u)
> +{
> +    if (!u->params.mcq) {
> +        return true;
> +    }
> +
> +    for (int qid = 0; qid < ARRAY_SIZE(u->sq); qid++) {
> +        if (!ufs_mcq_sq_empty(u, qid) || !u->sq[qid]) {

This is still not correct.

Only actually created MCQ queues are non-NULL, so NULL entries must be 
skipped, not treated as non-idle.

Please change it like this:
if (!u->sq[qid]) {
     continue;
}
if (!ufs_mcq_sq_empty(u, qid)) {
     return false;
}


and:
if (!u->cq[qid]) {
     continue;
}
if (!ufs_mcq_cq_empty(u, qid)) {
     return false;
}
if (!QTAILQ_EMPTY(&u->cq[qid]->req_list)) {
     return false;
}


> +            return false;
> +        }
> +
> +        /* internal ongoing MCQ request check */
> +        UfsSq *sq = u->sq[qid];
> +        for (int i = 0; i < sq->size; i++) {
> +            if (sq->req[i].state != UFS_REQUEST_IDLE) {
> +                return false;
> +            }
> +        }
> +    }
> +
> +    for (int qid = 0; qid < ARRAY_SIZE(u->cq); qid++) {
> +        if (!ufs_mcq_cq_empty(u, qid) || !u->cq[qid]) {
> +            return false;
> +        }
> +
> +        if (!QTAILQ_EMPTY(&u->cq[qid]->req_list)) {
> +            return false;
> +        }
> +    }
> +
> +    return true;
> +}
> +
> +#define UFS_IDLE_TIMER_TICK 100 /* 0.1s */
> +static void ufs_idle_timer_cb(void *opaque)
> +{
> +    UfsHc *u = opaque;
> +    int64_t now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL_RT);
> +
> +    if (ufs_check_idle(u) && ufs_mcq_check_idle(u)) {
> +        ufs_process_idle(u);
> +    }
> +
> +    timer_mod(&u->idle_timer, now + UFS_IDLE_TIMER_TICK);
> +}
> +
>   static bool ufs_check_constraints(UfsHc *u, Error **errp)
>   {
>       if (u->params.nutrs > UFS_MAX_NUTRS) {
> @@ -1863,6 +1920,7 @@ static void ufs_init_hc(UfsHc *u)
>       uint32_t cap = 0;
>       uint32_t mcqconfig = 0;
>       uint32_t mcqcap = 0;
> +    int64_t now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL_RT);
>   
>       u->reg_size = pow2ceil(ufs_reg_size(u));
>   
> @@ -1959,6 +2017,9 @@ static void ufs_init_hc(UfsHc *u)
>        * dynamically
>        */
>       u->temperature = UFS_TEMPERATURE;
> +
> +    timer_init_ms(&u->idle_timer, QEMU_CLOCK_VIRTUAL_RT, ufs_idle_timer_cb, u);
> +    timer_mod(&u->idle_timer, now + UFS_IDLE_TIMER_TICK);
>   }
>   
>   static void ufs_realize(PCIDevice *pci_dev, Error **errp)
> @@ -1986,6 +2047,8 @@ static void ufs_exit(PCIDevice *pci_dev)
>   {
>       UfsHc *u = UFS(pci_dev);
>   
> +    timer_del(&u->idle_timer);
> +
>       qemu_free_irq(u->irq);
>   
>       qemu_bh_delete(u->doorbell_bh);
> diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h
> index 13d964c5ae5ec430a98b2ef71987cb9279e9a317..b5f040302129f4d02732ddd20ef82eb33c41922a 100644
> --- a/hw/ufs/ufs.h
> +++ b/hw/ufs/ufs.h
> @@ -148,6 +148,8 @@ typedef struct UfsHc {
>       UfsCq *cq[UFS_MAX_MCQ_QNUM];
>   
>       uint8_t temperature;
> +
> +    QEMUTimer idle_timer;
>   } UfsHc;
>   
>   static inline uint32_t ufs_mcq_sq_tail(UfsHc *u, uint32_t qid)
>