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 <[email protected]>
---
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 <[email protected]>
 S:     Maintained
 F:     drivers/virtio/virtio_rtc.c
+F:     test/dm/virtio_rtc.c
 
 WATCHDOG
 M:     Stefan Roese <[email protected]>
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 <[email protected]>
+ *
+ * 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

Reply via email to