On Wed, Oct 14, 2026 at 10:07 PM Hung-Chun Tseng
<[email protected]> 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. At probe
> time the driver 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 <[email protected]>
> ---
>  drivers/virtio/Kconfig         |   9 ++
>  drivers/virtio/Makefile        |   1 +
>  drivers/virtio/virtio-uclass.c |   1 +
>  drivers/virtio/virtio_rtc.c    | 206 +++++++++++++++++++++++++++++++++
>  include/virtio.h               |   4 +-
>  5 files changed, 220 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..7d0dc9c7f
> --- /dev/null
> +++ b/drivers/virtio/virtio_rtc.c
> @@ -0,0 +1,206 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2026, Hung-Chun Tseng <[email protected]>
> + *
> + * 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;
> +};
> +
> +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 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),
> +               .clock_id = cpu_to_le16(priv->clock_id),
> +       };
> +       struct virtio_rtc_resp_read resp = {};
> +       u64 now;
> +       int ret;
> +
> +       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 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;
> +                       return 0;
> +               }
> +       }
> +
> +       return -ENODEV;
> +}
> +
> +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;
> +       }
> +
> +       ret = virtio_rtc_find_utc_clock(dev);
> +       if (ret) {
> +               debug("%s: no UTC clock found\n", __func__);

At this point the driver has not announced DRIVER_OK yet, so it should
not initiate any virtqueu operation. The discovery of the UTC clock
can be delayed to virtio_rtc_get() when operating on the virtqueue
buffer is safe to do.

> +               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
> --

Regards,
Bin

Reply via email to