* [PATCH v4 1/5] virtio: rtc: Add virtio RTC driver
2026-10-21 11:07 [PATCH v4 0/5] virtio: Add a virtio RTC driver Hung-Chun Tseng
@ 2026-10-21 11:07 ` Hung-Chun Tseng
2026-10-27 6:32 ` Kuan-Wei Chiu
2026-10-21 11:07 ` [PATCH v4 2/5] doc: virtio: List the supported device types Hung-Chun Tseng
` (3 subsequent siblings)
4 siblings, 1 reply; 8+ messages in thread
From: Hung-Chun Tseng @ 2026-10-21 11:07 UTC (permalink / raw)
To: u-boot; +Cc: bmeng.cn, trini, visitorckw, sjg, xypron.glpk
QEMU provides a virtio RTC device (virtio-rtc-pci, virtio-rtc-device)
since v11.1.0, but U-Boot has no driver for it.
The virtqueue must not be used before the virtio uclass sets DRIVER_OK,
which happens after probe(), so the clock is looked up on the first read.
Like Linux, the driver prefers a VIRTIO_RTC_CLOCK_UTC_SMEARED clock, as
it never steps on leap seconds. QEMU master reports such a clock since
"virtio-rtc: Report smeared UTC clock type", but v11.1 reports
VIRTIO_RTC_CLOCK_UTC, so the driver falls back to another UTC clock
rather than leave U-Boot without an RTC there.
The set of clocks never changes, so if none of them is UTC, the driver
remembers it and fails later reads at once.
Signed-off-by: Hung-Chun Tseng <alan.tseng.cs@gmail.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
---
Changes in v4:
- Cut the commit message down to why the driver works as it does
(Kuan-Wei)
- Say that QEMU master now reports a smeared UTC clock and that the
fallback is for v11.1 (Kuan-Wei)
- Drop the comment on giving up after a failed request, which is plain
error handling (Kuan-Wei)
Changes in v3:
- Give up on a request after one second instead of polling forever
(Kuan-Wei)
- Remember that no clock is UTC, so that later reads fail at once
(Simon)
- Expand the VIRTIO_RTC help text (Simon)
- Drop __packed from the message structs, which are naturally aligned
(Kuan-Wei)
- Prefer a VIRTIO_RTC_CLOCK_UTC_SMEARED clock as Linux does, fall back
to the first other UTC clock, and explain why in the commit message
(Kuan-Wei)
- Stop looking for a clock as soon as a request fails, as Linux does
(Kuan-Wei)
- Mention in the commit message that sandbox tests follow (Simon)
Changes in v2:
- Look for the UTC clock on the first virtio_rtc_get() instead of in
probe(), as the virtqueue must not be used before DRIVER_OK (Bin)
drivers/virtio/Kconfig | 11 ++
drivers/virtio/Makefile | 1 +
drivers/virtio/virtio-uclass.c | 1 +
drivers/virtio/virtio_rtc.c | 244 +++++++++++++++++++++++++++++++++
include/virtio.h | 4 +-
5 files changed, 260 insertions(+), 1 deletion(-)
create mode 100644 drivers/virtio/virtio_rtc.c
diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig
index 55b444280..48ab669f3 100644
--- a/drivers/virtio/Kconfig
+++ b/drivers/virtio/Kconfig
@@ -84,4 +84,15 @@ config VIRTIO_9P
help
This enables the 9P transport driver over virtio.
It can be used with QEMU based targets.
+
+config VIRTIO_RTC
+ bool "virtio RTC driver"
+ depends on DM_RTC
+ depends on VIRTIO
+ default y
+ help
+ This enables the virtio RTC driver, which reads the UTC clock
+ exposed by a virtio RTC device. QEMU v11.1.0 or later provides
+ it as virtio-rtc-pci and virtio-rtc-device. The clocks are
+ read-only, so the time cannot be set.
endmenu
diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile
index 6e7bcfc8e..ddb5ce4eb 100644
--- a/drivers/virtio/Makefile
+++ b/drivers/virtio/Makefile
@@ -12,3 +12,4 @@ obj-$(CONFIG_VIRTIO_NET) += virtio_net.o
obj-$(CONFIG_VIRTIO_BLK) += virtio_blk.o
obj-$(CONFIG_VIRTIO_RNG) += virtio_rng.o
obj-$(CONFIG_VIRTIO_9P) += virtio_9p.o
+obj-$(CONFIG_VIRTIO_RTC) += virtio_rtc.o
diff --git a/drivers/virtio/virtio-uclass.c b/drivers/virtio/virtio-uclass.c
index c163b2fd7..c6cf55922 100644
--- a/drivers/virtio/virtio-uclass.c
+++ b/drivers/virtio/virtio-uclass.c
@@ -31,6 +31,7 @@ static const char *const virtio_drv_name[VIRTIO_ID_MAX_NUM] = {
[VIRTIO_ID_BLOCK] = VIRTIO_BLK_DRV_NAME,
[VIRTIO_ID_RNG] = VIRTIO_RNG_DRV_NAME,
[VIRTIO_ID_9P] = VIRTIO_9P_DRV_NAME,
+ [VIRTIO_ID_CLOCK] = VIRTIO_RTC_DRV_NAME,
};
int virtio_get_config(struct udevice *vdev, unsigned int offset,
diff --git a/drivers/virtio/virtio_rtc.c b/drivers/virtio/virtio_rtc.c
new file mode 100644
index 000000000..f164c75e0
--- /dev/null
+++ b/drivers/virtio/virtio_rtc.c
@@ -0,0 +1,244 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2026, Hung-Chun Tseng <alan.tseng.cs@gmail.com>
+ *
+ * Driver for the virtio RTC device (virtio device ID 17). The device
+ * exposes one or more read-only clocks through a request virtqueue; this
+ * driver reads a UTC clock, preferring one which smears leap seconds, and
+ * reports it through the RTC uclass.
+ */
+
+#include <div64.h>
+#include <dm.h>
+#include <log.h>
+#include <rtc.h>
+#include <time.h>
+#include <virtio_types.h>
+#include <virtio.h>
+#include <virtio_ring.h>
+#include <linux/types.h>
+
+#define VIRTIO_RTC_TIMEOUT_MS 1000
+
+#define VIRTIO_RTC_REQ_READ 0x0001
+#define VIRTIO_RTC_REQ_CFG 0x1000
+#define VIRTIO_RTC_REQ_CLOCK_CAP 0x1001
+
+#define VIRTIO_RTC_S_OK 0
+
+#define VIRTIO_RTC_CLOCK_UTC 0
+#define VIRTIO_RTC_CLOCK_UTC_SMEARED 3
+#define VIRTIO_RTC_CLOCK_UTC_MAYBE_SMEARED 4
+
+struct virtio_rtc_req_head {
+ __le16 msg_type;
+ u8 reserved[6];
+};
+
+struct virtio_rtc_resp_head {
+ u8 status;
+ u8 reserved[7];
+};
+
+/* Request for VIRTIO_RTC_REQ_READ and VIRTIO_RTC_REQ_CLOCK_CAP */
+struct virtio_rtc_req_clock {
+ struct virtio_rtc_req_head head;
+ __le16 clock_id;
+ u8 reserved[6];
+};
+
+struct virtio_rtc_resp_read {
+ struct virtio_rtc_resp_head head;
+ __le64 clock_reading;
+};
+
+struct virtio_rtc_resp_cfg {
+ struct virtio_rtc_resp_head head;
+ __le16 num_clocks;
+ u8 reserved[6];
+};
+
+struct virtio_rtc_resp_clock_cap {
+ struct virtio_rtc_resp_head head;
+ u8 type;
+ u8 leap_second_smearing;
+ u8 flags;
+ u8 reserved[5];
+};
+
+struct virtio_rtc_priv {
+ struct virtqueue *vq;
+ u16 clock_id;
+ bool clock_found;
+ bool no_utc_clock;
+};
+
+static int virtio_rtc_xfer(struct udevice *dev, void *req, size_t req_len,
+ void *resp, size_t resp_len)
+{
+ struct virtio_rtc_priv *priv = dev_get_priv(dev);
+ struct virtio_rtc_resp_head *head = resp;
+ struct virtio_sg req_sg = { req, req_len };
+ struct virtio_sg resp_sg = { resp, resp_len };
+ struct virtio_sg *sgs[] = { &req_sg, &resp_sg };
+ unsigned int len;
+ ulong start;
+ int ret;
+
+ ret = virtqueue_add(priv->vq, sgs, 1, 1);
+ if (ret)
+ return ret;
+
+ virtqueue_kick(priv->vq);
+
+ start = get_timer(0);
+ while (!virtqueue_get_buf(priv->vq, &len)) {
+ if (get_timer(start) > VIRTIO_RTC_TIMEOUT_MS)
+ return -ETIMEDOUT;
+ }
+
+ if (len < sizeof(*head) || head->status != VIRTIO_RTC_S_OK)
+ return -EIO;
+
+ if (len < resp_len)
+ return -EPROTO;
+
+ return 0;
+}
+
+static bool virtio_rtc_is_utc(u8 type)
+{
+ return type == VIRTIO_RTC_CLOCK_UTC ||
+ type == VIRTIO_RTC_CLOCK_UTC_SMEARED ||
+ type == VIRTIO_RTC_CLOCK_UTC_MAYBE_SMEARED;
+}
+
+static int virtio_rtc_find_utc_clock(struct udevice *dev)
+{
+ struct virtio_rtc_priv *priv = dev_get_priv(dev);
+ struct virtio_rtc_req_head cfg_req = {
+ .msg_type = cpu_to_le16(VIRTIO_RTC_REQ_CFG),
+ };
+ struct virtio_rtc_resp_cfg cfg = {};
+ u16 num_clocks, id, utc_id = 0;
+ bool utc_found = false;
+ int ret;
+
+ ret = virtio_rtc_xfer(dev, &cfg_req, sizeof(cfg_req), &cfg, sizeof(cfg));
+ if (ret)
+ return ret;
+
+ num_clocks = le16_to_cpu(cfg.num_clocks);
+ for (id = 0; id < num_clocks; id++) {
+ struct virtio_rtc_req_clock req = {
+ .head.msg_type = cpu_to_le16(VIRTIO_RTC_REQ_CLOCK_CAP),
+ .clock_id = cpu_to_le16(id),
+ };
+ struct virtio_rtc_resp_clock_cap cap = {};
+
+ ret = virtio_rtc_xfer(dev, &req, sizeof(req), &cap, sizeof(cap));
+ if (ret)
+ return ret;
+
+ /*
+ * Like Linux, prefer a clock which smears leap seconds, but fall
+ * back to another UTC clock, which is all that QEMU v11.1 has.
+ */
+ if (cap.type == VIRTIO_RTC_CLOCK_UTC_SMEARED) {
+ utc_id = id;
+ utc_found = true;
+ break;
+ }
+
+ if (!utc_found && virtio_rtc_is_utc(cap.type)) {
+ utc_id = id;
+ utc_found = true;
+ }
+ }
+
+ /* The set of clocks is fixed, so remember that none of them is UTC */
+ if (!utc_found) {
+ priv->no_utc_clock = true;
+ return -ENODEV;
+ }
+
+ priv->clock_id = utc_id;
+ priv->clock_found = true;
+
+ return 0;
+}
+
+static int virtio_rtc_get(struct udevice *dev, struct rtc_time *time)
+{
+ struct virtio_rtc_priv *priv = dev_get_priv(dev);
+ struct virtio_rtc_req_clock req = {
+ .head.msg_type = cpu_to_le16(VIRTIO_RTC_REQ_READ),
+ };
+ struct virtio_rtc_resp_read resp = {};
+ u64 now;
+ int ret;
+
+ /*
+ * The virtqueue must not be used before DRIVER_OK, which the virtio
+ * uclass sets only after probe(), so look for the clock on first use.
+ */
+ if (priv->no_utc_clock)
+ return -ENODEV;
+
+ if (!priv->clock_found) {
+ ret = virtio_rtc_find_utc_clock(dev);
+ if (ret)
+ return ret;
+ }
+
+ req.clock_id = cpu_to_le16(priv->clock_id);
+
+ ret = virtio_rtc_xfer(dev, &req, sizeof(req), &resp, sizeof(resp));
+ if (ret)
+ return ret;
+
+ now = le64_to_cpu(resp.clock_reading);
+ do_div(now, 1000000000U);
+ rtc_to_tm(now, time);
+
+ return 0;
+}
+
+static int virtio_rtc_bind(struct udevice *dev)
+{
+ struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(dev->parent);
+
+ /* The alarm feature is not supported */
+ virtio_driver_features_init(uc_priv, NULL, 0, NULL, 0);
+
+ return 0;
+}
+
+static int virtio_rtc_probe(struct udevice *dev)
+{
+ struct virtio_rtc_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ ret = virtio_find_vqs(dev, 1, &priv->vq);
+ if (ret < 0) {
+ debug("%s: virtio_find_vqs failed\n", __func__);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct rtc_ops virtio_rtc_ops = {
+ .get = virtio_rtc_get,
+};
+
+U_BOOT_DRIVER(virtio_rtc) = {
+ .name = VIRTIO_RTC_DRV_NAME,
+ .id = UCLASS_RTC,
+ .bind = virtio_rtc_bind,
+ .probe = virtio_rtc_probe,
+ .remove = virtio_reset,
+ .ops = &virtio_rtc_ops,
+ .priv_auto = sizeof(struct virtio_rtc_priv),
+ .flags = DM_FLAG_ACTIVE_DMA,
+};
diff --git a/include/virtio.h b/include/virtio.h
index b981eead5..95069a2de 100644
--- a/include/virtio.h
+++ b/include/virtio.h
@@ -32,12 +32,14 @@
#define VIRTIO_ID_BLOCK 2 /* virtio block */
#define VIRTIO_ID_RNG 4 /* virtio rng */
#define VIRTIO_ID_9P 9 /* virtio 9p */
-#define VIRTIO_ID_MAX_NUM 10
+#define VIRTIO_ID_CLOCK 17 /* virtio clock/timer (rtc) */
+#define VIRTIO_ID_MAX_NUM 18
#define VIRTIO_NET_DRV_NAME "virtio-net"
#define VIRTIO_BLK_DRV_NAME "virtio-blk"
#define VIRTIO_RNG_DRV_NAME "virtio-rng"
#define VIRTIO_9P_DRV_NAME "virtio-9p"
+#define VIRTIO_RTC_DRV_NAME "virtio-rtc"
/* Feature bits for virtio 9P */
#define VIRTIO_9P_MOUNT_TAG 0
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH v4 1/5] virtio: rtc: Add virtio RTC driver
2026-10-21 11:07 ` [PATCH v4 1/5] virtio: rtc: Add " Hung-Chun Tseng
@ 2026-10-27 6:32 ` Kuan-Wei Chiu
0 siblings, 0 replies; 8+ messages in thread
From: Kuan-Wei Chiu @ 2026-10-27 6:32 UTC (permalink / raw)
To: Hung-Chun Tseng; +Cc: u-boot, bmeng.cn, trini, sjg, xypron.glpk
On Wed, Oct 21, 2026 at 07:07:02PM +0800, Hung-Chun Tseng wrote:
> QEMU provides a virtio RTC device (virtio-rtc-pci, virtio-rtc-device)
> since v11.1.0, but U-Boot has no driver for it.
>
> The virtqueue must not be used before the virtio uclass sets DRIVER_OK,
> which happens after probe(), so the clock is looked up on the first read.
>
> Like Linux, the driver prefers a VIRTIO_RTC_CLOCK_UTC_SMEARED clock, as
> it never steps on leap seconds. QEMU master reports such a clock since
> "virtio-rtc: Report smeared UTC clock type", but v11.1 reports
> VIRTIO_RTC_CLOCK_UTC, so the driver falls back to another UTC clock
> rather than leave U-Boot without an RTC there.
>
> The set of clocks never changes, so if none of them is UTC, the driver
> remembers it and fails later reads at once.
>
> Signed-off-by: Hung-Chun Tseng <alan.tseng.cs@gmail.com>
> Reviewed-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Kuan-Wei Chiu <visitorckw@gmail.com>
Regards,
Kuan-Wei
> ---
> Changes in v4:
> - Cut the commit message down to why the driver works as it does
> (Kuan-Wei)
> - Say that QEMU master now reports a smeared UTC clock and that the
> fallback is for v11.1 (Kuan-Wei)
> - Drop the comment on giving up after a failed request, which is plain
> error handling (Kuan-Wei)
>
> Changes in v3:
> - Give up on a request after one second instead of polling forever
> (Kuan-Wei)
> - Remember that no clock is UTC, so that later reads fail at once
> (Simon)
> - Expand the VIRTIO_RTC help text (Simon)
> - Drop __packed from the message structs, which are naturally aligned
> (Kuan-Wei)
> - Prefer a VIRTIO_RTC_CLOCK_UTC_SMEARED clock as Linux does, fall back
> to the first other UTC clock, and explain why in the commit message
> (Kuan-Wei)
> - Stop looking for a clock as soon as a request fails, as Linux does
> (Kuan-Wei)
> - Mention in the commit message that sandbox tests follow (Simon)
>
> Changes in v2:
> - Look for the UTC clock on the first virtio_rtc_get() instead of in
> probe(), as the virtqueue must not be used before DRIVER_OK (Bin)
>
> drivers/virtio/Kconfig | 11 ++
> drivers/virtio/Makefile | 1 +
> drivers/virtio/virtio-uclass.c | 1 +
> drivers/virtio/virtio_rtc.c | 244 +++++++++++++++++++++++++++++++++
> include/virtio.h | 4 +-
> 5 files changed, 260 insertions(+), 1 deletion(-)
> create mode 100644 drivers/virtio/virtio_rtc.c
>
> diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig
> index 55b444280..48ab669f3 100644
> --- a/drivers/virtio/Kconfig
> +++ b/drivers/virtio/Kconfig
> @@ -84,4 +84,15 @@ config VIRTIO_9P
> help
> This enables the 9P transport driver over virtio.
> It can be used with QEMU based targets.
> +
> +config VIRTIO_RTC
> + bool "virtio RTC driver"
> + depends on DM_RTC
> + depends on VIRTIO
> + default y
> + help
> + This enables the virtio RTC driver, which reads the UTC clock
> + exposed by a virtio RTC device. QEMU v11.1.0 or later provides
> + it as virtio-rtc-pci and virtio-rtc-device. The clocks are
> + read-only, so the time cannot be set.
> endmenu
> diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile
> index 6e7bcfc8e..ddb5ce4eb 100644
> --- a/drivers/virtio/Makefile
> +++ b/drivers/virtio/Makefile
> @@ -12,3 +12,4 @@ obj-$(CONFIG_VIRTIO_NET) += virtio_net.o
> obj-$(CONFIG_VIRTIO_BLK) += virtio_blk.o
> obj-$(CONFIG_VIRTIO_RNG) += virtio_rng.o
> obj-$(CONFIG_VIRTIO_9P) += virtio_9p.o
> +obj-$(CONFIG_VIRTIO_RTC) += virtio_rtc.o
> diff --git a/drivers/virtio/virtio-uclass.c b/drivers/virtio/virtio-uclass.c
> index c163b2fd7..c6cf55922 100644
> --- a/drivers/virtio/virtio-uclass.c
> +++ b/drivers/virtio/virtio-uclass.c
> @@ -31,6 +31,7 @@ static const char *const virtio_drv_name[VIRTIO_ID_MAX_NUM] = {
> [VIRTIO_ID_BLOCK] = VIRTIO_BLK_DRV_NAME,
> [VIRTIO_ID_RNG] = VIRTIO_RNG_DRV_NAME,
> [VIRTIO_ID_9P] = VIRTIO_9P_DRV_NAME,
> + [VIRTIO_ID_CLOCK] = VIRTIO_RTC_DRV_NAME,
> };
>
> int virtio_get_config(struct udevice *vdev, unsigned int offset,
> diff --git a/drivers/virtio/virtio_rtc.c b/drivers/virtio/virtio_rtc.c
> new file mode 100644
> index 000000000..f164c75e0
> --- /dev/null
> +++ b/drivers/virtio/virtio_rtc.c
> @@ -0,0 +1,244 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2026, Hung-Chun Tseng <alan.tseng.cs@gmail.com>
> + *
> + * Driver for the virtio RTC device (virtio device ID 17). The device
> + * exposes one or more read-only clocks through a request virtqueue; this
> + * driver reads a UTC clock, preferring one which smears leap seconds, and
> + * reports it through the RTC uclass.
> + */
> +
> +#include <div64.h>
> +#include <dm.h>
> +#include <log.h>
> +#include <rtc.h>
> +#include <time.h>
> +#include <virtio_types.h>
> +#include <virtio.h>
> +#include <virtio_ring.h>
> +#include <linux/types.h>
> +
> +#define VIRTIO_RTC_TIMEOUT_MS 1000
> +
> +#define VIRTIO_RTC_REQ_READ 0x0001
> +#define VIRTIO_RTC_REQ_CFG 0x1000
> +#define VIRTIO_RTC_REQ_CLOCK_CAP 0x1001
> +
> +#define VIRTIO_RTC_S_OK 0
> +
> +#define VIRTIO_RTC_CLOCK_UTC 0
> +#define VIRTIO_RTC_CLOCK_UTC_SMEARED 3
> +#define VIRTIO_RTC_CLOCK_UTC_MAYBE_SMEARED 4
> +
> +struct virtio_rtc_req_head {
> + __le16 msg_type;
> + u8 reserved[6];
> +};
> +
> +struct virtio_rtc_resp_head {
> + u8 status;
> + u8 reserved[7];
> +};
> +
> +/* Request for VIRTIO_RTC_REQ_READ and VIRTIO_RTC_REQ_CLOCK_CAP */
> +struct virtio_rtc_req_clock {
> + struct virtio_rtc_req_head head;
> + __le16 clock_id;
> + u8 reserved[6];
> +};
> +
> +struct virtio_rtc_resp_read {
> + struct virtio_rtc_resp_head head;
> + __le64 clock_reading;
> +};
> +
> +struct virtio_rtc_resp_cfg {
> + struct virtio_rtc_resp_head head;
> + __le16 num_clocks;
> + u8 reserved[6];
> +};
> +
> +struct virtio_rtc_resp_clock_cap {
> + struct virtio_rtc_resp_head head;
> + u8 type;
> + u8 leap_second_smearing;
> + u8 flags;
> + u8 reserved[5];
> +};
> +
> +struct virtio_rtc_priv {
> + struct virtqueue *vq;
> + u16 clock_id;
> + bool clock_found;
> + bool no_utc_clock;
> +};
> +
> +static int virtio_rtc_xfer(struct udevice *dev, void *req, size_t req_len,
> + void *resp, size_t resp_len)
> +{
> + struct virtio_rtc_priv *priv = dev_get_priv(dev);
> + struct virtio_rtc_resp_head *head = resp;
> + struct virtio_sg req_sg = { req, req_len };
> + struct virtio_sg resp_sg = { resp, resp_len };
> + struct virtio_sg *sgs[] = { &req_sg, &resp_sg };
> + unsigned int len;
> + ulong start;
> + int ret;
> +
> + ret = virtqueue_add(priv->vq, sgs, 1, 1);
> + if (ret)
> + return ret;
> +
> + virtqueue_kick(priv->vq);
> +
> + start = get_timer(0);
> + while (!virtqueue_get_buf(priv->vq, &len)) {
> + if (get_timer(start) > VIRTIO_RTC_TIMEOUT_MS)
> + return -ETIMEDOUT;
> + }
> +
> + if (len < sizeof(*head) || head->status != VIRTIO_RTC_S_OK)
> + return -EIO;
> +
> + if (len < resp_len)
> + return -EPROTO;
> +
> + return 0;
> +}
> +
> +static bool virtio_rtc_is_utc(u8 type)
> +{
> + return type == VIRTIO_RTC_CLOCK_UTC ||
> + type == VIRTIO_RTC_CLOCK_UTC_SMEARED ||
> + type == VIRTIO_RTC_CLOCK_UTC_MAYBE_SMEARED;
> +}
> +
> +static int virtio_rtc_find_utc_clock(struct udevice *dev)
> +{
> + struct virtio_rtc_priv *priv = dev_get_priv(dev);
> + struct virtio_rtc_req_head cfg_req = {
> + .msg_type = cpu_to_le16(VIRTIO_RTC_REQ_CFG),
> + };
> + struct virtio_rtc_resp_cfg cfg = {};
> + u16 num_clocks, id, utc_id = 0;
> + bool utc_found = false;
> + int ret;
> +
> + ret = virtio_rtc_xfer(dev, &cfg_req, sizeof(cfg_req), &cfg, sizeof(cfg));
> + if (ret)
> + return ret;
> +
> + num_clocks = le16_to_cpu(cfg.num_clocks);
> + for (id = 0; id < num_clocks; id++) {
> + struct virtio_rtc_req_clock req = {
> + .head.msg_type = cpu_to_le16(VIRTIO_RTC_REQ_CLOCK_CAP),
> + .clock_id = cpu_to_le16(id),
> + };
> + struct virtio_rtc_resp_clock_cap cap = {};
> +
> + ret = virtio_rtc_xfer(dev, &req, sizeof(req), &cap, sizeof(cap));
> + if (ret)
> + return ret;
> +
> + /*
> + * Like Linux, prefer a clock which smears leap seconds, but fall
> + * back to another UTC clock, which is all that QEMU v11.1 has.
> + */
> + if (cap.type == VIRTIO_RTC_CLOCK_UTC_SMEARED) {
> + utc_id = id;
> + utc_found = true;
> + break;
> + }
> +
> + if (!utc_found && virtio_rtc_is_utc(cap.type)) {
> + utc_id = id;
> + utc_found = true;
> + }
> + }
> +
> + /* The set of clocks is fixed, so remember that none of them is UTC */
> + if (!utc_found) {
> + priv->no_utc_clock = true;
> + return -ENODEV;
> + }
> +
> + priv->clock_id = utc_id;
> + priv->clock_found = true;
> +
> + return 0;
> +}
> +
> +static int virtio_rtc_get(struct udevice *dev, struct rtc_time *time)
> +{
> + struct virtio_rtc_priv *priv = dev_get_priv(dev);
> + struct virtio_rtc_req_clock req = {
> + .head.msg_type = cpu_to_le16(VIRTIO_RTC_REQ_READ),
> + };
> + struct virtio_rtc_resp_read resp = {};
> + u64 now;
> + int ret;
> +
> + /*
> + * The virtqueue must not be used before DRIVER_OK, which the virtio
> + * uclass sets only after probe(), so look for the clock on first use.
> + */
> + if (priv->no_utc_clock)
> + return -ENODEV;
> +
> + if (!priv->clock_found) {
> + ret = virtio_rtc_find_utc_clock(dev);
> + if (ret)
> + return ret;
> + }
> +
> + req.clock_id = cpu_to_le16(priv->clock_id);
> +
> + ret = virtio_rtc_xfer(dev, &req, sizeof(req), &resp, sizeof(resp));
> + if (ret)
> + return ret;
> +
> + now = le64_to_cpu(resp.clock_reading);
> + do_div(now, 1000000000U);
> + rtc_to_tm(now, time);
> +
> + return 0;
> +}
> +
> +static int virtio_rtc_bind(struct udevice *dev)
> +{
> + struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(dev->parent);
> +
> + /* The alarm feature is not supported */
> + virtio_driver_features_init(uc_priv, NULL, 0, NULL, 0);
> +
> + return 0;
> +}
> +
> +static int virtio_rtc_probe(struct udevice *dev)
> +{
> + struct virtio_rtc_priv *priv = dev_get_priv(dev);
> + int ret;
> +
> + ret = virtio_find_vqs(dev, 1, &priv->vq);
> + if (ret < 0) {
> + debug("%s: virtio_find_vqs failed\n", __func__);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static const struct rtc_ops virtio_rtc_ops = {
> + .get = virtio_rtc_get,
> +};
> +
> +U_BOOT_DRIVER(virtio_rtc) = {
> + .name = VIRTIO_RTC_DRV_NAME,
> + .id = UCLASS_RTC,
> + .bind = virtio_rtc_bind,
> + .probe = virtio_rtc_probe,
> + .remove = virtio_reset,
> + .ops = &virtio_rtc_ops,
> + .priv_auto = sizeof(struct virtio_rtc_priv),
> + .flags = DM_FLAG_ACTIVE_DMA,
> +};
> diff --git a/include/virtio.h b/include/virtio.h
> index b981eead5..95069a2de 100644
> --- a/include/virtio.h
> +++ b/include/virtio.h
> @@ -32,12 +32,14 @@
> #define VIRTIO_ID_BLOCK 2 /* virtio block */
> #define VIRTIO_ID_RNG 4 /* virtio rng */
> #define VIRTIO_ID_9P 9 /* virtio 9p */
> -#define VIRTIO_ID_MAX_NUM 10
> +#define VIRTIO_ID_CLOCK 17 /* virtio clock/timer (rtc) */
> +#define VIRTIO_ID_MAX_NUM 18
>
> #define VIRTIO_NET_DRV_NAME "virtio-net"
> #define VIRTIO_BLK_DRV_NAME "virtio-blk"
> #define VIRTIO_RNG_DRV_NAME "virtio-rng"
> #define VIRTIO_9P_DRV_NAME "virtio-9p"
> +#define VIRTIO_RTC_DRV_NAME "virtio-rtc"
>
> /* Feature bits for virtio 9P */
> #define VIRTIO_9P_MOUNT_TAG 0
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v4 2/5] doc: virtio: List the supported device types
2026-10-21 11:07 [PATCH v4 0/5] virtio: Add a virtio RTC driver Hung-Chun Tseng
2026-10-21 11:07 ` [PATCH v4 1/5] virtio: rtc: Add " Hung-Chun Tseng
@ 2026-10-21 11:07 ` Hung-Chun Tseng
2026-10-27 6:08 ` Kuan-Wei Chiu
2026-10-21 11:07 ` [PATCH v4 3/5] MAINTAINERS: Add entry for virtio RTC Hung-Chun Tseng
` (2 subsequent siblings)
4 siblings, 1 reply; 8+ messages in thread
From: Hung-Chun Tseng @ 2026-10-21 11:07 UTC (permalink / raw)
To: u-boot; +Cc: bmeng.cn, trini, visitorckw, sjg, xypron.glpk, Bin Meng
The list of supported device types still only named network and block
devices. Turn it into a bulleted list, which is easier to extend, and
add the entropy, 9P and RTC devices. Also note that the virtio RTC
device needs QEMU v11.1.0 or later.
Signed-off-by: Hung-Chun Tseng <alan.tseng.cs@gmail.com>
Reviewed-by: Bin Meng <bin.meng@processmission.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
---
(no changes since v3)
Changes in v3:
- Retitle from "doc: virtio: Mention the RTC device", as the patch
lists other devices too (Kuan-Wei)
- Turn the list of devices into a bulleted list (Kuan-Wei)
- Keep the Reviewed-by tags of Bin and Simon, as only the layout changed
doc/develop/driver-model/virtio.rst | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/doc/develop/driver-model/virtio.rst b/doc/develop/driver-model/virtio.rst
index e5da9f430..7b2f31b3d 100644
--- a/doc/develop/driver-model/virtio.rst
+++ b/doc/develop/driver-model/virtio.rst
@@ -28,7 +28,13 @@ queues configuration and buffer transfers are nearly identical. Both MMIO
and PCI transport options are supported in U-Boot.
The VirtIO spec defines a lots of VirtIO device types, however at present only
-network and block device, the most two commonly used devices, are supported.
+the following devices are supported.
+
+ - network
+ - block
+ - entropy (rng)
+ - 9P transport
+ - RTC
The following QEMU targets are supported.
@@ -72,6 +78,8 @@ Requirements
------------
It is required that QEMU v2.5.0+ should be used to test U-Boot VirtIO support
on QEMU ARM and x86, and v2.12.0+ on QEMU RISC-V.
+The VirtIO RTC device (``-device virtio-rtc-pci`` or
+``-device virtio-rtc-device``) requires QEMU v11.1.0+.
Testing
-------
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH v4 2/5] doc: virtio: List the supported device types
2026-10-21 11:07 ` [PATCH v4 2/5] doc: virtio: List the supported device types Hung-Chun Tseng
@ 2026-10-27 6:08 ` Kuan-Wei Chiu
0 siblings, 0 replies; 8+ messages in thread
From: Kuan-Wei Chiu @ 2026-10-27 6:08 UTC (permalink / raw)
To: Hung-Chun Tseng; +Cc: u-boot, bmeng.cn, trini, sjg, xypron.glpk, Bin Meng
On Wed, Oct 21, 2026 at 07:07:03PM +0800, Hung-Chun Tseng wrote:
> The list of supported device types still only named network and block
> devices. Turn it into a bulleted list, which is easier to extend, and
> add the entropy, 9P and RTC devices. Also note that the virtio RTC
> device needs QEMU v11.1.0 or later.
>
> Signed-off-by: Hung-Chun Tseng <alan.tseng.cs@gmail.com>
> Reviewed-by: Bin Meng <bin.meng@processmission.com>
> Reviewed-by: Simon Glass <sjg@chromium.org>
> ---
> (no changes since v3)
>
> Changes in v3:
> - Retitle from "doc: virtio: Mention the RTC device", as the patch
> lists other devices too (Kuan-Wei)
> - Turn the list of devices into a bulleted list (Kuan-Wei)
> - Keep the Reviewed-by tags of Bin and Simon, as only the layout changed
>
> doc/develop/driver-model/virtio.rst | 10 +++++++++-
Later in this same file under Development Flow still states:
"At present only VirtIO network card (device ID 1) and block device
(device ID 2) are supported."
Let's update that section as well.
Regards,
Kuan-Wei
> 1 file changed, 9 insertions(+), 1 deletion(-)
>
> diff --git a/doc/develop/driver-model/virtio.rst b/doc/develop/driver-model/virtio.rst
> index e5da9f430..7b2f31b3d 100644
> --- a/doc/develop/driver-model/virtio.rst
> +++ b/doc/develop/driver-model/virtio.rst
> @@ -28,7 +28,13 @@ queues configuration and buffer transfers are nearly identical. Both MMIO
> and PCI transport options are supported in U-Boot.
>
> The VirtIO spec defines a lots of VirtIO device types, however at present only
> -network and block device, the most two commonly used devices, are supported.
> +the following devices are supported.
> +
> + - network
> + - block
> + - entropy (rng)
> + - 9P transport
> + - RTC
>
> The following QEMU targets are supported.
>
> @@ -72,6 +78,8 @@ Requirements
> ------------
> It is required that QEMU v2.5.0+ should be used to test U-Boot VirtIO support
> on QEMU ARM and x86, and v2.12.0+ on QEMU RISC-V.
> +The VirtIO RTC device (``-device virtio-rtc-pci`` or
> +``-device virtio-rtc-device``) requires QEMU v11.1.0+.
>
> Testing
> -------
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v4 3/5] MAINTAINERS: Add entry for virtio RTC
2026-10-21 11:07 [PATCH v4 0/5] virtio: Add a virtio RTC driver Hung-Chun Tseng
2026-10-21 11:07 ` [PATCH v4 1/5] virtio: rtc: Add " Hung-Chun Tseng
2026-10-21 11:07 ` [PATCH v4 2/5] doc: virtio: List the supported device types Hung-Chun Tseng
@ 2026-10-21 11:07 ` Hung-Chun Tseng
2026-10-21 11:07 ` [PATCH v4 4/5] virtio: sandbox: Let tests emulate the device side of a virtqueue Hung-Chun Tseng
2026-10-21 11:07 ` [PATCH v4 5/5] test: dm: Add tests for the virtio RTC driver Hung-Chun Tseng
4 siblings, 0 replies; 8+ messages in thread
From: Hung-Chun Tseng @ 2026-10-21 11:07 UTC (permalink / raw)
To: u-boot; +Cc: bmeng.cn, trini, visitorckw, sjg, xypron.glpk, Bin Meng
Add myself as the maintainer of the virtio RTC driver.
Signed-off-by: Hung-Chun Tseng <alan.tseng.cs@gmail.com>
Reviewed-by: Bin Meng <bin.meng@processmission.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
---
(no changes since v3)
Changes in v3:
- Add test/dm/virtio_rtc.c, which this version adds
MAINTAINERS | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 28d090d3c..2ce94d7f5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1872,6 +1872,11 @@ F: include/virtio*.h
F: test/dm/virtio.c
F: doc/develop/driver-model/virtio.rst
+VIRTIO RTC
+M: Hung-Chun Tseng <alan.tseng.cs@gmail.com>
+S: Maintained
+F: drivers/virtio/virtio_rtc.c
+
WATCHDOG
M: Stefan Roese <stefan.roese@mailbox.org>
S: Maintained
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v4 4/5] virtio: sandbox: Let tests emulate the device side of a virtqueue
2026-10-21 11:07 [PATCH v4 0/5] virtio: Add a virtio RTC driver Hung-Chun Tseng
` (2 preceding siblings ...)
2026-10-21 11:07 ` [PATCH v4 3/5] MAINTAINERS: Add entry for virtio RTC Hung-Chun Tseng
@ 2026-10-21 11:07 ` Hung-Chun Tseng
2026-10-21 11:07 ` [PATCH v4 5/5] test: dm: Add tests for the virtio RTC driver Hung-Chun Tseng
4 siblings, 0 replies; 8+ messages in thread
From: Hung-Chun Tseng @ 2026-10-21 11:07 UTC (permalink / raw)
To: u-boot; +Cc: bmeng.cn, trini, visitorckw, sjg, xypron.glpk
The sandbox virtio transport ignores notifications, so a driver that
waits for the device to answer a request cannot be tested beyond its
probe. The virtio-rng test works around this by writing to the used
ring of the driver's virtqueue before reading.
Add sandbox_virtio_set_notify() so that a test can install a handler
which is called when the driver kicks a virtqueue. The handler can then
process the available ring and fill in the used ring, as a real device
would. Without a handler, notifications are still ignored.
Signed-off-by: Hung-Chun Tseng <alan.tseng.cs@gmail.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
---
(no changes since v3)
Changes in v3:
- New patch, needed by the next one
arch/sandbox/include/asm/test.h | 25 +++++++++++++++++++++++++
drivers/virtio/virtio_sandbox.c | 15 +++++++++++++++
2 files changed, 40 insertions(+)
diff --git a/arch/sandbox/include/asm/test.h b/arch/sandbox/include/asm/test.h
index 043879002..d95de1095 100644
--- a/arch/sandbox/include/asm/test.h
+++ b/arch/sandbox/include/asm/test.h
@@ -11,6 +11,8 @@
#include <pci_ids.h>
struct unit_test_state;
+struct udevice;
+struct virtqueue;
/* The sandbox driver always permits an I2C device with this address */
#define SANDBOX_I2C_TEST_ADDR 0x59
@@ -369,4 +371,27 @@ bool sandbox_sf_bootdev_enabled(void);
*/
void sandbox_sf_set_enable_bootdevs(bool enable);
+/**
+ * typedef sandbox_virtio_notify_t - Emulate the device side of a virtqueue
+ *
+ * @dev: sandbox virtio transport device
+ * @vq: virtqueue that was kicked
+ * Returns: 0 on success, or -ve error code
+ */
+typedef int (*sandbox_virtio_notify_t)(struct udevice *dev,
+ struct virtqueue *vq);
+
+/**
+ * sandbox_virtio_set_notify() - Set a handler for virtqueue notifications
+ *
+ * The sandbox virtio transport ignores notifications by default. A test can
+ * set a handler here to process the buffers the driver adds, like a real
+ * device would.
+ *
+ * @dev: sandbox virtio transport device
+ * @notify: handler to call, or NULL to ignore notifications again
+ */
+void sandbox_virtio_set_notify(struct udevice *dev,
+ sandbox_virtio_notify_t notify);
+
#endif
diff --git a/drivers/virtio/virtio_sandbox.c b/drivers/virtio/virtio_sandbox.c
index 0f1ebef22..3b8a4d5a6 100644
--- a/drivers/virtio/virtio_sandbox.c
+++ b/drivers/virtio/virtio_sandbox.c
@@ -13,6 +13,7 @@
#include <linux/compat.h>
#include <linux/err.h>
#include <linux/io.h>
+#include <asm/test.h>
struct virtio_sandbox_priv {
u8 id;
@@ -22,6 +23,7 @@ struct virtio_sandbox_priv {
ulong queue_desc;
ulong queue_available;
ulong queue_used;
+ sandbox_virtio_notify_t notify;
};
static int virtio_sandbox_get_config(struct udevice *udev, unsigned int offset,
@@ -150,9 +152,22 @@ static int virtio_sandbox_find_vqs(struct udevice *udev, unsigned int nvqs,
static int virtio_sandbox_notify(struct udevice *udev, struct virtqueue *vq)
{
+ struct virtio_sandbox_priv *priv = dev_get_priv(udev);
+
+ if (priv->notify)
+ return priv->notify(udev, vq);
+
return 0;
}
+void sandbox_virtio_set_notify(struct udevice *udev,
+ sandbox_virtio_notify_t notify)
+{
+ struct virtio_sandbox_priv *priv = dev_get_priv(udev);
+
+ priv->notify = notify;
+}
+
static int virtio_sandbox_probe(struct udevice *udev)
{
struct virtio_sandbox_priv *priv = dev_get_priv(udev);
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH v4 5/5] test: dm: Add tests for the virtio RTC driver
2026-10-21 11:07 [PATCH v4 0/5] virtio: Add a virtio RTC driver Hung-Chun Tseng
` (3 preceding siblings ...)
2026-10-21 11:07 ` [PATCH v4 4/5] virtio: sandbox: Let tests emulate the device side of a virtqueue Hung-Chun Tseng
@ 2026-10-21 11:07 ` Hung-Chun Tseng
4 siblings, 0 replies; 8+ messages in thread
From: Hung-Chun Tseng @ 2026-10-21 11:07 UTC (permalink / raw)
To: u-boot; +Cc: bmeng.cn, trini, visitorckw, sjg, xypron.glpk
Add a virtio RTC device to the sandbox test device tree and emulate it
through the sandbox virtio transport. The emulated device answers the
VIRTIO_RTC_REQ_CFG, VIRTIO_RTC_REQ_CLOCK_CAP and VIRTIO_RTC_REQ_READ
requests from a configurable list of clocks.
The tests check that the driver:
- does not use the virtqueue in probe()
- reads the time from the first UTC clock and looks it up only once
- remembers that there is no UTC clock and does not ask again
- reports a failed request and looks for the clock again next time
- gives up with -ETIMEDOUT when the device does not answer
Signed-off-by: Hung-Chun Tseng <alan.tseng.cs@gmail.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
---
(no changes since v3)
Changes in v3:
- New patch (Simon)
MAINTAINERS | 1 +
arch/sandbox/dts/test.dts | 5 +
test/dm/Makefile | 1 +
test/dm/virtio_rtc.c | 306 ++++++++++++++++++++++++++++++++++++++
4 files changed, 313 insertions(+)
create mode 100644 test/dm/virtio_rtc.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 2ce94d7f5..486bfea29 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1876,6 +1876,7 @@ VIRTIO RTC
M: Hung-Chun Tseng <alan.tseng.cs@gmail.com>
S: Maintained
F: drivers/virtio/virtio_rtc.c
+F: test/dm/virtio_rtc.c
WATCHDOG
M: Stefan Roese <stefan.roese@mailbox.org>
diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts
index 25c99ef81..758b04a1b 100644
--- a/arch/sandbox/dts/test.dts
+++ b/arch/sandbox/dts/test.dts
@@ -1974,6 +1974,11 @@
virtio-type = <2>; /* block */
};
+ sandbox-virtio-rtc {
+ compatible = "sandbox,virtio1";
+ virtio-type = <17>; /* clock */
+ };
+
sandbox_scmi {
compatible = "sandbox,scmi-devices";
power-domains = <&pwrdom_scmi 2>;
diff --git a/test/dm/Makefile b/test/dm/Makefile
index 5dd6b57bb..975adce55 100644
--- a/test/dm/Makefile
+++ b/test/dm/Makefile
@@ -142,6 +142,7 @@ ifeq ($(CONFIG_VIRTIO_SANDBOX),y)
obj-y += virtio.o
obj-$(CONFIG_VIRTIO_RNG) += virtio_device.o
obj-$(CONFIG_VIRTIO_RNG) += virtio_rng.o
+obj-$(CONFIG_VIRTIO_RTC) += virtio_rtc.o
endif
ifeq ($(CONFIG_WDT_GPIO)$(CONFIG_WDT_SANDBOX),yy)
obj-y += wdt.o
diff --git a/test/dm/virtio_rtc.c b/test/dm/virtio_rtc.c
new file mode 100644
index 000000000..23f7934dd
--- /dev/null
+++ b/test/dm/virtio_rtc.c
@@ -0,0 +1,306 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2026, Hung-Chun Tseng <alan.tseng.cs@gmail.com>
+ *
+ * Tests for the virtio RTC driver, with the device emulated through the
+ * sandbox virtio transport
+ */
+
+#include <dm.h>
+#include <rtc.h>
+#include <time.h>
+#include <virtio_types.h>
+#include <virtio.h>
+#include <virtio_ring.h>
+#include <asm/test.h>
+#include <dm/device-internal.h>
+#include <dm/test.h>
+#include <test/test.h>
+#include <test/ut.h>
+
+/* Message types and clock types from the virtio specification */
+#define REQ_READ 0x0001
+#define REQ_CFG 0x1000
+#define REQ_CLOCK_CAP 0x1001
+
+#define S_OK 0
+#define S_EIO 4
+
+#define CLOCK_UTC 0
+#define CLOCK_TAI 1
+#define CLOCK_MONOTONIC 2
+#define CLOCK_UTC_SMEARED 3
+#define CLOCK_UTC_MAYBE_SMEARED 4
+
+/* Request: le16 msg_type, 6 reserved bytes, then le16 clock_id */
+struct emul_req {
+ __le16 msg_type;
+ u8 reserved[6];
+ __le16 clock_id;
+};
+
+/* Response: u8 status, 7 reserved bytes, then the message payload */
+struct emul_resp {
+ u8 status;
+ u8 reserved[7];
+ union {
+ __le16 num_clocks;
+ u8 type;
+ __le64 clock_reading;
+ };
+};
+
+/* 2023-11-14 22:13:20 UTC, plus some nanoseconds */
+#define EMUL_TIME_S 1700000000ULL
+#define EMUL_TIME_NS (EMUL_TIME_S * 1000000000ULL + 123456789)
+
+#define EMUL_MAX_CLOCKS 4
+
+/**
+ * struct virtio_rtc_emul - State of the emulated device
+ *
+ * @num_clocks: number of clocks the device reports
+ * @types: type of each clock
+ * @cap_status: status to return for VIRTIO_RTC_REQ_CLOCK_CAP
+ * @no_reply: do not answer requests, but move time past the timeout
+ * @cfg_count: number of VIRTIO_RTC_REQ_CFG requests seen
+ * @cap_count: number of VIRTIO_RTC_REQ_CLOCK_CAP requests seen
+ * @read_count: number of VIRTIO_RTC_REQ_READ requests seen
+ * @read_clock: clock ID of the last VIRTIO_RTC_REQ_READ
+ * @last_avail: next available ring entry to process
+ */
+struct virtio_rtc_emul {
+ u16 num_clocks;
+ u8 types[EMUL_MAX_CLOCKS];
+ u8 cap_status;
+ bool no_reply;
+ int cfg_count;
+ int cap_count;
+ int read_count;
+ u16 read_clock;
+ u16 last_avail;
+};
+
+static struct virtio_rtc_emul emul;
+
+static u32 emul_handle(const struct emul_req *req, struct emul_resp *resp)
+{
+ u16 clock_id = le16_to_cpu(req->clock_id);
+
+ memset(resp, '\0', sizeof(*resp));
+ resp->status = S_OK;
+
+ switch (le16_to_cpu(req->msg_type)) {
+ case REQ_CFG:
+ emul.cfg_count++;
+ resp->num_clocks = cpu_to_le16(emul.num_clocks);
+ return 16;
+ case REQ_CLOCK_CAP:
+ emul.cap_count++;
+ resp->status = emul.cap_status;
+ if (clock_id >= emul.num_clocks)
+ resp->status = S_EIO;
+ else
+ resp->type = emul.types[clock_id];
+ return 16;
+ case REQ_READ:
+ emul.read_count++;
+ emul.read_clock = clock_id;
+ resp->clock_reading = cpu_to_le64(EMUL_TIME_NS);
+ return 16;
+ default:
+ resp->status = S_EIO;
+ return 8;
+ }
+}
+
+static int emul_notify(struct udevice *dev, struct virtqueue *vq)
+{
+ struct udevice *vdev = vq->vdev;
+ struct vring *vr = &vq->vring;
+
+ if (emul.no_reply) {
+ timer_test_add_offset(2000);
+ return 0;
+ }
+
+ while (emul.last_avail != virtio16_to_cpu(vdev, vr->avail->idx)) {
+ u16 head = virtio16_to_cpu(vdev, vr->avail->ring[emul.last_avail %
+ vr->num]);
+ struct vring_desc *out = &vr->desc[head];
+ struct vring_desc *in = &vr->desc[virtio16_to_cpu(vdev,
+ out->next)];
+ void *req = (void *)(uintptr_t)virtio64_to_cpu(vdev, out->addr);
+ void *resp = (void *)(uintptr_t)virtio64_to_cpu(vdev, in->addr);
+ u16 used = virtio16_to_cpu(vdev, vr->used->idx);
+ u32 len;
+
+ len = emul_handle(req, resp);
+ vr->used->ring[used % vr->num].id = cpu_to_virtio32(vdev, head);
+ vr->used->ring[used % vr->num].len = cpu_to_virtio32(vdev, len);
+ vr->used->idx = cpu_to_virtio16(vdev, used + 1);
+ emul.last_avail++;
+ }
+
+ return 0;
+}
+
+/* Set up the emulated device and return the virtio RTC device */
+static int setup(struct unit_test_state *uts, u16 num_clocks, const u8 *types,
+ struct udevice **devp)
+{
+ struct udevice *bus;
+
+ memset(&emul, '\0', sizeof(emul));
+ emul.num_clocks = num_clocks;
+ memcpy(emul.types, types, num_clocks);
+
+ ut_assertok(uclass_get_device_by_name(UCLASS_VIRTIO,
+ "sandbox-virtio-rtc", &bus));
+ sandbox_virtio_set_notify(bus, emul_notify);
+ ut_assertok(device_find_first_child(bus, devp));
+ ut_asserteq_str(VIRTIO_RTC_DRV_NAME, (*devp)->driver->name);
+ ut_assertok(device_probe(*devp));
+
+ return 0;
+}
+
+/* Test reading the time from the only UTC clock */
+static int dm_test_virtio_rtc_get(struct unit_test_state *uts)
+{
+ static const u8 types[] = { CLOCK_MONOTONIC, CLOCK_UTC, CLOCK_TAI };
+ struct udevice *dev;
+ struct rtc_time tm;
+
+ ut_assertok(setup(uts, ARRAY_SIZE(types), types, &dev));
+
+ /* probe() must not use the virtqueue */
+ ut_asserteq(0, emul.cfg_count);
+
+ ut_assertok(dm_rtc_get(dev, &tm));
+ ut_asserteq(1, emul.cfg_count);
+ ut_asserteq(3, emul.cap_count);
+ ut_asserteq(1, emul.read_count);
+ ut_asserteq(1, emul.read_clock);
+
+ ut_asserteq(2023, tm.tm_year);
+ ut_asserteq(11, tm.tm_mon);
+ ut_asserteq(14, tm.tm_mday);
+ ut_asserteq(22, tm.tm_hour);
+ ut_asserteq(13, tm.tm_min);
+ ut_asserteq(20, tm.tm_sec);
+ ut_asserteq(2, tm.tm_wday);
+
+ /* The clock is looked up only once */
+ ut_assertok(dm_rtc_get(dev, &tm));
+ ut_asserteq(1, emul.cfg_count);
+ ut_asserteq(3, emul.cap_count);
+ ut_asserteq(2, emul.read_count);
+
+ /* The clocks are read-only */
+ ut_asserteq(-ENOSYS, dm_rtc_set(dev, &tm));
+
+ return 0;
+}
+DM_TEST(dm_test_virtio_rtc_get, UTF_SCAN_FDT);
+
+/* Test that a clock which smears leap seconds is preferred */
+static int dm_test_virtio_rtc_smeared(struct unit_test_state *uts)
+{
+ static const u8 types[] = { CLOCK_UTC, CLOCK_UTC_MAYBE_SMEARED,
+ CLOCK_UTC_SMEARED, CLOCK_TAI };
+ struct udevice *dev;
+ struct rtc_time tm;
+
+ ut_assertok(setup(uts, ARRAY_SIZE(types), types, &dev));
+
+ /* The search stops at the smeared clock */
+ ut_assertok(dm_rtc_get(dev, &tm));
+ ut_asserteq(3, emul.cap_count);
+ ut_asserteq(2, emul.read_clock);
+
+ return 0;
+}
+DM_TEST(dm_test_virtio_rtc_smeared, UTF_SCAN_FDT);
+
+/* Test that the first UTC clock is used if none smears leap seconds */
+static int dm_test_virtio_rtc_fallback(struct unit_test_state *uts)
+{
+ static const u8 types[] = { CLOCK_TAI, CLOCK_UTC_MAYBE_SMEARED,
+ CLOCK_UTC };
+ struct udevice *dev;
+ struct rtc_time tm;
+
+ ut_assertok(setup(uts, ARRAY_SIZE(types), types, &dev));
+
+ ut_assertok(dm_rtc_get(dev, &tm));
+ ut_asserteq(3, emul.cap_count);
+ ut_asserteq(1, emul.read_clock);
+
+ return 0;
+}
+DM_TEST(dm_test_virtio_rtc_fallback, UTF_SCAN_FDT);
+
+/* Test that a device without a UTC clock is only queried once */
+static int dm_test_virtio_rtc_no_utc(struct unit_test_state *uts)
+{
+ static const u8 types[] = { CLOCK_TAI, CLOCK_MONOTONIC };
+ struct udevice *dev;
+ struct rtc_time tm;
+
+ ut_assertok(setup(uts, ARRAY_SIZE(types), types, &dev));
+
+ ut_asserteq(-ENODEV, dm_rtc_get(dev, &tm));
+ ut_asserteq(1, emul.cfg_count);
+ ut_asserteq(2, emul.cap_count);
+
+ ut_asserteq(-ENODEV, dm_rtc_get(dev, &tm));
+ ut_asserteq(1, emul.cfg_count);
+ ut_asserteq(2, emul.cap_count);
+ ut_asserteq(0, emul.read_count);
+
+ return 0;
+}
+DM_TEST(dm_test_virtio_rtc_no_utc, UTF_SCAN_FDT);
+
+/* Test that a failed request stops the search and is not remembered */
+static int dm_test_virtio_rtc_error(struct unit_test_state *uts)
+{
+ static const u8 types[] = { CLOCK_TAI, CLOCK_UTC };
+ struct udevice *dev;
+ struct rtc_time tm;
+
+ ut_assertok(setup(uts, ARRAY_SIZE(types), types, &dev));
+
+ emul.cap_status = S_EIO;
+ ut_asserteq(-EIO, dm_rtc_get(dev, &tm));
+ ut_asserteq(1, emul.cap_count);
+
+ /* The next read looks for the clock again */
+ emul.cap_status = S_OK;
+ ut_assertok(dm_rtc_get(dev, &tm));
+ ut_asserteq(2, emul.cfg_count);
+ ut_asserteq(3, emul.cap_count);
+ ut_asserteq(1, emul.read_count);
+ ut_asserteq(1, emul.read_clock);
+
+ return 0;
+}
+DM_TEST(dm_test_virtio_rtc_error, UTF_SCAN_FDT);
+
+/* Test that the driver gives up if the device does not answer */
+static int dm_test_virtio_rtc_timeout(struct unit_test_state *uts)
+{
+ static const u8 types[] = { CLOCK_UTC };
+ struct udevice *dev;
+ struct rtc_time tm;
+
+ ut_assertok(setup(uts, ARRAY_SIZE(types), types, &dev));
+
+ emul.no_reply = true;
+ ut_asserteq(-ETIMEDOUT, dm_rtc_get(dev, &tm));
+ ut_asserteq(0, emul.cfg_count);
+
+ return 0;
+}
+DM_TEST(dm_test_virtio_rtc_timeout, UTF_SCAN_FDT);
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread