* [PATCH v6 0/6] virtio: Add a virtio RTC driver
@ 2026-11-02 3:04 Hung-Chun Tseng
2026-11-02 3:04 ` [PATCH v6 1/6] virtio: rtc: Add " Hung-Chun Tseng
` (5 more replies)
0 siblings, 6 replies; 8+ messages in thread
From: Hung-Chun Tseng @ 2026-11-02 3:04 UTC (permalink / raw)
To: u-boot; +Cc: bmeng.cn, trini, visitorckw, sjg, xypron.glpk
QEMU v11.1.0 added a virtio RTC device (virtio-rtc-pci and
virtio-rtc-device), but U-Boot has no driver for it. This series adds
one, so that "date" and anything else using dm_rtc_get() work on any
virtio-capable QEMU target.
Patch 1 adds the driver, patch 2 updates the virtio documentation and
patch 3 adds a MAINTAINERS entry. Patch 4 lets sandbox tests answer
virtqueue requests as the device would, which patch 5 uses to test the
driver. Patch 6 documents how to reach the virtio RTC.
Changes in v6:
- Add patch 6, the documentation follow-up Simon suggested on v5, which
I first sent on its own. Following Simon's review of it, the virtio
RTC now has its own section, which also takes the QEMU version note
from patch 2, and x86 is described like ARM and RISC-V: with DM_RTC
enabled, the MC146818 CMOS RTC gets sequence 0. The Status section
already lists the RTC since patch 2.
- Rebase onto main. Patches 1-5 are otherwise unchanged.
Tested with qemu-riscv64_smode_defconfig and RTC_GOLDFISH disabled, on
QEMU v11.1.2, which reports a UTC clock, and on QEMU master, which
reports a smeared UTC clock:
- With -device virtio-rtc-pci and with -device virtio-rtc-device,
"date" reads the host time and advances between reads.
- Setting the time fails, as the clocks are read-only.
- With no virtio RTC device, "date" fails cleanly with err=-19.
With qemu-x86_64_defconfig plus DM_RTC on QEMU v11.1.2, "dm uclass rtc"
shows the CMOS RTC at sequence 0 and the virtio RTC at 1; with
RTC_MC146818 disabled, the virtio RTC is sequence 0 and "date" reads it.
On sandbox, "ut dm virtio_rtc*" passes.
Hung-Chun Tseng (6):
virtio: rtc: Add virtio RTC driver
doc: virtio: List the supported device types
MAINTAINERS: Add entry for virtio RTC
virtio: sandbox: Let tests emulate the device side of a virtqueue
test: dm: Add tests for the virtio RTC driver
doc: virtio: Explain how to reach the virtio RTC
MAINTAINERS | 6 +
arch/sandbox/dts/test.dts | 5 +
arch/sandbox/include/asm/test.h | 25 +++
doc/develop/driver-model/virtio.rst | 26 ++-
drivers/virtio/Kconfig | 11 +
drivers/virtio/Makefile | 1 +
drivers/virtio/virtio-uclass.c | 1 +
drivers/virtio/virtio_rtc.c | 244 ++++++++++++++++++++++
drivers/virtio/virtio_sandbox.c | 15 ++
include/virtio.h | 4 +-
test/dm/Makefile | 1 +
test/dm/virtio_rtc.c | 306 ++++++++++++++++++++++++++++
12 files changed, 640 insertions(+), 5 deletions(-)
create mode 100644 drivers/virtio/virtio_rtc.c
create mode 100644 test/dm/virtio_rtc.c
--
2.43.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v6 1/6] virtio: rtc: Add virtio RTC driver
2026-11-02 3:04 [PATCH v6 0/6] virtio: Add a virtio RTC driver Hung-Chun Tseng
@ 2026-11-02 3:04 ` Hung-Chun Tseng
2026-11-02 3:04 ` [PATCH v6 2/6] doc: virtio: List the supported device types Hung-Chun Tseng
` (4 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Hung-Chun Tseng @ 2026-11-02 3:04 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>
Reviewed-by: Kuan-Wei Chiu <visitorckw@gmail.com>
---
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 978ef0d1c..75c234a89 100644
--- a/drivers/virtio/virtio-uclass.c
+++ b/drivers/virtio/virtio-uclass.c
@@ -32,6 +32,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 30e5005e9..fb8b396d8 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
* [PATCH v6 2/6] doc: virtio: List the supported device types
2026-11-02 3:04 [PATCH v6 0/6] virtio: Add a virtio RTC driver Hung-Chun Tseng
2026-11-02 3:04 ` [PATCH v6 1/6] virtio: rtc: Add " Hung-Chun Tseng
@ 2026-11-02 3:04 ` Hung-Chun Tseng
2026-11-02 3:04 ` [PATCH v6 3/6] MAINTAINERS: Add entry for virtio RTC Hung-Chun Tseng
` (3 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Hung-Chun Tseng @ 2026-11-02 3:04 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.
The Development Flow section repeated the old list; point it to the one
at the top instead.
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>
---
doc/develop/driver-model/virtio.rst | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/doc/develop/driver-model/virtio.rst b/doc/develop/driver-model/virtio.rst
index e5da9f430..30df0e944 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
-------
@@ -246,9 +254,8 @@ So it's easy to tell which device these functions are operating on.
Development Flow
----------------
-At present only VirtIO network card (device ID 1) and block device (device
-ID 2) are supported. If you want to develop new driver for new devices,
-please follow the guideline below.
+The supported devices are listed at the top of this document. If you want
+to develop new driver for new devices, please follow the guideline below.
1. add new device ID in virtio.h
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v6 3/6] MAINTAINERS: Add entry for virtio RTC
2026-11-02 3:04 [PATCH v6 0/6] virtio: Add a virtio RTC driver Hung-Chun Tseng
2026-11-02 3:04 ` [PATCH v6 1/6] virtio: rtc: Add " Hung-Chun Tseng
2026-11-02 3:04 ` [PATCH v6 2/6] doc: virtio: List the supported device types Hung-Chun Tseng
@ 2026-11-02 3:04 ` Hung-Chun Tseng
2026-11-02 3:04 ` [PATCH v6 4/6] virtio: sandbox: Let tests emulate the device side of a virtqueue Hung-Chun Tseng
` (2 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Hung-Chun Tseng @ 2026-11-02 3:04 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>
---
MAINTAINERS | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index af3ca0f1b..b82b91494 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1887,6 +1887,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 v6 4/6] virtio: sandbox: Let tests emulate the device side of a virtqueue
2026-11-02 3:04 [PATCH v6 0/6] virtio: Add a virtio RTC driver Hung-Chun Tseng
` (2 preceding siblings ...)
2026-11-02 3:04 ` [PATCH v6 3/6] MAINTAINERS: Add entry for virtio RTC Hung-Chun Tseng
@ 2026-11-02 3:04 ` Hung-Chun Tseng
2026-11-02 3:04 ` [PATCH v6 5/6] test: dm: Add tests for the virtio RTC driver Hung-Chun Tseng
2026-11-02 3:04 ` [PATCH v6 6/6] doc: virtio: Explain how to reach the virtio RTC Hung-Chun Tseng
5 siblings, 0 replies; 8+ messages in thread
From: Hung-Chun Tseng @ 2026-11-02 3:04 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>
---
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 v6 5/6] test: dm: Add tests for the virtio RTC driver
2026-11-02 3:04 [PATCH v6 0/6] virtio: Add a virtio RTC driver Hung-Chun Tseng
` (3 preceding siblings ...)
2026-11-02 3:04 ` [PATCH v6 4/6] virtio: sandbox: Let tests emulate the device side of a virtqueue Hung-Chun Tseng
@ 2026-11-02 3:04 ` Hung-Chun Tseng
2026-11-02 3:04 ` [PATCH v6 6/6] doc: virtio: Explain how to reach the virtio RTC Hung-Chun Tseng
5 siblings, 0 replies; 8+ messages in thread
From: Hung-Chun Tseng @ 2026-11-02 3:04 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>
---
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 b82b91494..22070983d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1891,6 +1891,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 02a9ca29b..405f6921e 100644
--- a/arch/sandbox/dts/test.dts
+++ b/arch/sandbox/dts/test.dts
@@ -1996,6 +1996,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 1b3a20aa9..d4a112c32 100644
--- a/test/dm/Makefile
+++ b/test/dm/Makefile
@@ -155,6 +155,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
* [PATCH v6 6/6] doc: virtio: Explain how to reach the virtio RTC
2026-11-02 3:04 [PATCH v6 0/6] virtio: Add a virtio RTC driver Hung-Chun Tseng
` (4 preceding siblings ...)
2026-11-02 3:04 ` [PATCH v6 5/6] test: dm: Add tests for the virtio RTC driver Hung-Chun Tseng
@ 2026-11-02 3:04 ` Hung-Chun Tseng
2026-11-03 14:49 ` Simon Glass
5 siblings, 1 reply; 8+ messages in thread
From: Hung-Chun Tseng @ 2026-11-02 3:04 UTC (permalink / raw)
To: u-boot; +Cc: bmeng.cn, trini, visitorckw, sjg, xypron.glpk
The date command only uses RTC sequence 0. On the QEMU ARM, RISC-V and
x86 machines that is the PL031, Goldfish or MC146818 RTC, so date never
reads the virtio RTC unless that driver is disabled. rtc dev does not
help, as rtc read reads raw registers, which the virtio RTC does not
have.
Give the virtio RTC its own section and say this there, along with the
QEMU version it needs and that x86 needs DM_RTC.
Suggested-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Hung-Chun Tseng <alan.tseng.cs@gmail.com>
---
doc/develop/driver-model/virtio.rst | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/doc/develop/driver-model/virtio.rst b/doc/develop/driver-model/virtio.rst
index 30df0e944..b9f05f3ae 100644
--- a/doc/develop/driver-model/virtio.rst
+++ b/doc/develop/driver-model/virtio.rst
@@ -78,8 +78,19 @@ 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.
+
+VirtIO RTC
+----------
The VirtIO RTC device (``-device virtio-rtc-pci`` or
-``-device virtio-rtc-device``) requires QEMU v11.1.0+.
+``-device virtio-rtc-device``) requires QEMU v11.1.0+. The driver needs
+``CONFIG_DM_RTC``, which QEMU x86 does not enable by default.
+
+The ``date`` command only uses RTC sequence 0. The QEMU ARM, RISC-V and x86
+machines also have a PL031, Goldfish or MC146818 RTC, which gets sequence 0,
+so ``date`` reads the VirtIO RTC only if ``CONFIG_RTC_PL031``,
+``CONFIG_RTC_GOLDFISH`` or ``CONFIG_RTC_MC146818`` is disabled.
+``dm uclass rtc`` shows the sequence numbers. ``rtc dev`` does not help, as
+``rtc read`` reads raw registers, which the VirtIO RTC does not have.
Testing
-------
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v6 6/6] doc: virtio: Explain how to reach the virtio RTC
2026-11-02 3:04 ` [PATCH v6 6/6] doc: virtio: Explain how to reach the virtio RTC Hung-Chun Tseng
@ 2026-11-03 14:49 ` Simon Glass
0 siblings, 0 replies; 8+ messages in thread
From: Simon Glass @ 2026-11-03 14:49 UTC (permalink / raw)
To: alan.tseng.cs; +Cc: u-boot, bmeng.cn, trini, visitorckw, sjg, xypron.glpk
Hi Hung-Chun,
On 2026-11-02T03:04:36+11:00, Hung-Chun Tseng <alan.tseng.cs@gmail.com> wrote:
> doc: virtio: Explain how to reach the virtio RTC
>
> The date command only uses RTC sequence 0. On the QEMU ARM, RISC-V and
> x86 machines that is the PL031, Goldfish or MC146818 RTC, so date never
> reads the virtio RTC unless that driver is disabled. rtc dev does not
> help, as rtc read reads raw registers, which the virtio RTC does not
> have.
>
> Give the virtio RTC its own section and say this there, along with the
> QEMU version it needs and that x86 needs DM_RTC.
Please can you drop the DM_RTC part here too, once the doc is updated
(see below).
>
> Suggested-by: Simon Glass <sjg@chromium.org>
> Signed-off-by: Hung-Chun Tseng <alan.tseng.cs@gmail.com>
>
> doc/develop/driver-model/virtio.rst | 13 ++++++++++++-
> 1 file changed, 12 insertions(+), 1 deletion(-)
> diff --git a/doc/develop/driver-model/virtio.rst b/doc/develop/driver-model/virtio.rst
> @@ -78,8 +78,19 @@ Requirements
> +``-device virtio-rtc-device``) requires QEMU v11.1.0+. The driver needs
> +``CONFIG_DM_RTC``, which QEMU x86 does not enable by default.
Just to check, how did you find this? The config X86 entry in
arch/Kconfig has 'imply DM_RTC', and both qemu-x86_defconfig and
qemu-x86_64_defconfig give me CONFIG_DM_RTC=y and CONFIG_VIRTIO_RTC=y,
so the driver looks to be already enabled on QEMU x86. Please can you
drop the sentence, unless there is a case I am missing.
Regards,
Simon
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-11-03 14:49 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-02 3:04 [PATCH v6 0/6] virtio: Add a virtio RTC driver Hung-Chun Tseng
2026-11-02 3:04 ` [PATCH v6 1/6] virtio: rtc: Add " Hung-Chun Tseng
2026-11-02 3:04 ` [PATCH v6 2/6] doc: virtio: List the supported device types Hung-Chun Tseng
2026-11-02 3:04 ` [PATCH v6 3/6] MAINTAINERS: Add entry for virtio RTC Hung-Chun Tseng
2026-11-02 3:04 ` [PATCH v6 4/6] virtio: sandbox: Let tests emulate the device side of a virtqueue Hung-Chun Tseng
2026-11-02 3:04 ` [PATCH v6 5/6] test: dm: Add tests for the virtio RTC driver Hung-Chun Tseng
2026-11-02 3:04 ` [PATCH v6 6/6] doc: virtio: Explain how to reach the virtio RTC Hung-Chun Tseng
2026-11-03 14:49 ` Simon Glass
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox