Re: [RFC PATCH v4 4/4] virtio_rtc: Add RTC class driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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




[Index of Archives]     [KVM Development]     [Libvirt Development]     [Libvirt Users]     [CentOS Virtualization]     [Netdev]     [Ethernet Bridging]     [Linux Wireless]     [Kernel Newbies]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite Forum]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux