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__); + 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
