diff --git a/config-libkrunfw_aarch64 b/config-libkrunfw_aarch64 index 2588cce..afed67b 100644 --- a/config-libkrunfw_aarch64 +++ b/config-libkrunfw_aarch64 @@ -2279,6 +2279,10 @@ CONFIG_VIRTIO_MENU=y CONFIG_VIRTIO_BALLOON=y # CONFIG_VIRTIO_MEM is not set CONFIG_VIRTIO_INPUT=y +CONFIG_VIRTIO_RTC=y +CONFIG_VIRTIO_RTC_PTP=y +CONFIG_VIRTIO_RTC_ARM=y +CONFIG_VIRTIO_RTC_CLASS=y CONFIG_VIRTIO_MMIO=y CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y CONFIG_VIRTIO_DMA_SHARED_BUFFER=y diff --git a/config-libkrunfw_x86_64 b/config-libkrunfw_x86_64 index 4783d50..513fce8 100644 --- a/config-libkrunfw_x86_64 +++ b/config-libkrunfw_x86_64 @@ -1394,7 +1394,7 @@ CONFIG_EDAC_ATOMIC_SCRUB=y CONFIG_EDAC_SUPPORT=y CONFIG_RTC_LIB=y CONFIG_RTC_MC146818_LIB=y -# CONFIG_RTC_CLASS is not set +CONFIG_RTC_CLASS=y # CONFIG_DMADEVICES is not set # @@ -1419,6 +1419,9 @@ CONFIG_VIRTIO_MENU=y CONFIG_VIRTIO_BALLOON=y # CONFIG_VIRTIO_MEM is not set CONFIG_VIRTIO_INPUT=y +CONFIG_VIRTIO_RTC=y +CONFIG_VIRTIO_RTC_PTP=y +CONFIG_VIRTIO_RTC_CLASS=y CONFIG_VIRTIO_MMIO=y CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y # CONFIG_VDPA is not set diff --git a/patches/0028-virtio_rtc-Add-module-and-driver-core.patch b/patches/0028-virtio_rtc-Add-module-and-driver-core.patch new file mode 100644 index 0000000..04f1561 --- /dev/null +++ b/patches/0028-virtio_rtc-Add-module-and-driver-core.patch @@ -0,0 +1,1064 @@ +From 0623c759276885c3ae88197ba6fb5c9c6ba8612f Mon Sep 17 00:00:00 2001 +From: Peter Hilber +Date: Fri, 9 May 2025 18:07:22 +0200 +Subject: [PATCH] virtio_rtc: Add module and driver core + +Add the virtio_rtc module and driver core. The virtio_rtc module implements +a driver compatible with the proposed Virtio RTC device specification. +The Virtio RTC (Real Time Clock) device provides information about current +time. The device can provide different clocks, e.g. for the UTC or TAI time +standards, or for physical time elapsed since some past epoch. The driver +can read the clocks with simple or more accurate methods. + +Implement the core, which interacts with the Virtio RTC device. Apart from +this, the core does not expose functionality outside of the virtio_rtc +module. Follow-up patches will expose PTP clocks and an RTC Class device. + +Provide synchronous messaging, which is enough for the expected time +synchronization use cases through PTP clocks (similar to ptp_kvm) or RTC +Class device. + +Signed-off-by: Peter Hilber +Message-Id: <20250509160734.1772-2-quic_philber@quicinc.com> +Signed-off-by: Michael S. Tsirkin +--- + MAINTAINERS | 7 + + drivers/virtio/Kconfig | 13 + + drivers/virtio/Makefile | 2 + + drivers/virtio/virtio_rtc_driver.c | 786 +++++++++++++++++++++++++++ + drivers/virtio/virtio_rtc_internal.h | 24 + + include/uapi/linux/virtio_rtc.h | 151 +++++ + 6 files changed, 983 insertions(+) + create mode 100644 drivers/virtio/virtio_rtc_driver.c + create mode 100644 drivers/virtio/virtio_rtc_internal.h + create mode 100644 include/uapi/linux/virtio_rtc.h + +diff --git a/MAINTAINERS b/MAINTAINERS +index d48dd6726fe6bd..56c487bbea9c02 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -25827,6 +25827,13 @@ S: Maintained + F: drivers/nvdimm/nd_virtio.c + F: drivers/nvdimm/virtio_pmem.c + ++VIRTIO RTC DRIVER ++M: Peter Hilber ++L: virtualization@lists.linux.dev ++S: Maintained ++F: drivers/virtio/virtio_rtc_* ++F: include/uapi/linux/virtio_rtc.h ++ + VIRTIO SOUND DRIVER + M: Anton Yakovlev + M: "Michael S. Tsirkin" +diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig +index 2eb747311bfd9c..83bcb06acb6c61 100644 +--- a/drivers/virtio/Kconfig ++++ b/drivers/virtio/Kconfig +@@ -188,4 +188,17 @@ config VIRTIO_DEBUG + + If unsure, say N. + ++config VIRTIO_RTC ++ tristate "Virtio RTC driver" ++ depends on VIRTIO ++ depends on PTP_1588_CLOCK_OPTIONAL ++ help ++ This driver provides current time from a Virtio RTC device. The driver ++ provides the time through one or more clocks. ++ ++ To compile this code as a module, choose M here: the module will be ++ called virtio_rtc. ++ ++ If unsure, say M. ++ + endif # VIRTIO_MENU +diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile +index 58b2b0489fc9d7..c41c4c0f926489 100644 +--- a/drivers/virtio/Makefile ++++ b/drivers/virtio/Makefile +@@ -14,3 +14,5 @@ obj-$(CONFIG_VIRTIO_VDPA) += virtio_vdpa.o + obj-$(CONFIG_VIRTIO_MEM) += virtio_mem.o + obj-$(CONFIG_VIRTIO_DMA_SHARED_BUFFER) += virtio_dma_buf.o + obj-$(CONFIG_VIRTIO_DEBUG) += virtio_debug.o ++obj-$(CONFIG_VIRTIO_RTC) += virtio_rtc.o ++virtio_rtc-y := virtio_rtc_driver.o +diff --git a/drivers/virtio/virtio_rtc_driver.c b/drivers/virtio/virtio_rtc_driver.c +new file mode 100644 +index 00000000000000..c87ee4e6342454 +--- /dev/null ++++ b/drivers/virtio/virtio_rtc_driver.c +@@ -0,0 +1,786 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * virtio_rtc driver core ++ * ++ * Copyright (C) 2022-2024 OpenSynergy GmbH ++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "virtio_rtc_internal.h" ++ ++/* virtqueue order */ ++enum { ++ VIORTC_REQUESTQ, ++ VIORTC_MAX_NR_QUEUES, ++}; ++ ++/** ++ * struct viortc_vq - virtqueue abstraction ++ * @vq: virtqueue ++ * @lock: protects access to vq ++ */ ++struct viortc_vq { ++ struct virtqueue *vq; ++ spinlock_t lock; ++}; ++ ++/** ++ * struct viortc_dev - virtio_rtc device data ++ * @vdev: virtio device ++ * @vqs: virtqueues ++ * @num_clocks: # of virtio_rtc clocks ++ */ ++struct viortc_dev { ++ struct virtio_device *vdev; ++ struct viortc_vq vqs[VIORTC_MAX_NR_QUEUES]; ++ u16 num_clocks; ++}; ++ ++/** ++ * struct viortc_msg - Message requested by driver, responded by device. ++ * @viortc: device data ++ * @req: request buffer ++ * @resp: response buffer ++ * @responded: vqueue callback signals response reception ++ * @refcnt: Message reference count, message and buffers will be deallocated ++ * once 0. refcnt is decremented in the vqueue callback and in the ++ * thread waiting on the responded completion. ++ * If a message response wait function times out, the message will be ++ * freed upon late reception (refcnt will reach 0 in the callback), or ++ * device removal. ++ * @req_size: size of request in bytes ++ * @resp_cap: maximum size of response in bytes ++ * @resp_actual_size: actual size of response ++ */ ++struct viortc_msg { ++ struct viortc_dev *viortc; ++ void *req; ++ void *resp; ++ struct completion responded; ++ refcount_t refcnt; ++ unsigned int req_size; ++ unsigned int resp_cap; ++ unsigned int resp_actual_size; ++}; ++ ++/** ++ * viortc_msg_init() - Allocate and initialize requestq message. ++ * @viortc: device data ++ * @msg_type: virtio_rtc message type ++ * @req_size: size of request buffer to be allocated ++ * @resp_cap: size of response buffer to be allocated ++ * ++ * Initializes the message refcnt to 2. The refcnt will be decremented once in ++ * the virtqueue callback, and once in the thread waiting on the message (on ++ * completion or timeout). ++ * ++ * Context: Process context. ++ * Return: non-NULL on success. ++ */ ++static struct viortc_msg *viortc_msg_init(struct viortc_dev *viortc, ++ u16 msg_type, unsigned int req_size, ++ unsigned int resp_cap) ++{ ++ struct device *dev = &viortc->vdev->dev; ++ struct virtio_rtc_req_head *req_head; ++ struct viortc_msg *msg; ++ ++ msg = devm_kzalloc(dev, sizeof(*msg), GFP_KERNEL); ++ if (!msg) ++ return NULL; ++ ++ init_completion(&msg->responded); ++ ++ msg->req = devm_kzalloc(dev, req_size, GFP_KERNEL); ++ if (!msg->req) ++ goto err_free_msg; ++ ++ req_head = msg->req; ++ ++ msg->resp = devm_kzalloc(dev, resp_cap, GFP_KERNEL); ++ if (!msg->resp) ++ goto err_free_msg_req; ++ ++ msg->viortc = viortc; ++ msg->req_size = req_size; ++ msg->resp_cap = resp_cap; ++ ++ refcount_set(&msg->refcnt, 2); ++ ++ req_head->msg_type = virtio_cpu_to_le(msg_type, req_head->msg_type); ++ ++ return msg; ++ ++err_free_msg_req: ++ devm_kfree(dev, msg->req); ++ ++err_free_msg: ++ devm_kfree(dev, msg); ++ ++ return NULL; ++} ++ ++/** ++ * viortc_msg_release() - Decrement message refcnt, potentially free message. ++ * @msg: message requested by driver ++ * ++ * Context: Any context. ++ */ ++static void viortc_msg_release(struct viortc_msg *msg) ++{ ++ struct device *dev; ++ ++ if (refcount_dec_and_test(&msg->refcnt)) { ++ dev = &msg->viortc->vdev->dev; ++ ++ devm_kfree(dev, msg->req); ++ devm_kfree(dev, msg->resp); ++ devm_kfree(dev, msg); ++ } ++} ++ ++/** ++ * viortc_do_cb() - generic virtqueue callback logic ++ * @vq: virtqueue ++ * @handle_buf: function to process a used buffer ++ * ++ * Context: virtqueue callback, typically interrupt. Takes and releases vq lock. ++ */ ++static void viortc_do_cb(struct virtqueue *vq, ++ void (*handle_buf)(void *token, unsigned int len, ++ struct virtqueue *vq, ++ struct viortc_vq *viortc_vq, ++ struct viortc_dev *viortc)) ++{ ++ struct viortc_dev *viortc = vq->vdev->priv; ++ struct viortc_vq *viortc_vq; ++ bool cb_enabled = true; ++ unsigned long flags; ++ unsigned int len; ++ void *token; ++ ++ viortc_vq = &viortc->vqs[vq->index]; ++ ++ for (;;) { ++ spin_lock_irqsave(&viortc_vq->lock, flags); ++ ++ if (cb_enabled) { ++ virtqueue_disable_cb(vq); ++ cb_enabled = false; ++ } ++ ++ token = virtqueue_get_buf(vq, &len); ++ if (!token) { ++ if (virtqueue_enable_cb(vq)) { ++ spin_unlock_irqrestore(&viortc_vq->lock, flags); ++ return; ++ } ++ cb_enabled = true; ++ } ++ ++ spin_unlock_irqrestore(&viortc_vq->lock, flags); ++ ++ if (token) ++ handle_buf(token, len, vq, viortc_vq, viortc); ++ } ++} ++ ++/** ++ * viortc_requestq_hdlr() - process a requestq used buffer ++ * @token: token identifying the buffer ++ * @len: bytes written by device ++ * @vq: virtqueue ++ * @viortc_vq: device specific data for virtqueue ++ * @viortc: device data ++ * ++ * Signals completion for each received message. ++ * ++ * Context: virtqueue callback ++ */ ++static void viortc_requestq_hdlr(void *token, unsigned int len, ++ struct virtqueue *vq, ++ struct viortc_vq *viortc_vq, ++ struct viortc_dev *viortc) ++{ ++ struct viortc_msg *msg = token; ++ ++ msg->resp_actual_size = len; ++ ++ complete(&msg->responded); ++ viortc_msg_release(msg); ++} ++ ++/** ++ * viortc_cb_requestq() - callback for requestq ++ * @vq: virtqueue ++ * ++ * Context: virtqueue callback ++ */ ++static void viortc_cb_requestq(struct virtqueue *vq) ++{ ++ viortc_do_cb(vq, viortc_requestq_hdlr); ++} ++ ++/** ++ * viortc_get_resp_errno() - converts virtio_rtc errnos to system errnos ++ * @resp_head: message response header ++ * ++ * Return: negative system errno, or 0 ++ */ ++static int viortc_get_resp_errno(struct virtio_rtc_resp_head *resp_head) ++{ ++ switch (virtio_le_to_cpu(resp_head->status)) { ++ case VIRTIO_RTC_S_OK: ++ return 0; ++ case VIRTIO_RTC_S_EOPNOTSUPP: ++ return -EOPNOTSUPP; ++ case VIRTIO_RTC_S_EINVAL: ++ return -EINVAL; ++ case VIRTIO_RTC_S_ENODEV: ++ return -ENODEV; ++ case VIRTIO_RTC_S_EIO: ++ default: ++ return -EIO; ++ } ++} ++ ++/** ++ * viortc_msg_xfer() - send message request, wait until message response ++ * @vq: virtqueue ++ * @msg: message with driver request ++ * @timeout_jiffies: message response timeout, 0 for no timeout ++ * ++ * Context: Process context. Takes and releases vq.lock. May sleep. ++ * Return: Zero on success, negative error code otherwise. ++ */ ++static int viortc_msg_xfer(struct viortc_vq *vq, struct viortc_msg *msg, ++ unsigned long timeout_jiffies) ++{ ++ struct scatterlist out_sg[1]; ++ struct scatterlist in_sg[1]; ++ struct scatterlist *sgs[2]; ++ unsigned long flags; ++ long timeout_ret; ++ bool notify; ++ int ret; ++ ++ sgs[0] = out_sg; ++ sgs[1] = in_sg; ++ ++ sg_init_one(out_sg, msg->req, msg->req_size); ++ sg_init_one(in_sg, msg->resp, msg->resp_cap); ++ ++ spin_lock_irqsave(&vq->lock, flags); ++ ++ ret = virtqueue_add_sgs(vq->vq, sgs, 1, 1, msg, GFP_ATOMIC); ++ if (ret) { ++ spin_unlock_irqrestore(&vq->lock, flags); ++ /* ++ * Release in place of the response callback, which will never ++ * come. ++ */ ++ viortc_msg_release(msg); ++ return ret; ++ } ++ ++ notify = virtqueue_kick_prepare(vq->vq); ++ ++ spin_unlock_irqrestore(&vq->lock, flags); ++ ++ if (notify) ++ virtqueue_notify(vq->vq); ++ ++ if (timeout_jiffies) { ++ timeout_ret = wait_for_completion_interruptible_timeout( ++ &msg->responded, timeout_jiffies); ++ ++ if (!timeout_ret) ++ return -ETIMEDOUT; ++ else if (timeout_ret < 0) ++ return (int)timeout_ret; ++ } else { ++ ret = wait_for_completion_interruptible(&msg->responded); ++ if (ret) ++ return ret; ++ } ++ ++ if (msg->resp_actual_size < sizeof(struct virtio_rtc_resp_head)) ++ return -EINVAL; ++ ++ ret = viortc_get_resp_errno(msg->resp); ++ if (ret) ++ return ret; ++ ++ /* ++ * There is not yet a case where returning a short message would make ++ * sense, so consider any deviation an error. ++ */ ++ if (msg->resp_actual_size != msg->resp_cap) ++ return -EINVAL; ++ ++ return 0; ++} ++ ++/* ++ * common message handle macros for messages of different types ++ */ ++ ++/** ++ * VIORTC_DECLARE_MSG_HDL_ONSTACK() - declare message handle on stack ++ * @hdl: message handle name ++ * @msg_id: message type id ++ * @msg_req: message request type ++ * @msg_resp: message response type ++ */ ++#define VIORTC_DECLARE_MSG_HDL_ONSTACK(hdl, msg_id, msg_req, msg_resp) \ ++ struct { \ ++ struct viortc_msg *msg; \ ++ msg_req *req; \ ++ msg_resp *resp; \ ++ unsigned int req_size; \ ++ unsigned int resp_cap; \ ++ u16 msg_type; \ ++ } hdl = { \ ++ NULL, NULL, NULL, sizeof(msg_req), sizeof(msg_resp), (msg_id), \ ++ } ++ ++/** ++ * VIORTC_MSG() - extract message from message handle ++ * @hdl: message handle ++ * ++ * Return: struct viortc_msg ++ */ ++#define VIORTC_MSG(hdl) ((hdl).msg) ++ ++/** ++ * VIORTC_MSG_INIT() - initialize message handle ++ * @hdl: message handle ++ * @viortc: device data (struct viortc_dev *) ++ * ++ * Context: Process context. ++ * Return: 0 on success, -ENOMEM otherwise. ++ */ ++#define VIORTC_MSG_INIT(hdl, viortc) \ ++ ({ \ ++ typeof(hdl) *_hdl = &(hdl); \ ++ \ ++ _hdl->msg = viortc_msg_init((viortc), _hdl->msg_type, \ ++ _hdl->req_size, _hdl->resp_cap); \ ++ if (_hdl->msg) { \ ++ _hdl->req = _hdl->msg->req; \ ++ _hdl->resp = _hdl->msg->resp; \ ++ } \ ++ _hdl->msg ? 0 : -ENOMEM; \ ++ }) ++ ++/** ++ * VIORTC_MSG_WRITE() - write a request message field ++ * @hdl: message handle ++ * @dest_member: request message field name ++ * @src_ptr: pointer to data of compatible type ++ * ++ * Writes the field in little-endian format. ++ */ ++#define VIORTC_MSG_WRITE(hdl, dest_member, src_ptr) \ ++ do { \ ++ typeof(hdl) _hdl = (hdl); \ ++ typeof(src_ptr) _src_ptr = (src_ptr); \ ++ \ ++ /* Sanity check: must match the member's type */ \ ++ typecheck(typeof(virtio_le_to_cpu(_hdl.req->dest_member)), \ ++ *_src_ptr); \ ++ \ ++ _hdl.req->dest_member = \ ++ virtio_cpu_to_le(*_src_ptr, _hdl.req->dest_member); \ ++ } while (0) ++ ++/** ++ * VIORTC_MSG_READ() - read from a response message field ++ * @hdl: message handle ++ * @src_member: response message field name ++ * @dest_ptr: pointer to data of compatible type ++ * ++ * Converts from little-endian format and writes to dest_ptr. ++ */ ++#define VIORTC_MSG_READ(hdl, src_member, dest_ptr) \ ++ do { \ ++ typeof(dest_ptr) _dest_ptr = (dest_ptr); \ ++ \ ++ /* Sanity check: must match the member's type */ \ ++ typecheck(typeof(virtio_le_to_cpu((hdl).resp->src_member)), \ ++ *_dest_ptr); \ ++ \ ++ *_dest_ptr = virtio_le_to_cpu((hdl).resp->src_member); \ ++ } while (0) ++ ++/* ++ * read requests ++ */ ++ ++/** timeout for clock readings, where timeouts are considered non-fatal */ ++#define VIORTC_MSG_READ_TIMEOUT secs_to_jiffies(60) ++ ++/** ++ * viortc_read() - VIRTIO_RTC_REQ_READ wrapper ++ * @viortc: device data ++ * @vio_clk_id: virtio_rtc clock id ++ * @reading: clock reading [ns] ++ * ++ * Context: Process context. ++ * Return: Zero on success, negative error code otherwise. ++ */ ++int viortc_read(struct viortc_dev *viortc, u16 vio_clk_id, u64 *reading) ++{ ++ VIORTC_DECLARE_MSG_HDL_ONSTACK(hdl, VIRTIO_RTC_REQ_READ, ++ struct virtio_rtc_req_read, ++ struct virtio_rtc_resp_read); ++ int ret; ++ ++ ret = VIORTC_MSG_INIT(hdl, viortc); ++ if (ret) ++ return ret; ++ ++ VIORTC_MSG_WRITE(hdl, clock_id, &vio_clk_id); ++ ++ ret = viortc_msg_xfer(&viortc->vqs[VIORTC_REQUESTQ], VIORTC_MSG(hdl), ++ VIORTC_MSG_READ_TIMEOUT); ++ if (ret) { ++ dev_dbg(&viortc->vdev->dev, "%s: xfer returned %d\n", __func__, ++ ret); ++ goto out_release; ++ } ++ ++ VIORTC_MSG_READ(hdl, clock_reading, reading); ++ ++out_release: ++ viortc_msg_release(VIORTC_MSG(hdl)); ++ ++ return ret; ++} ++ ++/** ++ * viortc_read_cross() - VIRTIO_RTC_REQ_READ_CROSS wrapper ++ * @viortc: device data ++ * @vio_clk_id: virtio_rtc clock id ++ * @hw_counter: virtio_rtc HW counter type ++ * @reading: clock reading [ns] ++ * @cycles: HW counter cycles during clock reading ++ * ++ * Context: Process context. ++ * Return: Zero on success, negative error code otherwise. ++ */ ++int viortc_read_cross(struct viortc_dev *viortc, u16 vio_clk_id, u8 hw_counter, ++ u64 *reading, u64 *cycles) ++{ ++ VIORTC_DECLARE_MSG_HDL_ONSTACK(hdl, VIRTIO_RTC_REQ_READ_CROSS, ++ struct virtio_rtc_req_read_cross, ++ struct virtio_rtc_resp_read_cross); ++ int ret; ++ ++ ret = VIORTC_MSG_INIT(hdl, viortc); ++ if (ret) ++ return ret; ++ ++ VIORTC_MSG_WRITE(hdl, clock_id, &vio_clk_id); ++ VIORTC_MSG_WRITE(hdl, hw_counter, &hw_counter); ++ ++ ret = viortc_msg_xfer(&viortc->vqs[VIORTC_REQUESTQ], VIORTC_MSG(hdl), ++ VIORTC_MSG_READ_TIMEOUT); ++ if (ret) { ++ dev_dbg(&viortc->vdev->dev, "%s: xfer returned %d\n", __func__, ++ ret); ++ goto out_release; ++ } ++ ++ VIORTC_MSG_READ(hdl, clock_reading, reading); ++ VIORTC_MSG_READ(hdl, counter_cycles, cycles); ++ ++out_release: ++ viortc_msg_release(VIORTC_MSG(hdl)); ++ ++ return ret; ++} ++ ++/* ++ * control requests ++ */ ++ ++/** ++ * viortc_cfg() - VIRTIO_RTC_REQ_CFG wrapper ++ * @viortc: device data ++ * @num_clocks: # of virtio_rtc clocks ++ * ++ * Context: Process context. ++ * Return: Zero on success, negative error code otherwise. ++ */ ++static int viortc_cfg(struct viortc_dev *viortc, u16 *num_clocks) ++{ ++ VIORTC_DECLARE_MSG_HDL_ONSTACK(hdl, VIRTIO_RTC_REQ_CFG, ++ struct virtio_rtc_req_cfg, ++ struct virtio_rtc_resp_cfg); ++ int ret; ++ ++ ret = VIORTC_MSG_INIT(hdl, viortc); ++ if (ret) ++ return ret; ++ ++ ret = viortc_msg_xfer(&viortc->vqs[VIORTC_REQUESTQ], VIORTC_MSG(hdl), ++ 0); ++ if (ret) { ++ dev_dbg(&viortc->vdev->dev, "%s: xfer returned %d\n", __func__, ++ ret); ++ goto out_release; ++ } ++ ++ VIORTC_MSG_READ(hdl, num_clocks, num_clocks); ++ ++out_release: ++ viortc_msg_release(VIORTC_MSG(hdl)); ++ ++ return ret; ++} ++ ++/** ++ * viortc_clock_cap() - VIRTIO_RTC_REQ_CLOCK_CAP wrapper ++ * @viortc: device data ++ * @vio_clk_id: virtio_rtc clock id ++ * @type: virtio_rtc clock type ++ * @leap_second_smearing: virtio_rtc smearing variant ++ * ++ * Context: Process context. ++ * Return: Zero on success, negative error code otherwise. ++ */ ++static int viortc_clock_cap(struct viortc_dev *viortc, u16 vio_clk_id, u8 *type, ++ u8 *leap_second_smearing) ++{ ++ VIORTC_DECLARE_MSG_HDL_ONSTACK(hdl, VIRTIO_RTC_REQ_CLOCK_CAP, ++ struct virtio_rtc_req_clock_cap, ++ struct virtio_rtc_resp_clock_cap); ++ int ret; ++ ++ ret = VIORTC_MSG_INIT(hdl, viortc); ++ if (ret) ++ return ret; ++ ++ VIORTC_MSG_WRITE(hdl, clock_id, &vio_clk_id); ++ ++ ret = viortc_msg_xfer(&viortc->vqs[VIORTC_REQUESTQ], VIORTC_MSG(hdl), ++ 0); ++ if (ret) { ++ dev_dbg(&viortc->vdev->dev, "%s: xfer returned %d\n", __func__, ++ ret); ++ goto out_release; ++ } ++ ++ VIORTC_MSG_READ(hdl, type, type); ++ VIORTC_MSG_READ(hdl, leap_second_smearing, leap_second_smearing); ++ ++out_release: ++ viortc_msg_release(VIORTC_MSG(hdl)); ++ ++ return ret; ++} ++ ++/** ++ * viortc_cross_cap() - VIRTIO_RTC_REQ_CROSS_CAP wrapper ++ * @viortc: device data ++ * @vio_clk_id: virtio_rtc clock id ++ * @hw_counter: virtio_rtc HW counter type ++ * @supported: xtstamping is supported for the vio_clk_id/hw_counter pair ++ * ++ * Context: Process context. ++ * Return: Zero on success, negative error code otherwise. ++ */ ++int viortc_cross_cap(struct viortc_dev *viortc, u16 vio_clk_id, u8 hw_counter, ++ bool *supported) ++{ ++ VIORTC_DECLARE_MSG_HDL_ONSTACK(hdl, VIRTIO_RTC_REQ_CROSS_CAP, ++ struct virtio_rtc_req_cross_cap, ++ struct virtio_rtc_resp_cross_cap); ++ u8 flags; ++ int ret; ++ ++ ret = VIORTC_MSG_INIT(hdl, viortc); ++ if (ret) ++ return ret; ++ ++ VIORTC_MSG_WRITE(hdl, clock_id, &vio_clk_id); ++ VIORTC_MSG_WRITE(hdl, hw_counter, &hw_counter); ++ ++ ret = viortc_msg_xfer(&viortc->vqs[VIORTC_REQUESTQ], VIORTC_MSG(hdl), ++ 0); ++ if (ret) { ++ dev_dbg(&viortc->vdev->dev, "%s: xfer returned %d\n", __func__, ++ ret); ++ goto out_release; ++ } ++ ++ VIORTC_MSG_READ(hdl, flags, &flags); ++ *supported = !!(flags & VIRTIO_RTC_FLAG_CROSS_CAP); ++ ++out_release: ++ viortc_msg_release(VIORTC_MSG(hdl)); ++ ++ return ret; ++} ++ ++/* ++ * init, deinit ++ */ ++ ++/** ++ * viortc_clocks_init() - init local representations of virtio_rtc clocks ++ * @viortc: device data ++ * ++ * Context: Process context. ++ * Return: Zero on success, negative error code otherwise. ++ */ ++static int viortc_clocks_init(struct viortc_dev *viortc) ++{ ++ u16 num_clocks; ++ int ret; ++ ++ ret = viortc_cfg(viortc, &num_clocks); ++ if (ret) ++ return ret; ++ ++ if (num_clocks < 1) { ++ dev_err(&viortc->vdev->dev, "device reported 0 clocks\n"); ++ return -ENODEV; ++ } ++ ++ viortc->num_clocks = num_clocks; ++ ++ /* In the future, PTP clocks will be initialized here. */ ++ (void)viortc_clock_cap; ++ ++ return ret; ++} ++ ++/** ++ * viortc_init_vqs() - init virtqueues ++ * @viortc: device data ++ * ++ * Inits virtqueues and associated data. ++ * ++ * Context: Process context. ++ * Return: Zero on success, negative error code otherwise. ++ */ ++static int viortc_init_vqs(struct viortc_dev *viortc) ++{ ++ struct virtqueue *vqs[VIORTC_MAX_NR_QUEUES]; ++ struct virtqueue_info vqs_info[] = { ++ { "requestq", viortc_cb_requestq }, ++ }; ++ struct virtio_device *vdev = viortc->vdev; ++ int nr_queues, ret; ++ ++ nr_queues = VIORTC_REQUESTQ + 1; ++ ++ ret = virtio_find_vqs(vdev, nr_queues, vqs, vqs_info, NULL); ++ if (ret) ++ return ret; ++ ++ viortc->vqs[VIORTC_REQUESTQ].vq = vqs[VIORTC_REQUESTQ]; ++ spin_lock_init(&viortc->vqs[VIORTC_REQUESTQ].lock); ++ ++ return 0; ++} ++ ++/** ++ * viortc_probe() - probe a virtio_rtc virtio device ++ * @vdev: virtio device ++ * ++ * Context: Process context. ++ * Return: Zero on success, negative error code otherwise. ++ */ ++static int viortc_probe(struct virtio_device *vdev) ++{ ++ struct viortc_dev *viortc; ++ int ret; ++ ++ viortc = devm_kzalloc(&vdev->dev, sizeof(*viortc), GFP_KERNEL); ++ if (!viortc) ++ return -ENOMEM; ++ ++ vdev->priv = viortc; ++ viortc->vdev = vdev; ++ ++ ret = viortc_init_vqs(viortc); ++ if (ret) ++ return ret; ++ ++ virtio_device_ready(vdev); ++ ++ ret = viortc_clocks_init(viortc); ++ if (ret) ++ goto err_reset_vdev; ++ ++ return 0; ++ ++err_reset_vdev: ++ virtio_reset_device(vdev); ++ vdev->config->del_vqs(vdev); ++ ++ return ret; ++} ++ ++/** ++ * viortc_remove() - remove a virtio_rtc virtio device ++ * @vdev: virtio device ++ */ ++static void viortc_remove(struct virtio_device *vdev) ++{ ++ /* In the future, PTP clocks will be deinitialized here. */ ++ ++ virtio_reset_device(vdev); ++ vdev->config->del_vqs(vdev); ++} ++ ++static int viortc_freeze(struct virtio_device *dev) ++{ ++ /* ++ * Do not reset the device, so that the device may still wake up the ++ * system through an alarmq notification. ++ */ ++ ++ return 0; ++} ++ ++static int viortc_restore(struct virtio_device *dev) ++{ ++ struct viortc_dev *viortc = dev->priv; ++ ++ return viortc_init_vqs(viortc); ++} ++ ++static struct virtio_device_id id_table[] = { ++ { VIRTIO_ID_CLOCK, VIRTIO_DEV_ANY_ID }, ++ { 0 }, ++}; ++MODULE_DEVICE_TABLE(virtio, id_table); ++ ++static struct virtio_driver virtio_rtc_drv = { ++ .driver.name = KBUILD_MODNAME, ++ .id_table = id_table, ++ .probe = viortc_probe, ++ .remove = viortc_remove, ++ .freeze = pm_sleep_ptr(viortc_freeze), ++ .restore = pm_sleep_ptr(viortc_restore), ++}; ++ ++module_virtio_driver(virtio_rtc_drv); ++ ++MODULE_DESCRIPTION("Virtio RTC driver"); ++MODULE_AUTHOR("Qualcomm Innovation Center, Inc."); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/virtio/virtio_rtc_internal.h b/drivers/virtio/virtio_rtc_internal.h +new file mode 100644 +index 00000000000000..9c249c15b68f92 +--- /dev/null ++++ b/drivers/virtio/virtio_rtc_internal.h +@@ -0,0 +1,24 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * virtio_rtc internal interfaces ++ * ++ * Copyright (C) 2022-2023 OpenSynergy GmbH ++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. ++ */ ++ ++#ifndef _VIRTIO_RTC_INTERNAL_H_ ++#define _VIRTIO_RTC_INTERNAL_H_ ++ ++#include ++ ++/* driver core IFs */ ++ ++struct viortc_dev; ++ ++int viortc_read(struct viortc_dev *viortc, u16 vio_clk_id, u64 *reading); ++int viortc_read_cross(struct viortc_dev *viortc, u16 vio_clk_id, u8 hw_counter, ++ u64 *reading, u64 *cycles); ++int viortc_cross_cap(struct viortc_dev *viortc, u16 vio_clk_id, u8 hw_counter, ++ bool *supported); ++ ++#endif /* _VIRTIO_RTC_INTERNAL_H_ */ +diff --git a/include/uapi/linux/virtio_rtc.h b/include/uapi/linux/virtio_rtc.h +new file mode 100644 +index 00000000000000..6b3af4e9bbfb06 +--- /dev/null ++++ b/include/uapi/linux/virtio_rtc.h +@@ -0,0 +1,151 @@ ++/* SPDX-License-Identifier: ((GPL-2.0+ WITH Linux-syscall-note) OR BSD-3-Clause) */ ++/* ++ * Copyright (C) 2022-2024 OpenSynergy GmbH ++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. ++ */ ++ ++#ifndef _LINUX_VIRTIO_RTC_H ++#define _LINUX_VIRTIO_RTC_H ++ ++#include ++ ++/* read request message types */ ++ ++#define VIRTIO_RTC_REQ_READ 0x0001 ++#define VIRTIO_RTC_REQ_READ_CROSS 0x0002 ++ ++/* control request message types */ ++ ++#define VIRTIO_RTC_REQ_CFG 0x1000 ++#define VIRTIO_RTC_REQ_CLOCK_CAP 0x1001 ++#define VIRTIO_RTC_REQ_CROSS_CAP 0x1002 ++ ++/* Message headers */ ++ ++/** common request header */ ++struct virtio_rtc_req_head { ++ __le16 msg_type; ++ __u8 reserved[6]; ++}; ++ ++/** common response header */ ++struct virtio_rtc_resp_head { ++#define VIRTIO_RTC_S_OK 0 ++#define VIRTIO_RTC_S_EOPNOTSUPP 2 ++#define VIRTIO_RTC_S_ENODEV 3 ++#define VIRTIO_RTC_S_EINVAL 4 ++#define VIRTIO_RTC_S_EIO 5 ++ __u8 status; ++ __u8 reserved[7]; ++}; ++ ++/* read requests */ ++ ++/* VIRTIO_RTC_REQ_READ message */ ++ ++struct virtio_rtc_req_read { ++ struct virtio_rtc_req_head head; ++ __le16 clock_id; ++ __u8 reserved[6]; ++}; ++ ++struct virtio_rtc_resp_read { ++ struct virtio_rtc_resp_head head; ++ __le64 clock_reading; ++}; ++ ++/* VIRTIO_RTC_REQ_READ_CROSS message */ ++ ++struct virtio_rtc_req_read_cross { ++ struct virtio_rtc_req_head head; ++ __le16 clock_id; ++/* Arm Generic Timer Counter-timer Virtual Count Register (CNTVCT_EL0) */ ++#define VIRTIO_RTC_COUNTER_ARM_VCT 0 ++/* x86 Time-Stamp Counter */ ++#define VIRTIO_RTC_COUNTER_X86_TSC 1 ++/* Invalid */ ++#define VIRTIO_RTC_COUNTER_INVALID 0xFF ++ __u8 hw_counter; ++ __u8 reserved[5]; ++}; ++ ++struct virtio_rtc_resp_read_cross { ++ struct virtio_rtc_resp_head head; ++ __le64 clock_reading; ++ __le64 counter_cycles; ++}; ++ ++/* control requests */ ++ ++/* VIRTIO_RTC_REQ_CFG message */ ++ ++struct virtio_rtc_req_cfg { ++ struct virtio_rtc_req_head head; ++ /* no request params */ ++}; ++ ++struct virtio_rtc_resp_cfg { ++ struct virtio_rtc_resp_head head; ++ /** # of clocks -> clock ids < num_clocks are valid */ ++ __le16 num_clocks; ++ __u8 reserved[6]; ++}; ++ ++/* VIRTIO_RTC_REQ_CLOCK_CAP message */ ++ ++struct virtio_rtc_req_clock_cap { ++ struct virtio_rtc_req_head head; ++ __le16 clock_id; ++ __u8 reserved[6]; ++}; ++ ++struct virtio_rtc_resp_clock_cap { ++ struct virtio_rtc_resp_head head; ++#define VIRTIO_RTC_CLOCK_UTC 0 ++#define VIRTIO_RTC_CLOCK_TAI 1 ++#define VIRTIO_RTC_CLOCK_MONOTONIC 2 ++#define VIRTIO_RTC_CLOCK_UTC_SMEARED 3 ++#define VIRTIO_RTC_CLOCK_UTC_MAYBE_SMEARED 4 ++ __u8 type; ++#define VIRTIO_RTC_SMEAR_UNSPECIFIED 0 ++#define VIRTIO_RTC_SMEAR_NOON_LINEAR 1 ++#define VIRTIO_RTC_SMEAR_UTC_SLS 2 ++ __u8 leap_second_smearing; ++ __u8 reserved[6]; ++}; ++ ++/* VIRTIO_RTC_REQ_CROSS_CAP message */ ++ ++struct virtio_rtc_req_cross_cap { ++ struct virtio_rtc_req_head head; ++ __le16 clock_id; ++ __u8 hw_counter; ++ __u8 reserved[5]; ++}; ++ ++struct virtio_rtc_resp_cross_cap { ++ struct virtio_rtc_resp_head head; ++#define VIRTIO_RTC_FLAG_CROSS_CAP (1 << 0) ++ __u8 flags; ++ __u8 reserved[7]; ++}; ++ ++/** Union of request types for requestq */ ++union virtio_rtc_req_requestq { ++ struct virtio_rtc_req_read read; ++ struct virtio_rtc_req_read_cross read_cross; ++ struct virtio_rtc_req_cfg cfg; ++ struct virtio_rtc_req_clock_cap clock_cap; ++ struct virtio_rtc_req_cross_cap cross_cap; ++}; ++ ++/** Union of response types for requestq */ ++union virtio_rtc_resp_requestq { ++ struct virtio_rtc_resp_read read; ++ struct virtio_rtc_resp_read_cross read_cross; ++ struct virtio_rtc_resp_cfg cfg; ++ struct virtio_rtc_resp_clock_cap clock_cap; ++ struct virtio_rtc_resp_cross_cap cross_cap; ++}; ++ ++#endif /* _LINUX_VIRTIO_RTC_H */ diff --git a/patches/0029-virtio_rtc-Add-PTP-clocks.patch b/patches/0029-virtio_rtc-Add-PTP-clocks.patch new file mode 100644 index 0000000..ae5121b --- /dev/null +++ b/patches/0029-virtio_rtc-Add-PTP-clocks.patch @@ -0,0 +1,686 @@ +From 9a17125a18f9ae1e1233a8e2d919059445b9d6fd Mon Sep 17 00:00:00 2001 +From: Peter Hilber +Date: Fri, 9 May 2025 18:07:23 +0200 +Subject: [PATCH] virtio_rtc: Add PTP clocks + +Expose the virtio_rtc clocks as PTP clocks to userspace, similar to +ptp_kvm. virtio_rtc can expose multiple clocks, e.g. a UTC clock and a +monotonic clock. + +Userspace should distinguish different clocks through the name assigned by +the driver. In particular, UTC-like clocks can also be distinguished by if +and how leap seconds are smeared. udev rules such as the following can be +used to get different symlinks for different clock types: + + SUBSYSTEM=="ptp", ATTR{clock_name}=="Virtio PTP type 0/variant 0", SYMLINK += "ptp_virtio" + SUBSYSTEM=="ptp", ATTR{clock_name}=="Virtio PTP type 1/variant 0", SYMLINK += "ptp_virtio_tai" + SUBSYSTEM=="ptp", ATTR{clock_name}=="Virtio PTP type 2/variant 0", SYMLINK += "ptp_virtio_monotonic" + SUBSYSTEM=="ptp", ATTR{clock_name}=="Virtio PTP type 3/variant 0", SYMLINK += "ptp_virtio_smear_unspecified" + SUBSYSTEM=="ptp", ATTR{clock_name}=="Virtio PTP type 3/variant 1", SYMLINK += "ptp_virtio_smear_noon_linear" + SUBSYSTEM=="ptp", ATTR{clock_name}=="Virtio PTP type 3/variant 2", SYMLINK += "ptp_virtio_smear_sls" + SUBSYSTEM=="ptp", ATTR{clock_name}=="Virtio PTP type 4/variant 0", SYMLINK += "ptp_virtio_maybe_smeared" + +The preferred PTP clock reading method is ioctl PTP_SYS_OFFSET_PRECISE2, +through the ptp_clock_info.getcrosststamp() op. For now, +PTP_SYS_OFFSET_PRECISE2 will return -EOPNOTSUPP through a weak function. +PTP_SYS_OFFSET_PRECISE2 requires cross-timestamping support for specific +clocksources, which will be added in the following. If the clocksource +specific code is enabled, check that the Virtio RTC device supports the +respective HW counter before obtaining an actual cross-timestamp from the +Virtio device. + +The Virtio RTC device response time may be higher than the timekeeper +seqcount increment interval. Therefore, obtain the cross-timestamp before +calling get_device_system_crosststamp(). + +As a fallback, support the ioctl PTP_SYS_OFFSET_EXTENDED2 for all +platforms. + +Assume that concurrency issues during PTP clock removal are avoided by the +posix_clock framework. + +Kconfig recursive dependencies prevent virtio_rtc from implicitly enabling +PTP_1588_CLOCK, therefore just warn the user if PTP_1588_CLOCK is not +available. Since virtio_rtc should in the future also expose clocks as RTC +class devices, do not depend VIRTIO_RTC on PTP_1588_CLOCK. + +Signed-off-by: Peter Hilber +Message-Id: <20250509160734.1772-3-quic_philber@quicinc.com> +Signed-off-by: Michael S. Tsirkin +--- + drivers/virtio/Kconfig | 24 +- + drivers/virtio/Makefile | 1 + + drivers/virtio/virtio_rtc_driver.c | 121 +++++++++- + drivers/virtio/virtio_rtc_internal.h | 46 ++++ + drivers/virtio/virtio_rtc_ptp.c | 347 +++++++++++++++++++++++++++ + 5 files changed, 535 insertions(+), 4 deletions(-) + create mode 100644 drivers/virtio/virtio_rtc_ptp.c + +diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig +index 83bcb06acb6c61..a14a2b77e1424d 100644 +--- a/drivers/virtio/Kconfig ++++ b/drivers/virtio/Kconfig +@@ -194,11 +194,33 @@ config VIRTIO_RTC + depends on PTP_1588_CLOCK_OPTIONAL + help + This driver provides current time from a Virtio RTC device. The driver +- provides the time through one or more clocks. ++ provides the time through one or more clocks. The Virtio RTC PTP ++ clocks must be enabled to expose the clocks to userspace. + + To compile this code as a module, choose M here: the module will be + called virtio_rtc. + + If unsure, say M. + ++if VIRTIO_RTC ++ ++comment "WARNING: Consider enabling VIRTIO_RTC_PTP." ++ depends on !VIRTIO_RTC_PTP ++ ++comment "Enable PTP_1588_CLOCK in order to enable VIRTIO_RTC_PTP." ++ depends on PTP_1588_CLOCK=n ++ ++config VIRTIO_RTC_PTP ++ bool "Virtio RTC PTP clocks" ++ default y ++ depends on PTP_1588_CLOCK ++ help ++ This exposes any Virtio RTC clocks as PTP Hardware Clocks (PHCs) to ++ userspace. The PHC sysfs attribute "clock_name" describes the clock ++ type. ++ ++ If unsure, say Y. ++ ++endif # VIRTIO_RTC ++ + endif # VIRTIO_MENU +diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile +index c41c4c0f926489..88d6fb8d47315e 100644 +--- a/drivers/virtio/Makefile ++++ b/drivers/virtio/Makefile +@@ -16,3 +16,4 @@ obj-$(CONFIG_VIRTIO_DMA_SHARED_BUFFER) += virtio_dma_buf.o + obj-$(CONFIG_VIRTIO_DEBUG) += virtio_debug.o + obj-$(CONFIG_VIRTIO_RTC) += virtio_rtc.o + virtio_rtc-y := virtio_rtc_driver.o ++virtio_rtc-$(CONFIG_VIRTIO_RTC_PTP) += virtio_rtc_ptp.o +diff --git a/drivers/virtio/virtio_rtc_driver.c b/drivers/virtio/virtio_rtc_driver.c +index c87ee4e6342454..7499908ee34c39 100644 +--- a/drivers/virtio/virtio_rtc_driver.c ++++ b/drivers/virtio/virtio_rtc_driver.c +@@ -38,11 +38,16 @@ struct viortc_vq { + * struct viortc_dev - virtio_rtc device data + * @vdev: virtio device + * @vqs: virtqueues ++ * @clocks_to_unregister: Clock references, which are only used during device ++ * removal. ++ * For other uses, there would be a race between device ++ * creation and setting the pointers here. + * @num_clocks: # of virtio_rtc clocks + */ + struct viortc_dev { + struct virtio_device *vdev; + struct viortc_vq vqs[VIORTC_MAX_NR_QUEUES]; ++ struct viortc_ptp_clock **clocks_to_unregister; + u16 num_clocks; + }; + +@@ -638,6 +643,99 @@ int viortc_cross_cap(struct viortc_dev *viortc, u16 vio_clk_id, u8 hw_counter, + * init, deinit + */ + ++/** ++ * viortc_init_ptp_clock() - init and register PTP clock ++ * @viortc: device data ++ * @vio_clk_id: virtio_rtc clock id ++ * @clock_type: virtio_rtc clock type ++ * @leap_second_smearing: virtio_rtc leap second smearing ++ * ++ * Context: Process context. ++ * Return: Positive if registered, zero if not supported by configuration, ++ * negative error code otherwise. ++ */ ++static int viortc_init_ptp_clock(struct viortc_dev *viortc, u16 vio_clk_id, ++ u8 clock_type, u8 leap_second_smearing) ++{ ++ struct device *dev = &viortc->vdev->dev; ++ char ptp_clock_name[PTP_CLOCK_NAME_LEN]; ++ struct viortc_ptp_clock *vio_ptp; ++ ++ snprintf(ptp_clock_name, PTP_CLOCK_NAME_LEN, ++ "Virtio PTP type %hhu/variant %hhu", clock_type, ++ leap_second_smearing); ++ ++ vio_ptp = viortc_ptp_register(viortc, dev, vio_clk_id, ptp_clock_name); ++ if (IS_ERR(vio_ptp)) { ++ dev_err(dev, "failed to register PTP clock '%s'\n", ++ ptp_clock_name); ++ return PTR_ERR(vio_ptp); ++ } ++ ++ viortc->clocks_to_unregister[vio_clk_id] = vio_ptp; ++ ++ return !!vio_ptp; ++} ++ ++/** ++ * viortc_init_clock() - init local representation of virtio_rtc clock ++ * @viortc: device data ++ * @vio_clk_id: virtio_rtc clock id ++ * ++ * Initializes PHC to represent virtio_rtc clock. ++ * ++ * Context: Process context. ++ * Return: Zero on success, negative error code otherwise. ++ */ ++static int viortc_init_clock(struct viortc_dev *viortc, u16 vio_clk_id) ++{ ++ u8 clock_type, leap_second_smearing; ++ bool is_exposed = false; ++ int ret; ++ ++ ret = viortc_clock_cap(viortc, vio_clk_id, &clock_type, ++ &leap_second_smearing); ++ if (ret) ++ return ret; ++ ++ if (IS_ENABLED(CONFIG_VIRTIO_RTC_PTP)) { ++ ret = viortc_init_ptp_clock(viortc, vio_clk_id, clock_type, ++ leap_second_smearing); ++ if (ret < 0) ++ return ret; ++ if (ret > 0) ++ is_exposed = true; ++ } ++ ++ if (!is_exposed) ++ dev_warn(&viortc->vdev->dev, ++ "cannot expose clock %d (type %d, variant %d) to userspace\n", ++ vio_clk_id, clock_type, leap_second_smearing); ++ ++ return 0; ++} ++ ++/** ++ * viortc_clocks_deinit() - unregister PHCs ++ * @viortc: device data ++ */ ++static void viortc_clocks_deinit(struct viortc_dev *viortc) ++{ ++ struct viortc_ptp_clock *vio_ptp; ++ unsigned int i; ++ ++ for (i = 0; i < viortc->num_clocks; i++) { ++ vio_ptp = viortc->clocks_to_unregister[i]; ++ ++ if (!vio_ptp) ++ continue; ++ ++ viortc->clocks_to_unregister[i] = NULL; ++ ++ WARN_ON(viortc_ptp_unregister(vio_ptp, &viortc->vdev->dev)); ++ } ++} ++ + /** + * viortc_clocks_init() - init local representations of virtio_rtc clocks + * @viortc: device data +@@ -648,6 +746,7 @@ int viortc_cross_cap(struct viortc_dev *viortc, u16 vio_clk_id, u8 hw_counter, + static int viortc_clocks_init(struct viortc_dev *viortc) + { + u16 num_clocks; ++ unsigned int i; + int ret; + + ret = viortc_cfg(viortc, &num_clocks); +@@ -661,8 +760,22 @@ static int viortc_clocks_init(struct viortc_dev *viortc) + + viortc->num_clocks = num_clocks; + +- /* In the future, PTP clocks will be initialized here. */ +- (void)viortc_clock_cap; ++ viortc->clocks_to_unregister = ++ devm_kcalloc(&viortc->vdev->dev, num_clocks, ++ sizeof(*viortc->clocks_to_unregister), GFP_KERNEL); ++ if (!viortc->clocks_to_unregister) ++ return -ENOMEM; ++ ++ for (i = 0; i < num_clocks; i++) { ++ ret = viortc_init_clock(viortc, i); ++ if (ret) ++ goto err_deinit_clocks; ++ } ++ ++ return 0; ++ ++err_deinit_clocks: ++ viortc_clocks_deinit(viortc); + + return ret; + } +@@ -741,7 +854,9 @@ static int viortc_probe(struct virtio_device *vdev) + */ + static void viortc_remove(struct virtio_device *vdev) + { +- /* In the future, PTP clocks will be deinitialized here. */ ++ struct viortc_dev *viortc = vdev->priv; ++ ++ viortc_clocks_deinit(viortc); + + virtio_reset_device(vdev); + vdev->config->del_vqs(vdev); +diff --git a/drivers/virtio/virtio_rtc_internal.h b/drivers/virtio/virtio_rtc_internal.h +index 9c249c15b68f92..2e589903d04f29 100644 +--- a/drivers/virtio/virtio_rtc_internal.h ++++ b/drivers/virtio/virtio_rtc_internal.h +@@ -9,6 +9,7 @@ + #ifndef _VIRTIO_RTC_INTERNAL_H_ + #define _VIRTIO_RTC_INTERNAL_H_ + ++#include + #include + + /* driver core IFs */ +@@ -21,4 +22,49 @@ int viortc_read_cross(struct viortc_dev *viortc, u16 vio_clk_id, u8 hw_counter, + int viortc_cross_cap(struct viortc_dev *viortc, u16 vio_clk_id, u8 hw_counter, + bool *supported); + ++/* PTP IFs */ ++ ++struct viortc_ptp_clock; ++ ++#if IS_ENABLED(CONFIG_VIRTIO_RTC_PTP) ++ ++struct viortc_ptp_clock *viortc_ptp_register(struct viortc_dev *viortc, ++ struct device *parent_dev, ++ u16 vio_clk_id, ++ const char *ptp_clock_name); ++int viortc_ptp_unregister(struct viortc_ptp_clock *vio_ptp, ++ struct device *parent_dev); ++ ++#else ++ ++static inline struct viortc_ptp_clock * ++viortc_ptp_register(struct viortc_dev *viortc, struct device *parent_dev, ++ u16 vio_clk_id, const char *ptp_clock_name) ++{ ++ return NULL; ++} ++ ++static inline int viortc_ptp_unregister(struct viortc_ptp_clock *vio_ptp, ++ struct device *parent_dev) ++{ ++ return -ENODEV; ++} ++ ++#endif ++ ++/* HW counter IFs */ ++ ++/** ++ * viortc_hw_xtstamp_params() - get HW-specific xtstamp params ++ * @hw_counter: virtio_rtc HW counter type ++ * @cs_id: clocksource id corresponding to hw_counter ++ * ++ * Gets the HW-specific xtstamp params. Returns an error if the driver cannot ++ * support xtstamp. ++ * ++ * Context: Process context. ++ * Return: Zero on success, negative error code otherwise. ++ */ ++int viortc_hw_xtstamp_params(u8 *hw_counter, enum clocksource_ids *cs_id); ++ + #endif /* _VIRTIO_RTC_INTERNAL_H_ */ +diff --git a/drivers/virtio/virtio_rtc_ptp.c b/drivers/virtio/virtio_rtc_ptp.c +new file mode 100644 +index 00000000000000..f84599950cd4d4 +--- /dev/null ++++ b/drivers/virtio/virtio_rtc_ptp.c +@@ -0,0 +1,347 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Expose virtio_rtc clocks as PTP clocks. ++ * ++ * Copyright (C) 2022-2023 OpenSynergy GmbH ++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. ++ * ++ * Derived from ptp_kvm_common.c, virtual PTP 1588 clock for use with KVM ++ * guests. ++ * ++ * Copyright (C) 2017 Red Hat Inc. ++ */ ++ ++#include ++#include ++#include ++ ++#include ++ ++#include "virtio_rtc_internal.h" ++ ++/** ++ * struct viortc_ptp_clock - PTP clock abstraction ++ * @ptp_clock: PTP clock handle for unregistering ++ * @viortc: virtio_rtc device data ++ * @ptp_info: PTP clock description ++ * @vio_clk_id: virtio_rtc clock id ++ * @have_cross: device supports crosststamp with available HW counter ++ */ ++struct viortc_ptp_clock { ++ struct ptp_clock *ptp_clock; ++ struct viortc_dev *viortc; ++ struct ptp_clock_info ptp_info; ++ u16 vio_clk_id; ++ bool have_cross; ++}; ++ ++/** ++ * struct viortc_ptp_cross_ctx - context for get_device_system_crosststamp() ++ * @device_time: device clock reading ++ * @system_counterval: HW counter value at device_time ++ * ++ * Provides the already obtained crosststamp to get_device_system_crosststamp(). ++ */ ++struct viortc_ptp_cross_ctx { ++ ktime_t device_time; ++ struct system_counterval_t system_counterval; ++}; ++ ++/* Weak function in case get_device_system_crosststamp() is not supported */ ++int __weak viortc_hw_xtstamp_params(u8 *hw_counter, enum clocksource_ids *cs_id) ++{ ++ return -EOPNOTSUPP; ++} ++ ++/** ++ * viortc_ptp_get_time_fn() - callback for get_device_system_crosststamp() ++ * @device_time: device clock reading ++ * @system_counterval: HW counter value at device_time ++ * @ctx: context with already obtained crosststamp ++ * ++ * Return: zero (success). ++ */ ++static int viortc_ptp_get_time_fn(ktime_t *device_time, ++ struct system_counterval_t *system_counterval, ++ void *ctx) ++{ ++ struct viortc_ptp_cross_ctx *vio_ctx = ctx; ++ ++ *device_time = vio_ctx->device_time; ++ *system_counterval = vio_ctx->system_counterval; ++ ++ return 0; ++} ++ ++/** ++ * viortc_ptp_do_xtstamp() - get crosststamp from device ++ * @vio_ptp: virtio_rtc PTP clock ++ * @hw_counter: virtio_rtc HW counter type ++ * @cs_id: clocksource id corresponding to hw_counter ++ * @ctx: context for get_device_system_crosststamp() ++ * ++ * Reads HW-specific crosststamp from device. ++ * ++ * Context: Process context. ++ * Return: Zero on success, negative error code otherwise. ++ */ ++static int viortc_ptp_do_xtstamp(struct viortc_ptp_clock *vio_ptp, ++ u8 hw_counter, enum clocksource_ids cs_id, ++ struct viortc_ptp_cross_ctx *ctx) ++{ ++ u64 max_ns, ns; ++ int ret; ++ ++ ctx->system_counterval.cs_id = cs_id; ++ ++ ret = viortc_read_cross(vio_ptp->viortc, vio_ptp->vio_clk_id, ++ hw_counter, &ns, ++ &ctx->system_counterval.cycles); ++ if (ret) ++ return ret; ++ ++ max_ns = (u64)ktime_to_ns(KTIME_MAX); ++ if (ns > max_ns) ++ return -EINVAL; ++ ++ ctx->device_time = ns_to_ktime(ns); ++ ++ return 0; ++} ++ ++/* ++ * PTP clock operations ++ */ ++ ++/** ++ * viortc_ptp_getcrosststamp() - PTP clock getcrosststamp op ++ * @ptp: PTP clock info ++ * @xtstamp: crosststamp ++ * ++ * Context: Process context. ++ * Return: Zero on success, negative error code otherwise. ++ */ ++static int viortc_ptp_getcrosststamp(struct ptp_clock_info *ptp, ++ struct system_device_crosststamp *xtstamp) ++{ ++ struct viortc_ptp_clock *vio_ptp = ++ container_of(ptp, struct viortc_ptp_clock, ptp_info); ++ struct system_time_snapshot history_begin; ++ struct viortc_ptp_cross_ctx ctx; ++ enum clocksource_ids cs_id; ++ u8 hw_counter; ++ int ret; ++ ++ if (!vio_ptp->have_cross) ++ return -EOPNOTSUPP; ++ ++ ret = viortc_hw_xtstamp_params(&hw_counter, &cs_id); ++ if (ret) ++ return ret; ++ ++ ktime_get_snapshot(&history_begin); ++ if (history_begin.cs_id != cs_id) ++ return -EOPNOTSUPP; ++ ++ /* ++ * Getting the timestamp can take many milliseconds with a slow Virtio ++ * device. This is too long for viortc_ptp_get_time_fn() passed to ++ * get_device_system_crosststamp(), which has to usually return before ++ * the timekeeper seqcount increases (every tick or so). ++ * ++ * So, get the actual cross-timestamp first. ++ */ ++ ret = viortc_ptp_do_xtstamp(vio_ptp, hw_counter, cs_id, &ctx); ++ if (ret) ++ return ret; ++ ++ ret = get_device_system_crosststamp(viortc_ptp_get_time_fn, &ctx, ++ &history_begin, xtstamp); ++ if (ret) ++ pr_debug("%s: get_device_system_crosststamp() returned %d\n", ++ __func__, ret); ++ ++ return ret; ++} ++ ++/* viortc_ptp_adjfine() - unsupported PTP clock adjfine op */ ++static int viortc_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) ++{ ++ return -EOPNOTSUPP; ++} ++ ++/* viortc_ptp_adjtime() - unsupported PTP clock adjtime op */ ++static int viortc_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) ++{ ++ return -EOPNOTSUPP; ++} ++ ++/* viortc_ptp_settime64() - unsupported PTP clock settime64 op */ ++static int viortc_ptp_settime64(struct ptp_clock_info *ptp, ++ const struct timespec64 *ts) ++{ ++ return -EOPNOTSUPP; ++} ++ ++/* ++ * viortc_ptp_gettimex64() - PTP clock gettimex64 op ++ * ++ * Context: Process context. ++ */ ++static int viortc_ptp_gettimex64(struct ptp_clock_info *ptp, ++ struct timespec64 *ts, ++ struct ptp_system_timestamp *sts) ++{ ++ struct viortc_ptp_clock *vio_ptp = ++ container_of(ptp, struct viortc_ptp_clock, ptp_info); ++ int ret; ++ u64 ns; ++ ++ ptp_read_system_prets(sts); ++ ret = viortc_read(vio_ptp->viortc, vio_ptp->vio_clk_id, &ns); ++ ptp_read_system_postts(sts); ++ ++ if (ret) ++ return ret; ++ ++ if (ns > (u64)S64_MAX) ++ return -EINVAL; ++ ++ *ts = ns_to_timespec64((s64)ns); ++ ++ return 0; ++} ++ ++/* viortc_ptp_enable() - unsupported PTP clock enable op */ ++static int viortc_ptp_enable(struct ptp_clock_info *ptp, ++ struct ptp_clock_request *rq, int on) ++{ ++ return -EOPNOTSUPP; ++} ++ ++/* ++ * viortc_ptp_info_template - ptp_clock_info template ++ * ++ * The .name member will be set for individual virtio_rtc PTP clocks. ++ * ++ * The .getcrosststamp member will be cleared for PTP clocks not supporting ++ * crosststamp. ++ */ ++static const struct ptp_clock_info viortc_ptp_info_template = { ++ .owner = THIS_MODULE, ++ /* .name is set according to clock type */ ++ .adjfine = viortc_ptp_adjfine, ++ .adjtime = viortc_ptp_adjtime, ++ .gettimex64 = viortc_ptp_gettimex64, ++ .settime64 = viortc_ptp_settime64, ++ .enable = viortc_ptp_enable, ++ .getcrosststamp = viortc_ptp_getcrosststamp, ++}; ++ ++/** ++ * viortc_ptp_unregister() - PTP clock unregistering wrapper ++ * @vio_ptp: virtio_rtc PTP clock ++ * @parent_dev: parent device of PTP clock ++ * ++ * Return: Zero on success, negative error code otherwise. ++ */ ++int viortc_ptp_unregister(struct viortc_ptp_clock *vio_ptp, ++ struct device *parent_dev) ++{ ++ int ret = ptp_clock_unregister(vio_ptp->ptp_clock); ++ ++ if (!ret) ++ devm_kfree(parent_dev, vio_ptp); ++ ++ return ret; ++} ++ ++/** ++ * viortc_ptp_get_cross_cap() - get xtstamp support info from device ++ * @viortc: virtio_rtc device data ++ * @vio_ptp: virtio_rtc PTP clock abstraction ++ * ++ * Context: Process context. ++ * Return: Zero on success, negative error code otherwise. ++ */ ++static int viortc_ptp_get_cross_cap(struct viortc_dev *viortc, ++ struct viortc_ptp_clock *vio_ptp) ++{ ++ enum clocksource_ids cs_id; ++ bool xtstamp_supported; ++ u8 hw_counter; ++ int ret; ++ ++ ret = viortc_hw_xtstamp_params(&hw_counter, &cs_id); ++ if (ret) { ++ vio_ptp->have_cross = false; ++ return 0; ++ } ++ ++ ret = viortc_cross_cap(viortc, vio_ptp->vio_clk_id, hw_counter, ++ &xtstamp_supported); ++ if (ret) ++ return ret; ++ ++ vio_ptp->have_cross = xtstamp_supported; ++ ++ return 0; ++} ++ ++/** ++ * viortc_ptp_register() - prepare and register PTP clock ++ * @viortc: virtio_rtc device data ++ * @parent_dev: parent device for PTP clock ++ * @vio_clk_id: id of virtio_rtc clock which backs PTP clock ++ * @ptp_clock_name: PTP clock name ++ * ++ * Context: Process context. ++ * Return: Pointer on success, ERR_PTR() otherwise; NULL if PTP clock support ++ * not available. ++ */ ++struct viortc_ptp_clock *viortc_ptp_register(struct viortc_dev *viortc, ++ struct device *parent_dev, ++ u16 vio_clk_id, ++ const char *ptp_clock_name) ++{ ++ struct viortc_ptp_clock *vio_ptp; ++ struct ptp_clock *ptp_clock; ++ ssize_t len; ++ int ret; ++ ++ vio_ptp = devm_kzalloc(parent_dev, sizeof(*vio_ptp), GFP_KERNEL); ++ if (!vio_ptp) ++ return ERR_PTR(-ENOMEM); ++ ++ vio_ptp->viortc = viortc; ++ vio_ptp->vio_clk_id = vio_clk_id; ++ vio_ptp->ptp_info = viortc_ptp_info_template; ++ len = strscpy(vio_ptp->ptp_info.name, ptp_clock_name, ++ sizeof(vio_ptp->ptp_info.name)); ++ if (len < 0) { ++ ret = len; ++ goto err_free_dev; ++ } ++ ++ ret = viortc_ptp_get_cross_cap(viortc, vio_ptp); ++ if (ret) ++ goto err_free_dev; ++ ++ if (!vio_ptp->have_cross) ++ vio_ptp->ptp_info.getcrosststamp = NULL; ++ ++ ptp_clock = ptp_clock_register(&vio_ptp->ptp_info, parent_dev); ++ if (IS_ERR(ptp_clock)) ++ goto err_on_register; ++ ++ vio_ptp->ptp_clock = ptp_clock; ++ ++ return vio_ptp; ++ ++err_on_register: ++ ret = PTR_ERR(ptp_clock); ++ ++err_free_dev: ++ devm_kfree(parent_dev, vio_ptp); ++ return ERR_PTR(ret); ++} diff --git a/patches/0030-virtio_rtc-Add-Arm-Generic-Timer-cross-timestamping.patch b/patches/0030-virtio_rtc-Add-Arm-Generic-Timer-cross-timestamping.patch new file mode 100644 index 0000000..fbb4653 --- /dev/null +++ b/patches/0030-virtio_rtc-Add-Arm-Generic-Timer-cross-timestamping.patch @@ -0,0 +1,84 @@ +From e2ef16757bbe1d4566093179944e9ebe730b2701 Mon Sep 17 00:00:00 2001 +From: Peter Hilber +Date: Fri, 9 May 2025 18:07:24 +0200 +Subject: [PATCH] virtio_rtc: Add Arm Generic Timer cross-timestamping + +For platforms using the Arm Generic Timer, add precise cross-timestamping +support to virtio_rtc. + +Always report the CP15 virtual counter as the HW counter in use by +arm_arch_timer, since the Linux kernel's usage of the Arm Generic Timer +should always be compatible with this. + +Signed-off-by: Peter Hilber +Message-Id: <20250509160734.1772-4-quic_philber@quicinc.com> +Signed-off-by: Michael S. Tsirkin +--- + drivers/virtio/Kconfig | 13 +++++++++++++ + drivers/virtio/Makefile | 1 + + drivers/virtio/virtio_rtc_arm.c | 23 +++++++++++++++++++++++ + 3 files changed, 37 insertions(+) + create mode 100644 drivers/virtio/virtio_rtc_arm.c + +diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig +index a14a2b77e1424d..3d8b366c06250d 100644 +--- a/drivers/virtio/Kconfig ++++ b/drivers/virtio/Kconfig +@@ -221,6 +221,19 @@ config VIRTIO_RTC_PTP + + If unsure, say Y. + ++config VIRTIO_RTC_ARM ++ bool "Virtio RTC cross-timestamping using Arm Generic Timer" ++ default y ++ depends on VIRTIO_RTC_PTP && ARM_ARCH_TIMER ++ help ++ This enables Virtio RTC cross-timestamping using the Arm Generic Timer. ++ It only has an effect if the Virtio RTC device also supports this. The ++ cross-timestamp is available through the PTP clock driver precise ++ cross-timestamp ioctl (PTP_SYS_OFFSET_PRECISE2 aka ++ PTP_SYS_OFFSET_PRECISE). ++ ++ If unsure, say Y. ++ + endif # VIRTIO_RTC + + endif # VIRTIO_MENU +diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile +index 88d6fb8d47315e..dbd77f124ba945 100644 +--- a/drivers/virtio/Makefile ++++ b/drivers/virtio/Makefile +@@ -17,3 +17,4 @@ obj-$(CONFIG_VIRTIO_DEBUG) += virtio_debug.o + obj-$(CONFIG_VIRTIO_RTC) += virtio_rtc.o + virtio_rtc-y := virtio_rtc_driver.o + virtio_rtc-$(CONFIG_VIRTIO_RTC_PTP) += virtio_rtc_ptp.o ++virtio_rtc-$(CONFIG_VIRTIO_RTC_ARM) += virtio_rtc_arm.o +diff --git a/drivers/virtio/virtio_rtc_arm.c b/drivers/virtio/virtio_rtc_arm.c +new file mode 100644 +index 00000000000000..211299d7287010 +--- /dev/null ++++ b/drivers/virtio/virtio_rtc_arm.c +@@ -0,0 +1,23 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Provides cross-timestamp params for Arm. ++ * ++ * Copyright (C) 2022-2023 OpenSynergy GmbH ++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. ++ */ ++ ++#include ++ ++#include ++ ++#include "virtio_rtc_internal.h" ++ ++/* see header for doc */ ++ ++int viortc_hw_xtstamp_params(u8 *hw_counter, enum clocksource_ids *cs_id) ++{ ++ *hw_counter = VIRTIO_RTC_COUNTER_ARM_VCT; ++ *cs_id = CSID_ARM_ARCH_COUNTER; ++ ++ return 0; ++} diff --git a/patches/0031-virtio_rtc-Add-RTC-class-driver.patch b/patches/0031-virtio_rtc-Add-RTC-class-driver.patch new file mode 100644 index 0000000..dd0f811 --- /dev/null +++ b/patches/0031-virtio_rtc-Add-RTC-class-driver.patch @@ -0,0 +1,1285 @@ +From 9d4f22fd563e0cd02e8448e84d057e7c0132a586 Mon Sep 17 00:00:00 2001 +From: Peter Hilber +Date: Fri, 9 May 2025 18:07:25 +0200 +Subject: [PATCH] virtio_rtc: Add RTC class driver + +Expose the virtio-rtc UTC-like clock as an RTC clock to userspace - if it +is present, and if it does not step on leap seconds. The RTC class enables +the virtio-rtc device to resume the system from sleep states on RTC alarm. + +Support RTC alarm if the virtio-rtc alarm feature is present. The +virtio-rtc device signals an alarm by marking an alarmq buffer as used. + +Peculiarities +------------- + +A virtio-rtc clock is a bit special for an RTC clock in that + +- the clock may step (also backwards) autonomously at any time and + +- the device, and its notification mechanism, will be reset during boot or + resume from sleep. + +The virtio-rtc device avoids that the driver might miss an alarm. The +device signals an alarm whenever the clock has reached or passed the alarm +time, and also when the device is reset (on boot or resume from sleep), if +the alarm time is in the past. + +Open Issue +---------- + +The CLOCK_BOOTTIME_ALARM will use the RTC clock to wake up from sleep, and +implicitly assumes that no RTC clock steps will occur during sleep. The RTC +class driver does not know whether the current alarm is a real-time alarm +or a boot-time alarm. + +Perhaps this might be handled by the driver also setting a virtio-rtc +monotonic alarm (which uses a clock similar to CLOCK_BOOTTIME_ALARM). The +virtio-rtc monotonic alarm would just be used to wake up in case it was a +CLOCK_BOOTTIME_ALARM alarm. + +Otherwise, the behavior should not differ from other RTC class drivers. + +Signed-off-by: Peter Hilber +Acked-by: Alexandre Belloni +Message-Id: <20250509160734.1772-5-quic_philber@quicinc.com> +Signed-off-by: Michael S. Tsirkin +--- + drivers/virtio/Kconfig | 22 +- + drivers/virtio/Makefile | 1 + + drivers/virtio/virtio_rtc_class.c | 262 ++++++++++++++ + drivers/virtio/virtio_rtc_driver.c | 520 ++++++++++++++++++++++++++- + drivers/virtio/virtio_rtc_internal.h | 52 +++ + include/uapi/linux/virtio_rtc.h | 88 ++++- + 6 files changed, 934 insertions(+), 11 deletions(-) + create mode 100644 drivers/virtio/virtio_rtc_class.c + +diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig +index 3d8b366c06250d..6db5235a7693d6 100644 +--- a/drivers/virtio/Kconfig ++++ b/drivers/virtio/Kconfig +@@ -195,7 +195,8 @@ config VIRTIO_RTC + help + This driver provides current time from a Virtio RTC device. The driver + provides the time through one or more clocks. The Virtio RTC PTP +- clocks must be enabled to expose the clocks to userspace. ++ clocks and/or the Real Time Clock driver for Virtio RTC must be ++ enabled to expose the clocks to userspace. + + To compile this code as a module, choose M here: the module will be + called virtio_rtc. +@@ -204,8 +205,8 @@ config VIRTIO_RTC + + if VIRTIO_RTC + +-comment "WARNING: Consider enabling VIRTIO_RTC_PTP." +- depends on !VIRTIO_RTC_PTP ++comment "WARNING: Consider enabling VIRTIO_RTC_PTP and/or VIRTIO_RTC_CLASS." ++ depends on !VIRTIO_RTC_PTP && !VIRTIO_RTC_CLASS + + comment "Enable PTP_1588_CLOCK in order to enable VIRTIO_RTC_PTP." + depends on PTP_1588_CLOCK=n +@@ -234,6 +235,21 @@ config VIRTIO_RTC_ARM + + If unsure, say Y. + ++comment "Enable RTC_CLASS in order to enable VIRTIO_RTC_CLASS." ++ depends on RTC_CLASS=n ++ ++config VIRTIO_RTC_CLASS ++ bool "Real Time Clock driver for Virtio RTC" ++ default y ++ depends on RTC_CLASS ++ help ++ This exposes the Virtio RTC UTC-like clock as a Linux Real Time Clock. ++ It only has an effect if the Virtio RTC device has a UTC-like clock ++ which smears leap seconds to avoid steps. The Real Time Clock is ++ read-only, and may support setting an alarm. ++ ++ If unsure, say Y. ++ + endif # VIRTIO_RTC + + endif # VIRTIO_MENU +diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile +index dbd77f124ba945..eefcfe90d6b8b0 100644 +--- a/drivers/virtio/Makefile ++++ b/drivers/virtio/Makefile +@@ -18,3 +18,4 @@ obj-$(CONFIG_VIRTIO_RTC) += virtio_rtc.o + virtio_rtc-y := virtio_rtc_driver.o + virtio_rtc-$(CONFIG_VIRTIO_RTC_PTP) += virtio_rtc_ptp.o + virtio_rtc-$(CONFIG_VIRTIO_RTC_ARM) += virtio_rtc_arm.o ++virtio_rtc-$(CONFIG_VIRTIO_RTC_CLASS) += virtio_rtc_class.o +diff --git a/drivers/virtio/virtio_rtc_class.c b/drivers/virtio/virtio_rtc_class.c +new file mode 100644 +index 00000000000000..05d6d28255cf3d +--- /dev/null ++++ b/drivers/virtio/virtio_rtc_class.c +@@ -0,0 +1,262 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * virtio_rtc RTC class driver ++ * ++ * Copyright (C) 2023 OpenSynergy GmbH ++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "virtio_rtc_internal.h" ++ ++/** ++ * struct viortc_class - RTC class wrapper ++ * @viortc: virtio_rtc device data ++ * @rtc: RTC device ++ * @vio_clk_id: virtio_rtc clock id ++ * @stopped: Whether RTC ops are disallowed. Access protected by rtc_lock(). ++ */ ++struct viortc_class { ++ struct viortc_dev *viortc; ++ struct rtc_device *rtc; ++ u16 vio_clk_id; ++ bool stopped; ++}; ++ ++/** ++ * viortc_class_get_locked() - get RTC class wrapper, if ops allowed ++ * @dev: virtio device ++ * ++ * Gets the RTC class wrapper from the virtio device, if it is available and ++ * ops are allowed. ++ * ++ * Context: Caller must hold rtc_lock(). ++ * Return: RTC class wrapper if available and ops allowed, ERR_PTR otherwise. ++ */ ++static struct viortc_class *viortc_class_get_locked(struct device *dev) ++{ ++ struct viortc_class *viortc_class; ++ ++ viortc_class = viortc_class_from_dev(dev); ++ if (IS_ERR(viortc_class)) ++ return viortc_class; ++ ++ if (viortc_class->stopped) ++ return ERR_PTR(-EBUSY); ++ ++ return viortc_class; ++} ++ ++/** ++ * viortc_class_read_time() - RTC class op read_time ++ * @dev: virtio device ++ * @tm: read time ++ * ++ * Context: Process context. ++ * Return: Zero on success, negative error code otherwise. ++ */ ++static int viortc_class_read_time(struct device *dev, struct rtc_time *tm) ++{ ++ struct viortc_class *viortc_class; ++ time64_t sec; ++ int ret; ++ u64 ns; ++ ++ viortc_class = viortc_class_get_locked(dev); ++ if (IS_ERR(viortc_class)) ++ return PTR_ERR(viortc_class); ++ ++ ret = viortc_read(viortc_class->viortc, viortc_class->vio_clk_id, &ns); ++ if (ret) ++ return ret; ++ ++ sec = div_u64(ns, NSEC_PER_SEC); ++ ++ rtc_time64_to_tm(sec, tm); ++ ++ return 0; ++} ++ ++/** ++ * viortc_class_read_alarm() - RTC class op read_alarm ++ * @dev: virtio device ++ * @alrm: alarm read out ++ * ++ * Context: Process context. ++ * Return: Zero on success, negative error code otherwise. ++ */ ++static int viortc_class_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) ++{ ++ struct viortc_class *viortc_class; ++ time64_t alarm_time_sec; ++ u64 alarm_time_ns; ++ bool enabled; ++ int ret; ++ ++ viortc_class = viortc_class_get_locked(dev); ++ if (IS_ERR(viortc_class)) ++ return PTR_ERR(viortc_class); ++ ++ ret = viortc_read_alarm(viortc_class->viortc, viortc_class->vio_clk_id, ++ &alarm_time_ns, &enabled); ++ if (ret) ++ return ret; ++ ++ alarm_time_sec = div_u64(alarm_time_ns, NSEC_PER_SEC); ++ rtc_time64_to_tm(alarm_time_sec, &alrm->time); ++ ++ alrm->enabled = enabled; ++ ++ return 0; ++} ++ ++/** ++ * viortc_class_set_alarm() - RTC class op set_alarm ++ * @dev: virtio device ++ * @alrm: alarm to set ++ * ++ * Context: Process context. ++ * Return: Zero on success, negative error code otherwise. ++ */ ++static int viortc_class_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) ++{ ++ struct viortc_class *viortc_class; ++ time64_t alarm_time_sec; ++ u64 alarm_time_ns; ++ ++ viortc_class = viortc_class_get_locked(dev); ++ if (IS_ERR(viortc_class)) ++ return PTR_ERR(viortc_class); ++ ++ alarm_time_sec = rtc_tm_to_time64(&alrm->time); ++ ++ if (alarm_time_sec < 0) ++ return -EINVAL; ++ ++ if (check_mul_overflow((u64)alarm_time_sec, (u64)NSEC_PER_SEC, ++ &alarm_time_ns)) ++ return -EINVAL; ++ ++ return viortc_set_alarm(viortc_class->viortc, viortc_class->vio_clk_id, ++ alarm_time_ns, alrm->enabled); ++} ++ ++/** ++ * viortc_class_alarm_irq_enable() - RTC class op alarm_irq_enable ++ * @dev: virtio device ++ * @enabled: enable or disable alarm IRQ ++ * ++ * Context: Process context. ++ * Return: Zero on success, negative error code otherwise. ++ */ ++static int viortc_class_alarm_irq_enable(struct device *dev, ++ unsigned int enabled) ++{ ++ struct viortc_class *viortc_class; ++ ++ viortc_class = viortc_class_get_locked(dev); ++ if (IS_ERR(viortc_class)) ++ return PTR_ERR(viortc_class); ++ ++ return viortc_set_alarm_enabled(viortc_class->viortc, ++ viortc_class->vio_clk_id, enabled); ++} ++ ++static const struct rtc_class_ops viortc_class_ops = { ++ .read_time = viortc_class_read_time, ++ .read_alarm = viortc_class_read_alarm, ++ .set_alarm = viortc_class_set_alarm, ++ .alarm_irq_enable = viortc_class_alarm_irq_enable, ++}; ++ ++/** ++ * viortc_class_alarm() - propagate alarm notification as alarm interrupt ++ * @viortc_class: RTC class wrapper ++ * @vio_clk_id: virtio_rtc clock id ++ * ++ * Context: Any context. ++ */ ++void viortc_class_alarm(struct viortc_class *viortc_class, u16 vio_clk_id) ++{ ++ if (vio_clk_id != viortc_class->vio_clk_id) { ++ dev_warn_ratelimited(&viortc_class->rtc->dev, ++ "ignoring alarm for clock id %d, expected id %d\n", ++ vio_clk_id, viortc_class->vio_clk_id); ++ return; ++ } ++ ++ rtc_update_irq(viortc_class->rtc, 1, RTC_AF | RTC_IRQF); ++} ++ ++/** ++ * viortc_class_stop() - disallow RTC class ops ++ * @viortc_class: RTC class wrapper ++ * ++ * Context: Process context. Caller must NOT hold rtc_lock(). ++ */ ++void viortc_class_stop(struct viortc_class *viortc_class) ++{ ++ rtc_lock(viortc_class->rtc); ++ ++ viortc_class->stopped = true; ++ ++ rtc_unlock(viortc_class->rtc); ++} ++ ++/** ++ * viortc_class_register() - register RTC class device ++ * @viortc_class: RTC class wrapper ++ * ++ * Context: Process context. ++ * Return: Zero on success, negative error code otherwise. ++ */ ++int viortc_class_register(struct viortc_class *viortc_class) ++{ ++ return devm_rtc_register_device(viortc_class->rtc); ++} ++ ++/** ++ * viortc_class_init() - init RTC class wrapper and device ++ * @viortc: device data ++ * @vio_clk_id: virtio_rtc clock id ++ * @have_alarm: have alarm feature ++ * @parent_dev: virtio device ++ * ++ * Context: Process context. ++ * Return: RTC class wrapper on success, ERR_PTR otherwise. ++ */ ++struct viortc_class *viortc_class_init(struct viortc_dev *viortc, ++ u16 vio_clk_id, bool have_alarm, ++ struct device *parent_dev) ++{ ++ struct viortc_class *viortc_class; ++ struct rtc_device *rtc; ++ ++ viortc_class = ++ devm_kzalloc(parent_dev, sizeof(*viortc_class), GFP_KERNEL); ++ if (!viortc_class) ++ return ERR_PTR(-ENOMEM); ++ ++ rtc = devm_rtc_allocate_device(parent_dev); ++ if (IS_ERR(rtc)) ++ return ERR_CAST(rtc); ++ ++ viortc_class->viortc = viortc; ++ viortc_class->rtc = rtc; ++ viortc_class->vio_clk_id = vio_clk_id; ++ ++ if (!have_alarm) ++ clear_bit(RTC_FEATURE_ALARM, rtc->features); ++ clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, rtc->features); ++ ++ rtc->ops = &viortc_class_ops; ++ rtc->range_max = div_u64(U64_MAX, NSEC_PER_SEC); ++ ++ return viortc_class; ++} +diff --git a/drivers/virtio/virtio_rtc_driver.c b/drivers/virtio/virtio_rtc_driver.c +index 7499908ee34c39..a57d5e06e19d2d 100644 +--- a/drivers/virtio/virtio_rtc_driver.c ++++ b/drivers/virtio/virtio_rtc_driver.c +@@ -18,9 +18,12 @@ + + #include "virtio_rtc_internal.h" + ++#define VIORTC_ALARMQ_BUF_CAP sizeof(union virtio_rtc_notif_alarmq) ++ + /* virtqueue order */ + enum { + VIORTC_REQUESTQ, ++ VIORTC_ALARMQ, + VIORTC_MAX_NR_QUEUES, + }; + +@@ -37,17 +40,23 @@ struct viortc_vq { + /** + * struct viortc_dev - virtio_rtc device data + * @vdev: virtio device ++ * @viortc_class: RTC class wrapper for UTC-like clock, NULL if not available + * @vqs: virtqueues + * @clocks_to_unregister: Clock references, which are only used during device + * removal. + * For other uses, there would be a race between device + * creation and setting the pointers here. ++ * @alarmq_bufs: alarmq buffers list ++ * @num_alarmq_bufs: # of alarmq buffers + * @num_clocks: # of virtio_rtc clocks + */ + struct viortc_dev { + struct virtio_device *vdev; ++ struct viortc_class *viortc_class; + struct viortc_vq vqs[VIORTC_MAX_NR_QUEUES]; + struct viortc_ptp_clock **clocks_to_unregister; ++ void **alarmq_bufs; ++ unsigned int num_alarmq_bufs; + u16 num_clocks; + }; + +@@ -78,6 +87,60 @@ struct viortc_msg { + unsigned int resp_actual_size; + }; + ++/** ++ * viortc_class_from_dev() - Get RTC class object from virtio device. ++ * @dev: virtio device ++ * ++ * Context: Any context. ++ * Return: RTC class object if available, ERR_PTR otherwise. ++ */ ++struct viortc_class *viortc_class_from_dev(struct device *dev) ++{ ++ struct virtio_device *vdev; ++ struct viortc_dev *viortc; ++ ++ vdev = container_of(dev, typeof(*vdev), dev); ++ viortc = vdev->priv; ++ ++ return viortc->viortc_class ?: ERR_PTR(-ENODEV); ++} ++ ++/** ++ * viortc_alarms_supported() - Whether device and driver support alarms. ++ * @vdev: virtio device ++ * ++ * NB: Device and driver may not support alarms for the same clocks. ++ * ++ * Context: Any context. ++ * Return: True if both device and driver can support alarms. ++ */ ++static bool viortc_alarms_supported(struct virtio_device *vdev) ++{ ++ return IS_ENABLED(CONFIG_VIRTIO_RTC_CLASS) && ++ virtio_has_feature(vdev, VIRTIO_RTC_F_ALARM); ++} ++ ++/** ++ * viortc_feed_vq() - Make a device write-only buffer available. ++ * @viortc: device data ++ * @vq: notification virtqueue ++ * @buf: buffer ++ * @buf_len: buffer capacity in bytes ++ * @data: token, identifying buffer ++ * ++ * Context: Caller must prevent concurrent access to vq. ++ * Return: Zero on success, negative error code otherwise. ++ */ ++static int viortc_feed_vq(struct viortc_dev *viortc, struct virtqueue *vq, ++ void *buf, unsigned int buf_len, void *data) ++{ ++ struct scatterlist sg; ++ ++ sg_init_one(&sg, buf, buf_len); ++ ++ return virtqueue_add_inbuf(vq, &sg, 1, data, GFP_ATOMIC); ++} ++ + /** + * viortc_msg_init() - Allocate and initialize requestq message. + * @viortc: device data +@@ -236,6 +299,85 @@ static void viortc_cb_requestq(struct virtqueue *vq) + viortc_do_cb(vq, viortc_requestq_hdlr); + } + ++/** ++ * viortc_alarmq_hdlr() - process an alarmq used buffer ++ * @token: token identifying the buffer ++ * @len: bytes written by device ++ * @vq: virtqueue ++ * @viortc_vq: device specific data for virtqueue ++ * @viortc: device data ++ * ++ * Processes a VIRTIO_RTC_NOTIF_ALARM notification by calling the RTC class ++ * driver. Makes the buffer available again. ++ * ++ * Context: virtqueue callback ++ */ ++static void viortc_alarmq_hdlr(void *token, unsigned int len, ++ struct virtqueue *vq, ++ struct viortc_vq *viortc_vq, ++ struct viortc_dev *viortc) ++{ ++ struct virtio_rtc_notif_alarm *notif = token; ++ struct virtio_rtc_notif_head *head = token; ++ unsigned long flags; ++ u16 clock_id; ++ bool notify; ++ ++ if (len < sizeof(*head)) { ++ dev_err_ratelimited(&viortc->vdev->dev, ++ "%s: ignoring notification with short header\n", ++ __func__); ++ goto feed_vq; ++ } ++ ++ if (virtio_le_to_cpu(head->msg_type) != VIRTIO_RTC_NOTIF_ALARM) { ++ dev_err_ratelimited(&viortc->vdev->dev, ++ "%s: ignoring unknown notification type 0x%x\n", ++ __func__, virtio_le_to_cpu(head->msg_type)); ++ goto feed_vq; ++ } ++ ++ if (len < sizeof(*notif)) { ++ dev_err_ratelimited(&viortc->vdev->dev, ++ "%s: ignoring too small alarm notification\n", ++ __func__); ++ goto feed_vq; ++ } ++ ++ clock_id = virtio_le_to_cpu(notif->clock_id); ++ ++ if (!viortc->viortc_class) ++ dev_warn_ratelimited(&viortc->vdev->dev, ++ "ignoring alarm, no RTC class device available\n"); ++ else ++ viortc_class_alarm(viortc->viortc_class, clock_id); ++ ++feed_vq: ++ spin_lock_irqsave(&viortc_vq->lock, flags); ++ ++ if (viortc_feed_vq(viortc, vq, notif, VIORTC_ALARMQ_BUF_CAP, token)) ++ dev_warn(&viortc->vdev->dev, ++ "%s: failed to re-expose input buffer\n", __func__); ++ ++ notify = virtqueue_kick_prepare(vq); ++ ++ spin_unlock_irqrestore(&viortc_vq->lock, flags); ++ ++ if (notify) ++ virtqueue_notify(vq); ++} ++ ++/** ++ * viortc_cb_alarmq() - callback for alarmq ++ * @vq: virtqueue ++ * ++ * Context: virtqueue callback ++ */ ++static void viortc_cb_alarmq(struct virtqueue *vq) ++{ ++ viortc_do_cb(vq, viortc_alarmq_hdlr); ++} ++ + /** + * viortc_get_resp_errno() - converts virtio_rtc errnos to system errnos + * @resp_head: message response header +@@ -561,12 +703,13 @@ static int viortc_cfg(struct viortc_dev *viortc, u16 *num_clocks) + * @vio_clk_id: virtio_rtc clock id + * @type: virtio_rtc clock type + * @leap_second_smearing: virtio_rtc smearing variant ++ * @flags: struct virtio_rtc_resp_clock_cap.flags + * + * Context: Process context. + * Return: Zero on success, negative error code otherwise. + */ + static int viortc_clock_cap(struct viortc_dev *viortc, u16 vio_clk_id, u8 *type, +- u8 *leap_second_smearing) ++ u8 *leap_second_smearing, u8 *flags) + { + VIORTC_DECLARE_MSG_HDL_ONSTACK(hdl, VIRTIO_RTC_REQ_CLOCK_CAP, + struct virtio_rtc_req_clock_cap, +@@ -589,6 +732,7 @@ static int viortc_clock_cap(struct viortc_dev *viortc, u16 vio_clk_id, u8 *type, + + VIORTC_MSG_READ(hdl, type, type); + VIORTC_MSG_READ(hdl, leap_second_smearing, leap_second_smearing); ++ VIORTC_MSG_READ(hdl, flags, flags); + + out_release: + viortc_msg_release(VIORTC_MSG(hdl)); +@@ -639,10 +783,189 @@ int viortc_cross_cap(struct viortc_dev *viortc, u16 vio_clk_id, u8 hw_counter, + return ret; + } + ++/** ++ * viortc_read_alarm() - VIRTIO_RTC_REQ_READ_ALARM wrapper ++ * @viortc: device data ++ * @vio_clk_id: virtio_rtc clock id ++ * @alarm_time: alarm time in ns ++ * @enabled: whether alarm is enabled ++ * ++ * Context: Process context. ++ * Return: Zero on success, negative error code otherwise. ++ */ ++int viortc_read_alarm(struct viortc_dev *viortc, u16 vio_clk_id, ++ u64 *alarm_time, bool *enabled) ++{ ++ VIORTC_DECLARE_MSG_HDL_ONSTACK(hdl, VIRTIO_RTC_REQ_READ_ALARM, ++ struct virtio_rtc_req_read_alarm, ++ struct virtio_rtc_resp_read_alarm); ++ u8 flags; ++ int ret; ++ ++ ret = VIORTC_MSG_INIT(hdl, viortc); ++ if (ret) ++ return ret; ++ ++ VIORTC_MSG_WRITE(hdl, clock_id, &vio_clk_id); ++ ++ ret = viortc_msg_xfer(&viortc->vqs[VIORTC_REQUESTQ], VIORTC_MSG(hdl), ++ 0); ++ if (ret) { ++ dev_dbg(&viortc->vdev->dev, "%s: xfer returned %d\n", __func__, ++ ret); ++ goto out_release; ++ } ++ ++ VIORTC_MSG_READ(hdl, alarm_time, alarm_time); ++ VIORTC_MSG_READ(hdl, flags, &flags); ++ ++ *enabled = !!(flags & VIRTIO_RTC_FLAG_ALARM_ENABLED); ++ ++out_release: ++ viortc_msg_release(VIORTC_MSG(hdl)); ++ ++ return ret; ++} ++ ++/** ++ * viortc_set_alarm() - VIRTIO_RTC_REQ_SET_ALARM wrapper ++ * @viortc: device data ++ * @vio_clk_id: virtio_rtc clock id ++ * @alarm_time: alarm time in ns ++ * @alarm_enable: enable or disable alarm ++ * ++ * Context: Process context. ++ * Return: Zero on success, negative error code otherwise. ++ */ ++int viortc_set_alarm(struct viortc_dev *viortc, u16 vio_clk_id, u64 alarm_time, ++ bool alarm_enable) ++{ ++ VIORTC_DECLARE_MSG_HDL_ONSTACK(hdl, VIRTIO_RTC_REQ_SET_ALARM, ++ struct virtio_rtc_req_set_alarm, ++ struct virtio_rtc_resp_set_alarm); ++ u8 flags = 0; ++ int ret; ++ ++ ret = VIORTC_MSG_INIT(hdl, viortc); ++ if (ret) ++ return ret; ++ ++ if (alarm_enable) ++ flags |= VIRTIO_RTC_FLAG_ALARM_ENABLED; ++ ++ VIORTC_MSG_WRITE(hdl, clock_id, &vio_clk_id); ++ VIORTC_MSG_WRITE(hdl, alarm_time, &alarm_time); ++ VIORTC_MSG_WRITE(hdl, flags, &flags); ++ ++ ret = viortc_msg_xfer(&viortc->vqs[VIORTC_REQUESTQ], VIORTC_MSG(hdl), ++ 0); ++ if (ret) { ++ dev_dbg(&viortc->vdev->dev, "%s: xfer returned %d\n", __func__, ++ ret); ++ goto out_release; ++ } ++ ++out_release: ++ viortc_msg_release(VIORTC_MSG(hdl)); ++ ++ return ret; ++} ++ ++/** ++ * viortc_set_alarm_enabled() - VIRTIO_RTC_REQ_SET_ALARM_ENABLED wrapper ++ * @viortc: device data ++ * @vio_clk_id: virtio_rtc clock id ++ * @alarm_enable: enable or disable alarm ++ * ++ * Context: Process context. ++ * Return: Zero on success, negative error code otherwise. ++ */ ++int viortc_set_alarm_enabled(struct viortc_dev *viortc, u16 vio_clk_id, ++ bool alarm_enable) ++{ ++ VIORTC_DECLARE_MSG_HDL_ONSTACK(hdl, VIRTIO_RTC_REQ_SET_ALARM_ENABLED, ++ struct virtio_rtc_req_set_alarm_enabled, ++ struct virtio_rtc_resp_set_alarm_enabled); ++ u8 flags = 0; ++ int ret; ++ ++ ret = VIORTC_MSG_INIT(hdl, viortc); ++ if (ret) ++ return ret; ++ ++ if (alarm_enable) ++ flags |= VIRTIO_RTC_FLAG_ALARM_ENABLED; ++ ++ VIORTC_MSG_WRITE(hdl, clock_id, &vio_clk_id); ++ VIORTC_MSG_WRITE(hdl, flags, &flags); ++ ++ ret = viortc_msg_xfer(&viortc->vqs[VIORTC_REQUESTQ], VIORTC_MSG(hdl), ++ 0); ++ if (ret) { ++ dev_dbg(&viortc->vdev->dev, "%s: xfer returned %d\n", __func__, ++ ret); ++ goto out_release; ++ } ++ ++out_release: ++ viortc_msg_release(VIORTC_MSG(hdl)); ++ ++ return ret; ++} ++ + /* + * init, deinit + */ + ++/** ++ * viortc_init_rtc_class_clock() - init and register a RTC class device ++ * @viortc: device data ++ * @vio_clk_id: virtio_rtc clock id ++ * @clock_type: virtio_rtc clock type ++ * @flags: struct virtio_rtc_resp_clock_cap.flags ++ * ++ * The clock must be a UTC-like clock. ++ * ++ * Context: Process context. ++ * Return: Positive if registered, zero if not supported by configuration, ++ * negative error code otherwise. ++ */ ++static int viortc_init_rtc_class_clock(struct viortc_dev *viortc, ++ u16 vio_clk_id, u8 clock_type, u8 flags) ++{ ++ struct virtio_device *vdev = viortc->vdev; ++ struct viortc_class *viortc_class; ++ struct device *dev = &vdev->dev; ++ bool have_alarm; ++ ++ if (clock_type != VIRTIO_RTC_CLOCK_UTC_SMEARED) { ++ dev_info(dev, ++ "not creating RTC class device for clock %d, which may step on leap seconds\n", ++ vio_clk_id); ++ return 0; ++ } ++ ++ if (viortc->viortc_class) { ++ dev_warn_once(dev, ++ "multiple UTC-like clocks are present, but creating only one RTC class device\n"); ++ return 0; ++ } ++ ++ have_alarm = viortc_alarms_supported(vdev) && ++ !!(flags & VIRTIO_RTC_FLAG_ALARM_CAP); ++ ++ viortc_class = viortc_class_init(viortc, vio_clk_id, have_alarm, dev); ++ if (IS_ERR(viortc_class)) ++ return PTR_ERR(viortc_class); ++ ++ viortc->viortc_class = viortc_class; ++ ++ if (have_alarm) ++ devm_device_init_wakeup(dev); ++ ++ return viortc_class_register(viortc_class) ?: 1; ++} ++ + /** + * viortc_init_ptp_clock() - init and register PTP clock + * @viortc: device data +@@ -682,22 +1005,34 @@ static int viortc_init_ptp_clock(struct viortc_dev *viortc, u16 vio_clk_id, + * @viortc: device data + * @vio_clk_id: virtio_rtc clock id + * +- * Initializes PHC to represent virtio_rtc clock. ++ * Initializes PHC and/or RTC class device to represent virtio_rtc clock. + * + * Context: Process context. + * Return: Zero on success, negative error code otherwise. + */ + static int viortc_init_clock(struct viortc_dev *viortc, u16 vio_clk_id) + { +- u8 clock_type, leap_second_smearing; ++ u8 clock_type, leap_second_smearing, flags; + bool is_exposed = false; + int ret; + + ret = viortc_clock_cap(viortc, vio_clk_id, &clock_type, +- &leap_second_smearing); ++ &leap_second_smearing, &flags); + if (ret) + return ret; + ++ if (IS_ENABLED(CONFIG_VIRTIO_RTC_CLASS) && ++ (clock_type == VIRTIO_RTC_CLOCK_UTC || ++ clock_type == VIRTIO_RTC_CLOCK_UTC_SMEARED || ++ clock_type == VIRTIO_RTC_CLOCK_UTC_MAYBE_SMEARED)) { ++ ret = viortc_init_rtc_class_clock(viortc, vio_clk_id, ++ clock_type, flags); ++ if (ret < 0) ++ return ret; ++ if (ret > 0) ++ is_exposed = true; ++ } ++ + if (IS_ENABLED(CONFIG_VIRTIO_RTC_PTP)) { + ret = viortc_init_ptp_clock(viortc, vio_clk_id, clock_type, + leap_second_smearing); +@@ -716,7 +1051,7 @@ static int viortc_init_clock(struct viortc_dev *viortc, u16 vio_clk_id) + } + + /** +- * viortc_clocks_deinit() - unregister PHCs ++ * viortc_clocks_deinit() - unregister PHCs, stop RTC ops + * @viortc: device data + */ + static void viortc_clocks_deinit(struct viortc_dev *viortc) +@@ -734,6 +1069,9 @@ static void viortc_clocks_deinit(struct viortc_dev *viortc) + + WARN_ON(viortc_ptp_unregister(vio_ptp, &viortc->vdev->dev)); + } ++ ++ if (viortc->viortc_class) ++ viortc_class_stop(viortc->viortc_class); + } + + /** +@@ -780,6 +1118,88 @@ static int viortc_clocks_init(struct viortc_dev *viortc) + return ret; + } + ++/** ++ * viortc_populate_vq() - populate alarmq with device-writable buffers ++ * @viortc: device data ++ * @viortc_vq: device specific data for virtqueue ++ * @buf_cap: device-writable buffer size in bytes ++ * @lock: lock queue during accesses ++ * ++ * Populates the alarmq with pre-allocated buffers. ++ * ++ * The caller is responsible for kicking the device. ++ * ++ * Context: Process context. ++ * Return: Zero on success, negative error code otherwise. ++ */ ++static int viortc_populate_vq(struct viortc_dev *viortc, ++ struct viortc_vq *viortc_vq, u32 buf_cap, ++ bool lock) ++{ ++ unsigned int num_elems, i; ++ struct virtqueue *vq; ++ unsigned long flags; ++ void *buf; ++ int ret; ++ ++ num_elems = viortc->num_alarmq_bufs; ++ vq = viortc_vq->vq; ++ ++ for (i = 0; i < num_elems; i++) { ++ buf = viortc->alarmq_bufs[i]; ++ ++ if (lock) { ++ spin_lock_irqsave(&viortc_vq->lock, flags); ++ ++ ret = viortc_feed_vq(viortc, vq, buf, buf_cap, buf); ++ ++ spin_unlock_irqrestore(&viortc_vq->lock, flags); ++ } else { ++ ret = viortc_feed_vq(viortc, vq, buf, buf_cap, buf); ++ } ++ ++ if (ret) ++ return ret; ++ } ++ ++ return 0; ++} ++ ++/** ++ * viortc_alloc_vq_bufs() - allocate alarmq buffers ++ * @viortc: device data ++ * @num_elems: # of buffers ++ * @buf_cap: per-buffer device-writable bytes ++ * ++ * Context: Process context. ++ * Return: Zero on success, negative error code otherwise. ++ */ ++static int viortc_alloc_vq_bufs(struct viortc_dev *viortc, ++ unsigned int num_elems, u32 buf_cap) ++{ ++ struct device *dev = &viortc->vdev->dev; ++ void **buf_list; ++ unsigned int i; ++ void *buf; ++ ++ buf_list = devm_kcalloc(dev, num_elems, sizeof(*buf_list), GFP_KERNEL); ++ if (!buf_list) ++ return -ENOMEM; ++ ++ viortc->alarmq_bufs = buf_list; ++ viortc->num_alarmq_bufs = num_elems; ++ ++ for (i = 0; i < num_elems; i++) { ++ buf = devm_kzalloc(dev, buf_cap, GFP_KERNEL); ++ if (!buf) ++ return -ENOMEM; ++ ++ buf_list[i] = buf; ++ } ++ ++ return 0; ++} ++ + /** + * viortc_init_vqs() - init virtqueues + * @viortc: device data +@@ -794,11 +1214,19 @@ static int viortc_init_vqs(struct viortc_dev *viortc) + struct virtqueue *vqs[VIORTC_MAX_NR_QUEUES]; + struct virtqueue_info vqs_info[] = { + { "requestq", viortc_cb_requestq }, ++ { "alarmq", viortc_cb_alarmq }, + }; + struct virtio_device *vdev = viortc->vdev; ++ unsigned int num_elems; + int nr_queues, ret; ++ bool have_alarms; + +- nr_queues = VIORTC_REQUESTQ + 1; ++ have_alarms = viortc_alarms_supported(vdev); ++ ++ if (have_alarms) ++ nr_queues = VIORTC_ALARMQ + 1; ++ else ++ nr_queues = VIORTC_REQUESTQ + 1; + + ret = virtio_find_vqs(vdev, nr_queues, vqs, vqs_info, NULL); + if (ret) +@@ -807,6 +1235,25 @@ static int viortc_init_vqs(struct viortc_dev *viortc) + viortc->vqs[VIORTC_REQUESTQ].vq = vqs[VIORTC_REQUESTQ]; + spin_lock_init(&viortc->vqs[VIORTC_REQUESTQ].lock); + ++ if (have_alarms) { ++ viortc->vqs[VIORTC_ALARMQ].vq = vqs[VIORTC_ALARMQ]; ++ spin_lock_init(&viortc->vqs[VIORTC_ALARMQ].lock); ++ ++ num_elems = virtqueue_get_vring_size(vqs[VIORTC_ALARMQ]); ++ if (num_elems == 0) ++ return -ENOSPC; ++ ++ if (!viortc->alarmq_bufs) { ++ ret = viortc_alloc_vq_bufs(viortc, num_elems, ++ VIORTC_ALARMQ_BUF_CAP); ++ if (ret) ++ return ret; ++ } else { ++ viortc->num_alarmq_bufs = ++ min(num_elems, viortc->num_alarmq_bufs); ++ } ++ } ++ + return 0; + } + +@@ -819,7 +1266,11 @@ static int viortc_init_vqs(struct viortc_dev *viortc) + */ + static int viortc_probe(struct virtio_device *vdev) + { ++ struct viortc_vq *alarm_viortc_vq; ++ struct virtqueue *alarm_vq; + struct viortc_dev *viortc; ++ unsigned long flags; ++ bool notify; + int ret; + + viortc = devm_kzalloc(&vdev->dev, sizeof(*viortc), GFP_KERNEL); +@@ -839,8 +1290,30 @@ static int viortc_probe(struct virtio_device *vdev) + if (ret) + goto err_reset_vdev; + ++ if (viortc_alarms_supported(vdev)) { ++ alarm_viortc_vq = &viortc->vqs[VIORTC_ALARMQ]; ++ alarm_vq = alarm_viortc_vq->vq; ++ ++ ret = viortc_populate_vq(viortc, alarm_viortc_vq, ++ VIORTC_ALARMQ_BUF_CAP, true); ++ if (ret) ++ goto err_deinit_clocks; ++ ++ spin_lock_irqsave(&alarm_viortc_vq->lock, flags); ++ notify = virtqueue_kick_prepare(alarm_vq); ++ spin_unlock_irqrestore(&alarm_viortc_vq->lock, flags); ++ ++ if (notify && !virtqueue_notify(alarm_vq)) { ++ ret = -EIO; ++ goto err_deinit_clocks; ++ } ++ } ++ + return 0; + ++err_deinit_clocks: ++ viortc_clocks_deinit(viortc); ++ + err_reset_vdev: + virtio_reset_device(vdev); + vdev->config->del_vqs(vdev); +@@ -875,10 +1348,41 @@ static int viortc_freeze(struct virtio_device *dev) + static int viortc_restore(struct virtio_device *dev) + { + struct viortc_dev *viortc = dev->priv; ++ struct viortc_vq *alarm_viortc_vq; ++ struct virtqueue *alarm_vq; ++ bool notify = false; ++ int ret; + +- return viortc_init_vqs(viortc); ++ ret = viortc_init_vqs(viortc); ++ if (ret) ++ return ret; ++ ++ alarm_viortc_vq = &viortc->vqs[VIORTC_ALARMQ]; ++ alarm_vq = alarm_viortc_vq->vq; ++ ++ if (viortc_alarms_supported(dev)) { ++ ret = viortc_populate_vq(viortc, alarm_viortc_vq, ++ VIORTC_ALARMQ_BUF_CAP, false); ++ if (ret) ++ return ret; ++ ++ notify = virtqueue_kick_prepare(alarm_vq); ++ } ++ ++ virtio_device_ready(dev); ++ ++ if (notify && !virtqueue_notify(alarm_vq)) ++ ret = -EIO; ++ ++ return ret; + } + ++static unsigned int features[] = { ++#if IS_ENABLED(CONFIG_VIRTIO_RTC_CLASS) ++ VIRTIO_RTC_F_ALARM, ++#endif ++}; ++ + static struct virtio_device_id id_table[] = { + { VIRTIO_ID_CLOCK, VIRTIO_DEV_ANY_ID }, + { 0 }, +@@ -887,6 +1391,8 @@ MODULE_DEVICE_TABLE(virtio, id_table); + + static struct virtio_driver virtio_rtc_drv = { + .driver.name = KBUILD_MODNAME, ++ .feature_table = features, ++ .feature_table_size = ARRAY_SIZE(features), + .id_table = id_table, + .probe = viortc_probe, + .remove = viortc_remove, +diff --git a/drivers/virtio/virtio_rtc_internal.h b/drivers/virtio/virtio_rtc_internal.h +index 2e589903d04f29..296afee6719b32 100644 +--- a/drivers/virtio/virtio_rtc_internal.h ++++ b/drivers/virtio/virtio_rtc_internal.h +@@ -9,6 +9,8 @@ + #ifndef _VIRTIO_RTC_INTERNAL_H_ + #define _VIRTIO_RTC_INTERNAL_H_ + ++#include ++#include + #include + #include + +@@ -21,6 +23,16 @@ int viortc_read_cross(struct viortc_dev *viortc, u16 vio_clk_id, u8 hw_counter, + u64 *reading, u64 *cycles); + int viortc_cross_cap(struct viortc_dev *viortc, u16 vio_clk_id, u8 hw_counter, + bool *supported); ++int viortc_read_alarm(struct viortc_dev *viortc, u16 vio_clk_id, ++ u64 *alarm_time, bool *enabled); ++int viortc_set_alarm(struct viortc_dev *viortc, u16 vio_clk_id, u64 alarm_time, ++ bool alarm_enable); ++int viortc_set_alarm_enabled(struct viortc_dev *viortc, u16 vio_clk_id, ++ bool alarm_enable); ++ ++struct viortc_class; ++ ++struct viortc_class *viortc_class_from_dev(struct device *dev); + + /* PTP IFs */ + +@@ -67,4 +79,44 @@ static inline int viortc_ptp_unregister(struct viortc_ptp_clock *vio_ptp, + */ + int viortc_hw_xtstamp_params(u8 *hw_counter, enum clocksource_ids *cs_id); + ++/* RTC class IFs */ ++ ++#if IS_ENABLED(CONFIG_VIRTIO_RTC_CLASS) ++ ++void viortc_class_alarm(struct viortc_class *viortc_class, u16 vio_clk_id); ++ ++void viortc_class_stop(struct viortc_class *viortc_class); ++ ++int viortc_class_register(struct viortc_class *viortc_class); ++ ++struct viortc_class *viortc_class_init(struct viortc_dev *viortc, ++ u16 vio_clk_id, bool have_alarm, ++ struct device *parent_dev); ++ ++#else /* CONFIG_VIRTIO_RTC_CLASS */ ++ ++static inline void viortc_class_alarm(struct viortc_class *viortc_class, ++ u16 vio_clk_id) ++{ ++} ++ ++static inline void viortc_class_stop(struct viortc_class *viortc_class) ++{ ++} ++ ++static inline int viortc_class_register(struct viortc_class *viortc_class) ++{ ++ return -ENODEV; ++} ++ ++static inline struct viortc_class *viortc_class_init(struct viortc_dev *viortc, ++ u16 vio_clk_id, ++ bool have_alarm, ++ struct device *parent_dev) ++{ ++ return ERR_PTR(-ENODEV); ++} ++ ++#endif /* CONFIG_VIRTIO_RTC_CLASS */ ++ + #endif /* _VIRTIO_RTC_INTERNAL_H_ */ +diff --git a/include/uapi/linux/virtio_rtc.h b/include/uapi/linux/virtio_rtc.h +index 6b3af4e9bbfb06..85ee8f0136613d 100644 +--- a/include/uapi/linux/virtio_rtc.h ++++ b/include/uapi/linux/virtio_rtc.h +@@ -9,6 +9,9 @@ + + #include + ++/* alarm feature */ ++#define VIRTIO_RTC_F_ALARM 0 ++ + /* read request message types */ + + #define VIRTIO_RTC_REQ_READ 0x0001 +@@ -19,6 +22,13 @@ + #define VIRTIO_RTC_REQ_CFG 0x1000 + #define VIRTIO_RTC_REQ_CLOCK_CAP 0x1001 + #define VIRTIO_RTC_REQ_CROSS_CAP 0x1002 ++#define VIRTIO_RTC_REQ_READ_ALARM 0x1003 ++#define VIRTIO_RTC_REQ_SET_ALARM 0x1004 ++#define VIRTIO_RTC_REQ_SET_ALARM_ENABLED 0x1005 ++ ++/* alarmq message types */ ++ ++#define VIRTIO_RTC_NOTIF_ALARM 0x2000 + + /* Message headers */ + +@@ -39,6 +49,12 @@ struct virtio_rtc_resp_head { + __u8 reserved[7]; + }; + ++/** common notification header */ ++struct virtio_rtc_notif_head { ++ __le16 msg_type; ++ __u8 reserved[6]; ++}; ++ + /* read requests */ + + /* VIRTIO_RTC_REQ_READ message */ +@@ -111,7 +127,9 @@ struct virtio_rtc_resp_clock_cap { + #define VIRTIO_RTC_SMEAR_NOON_LINEAR 1 + #define VIRTIO_RTC_SMEAR_UTC_SLS 2 + __u8 leap_second_smearing; +- __u8 reserved[6]; ++#define VIRTIO_RTC_FLAG_ALARM_CAP (1 << 0) ++ __u8 flags; ++ __u8 reserved[5]; + }; + + /* VIRTIO_RTC_REQ_CROSS_CAP message */ +@@ -130,6 +148,53 @@ struct virtio_rtc_resp_cross_cap { + __u8 reserved[7]; + }; + ++/* VIRTIO_RTC_REQ_READ_ALARM message */ ++ ++struct virtio_rtc_req_read_alarm { ++ struct virtio_rtc_req_head head; ++ __le16 clock_id; ++ __u8 reserved[6]; ++}; ++ ++struct virtio_rtc_resp_read_alarm { ++ struct virtio_rtc_resp_head head; ++ __le64 alarm_time; ++#define VIRTIO_RTC_FLAG_ALARM_ENABLED (1 << 0) ++ __u8 flags; ++ __u8 reserved[7]; ++}; ++ ++/* VIRTIO_RTC_REQ_SET_ALARM message */ ++ ++struct virtio_rtc_req_set_alarm { ++ struct virtio_rtc_req_head head; ++ __le64 alarm_time; ++ __le16 clock_id; ++ /* flag VIRTIO_RTC_FLAG_ALARM_ENABLED */ ++ __u8 flags; ++ __u8 reserved[5]; ++}; ++ ++struct virtio_rtc_resp_set_alarm { ++ struct virtio_rtc_resp_head head; ++ /* no response params */ ++}; ++ ++/* VIRTIO_RTC_REQ_SET_ALARM_ENABLED message */ ++ ++struct virtio_rtc_req_set_alarm_enabled { ++ struct virtio_rtc_req_head head; ++ __le16 clock_id; ++ /* flag VIRTIO_RTC_ALARM_ENABLED */ ++ __u8 flags; ++ __u8 reserved[5]; ++}; ++ ++struct virtio_rtc_resp_set_alarm_enabled { ++ struct virtio_rtc_resp_head head; ++ /* no response params */ ++}; ++ + /** Union of request types for requestq */ + union virtio_rtc_req_requestq { + struct virtio_rtc_req_read read; +@@ -137,6 +202,9 @@ union virtio_rtc_req_requestq { + struct virtio_rtc_req_cfg cfg; + struct virtio_rtc_req_clock_cap clock_cap; + struct virtio_rtc_req_cross_cap cross_cap; ++ struct virtio_rtc_req_read_alarm read_alarm; ++ struct virtio_rtc_req_set_alarm set_alarm; ++ struct virtio_rtc_req_set_alarm_enabled set_alarm_enabled; + }; + + /** Union of response types for requestq */ +@@ -146,6 +214,24 @@ union virtio_rtc_resp_requestq { + struct virtio_rtc_resp_cfg cfg; + struct virtio_rtc_resp_clock_cap clock_cap; + struct virtio_rtc_resp_cross_cap cross_cap; ++ struct virtio_rtc_resp_read_alarm read_alarm; ++ struct virtio_rtc_resp_set_alarm set_alarm; ++ struct virtio_rtc_resp_set_alarm_enabled set_alarm_enabled; ++}; ++ ++/* alarmq notifications */ ++ ++/* VIRTIO_RTC_NOTIF_ALARM notification */ ++ ++struct virtio_rtc_notif_alarm { ++ struct virtio_rtc_notif_head head; ++ __le16 clock_id; ++ __u8 reserved[6]; ++}; ++ ++/** Union of notification types for alarmq */ ++union virtio_rtc_notif_alarmq { ++ struct virtio_rtc_notif_alarm alarm; + }; + + #endif /* _LINUX_VIRTIO_RTC_H */ diff --git a/patches/0032-virtio_rtc-Fix-compatibility-for-kernel-6.12.patch b/patches/0032-virtio_rtc-Fix-compatibility-for-kernel-6.12.patch new file mode 100644 index 0000000..8b683d3 --- /dev/null +++ b/patches/0032-virtio_rtc-Fix-compatibility-for-kernel-6.12.patch @@ -0,0 +1,41 @@ +From: Dorinda Bassey +Date: Tue, 29 Apr 2026 11:35:00 +0200 +Subject: [PATCH] virtio_rtc: Fix compatibility for kernel 6.12 + +Backport virtio_rtc driver to kernel 6.12 by fixing API differences: + +1. secs_to_jiffies() doesn't exist in 6.12, use msecs_to_jiffies() +2. devm_device_init_wakeup() doesn't exist in 6.12, use device_init_wakeup() + +These functions were introduced in later kernels. The replacements +provide equivalent functionality. + +Signed-off-by: Dorinda Bassey +--- + drivers/virtio/virtio_rtc_driver.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/virtio/virtio_rtc_driver.c b/drivers/virtio/virtio_rtc_driver.c +index bc303c39d568..e7a9c4f6b2e5 100644 +--- a/drivers/virtio/virtio_rtc_driver.c ++++ b/drivers/virtio/virtio_rtc_driver.c +@@ -575,7 +575,7 @@ int viortc_cross_cap(struct viortc_dev *viortc, u16 vio_clk_id, u8 hw_counter, + */ + + /** timeout for clock readings, where timeouts are considered non-fatal */ +-#define VIORTC_MSG_READ_TIMEOUT secs_to_jiffies(60) ++#define VIORTC_MSG_READ_TIMEOUT msecs_to_jiffies(60 * 1000) + + /** + * viortc_read() - VIRTIO_RTC_REQ_READ wrapper +@@ -961,7 +961,7 @@ static int viortc_class_try_init(struct viortc_dev *viortc, bool have_alarm) + viortc->viortc_class = viortc_class; + + if (have_alarm) +- devm_device_init_wakeup(dev); ++ device_init_wakeup(dev, true); + + return viortc_class_register(viortc_class) ?: 1; + } +-- +2.43.0