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 | 47 +++++++++++++++++++++++++++++++++++++++++++++++
hw/ufs/ufs.h | 2 ++
2 files changed, 49 insertions(+)
diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c
index 69f82ab462d135fe6cda479891f9d8f26d19be9a..111ce3c3a0b03a1ead722f48b799e3ed0c403cce 100644
--- a/hw/ufs/ufs.c
+++ b/hw/ufs/ufs.c
@@ -1801,6 +1801,47 @@ 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)
+{
+ for (int i = 0; i < ARRAY_SIZE(u->sq); i++) {
+ if (!ufs_mcq_sq_empty(u, i)) {
+ return false;
+ }
+ }
+
+ for (int i = 0; i < ARRAY_SIZE(u->cq); i++) {
+ if (!ufs_mcq_cq_empty(u, i)) {
+ 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 +1904,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 +2001,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 +2031,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
On 4/2/2026 6:43 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 | 47 +++++++++++++++++++++++++++++++++++++++++++++++
> hw/ufs/ufs.h | 2 ++
> 2 files changed, 49 insertions(+)
>
> diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c
> index 69f82ab462d135fe6cda479891f9d8f26d19be9a..111ce3c3a0b03a1ead722f48b799e3ed0c403cce 100644
> --- a/hw/ufs/ufs.c
> +++ b/hw/ufs/ufs.c
> @@ -1801,6 +1801,47 @@ 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)
> +{
> + for (int i = 0; i < ARRAY_SIZE(u->sq); i++) {
> + if (!ufs_mcq_sq_empty(u, i)) {
> + return false;
> + }
> + }
> +
> + for (int i = 0; i < ARRAY_SIZE(u->cq); i++) {
> + if (!ufs_mcq_cq_empty(u, i)) {
> + return false;
> + }
> + }
> +
> + return true;
> +}
I think the idle check needs to consider the internal MCQ request state
as well, not just the guest-visible SQ/CQ state.
In MCQ, a request goes through
SQ free list -> RUNNING -> COMPLETE -> CQ pending list -> guest CQ
write-back -> free list again.
So a request can already be dequeued from SQ and still be
UFS_REQUEST_RUNNING, or it can already be completed and still be pending
CQ write-back.
The idle check should also cover active internal requests and pending CQ
completions, not just SQ/CQ head/tail visibility.
> +
> +#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 +1904,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 +2001,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 +2031,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)
>
© 2016 - 2026 Red Hat, Inc.