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
© 2016 - 2026 Red Hat, Inc.