Re: [RFC PATCH v4 4/4] virtio_rtc: Add RTC class driver
From: Alexandre Belloni
Date: Mon Dec 23 2024 - 06:03:24 EST
On 19/12/2024 21:11:10+0100, Peter Hilber wrote:
> 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 <quic_philber@xxxxxxxxxxx>
Acked-by: Alexandre Belloni <alexandre.belloni@xxxxxxxxxxx>
> ---
>
> Notes:
> v4:
>
> - Do not create RTC class device for clocks which may step on leap seconds
> (Alexandre Belloni).
>
> - Clear RTC class feature bit instead of defining reduced ops
> (Alexandre Belloni).
>
> - Use macros for 64-bit divisions.
>
> - Remove unnecessary memory barrier.
>
> - Cosmetic changes.
>
> v3:
>
> Added.
>
> drivers/virtio/Kconfig | 22 +-
> drivers/virtio/Makefile | 1 +
> drivers/virtio/virtio_rtc_class.c | 268 +++++++++++++++
> drivers/virtio/virtio_rtc_driver.c | 482 ++++++++++++++++++++++++++-
> drivers/virtio/virtio_rtc_internal.h | 52 +++
> include/uapi/linux/virtio_rtc.h | 85 +++++
> 6 files changed, 905 insertions(+), 5 deletions(-)
> create mode 100644 drivers/virtio/virtio_rtc_class.c
>
> diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig
> index 3d8b366c0625..6db5235a7693 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 dbd77f124ba9..eefcfe90d6b8 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 000000000000..e4a48eafad16
> --- /dev/null
> +++ b/drivers/virtio/virtio_rtc_class.c
> @@ -0,0 +1,268 @@
> +// 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 <linux/math64.h>
> +#include <linux/overflow.h>
> +#include <linux/rtc.h>
> +#include <linux/time64.h>
> +
> +#include <uapi/linux/virtio_rtc.h>
> +
> +#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 (WARN_ONCE(
> + !viortc_class,
> + "virtio_rtc: unexpected alarm, no RTC class device available\n"))
> + return;
> +
> + if (vio_clk_id != viortc_class->vio_clk_id) {
> + dev_err_ratelimited(&viortc_class->rtc->dev,
> + "%s: unexpected clock id %d != %d\n",
> + __func__, 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);
> +
> + viortc_class->viortc = viortc;
> +
> + rtc = devm_rtc_allocate_device(parent_dev);
> + if (IS_ERR(rtc))
> + return ERR_CAST(rtc);
> +
> + viortc_class->rtc = rtc;
> +
> + 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 83f975d9a6ae..f8b890afc528 100644
> --- a/drivers/virtio/virtio_rtc_driver.c
> +++ b/drivers/virtio/virtio_rtc_driver.c
> @@ -10,15 +10,20 @@
> #include <linux/virtio.h>
> #include <linux/virtio_ids.h>
> #include <linux/virtio_config.h>
> +#include <linux/device.h>
> #include <linux/module.h>
> +#include <linux/pm.h>
>
> #include <uapi/linux/virtio_rtc.h>
>
> #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,
> };
>
> @@ -35,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;
> };
>
> @@ -76,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
> @@ -238,6 +303,81 @@ 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: unknown notification type\n",
> + __func__);
> + goto feed_vq;
> + }
> +
> + if (len < sizeof(*notif)) {
> + dev_err_ratelimited(&viortc->vdev->dev,
> + "%s: alarm notification too small\n",
> + __func__);
> + goto feed_vq;
> + }
> +
> + clock_id = virtio_le_to_cpu(notif->clock_id);
> +
> + viortc_class_alarm(viortc->viortc_class, clock_id);
> +
> +feed_vq:
> + spin_lock_irqsave(&viortc_vq->lock, flags);
> +
> + WARN_ON(viortc_feed_vq(viortc, vq, notif, VIORTC_ALARMQ_BUF_CAP,
> + token));
> +
> + 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
> @@ -638,10 +778,192 @@ 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)
> + device_init_wakeup(dev, true);
> +
> + return viortc_class_register(viortc_class) ?: 1;
> +}
> +
> /**
> * viortc_init_ptp_clock() - init and register PTP clock
> * @viortc: device data
> @@ -697,6 +1019,18 @@ static int viortc_init_clock(struct viortc_dev *viortc, u16 vio_clk_id)
> 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 +1050,7 @@ static int viortc_init_clock(struct viortc_dev *viortc, u16 vio_clk_id)
> }
>
> /**
> - * viortc_clocks_exit() - unregister PHCs
> + * viortc_clocks_exit() - unregister PHCs, stop RTC ops
> * @viortc: device data
> */
> static void viortc_clocks_exit(struct viortc_dev *viortc)
> @@ -734,6 +1068,9 @@ static void viortc_clocks_exit(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 +1117,74 @@ static int viortc_clocks_init(struct viortc_dev *viortc)
> return ret;
> }
>
> +/**
> + * viortc_populate_vq() - populate alarmq with device-writable buffers
> + * @viortc: device data
> + * @vq: virtqueue
> + * @buf_cap: device-writable buffer size in bytes
> + *
> + * 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 virtqueue *vq,
> + u32 buf_cap)
> +{
> + unsigned int num_elems, i;
> + void *buf;
> + int ret;
> +
> + num_elems = viortc->num_alarmq_bufs;
> +
> + for (i = 0; i < num_elems; i++) {
> + buf = viortc->alarmq_bufs[i];
> +
> + 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
> @@ -795,11 +1200,19 @@ static int viortc_init_vqs(struct viortc_dev *viortc)
> struct virtio_device *vdev = viortc->vdev;
> struct virtqueue_info vqs_info[] = {
> { "requestq", viortc_cb_requestq },
> + { "alarmq", viortc_cb_alarmq },
> };
> struct virtqueue *vqs[VIORTC_MAX_NR_QUEUES];
> + unsigned int num_elems;
> + bool have_alarms;
> int nr_queues;
>
> - 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)
> @@ -808,6 +1221,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;
> }
>
> @@ -820,6 +1252,7 @@ static int viortc_init_vqs(struct viortc_dev *viortc)
> */
> static int viortc_probe(struct virtio_device *vdev)
> {
> + struct virtqueue *alarm_vq;
> struct viortc_dev *viortc;
> int ret;
>
> @@ -840,6 +1273,20 @@ static int viortc_probe(struct virtio_device *vdev)
> if (ret)
> goto err_reset_vdev;
>
> + if (viortc_alarms_supported(vdev)) {
> + alarm_vq = viortc->vqs[VIORTC_ALARMQ].vq;
> +
> + ret = viortc_populate_vq(viortc, alarm_vq,
> + VIORTC_ALARMQ_BUF_CAP);
> + if (ret)
> + goto err_reset_vdev;
> +
> + if (!virtqueue_kick(alarm_vq)) {
> + ret = -EIO;
> + goto err_reset_vdev;
> + }
> + }
> +
> return 0;
>
> err_reset_vdev:
> @@ -863,6 +1310,33 @@ static void viortc_remove(struct virtio_device *vdev)
> vdev->config->del_vqs(vdev);
> }
>
> +static int viortc_freeze(struct virtio_device *dev)
> +{
> + return 0;
> +}
> +
> +static int viortc_restore(struct virtio_device *dev)
> +{
> + struct viortc_dev *viortc = dev->priv;
> + int ret;
> +
> + ret = viortc_init_vqs(viortc);
> + if (ret)
> + return ret;
> +
> + if (viortc_alarms_supported(dev))
> + ret = viortc_populate_vq(viortc, viortc->vqs[VIORTC_ALARMQ].vq,
> + VIORTC_ALARMQ_BUF_CAP);
> +
> + 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 },
> @@ -871,9 +1345,13 @@ 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,
> + .freeze = pm_sleep_ptr(viortc_freeze),
> + .restore = pm_sleep_ptr(viortc_restore),
> };
>
> module_virtio_driver(virtio_rtc_drv);
> diff --git a/drivers/virtio/virtio_rtc_internal.h b/drivers/virtio/virtio_rtc_internal.h
> index 785356e488a8..e7f865259afd 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 <linux/device.h>
> +#include <linux/err.h>
> #include <linux/types.h>
> #include <linux/ptp_clock_kernel.h>
>
> @@ -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 8fb04846b397..f19895b36dd3 100644
> --- a/include/uapi/linux/virtio_rtc.h
> +++ b/include/uapi/linux/virtio_rtc.h
> @@ -9,6 +9,9 @@
>
> #include <linux/types.h>
>
> +/* 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 */
> @@ -160,6 +176,7 @@ struct virtio_rtc_resp_clock_cap {
> #define VIRTIO_RTC_FLAG_LEAP_CAP (1 << 0)
> #define VIRTIO_RTC_FLAG_TAI_OFFSET_CAP (1 << 1)
> #define VIRTIO_RTC_FLAG_SMEAR_OFFSET_CAP (1 << 2)
> +#define VIRTIO_RTC_FLAG_ALARM_CAP (1 << 3)
> __u8 flags;
> __u8 reserved[5];
> };
> @@ -180,6 +197,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_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;
> @@ -187,6 +251,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 */
> @@ -196,6 +263,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 */
> --
> 2.43.0
>
--
Alexandre Belloni, co-owner and COO, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com