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