* Re: [PATCH v2 1/3] virtio: rtc: Add virtio RTC driver
2026-10-14 19:06 ` [PATCH v2 1/3] virtio: rtc: Add " Hung-Chun Tseng
@ 2026-10-14 19:46 ` Kuan-Wei Chiu
2026-10-16 16:41 ` Hung-Chun Tseng
2026-10-16 21:47 ` Simon Glass
2026-10-17 18:28 ` Kuan-Wei Chiu
2 siblings, 1 reply; 11+ messages in thread
From: Kuan-Wei Chiu @ 2026-10-14 19:46 UTC (permalink / raw)
To: Hung-Chun Tseng
Cc: Bin Meng, Tom Rini, Simon Glass, Heinrich Schuchardt, u-boot
Hi Hung-Chun,
On Thu, Oct 15, 2026 at 03:06:29AM +0800, Hung-Chun Tseng wrote:
> Add a driver for the virtio RTC device (virtio device ID 17), which
> QEMU provides as virtio-rtc-pci and virtio-rtc-device since v11.1.0.
>
> The device exposes its clocks through a request virtqueue. As the
> virtqueue must not be used before the virtio uclass sets DRIVER_OK,
> which happens after probe(), the first read looks for the clock: it
> asks for the number of clocks with VIRTIO_RTC_REQ_CFG and picks the
> first one whose VIRTIO_RTC_REQ_CLOCK_CAP type is UTC, smeared UTC or
> maybe-smeared UTC. Reading the time sends a VIRTIO_RTC_REQ_READ for
> that clock and converts the nanoseconds since the epoch with
> rtc_to_tm(). The clocks are read-only, so there is no set operation,
> and the optional alarm feature is not negotiated.
>
> VIRTIO_RTC defaults to y when VIRTIO and DM_RTC are enabled, like
> VIRTIO_RNG does for DM_RNG.
>
> Signed-off-by: Hung-Chun Tseng <alan.tseng.cs@gmail.com>
Thanks for the patch.
Actually, when I sent out the qemu virtio-rtc code, I also had a local
dirty version of the virtio-rtc u-boot driver, but it has been sitting
on my TODO list. I might take a closer look at this patch in a day or
two. This also reminds me that I still have some other virtio drivers
locally that I would like to try upstreaming. I might try to send them
out within the next week or two.
> ---
>
> 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)
I would suggest leaving more time between sending out new patch
versions to give people a chance to review and reply, which helps avoid
spamming the list with rapid revisions. We have folks in various
timezones, and many of them are quite busy.
>
> drivers/virtio/Kconfig | 9 ++
> drivers/virtio/Makefile | 1 +
> drivers/virtio/virtio-uclass.c | 1 +
> drivers/virtio/virtio_rtc.c | 213 +++++++++++++++++++++++++++++++++
> include/virtio.h | 4 +-
> 5 files changed, 227 insertions(+), 1 deletion(-)
> create mode 100644 drivers/virtio/virtio_rtc.c
>
> diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig
> index 55b444280..c19138ef2 100644
> --- a/drivers/virtio/Kconfig
> +++ b/drivers/virtio/Kconfig
> @@ -84,4 +84,13 @@ 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 is the virtual RTC driver. It reads the UTC clock of a
> + virtio RTC device. It can be used with QEMU based targets.
> 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..f5d4a694b
> --- /dev/null
> +++ b/drivers/virtio/virtio_rtc.c
> @@ -0,0 +1,213 @@
> +// 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 the first UTC clock and reports it through the RTC uclass.
> + */
> +
> +#include <div64.h>
> +#include <dm.h>
> +#include <log.h>
> +#include <rtc.h>
> +#include <virtio_types.h>
> +#include <virtio.h>
> +#include <virtio_ring.h>
> +#include <linux/types.h>
> +
> +#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];
> +} __packed;
> +
> +struct virtio_rtc_resp_head {
> + u8 status;
> + u8 reserved[7];
> +} __packed;
> +
> +/* 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];
> +} __packed;
> +
> +struct virtio_rtc_resp_read {
> + struct virtio_rtc_resp_head head;
> + __le64 clock_reading;
> +} __packed;
> +
> +struct virtio_rtc_resp_cfg {
> + struct virtio_rtc_resp_head head;
> + __le16 num_clocks;
> + u8 reserved[6];
> +} __packed;
> +
> +struct virtio_rtc_resp_clock_cap {
> + struct virtio_rtc_resp_head head;
> + u8 type;
> + u8 leap_second_smearing;
> + u8 flags;
> + u8 reserved[5];
> +} __packed;
> +
> +struct virtio_rtc_priv {
> + struct virtqueue *vq;
> + u16 clock_id;
> + bool clock_found;
> +};
> +
> +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;
> + int ret;
> +
> + ret = virtqueue_add(priv->vq, sgs, 1, 1);
> + if (ret)
> + return ret;
> +
> + virtqueue_kick(priv->vq);
> +
> + while (!virtqueue_get_buf(priv->vq, &len))
IIRC, when I sent out the virtio-9p driver for review, Simon made the
same suggestion to add a timeout mechanism to the loop here to prevent
potential hangs. While it is true that many other virtio drivers are
currently written this way, it is on my TODO list to clean them up.
Regards,
Kuan-Wei
> + ;
> +
> + 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;
> + 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)
> + continue;
> +
> + if (virtio_rtc_is_utc(cap.type)) {
> + priv->clock_id = id;
> + priv->clock_found = true;
> + return 0;
> + }
> + }
> +
> + return -ENODEV;
> +}
> +
> +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->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] 11+ messages in thread* Re: [PATCH v2 1/3] virtio: rtc: Add virtio RTC driver
2026-10-14 19:46 ` Kuan-Wei Chiu
@ 2026-10-16 16:41 ` Hung-Chun Tseng
0 siblings, 0 replies; 11+ messages in thread
From: Hung-Chun Tseng @ 2026-10-16 16:41 UTC (permalink / raw)
To: Kuan-Wei Chiu
Cc: Bin Meng, Tom Rini, Simon Glass, Heinrich Schuchardt, u-boot
Hi Kuan-Wei,
On Thu, Oct 15, 2026 at 03:46:03AM +0800, Kuan-Wei Chiu wrote:
> Actually, when I sent out the qemu virtio-rtc code, I also had a local
> dirty version of the virtio-rtc u-boot driver, but it has been sitting
> on my TODO list. I might take a closer look at this patch in a day or
> two.
Thanks for taking a look, and for the QEMU device that made this easy
to test. If your local version handles something mine doesn't, I'm
happy to fold it in.
> I would suggest leaving more time between sending out new patch
> versions to give people a chance to review and reply, which helps avoid
> spamming the list with rapid revisions.
Understood, sorry about that. I'll hold v3 until you and others have
had time to go through v2.
> IIRC, when I sent out the virtio-9p driver for review, Simon made the
> same suggestion to add a timeout mechanism to the loop here to prevent
> potential hangs.
Good point. For v3 I have the loop bounded with get_timer(), giving up
with -ETIMEDOUT after one second, as virtio_9p does.
For your comments on 2/3, v3 also turns the device types into a
bulleted list and retitles the patch to "doc: virtio: List the
supported device types".
Thanks,
Hung-Chun
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v2 1/3] virtio: rtc: Add virtio RTC driver
2026-10-14 19:06 ` [PATCH v2 1/3] virtio: rtc: Add " Hung-Chun Tseng
2026-10-14 19:46 ` Kuan-Wei Chiu
@ 2026-10-16 21:47 ` Simon Glass
2026-10-17 18:28 ` Kuan-Wei Chiu
2 siblings, 0 replies; 11+ messages in thread
From: Simon Glass @ 2026-10-16 21:47 UTC (permalink / raw)
To: alan.tseng.cs
Cc: Bin Meng, Tom Rini, Kuan-Wei Chiu, Simon Glass,
Heinrich Schuchardt, u-boot
Hi Hung-Chun,
On 2026-10-14T19:06:28, Hung-Chun Tseng <alan.tseng.cs@gmail.com> wrote:
> virtio: rtc: Add virtio RTC driver
>
> Add a driver for the virtio RTC device (virtio device ID 17), which
> QEMU provides as virtio-rtc-pci and virtio-rtc-device since v11.1.0.
>
> The device exposes its clocks through a request virtqueue. As the
> virtqueue must not be used before the virtio uclass sets DRIVER_OK,
> which happens after probe(), the first read looks for the clock: it
> asks for the number of clocks with VIRTIO_RTC_REQ_CFG and picks the
> first one whose VIRTIO_RTC_REQ_CLOCK_CAP type is UTC, smeared UTC or
> maybe-smeared UTC. Reading the time sends a VIRTIO_RTC_REQ_READ for
> that clock and converts the nanoseconds since the epoch with
> rtc_to_tm(). The clocks are read-only, so there is no set operation,
> and the optional alarm feature is not negotiated.
>
> VIRTIO_RTC defaults to y when VIRTIO and DM_RTC are enabled, like
> VIRTIO_RNG does for DM_RNG.
>
> Signed-off-by: Hung-Chun Tseng <alan.tseng.cs@gmail.com>
>
> drivers/virtio/Kconfig | 9 ++
> drivers/virtio/Makefile | 1 +
> drivers/virtio/virtio-uclass.c | 1 +
> drivers/virtio/virtio_rtc.c | 213 +++++++++++++++++++++++++++++++++++++++++
> include/virtio.h | 4 +-
> 5 files changed, 227 insertions(+), 1 deletion(-)
> On sandbox, where VIRTIO_RTC now defaults to y,
> "ut dm rtc*" and "ut dm virtio*" pass.
Just to clarify, the sandbox virtio transport only exposes a
VIRTIO_ID_BLOCK device (see uc_priv->device in
drivers/virtio/virtio_sandbox.c), so neither of those actually probes
virtio_rtc — they only confirm the driver builds. Please mention that
in the commit message. A sandbox test along the lines of
test/dm/virtio_rng.c would be good as a follow-up.
> diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig
> @@ -84,4 +84,13 @@ config VIRTIO_9P
> +config VIRTIO_RTC
> + bool "virtio RTC driver"
> + depends on DM_RTC
> + depends on VIRTIO
> + default y
> + help
> + This is the virtual RTC driver. It reads the UTC clock of a
> + virtio RTC device. It can be used with QEMU based targets.
Please can you expand the help text - something like "This enables the
virtio RTC driver, which reads the UTC clock exposed by a virtio RTC
device (QEMU v11.1.0 or later provides virtio-rtc-pci and
virtio-rtc-device). The clocks are read-only, so the time cannot be
set."
> diff --git a/drivers/virtio/virtio_rtc.c b/drivers/virtio/virtio_rtc.c
> @@ -0,0 +1,213 @@
> +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->clock_found) {
> + ret = virtio_rtc_find_utc_clock(dev);
> + if (ret)
> + return ret;
> + }
If the first find fails (e.g. a device with only TAI or MONOTONIC
clocks), clock_found stays false and every subsequent 'date' re-walks
every clock. Since the clock set is fixed for the life of the device,
please cache the failure too - e.g. a state enum or a cached error
code — so the second call returns immediately.
Regards,
Simon
^ permalink raw reply [flat|nested] 11+ messages in thread* Re: [PATCH v2 1/3] virtio: rtc: Add virtio RTC driver
2026-10-14 19:06 ` [PATCH v2 1/3] virtio: rtc: Add " Hung-Chun Tseng
2026-10-14 19:46 ` Kuan-Wei Chiu
2026-10-16 21:47 ` Simon Glass
@ 2026-10-17 18:28 ` Kuan-Wei Chiu
2 siblings, 0 replies; 11+ messages in thread
From: Kuan-Wei Chiu @ 2026-10-17 18:28 UTC (permalink / raw)
To: Hung-Chun Tseng
Cc: Bin Meng, Tom Rini, Simon Glass, Heinrich Schuchardt, u-boot
Hi Hung-Chun,
Sorry for getting back on this late.
On Thu, Oct 15, 2026 at 03:06:29AM +0800, Hung-Chun Tseng wrote:
> Add a driver for the virtio RTC device (virtio device ID 17), which
> QEMU provides as virtio-rtc-pci and virtio-rtc-device since v11.1.0.
>
> The device exposes its clocks through a request virtqueue. As the
> virtqueue must not be used before the virtio uclass sets DRIVER_OK,
> which happens after probe(), the first read looks for the clock: it
> asks for the number of clocks with VIRTIO_RTC_REQ_CFG and picks the
> first one whose VIRTIO_RTC_REQ_CLOCK_CAP type is UTC, smeared UTC or
> maybe-smeared UTC. Reading the time sends a VIRTIO_RTC_REQ_READ for
> that clock and converts the nanoseconds since the epoch with
> rtc_to_tm(). The clocks are read-only, so there is no set operation,
> and the optional alarm feature is not negotiated.
>
> VIRTIO_RTC defaults to y when VIRTIO and DM_RTC are enabled, like
> VIRTIO_RNG does for DM_RNG.
>
> Signed-off-by: Hung-Chun Tseng <alan.tseng.cs@gmail.com>
> ---
>
> 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 | 9 ++
> drivers/virtio/Makefile | 1 +
> drivers/virtio/virtio-uclass.c | 1 +
> drivers/virtio/virtio_rtc.c | 213 +++++++++++++++++++++++++++++++++
> include/virtio.h | 4 +-
> 5 files changed, 227 insertions(+), 1 deletion(-)
> create mode 100644 drivers/virtio/virtio_rtc.c
>
> diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig
> index 55b444280..c19138ef2 100644
> --- a/drivers/virtio/Kconfig
> +++ b/drivers/virtio/Kconfig
> @@ -84,4 +84,13 @@ 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 is the virtual RTC driver. It reads the UTC clock of a
> + virtio RTC device. It can be used with QEMU based targets.
> 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..f5d4a694b
> --- /dev/null
> +++ b/drivers/virtio/virtio_rtc.c
> @@ -0,0 +1,213 @@
> +// 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 the first UTC clock and reports it through the RTC uclass.
> + */
> +
> +#include <div64.h>
> +#include <dm.h>
> +#include <log.h>
> +#include <rtc.h>
> +#include <virtio_types.h>
> +#include <virtio.h>
> +#include <virtio_ring.h>
> +#include <linux/types.h>
> +
> +#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];
> +} __packed;
IIUC, reserved[] is already intended to ensure natural alignment for
all members, so I'm wondering if adding __packed here is actually
counterintuitive?
> +
> +struct virtio_rtc_resp_head {
> + u8 status;
> + u8 reserved[7];
> +} __packed;
> +
> +/* 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];
> +} __packed;
> +
> +struct virtio_rtc_resp_read {
> + struct virtio_rtc_resp_head head;
> + __le64 clock_reading;
> +} __packed;
> +
> +struct virtio_rtc_resp_cfg {
> + struct virtio_rtc_resp_head head;
> + __le16 num_clocks;
> + u8 reserved[6];
> +} __packed;
> +
> +struct virtio_rtc_resp_clock_cap {
> + struct virtio_rtc_resp_head head;
> + u8 type;
> + u8 leap_second_smearing;
> + u8 flags;
> + u8 reserved[5];
> +} __packed;
> +
> +struct virtio_rtc_priv {
> + struct virtqueue *vq;
> + u16 clock_id;
> + bool clock_found;
> +};
> +
> +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;
> + int ret;
> +
> + ret = virtqueue_add(priv->vq, sgs, 1, 1);
> + if (ret)
> + return ret;
> +
> + virtqueue_kick(priv->vq);
> +
> + while (!virtqueue_get_buf(priv->vq, &len))
> + ;
> +
> + 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;
Linux only permits VIRTIO_RTC_CLOCK_UTC_SMEARED to be registered as an
RTC class device and deliberately skips VIRTIO_RTC_CLOCK_UTC and
VIRTIO_RTC_CLOCK_UTC_MAYBE_SMEARED. Of course, since U-Boot does not
have a PTP subsystem, we probably shouldn't follow Linux strictly here.
However, as an example, if a device exposes both VIRTIO_RTC_CLOCK_UTC
(e.g., clock 0 for PTP) and VIRTIO_RTC_CLOCK_UTC_SMEARED (e.g., clock 1
for RTC), the Linux RTC class binds to clock 1, whereas this U-Boot
driver simply picks the first UTC-like clock it encounters
unconditionally (clock 0). I'm not sure if this is what we want, or
whether you have considered this scenario? I feel like this is at least
worth mentioning in the commit message or in an code comment.
> +}
> +
> +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;
> + 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)
> + continue;
The Linux virtio-rtc driver stops and returns an error code when
viortc_clocks_init() fails. I'm not sure if we have any particular
reason to proceed here?
Regards,
Kuan-Wei
> +
> + if (virtio_rtc_is_utc(cap.type)) {
> + priv->clock_id = id;
> + priv->clock_found = true;
> + return 0;
> + }
> + }
> +
> + return -ENODEV;
> +}
> +
> +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->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] 11+ messages in thread