[PATCH 2/2] virtio-rtc: Add basic virtio-rtc support

Kuan-Wei Chiu posted 2 patches 1 day, 14 hours ago
Maintainers: Paolo Bonzini <pbonzini@redhat.com>, Kuan-Wei Chiu <visitorckw@gmail.com>, "Michael S. Tsirkin" <mst@redhat.com>, Cornelia Huck <cohuck@redhat.com>
[PATCH 2/2] virtio-rtc: Add basic virtio-rtc support
Posted by Kuan-Wei Chiu 1 day, 14 hours ago
Introduce the core device and PCI bindings for the virtio-rtc device
(VIRTIO_ID_CLOCK).

This implementation provides a read-only clock that returns the host's
time (QEMU_CLOCK_HOST) to the guest. It handles fundamental control
requests, reporting a single supported clock of type
VIRTIO_RTC_CLOCK_UTC, and responds to standard read requests.

- Virtio RTC Spec:
https://github.com/oasis-tcs/virtio-spec/tree/master/device-types/rtc
- Linux Virtio RTC driver:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/virtio/virtio_rtc_driver.c?h=v7.0-rc1

Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
---
 MAINTAINERS                    |   7 ++
 hw/virtio/Kconfig              |   5 +
 hw/virtio/meson.build          |   2 +
 hw/virtio/virtio-rtc-pci.c     |  65 +++++++++++
 hw/virtio/virtio-rtc.c         | 190 +++++++++++++++++++++++++++++++++
 include/hw/virtio/virtio-rtc.h |  22 ++++
 6 files changed, 291 insertions(+)
 create mode 100644 hw/virtio/virtio-rtc-pci.c
 create mode 100644 hw/virtio/virtio-rtc.c
 create mode 100644 include/hw/virtio/virtio-rtc.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 606b16762c..69dfd07a36 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2522,6 +2522,13 @@ F: include/system/rng*.h
 F: backends/rng*.c
 F: tests/qtest/virtio-rng-test.c
 
+virtio-rtc
+M: Kuan-Wei Chiu <visitorckw@gmail.com>
+S: Maintained
+F: hw/virtio/virtio-rtc.c
+F: hw/virtio/virtio-rtc-pci.c
+F: include/hw/virtio/virtio-rtc.h
+
 virtio-nsm
 M: Alexander Graf <graf@amazon.com>
 M: Dorjoy Chowdhury <dorjoychy111@gmail.com>
diff --git a/hw/virtio/Kconfig b/hw/virtio/Kconfig
index 8895682c61..fb8b53f287 100644
--- a/hw/virtio/Kconfig
+++ b/hw/virtio/Kconfig
@@ -76,6 +76,11 @@ config VIRTIO_MEM
     depends on VIRTIO_MEM_SUPPORTED
     select VIRTIO_MD
 
+config VIRTIO_RTC
+    bool
+    default y
+    depends on VIRTIO
+
 config VHOST_VSOCK_COMMON
     bool
     depends on VIRTIO
diff --git a/hw/virtio/meson.build b/hw/virtio/meson.build
index 6675b63ce6..68c89495a1 100644
--- a/hw/virtio/meson.build
+++ b/hw/virtio/meson.build
@@ -55,6 +55,7 @@ else
 endif
 system_virtio_ss.add(when: 'CONFIG_VHOST_USER_VSOCK', if_true: files('vhost-user-vsock.c'))
 system_virtio_ss.add(when: 'CONFIG_VIRTIO_RNG', if_true: files('virtio-rng.c'))
+system_virtio_ss.add(when: 'CONFIG_VIRTIO_RTC', if_true: files('virtio-rtc.c'))
 
 specific_virtio_ss.add(when: 'CONFIG_VIRTIO_BALLOON', if_true: files('virtio-balloon.c'))
 specific_virtio_ss.add(when: 'CONFIG_VHOST_USER_FS', if_true: files('vhost-user-fs.c'))
@@ -78,6 +79,7 @@ virtio_pci_ss.add(when: 'CONFIG_VIRTIO_CRYPTO', if_true: files('virtio-crypto-pc
 virtio_pci_ss.add(when: 'CONFIG_VIRTIO_INPUT_HOST', if_true: files('virtio-input-host-pci.c'))
 virtio_pci_ss.add(when: 'CONFIG_VIRTIO_INPUT', if_true: files('virtio-input-pci.c'))
 virtio_pci_ss.add(when: 'CONFIG_VIRTIO_RNG', if_true: files('virtio-rng-pci.c'))
+virtio_pci_ss.add(when: 'CONFIG_VIRTIO_RTC', if_true: files('virtio-rtc-pci.c'))
 virtio_pci_ss.add(when: 'CONFIG_VIRTIO_NSM', if_true: [files('virtio-nsm-pci.c', 'cbor-helpers.c'), libcbor])
 virtio_pci_ss.add(when: 'CONFIG_VIRTIO_BALLOON', if_true: files('virtio-balloon-pci.c'))
 virtio_pci_ss.add(when: 'CONFIG_VIRTIO_9P', if_true: files('virtio-9p-pci.c'))
diff --git a/hw/virtio/virtio-rtc-pci.c b/hw/virtio/virtio-rtc-pci.c
new file mode 100644
index 0000000000..fe25f21fbc
--- /dev/null
+++ b/hw/virtio/virtio-rtc-pci.c
@@ -0,0 +1,65 @@
+/*
+ * Virtio RTC PCI Bindings
+ *
+ * Copyright (c) 2026 Kuan-Wei Chiu <visitorckw@gmail.com>
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/virtio/virtio-pci.h"
+#include "hw/virtio/virtio-rtc.h"
+#include "standard-headers/linux/virtio_ids.h"
+
+typedef struct VirtIORtcPCI {
+    VirtIOPCIProxy parent_obj;
+    VirtIORtc vdev;
+} VirtIORtcPCI;
+
+#define TYPE_VIRTIO_RTC_PCI "virtio-rtc-pci-base"
+OBJECT_DECLARE_SIMPLE_TYPE(VirtIORtcPCI, VIRTIO_RTC_PCI)
+
+static void virtio_rtc_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp)
+{
+    VirtIORtcPCI *dev = VIRTIO_RTC_PCI(vpci_dev);
+    DeviceState *vdev = DEVICE(&dev->vdev);
+
+    qdev_realize(vdev, BUS(&vpci_dev->bus), errp);
+}
+
+static void virtio_rtc_pci_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass);
+    PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass);
+
+    set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+    k->realize = virtio_rtc_pci_realize;
+
+    pcidev_k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET;
+    pcidev_k->device_id = PCI_DEVICE_ID_VIRTIO_10_BASE + VIRTIO_ID_CLOCK;
+    pcidev_k->revision = 0x00;
+    pcidev_k->class_id = PCI_CLASS_SYSTEM_RTC;
+}
+
+static void virtio_rtc_pci_instance_init(Object *obj)
+{
+    VirtIORtcPCI *dev = VIRTIO_RTC_PCI(obj);
+
+    virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+                      TYPE_VIRTIO_RTC);
+}
+
+static const VirtioPCIDeviceTypeInfo virtio_rtc_pci_info = {
+    .base_name = TYPE_VIRTIO_RTC_PCI,
+    .non_transitional_name = "virtio-rtc-pci",
+    .instance_size = sizeof(VirtIORtcPCI),
+    .instance_init = virtio_rtc_pci_instance_init,
+    .class_init = virtio_rtc_pci_class_init,
+};
+
+static void virtio_rtc_pci_register(void)
+{
+    virtio_pci_types_register(&virtio_rtc_pci_info);
+}
+
+type_init(virtio_rtc_pci_register);
diff --git a/hw/virtio/virtio-rtc.c b/hw/virtio/virtio-rtc.c
new file mode 100644
index 0000000000..32de9c1650
--- /dev/null
+++ b/hw/virtio/virtio-rtc.c
@@ -0,0 +1,190 @@
+/*
+ * Virtio RTC device core
+ *
+ * Copyright (c) 2026 Kuan-Wei Chiu <visitorckw@gmail.com>
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/iov.h"
+#include "qemu/timer.h"
+#include "hw/virtio/virtio.h"
+#include "hw/virtio/virtio-rtc.h"
+#include "standard-headers/linux/virtio_ids.h"
+#include "standard-headers/linux/virtio_rtc.h"
+
+static void virtio_rtc_handle_request(VirtIODevice *vdev, VirtQueue *vq)
+{
+    VirtQueueElement *elem;
+    struct virtio_rtc_req_head req_head;
+    size_t written;
+
+    while ((elem = virtqueue_pop(vq, sizeof(VirtQueueElement)))) {
+        if (elem->out_num < 1 || elem->in_num < 1) {
+            virtio_error(vdev, "virtio-rtc: request missing in/out buffers");
+            virtqueue_detach_element(vq, elem, 0);
+            g_free(elem);
+            break;
+        }
+
+        if (iov_to_buf(elem->out_sg, elem->out_num, 0, &req_head,
+                       sizeof(req_head)) != sizeof(req_head)) {
+            virtio_error(vdev, "virtio-rtc: request header too short");
+            virtqueue_detach_element(vq, elem, 0);
+            g_free(elem);
+            break;
+        }
+
+        written = 0;
+
+        switch (le16_to_cpu(req_head.msg_type)) {
+        case VIRTIO_RTC_REQ_CFG: {
+            struct virtio_rtc_resp_cfg resp = {0};
+            resp.head.status = VIRTIO_RTC_S_OK;
+            resp.num_clocks = cpu_to_le16(1);
+            written = iov_from_buf(elem->in_sg, elem->in_num, 0, &resp,
+                                   sizeof(resp));
+            break;
+        }
+        case VIRTIO_RTC_REQ_CLOCK_CAP: {
+            struct virtio_rtc_req_clock_cap req;
+            struct virtio_rtc_resp_clock_cap resp = {0};
+
+            if (iov_to_buf(elem->out_sg, elem->out_num, 0, &req,
+                           sizeof(req)) != sizeof(req)) {
+                resp.head.status = VIRTIO_RTC_S_EINVAL;
+                written = iov_from_buf(elem->in_sg, elem->in_num, 0, &resp,
+                                       sizeof(resp.head));
+                break;
+            }
+
+            if (le16_to_cpu(req.clock_id) != 0) {
+                resp.head.status = VIRTIO_RTC_S_ENODEV;
+            } else {
+                resp.head.status = VIRTIO_RTC_S_OK;
+                resp.type = VIRTIO_RTC_CLOCK_UTC;
+            }
+            written = iov_from_buf(elem->in_sg, elem->in_num, 0, &resp,
+                                   sizeof(resp));
+            break;
+        }
+        case VIRTIO_RTC_REQ_CROSS_CAP: {
+            struct virtio_rtc_req_cross_cap req;
+            struct virtio_rtc_resp_cross_cap resp = {0};
+
+            if (iov_to_buf(elem->out_sg, elem->out_num, 0, &req,
+                     sizeof(req)) != sizeof(req)) {
+                resp.head.status = VIRTIO_RTC_S_EINVAL;
+                written = iov_from_buf(elem->in_sg, elem->in_num, 0, &resp,
+                                       sizeof(resp.head));
+                break;
+            }
+
+            if (le16_to_cpu(req.clock_id) != 0) {
+                resp.head.status = VIRTIO_RTC_S_ENODEV;
+            } else {
+                resp.head.status = VIRTIO_RTC_S_OK;
+            }
+            written = iov_from_buf(elem->in_sg, elem->in_num, 0, &resp,
+                                   sizeof(resp));
+            break;
+        }
+        case VIRTIO_RTC_REQ_READ: {
+            struct virtio_rtc_req_read req;
+            struct virtio_rtc_resp_read resp = {0};
+
+            if (iov_to_buf(elem->out_sg, elem->out_num, 0, &req,
+                           sizeof(req)) != sizeof(req)) {
+                resp.head.status = VIRTIO_RTC_S_EINVAL;
+                written = iov_from_buf(elem->in_sg, elem->in_num, 0, &resp,
+                                       sizeof(resp.head));
+                break;
+            }
+
+            if (le16_to_cpu(req.clock_id) != 0) {
+                resp.head.status = VIRTIO_RTC_S_ENODEV;
+                written = iov_from_buf(elem->in_sg, elem->in_num, 0, &resp,
+                                       sizeof(resp.head));
+            } else {
+                resp.head.status = VIRTIO_RTC_S_OK;
+                resp.clock_reading =
+                    cpu_to_le64(qemu_clock_get_ns(QEMU_CLOCK_HOST));
+                written = iov_from_buf(elem->in_sg, elem->in_num, 0, &resp,
+                                       sizeof(resp));
+            }
+            break;
+        }
+        default: {
+            struct virtio_rtc_resp_head resp = {0};
+            resp.status = VIRTIO_RTC_S_EOPNOTSUPP;
+            written = iov_from_buf(elem->in_sg, elem->in_num, 0, &resp,
+                                   sizeof(resp));
+            break;
+        }
+        }
+
+        virtqueue_push(vq, elem, written);
+        virtio_notify(vdev, vq);
+        g_free(elem);
+    }
+}
+
+static void virtio_rtc_device_realize(DeviceState *dev, Error **errp)
+{
+    VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+    VirtIORtc *vrtc = VIRTIO_RTC(dev);
+
+    virtio_init(vdev, VIRTIO_ID_CLOCK, 0);
+    vrtc->vq = virtio_add_queue(vdev, 64, virtio_rtc_handle_request);
+}
+
+static void virtio_rtc_device_unrealize(DeviceState *dev)
+{
+    VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+    VirtIORtc *vrtc = VIRTIO_RTC(dev);
+
+    virtio_delete_queue(vrtc->vq);
+    virtio_cleanup(vdev);
+}
+
+static uint64_t virtio_rtc_get_features(VirtIODevice *vdev, uint64_t f,
+                                        Error **errp)
+{
+    return f;
+}
+
+static const VMStateDescription vmstate_virtio_rtc = {
+    .name = "virtio-rtc",
+    .minimum_version_id = 1,
+    .version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_VIRTIO_DEVICE,
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+static void virtio_rtc_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
+
+    set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+    vdc->realize = virtio_rtc_device_realize;
+    vdc->unrealize = virtio_rtc_device_unrealize;
+    vdc->get_features = virtio_rtc_get_features;
+    dc->vmsd = &vmstate_virtio_rtc;
+}
+
+static const TypeInfo virtio_rtc_info = {
+    .name = TYPE_VIRTIO_RTC,
+    .parent = TYPE_VIRTIO_DEVICE,
+    .instance_size = sizeof(VirtIORtc),
+    .class_init = virtio_rtc_class_init,
+};
+
+static void virtio_rtc_register_types(void)
+{
+    type_register_static(&virtio_rtc_info);
+}
+
+type_init(virtio_rtc_register_types)
diff --git a/include/hw/virtio/virtio-rtc.h b/include/hw/virtio/virtio-rtc.h
new file mode 100644
index 0000000000..51aa35201a
--- /dev/null
+++ b/include/hw/virtio/virtio-rtc.h
@@ -0,0 +1,22 @@
+/*
+ * Virtio RTC device
+ *
+ * Copyright (c) 2026 Kuan-Wei Chiu <visitorckw@gmail.com>
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef QEMU_VIRTIO_RTC_H
+#define QEMU_VIRTIO_RTC_H
+
+#include "hw/virtio/virtio.h"
+#include "qom/object.h"
+
+#define TYPE_VIRTIO_RTC "virtio-rtc-device"
+OBJECT_DECLARE_SIMPLE_TYPE(VirtIORtc, VIRTIO_RTC)
+
+struct VirtIORtc {
+    VirtIODevice parent_obj;
+    VirtQueue *vq;
+};
+
+#endif
-- 
2.53.0.473.g4a7958ca14-goog