// SPDX-License-Identifier: GPL-2.0-or-later /* * virtio_rtc driver core * * Copyright (C) 2022-2024 OpenSynergy GmbH * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include "virtio_rtc_internal.h" #define VIORTC_ALARMQ_BUF_CAP sizeof(union virtio_rtc_notif_alarmq) /* virtqueue order */ enum { VIORTC_REQUESTQ, VIORTC_ALARMQ, VIORTC_MAX_NR_QUEUES, }; /** * struct viortc_vq - virtqueue abstraction * @vq: virtqueue * @lock: protects access to vq */ struct viortc_vq { struct virtqueue *vq; spinlock_t lock; }; /** * struct viortc_dev - virtio_rtc device data * @vdev: virtio device * @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; }; /** * struct viortc_msg - Message requested by driver, responded by device. * @viortc: device data * @req: request buffer * @resp: response buffer * @responded: vqueue callback signals response reception * @refcnt: Message reference count, message and buffers will be deallocated * once 0. refcnt is decremented in the vqueue callback and in the * thread waiting on the responded completion. * If a message response wait function times out, the message will be * freed upon late reception (refcnt will reach 0 in the callback), or * device removal. * @req_size: size of request in bytes * @resp_cap: maximum size of response in bytes * @resp_actual_size: actual size of response */ struct viortc_msg { struct viortc_dev *viortc; void *req; void *resp; struct completion responded; refcount_t refcnt; unsigned int req_size; unsigned int resp_cap; unsigned int resp_actual_size; }; /** * viortc_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 * @msg_type: virtio_rtc message type * @req_size: size of request buffer to be allocated * @resp_cap: size of response buffer to be allocated * * Initializes the message refcnt to 2. The refcnt will be decremented once in * the virtqueue callback, and once in the thread waiting on the message (on * completion or timeout). * * Context: Process context. * Return: non-NULL on success. */ static struct viortc_msg *viortc_msg_init(struct viortc_dev *viortc, u16 msg_type, unsigned int req_size, unsigned int resp_cap) { struct device *dev = &viortc->vdev->dev; struct virtio_rtc_req_head *req_head; struct viortc_msg *msg; msg = devm_kzalloc(dev, sizeof(*msg), GFP_KERNEL); if (!msg) return NULL; init_completion(&msg->responded); msg->req = devm_kzalloc(dev, req_size, GFP_KERNEL); if (!msg->req) goto err_free_msg; req_head = msg->req; msg->resp = devm_kzalloc(dev, resp_cap, GFP_KERNEL); if (!msg->resp) goto err_free_msg_req; msg->viortc = viortc; msg->req_size = req_size; msg->resp_cap = resp_cap; refcount_set(&msg->refcnt, 2); req_head->msg_type = virtio_cpu_to_le(msg_type, req_head->msg_type); return msg; err_free_msg_req: devm_kfree(dev, msg->req); err_free_msg: devm_kfree(dev, msg); return NULL; } /** * viortc_msg_release() - Decrement message refcnt, potentially free message. * @msg: message requested by driver * * Context: Any context. */ static void viortc_msg_release(struct viortc_msg *msg) { struct device *dev; if (refcount_dec_and_test(&msg->refcnt)) { dev = &msg->viortc->vdev->dev; devm_kfree(dev, msg->req); devm_kfree(dev, msg->resp); devm_kfree(dev, msg); } } /** * viortc_do_cb() - generic virtqueue callback logic * @vq: virtqueue * @handle_buf: function to process a used buffer * * Context: virtqueue callback, typically interrupt. Takes and releases vq lock. */ static void viortc_do_cb(struct virtqueue *vq, void (*handle_buf)(void *token, unsigned int len, struct virtqueue *vq, struct viortc_vq *viortc_vq, struct viortc_dev *viortc)) { struct viortc_dev *viortc = vq->vdev->priv; struct viortc_vq *viortc_vq; bool cb_enabled = true; unsigned long flags; unsigned int len; void *token; viortc_vq = &viortc->vqs[vq->index]; for (;;) { spin_lock_irqsave(&viortc_vq->lock, flags); if (cb_enabled) { virtqueue_disable_cb(vq); cb_enabled = false; } token = virtqueue_get_buf(vq, &len); if (!token) { if (virtqueue_enable_cb(vq)) { spin_unlock_irqrestore(&viortc_vq->lock, flags); return; } cb_enabled = true; } spin_unlock_irqrestore(&viortc_vq->lock, flags); if (token) handle_buf(token, len, vq, viortc_vq, viortc); } } /** * viortc_requestq_hdlr() - process a requestq used buffer * @token: token identifying the buffer * @len: bytes written by device * @vq: virtqueue * @viortc_vq: device specific data for virtqueue * @viortc: device data * * Signals completion for each received message. * * Context: virtqueue callback */ static void viortc_requestq_hdlr(void *token, unsigned int len, struct virtqueue *vq, struct viortc_vq *viortc_vq, struct viortc_dev *viortc) { struct viortc_msg *msg = token; msg->resp_actual_size = len; complete(&msg->responded); viortc_msg_release(msg); } /** * viortc_cb_requestq() - callback for requestq * @vq: virtqueue * * Context: virtqueue callback */ static void viortc_cb_requestq(struct virtqueue *vq) { viortc_do_cb(vq, viortc_requestq_hdlr); } /** * viortc_alarmq_hdlr() - process an alarmq used buffer * @token: token identifying the buffer * @len: bytes written by device * @vq: virtqueue * @viortc_vq: device specific data for virtqueue * @viortc: device data * * Processes a VIRTIO_RTC_NOTIF_ALARM notification by calling the RTC class * driver. Makes the buffer available again. * * Context: virtqueue callback */ static void viortc_alarmq_hdlr(void *token, unsigned int len, struct virtqueue *vq, struct viortc_vq *viortc_vq, struct viortc_dev *viortc) { struct virtio_rtc_notif_alarm *notif = token; struct virtio_rtc_notif_head *head = token; unsigned long flags; u16 clock_id; bool notify; if (len < sizeof(*head)) { dev_err_ratelimited(&viortc->vdev->dev, "%s: ignoring notification with short header\n", __func__); goto feed_vq; } if (virtio_le_to_cpu(head->msg_type) != VIRTIO_RTC_NOTIF_ALARM) { dev_err_ratelimited(&viortc->vdev->dev, "%s: ignoring unknown notification type 0x%x\n", __func__, virtio_le_to_cpu(head->msg_type)); goto feed_vq; } if (len < sizeof(*notif)) { dev_err_ratelimited(&viortc->vdev->dev, "%s: ignoring too small alarm notification\n", __func__); goto feed_vq; } clock_id = virtio_le_to_cpu(notif->clock_id); if (!viortc->viortc_class) dev_warn_ratelimited(&viortc->vdev->dev, "ignoring alarm, no RTC class device available\n"); else viortc_class_alarm(viortc->viortc_class, clock_id); feed_vq: spin_lock_irqsave(&viortc_vq->lock, flags); if (viortc_feed_vq(viortc, vq, notif, VIORTC_ALARMQ_BUF_CAP, token)) dev_warn(&viortc->vdev->dev, "%s: failed to re-expose input buffer\n", __func__); notify = virtqueue_kick_prepare(vq); spin_unlock_irqrestore(&viortc_vq->lock, flags); if (notify) virtqueue_notify(vq); } /** * viortc_cb_alarmq() - callback for alarmq * @vq: virtqueue * * Context: virtqueue callback */ static void viortc_cb_alarmq(struct virtqueue *vq) { viortc_do_cb(vq, viortc_alarmq_hdlr); } /** * viortc_get_resp_errno() - converts virtio_rtc errnos to system errnos * @resp_head: message response header * * Return: negative system errno, or 0 */ static int viortc_get_resp_errno(struct virtio_rtc_resp_head *resp_head) { switch (virtio_le_to_cpu(resp_head->status)) { case VIRTIO_RTC_S_OK: return 0; case VIRTIO_RTC_S_EOPNOTSUPP: return -EOPNOTSUPP; case VIRTIO_RTC_S_EINVAL: return -EINVAL; case VIRTIO_RTC_S_ENODEV: return -ENODEV; case VIRTIO_RTC_S_EIO: default: return -EIO; } } /** * viortc_msg_xfer() - send message request, wait until message response * @vq: virtqueue * @msg: message with driver request * @timeout_jiffies: message response timeout, 0 for no timeout * * Context: Process context. Takes and releases vq.lock. May sleep. * Return: Zero on success, negative error code otherwise. */ static int viortc_msg_xfer(struct viortc_vq *vq, struct viortc_msg *msg, unsigned long timeout_jiffies) { struct scatterlist out_sg[1]; struct scatterlist in_sg[1]; struct scatterlist *sgs[2]; unsigned long flags; long timeout_ret; bool notify; int ret; sgs[0] = out_sg; sgs[1] = in_sg; sg_init_one(out_sg, msg->req, msg->req_size); sg_init_one(in_sg, msg->resp, msg->resp_cap); spin_lock_irqsave(&vq->lock, flags); ret = virtqueue_add_sgs(vq->vq, sgs, 1, 1, msg, GFP_ATOMIC); if (ret) { spin_unlock_irqrestore(&vq->lock, flags); /* * Release in place of the response callback, which will never * come. */ viortc_msg_release(msg); return ret; } notify = virtqueue_kick_prepare(vq->vq); spin_unlock_irqrestore(&vq->lock, flags); if (notify) virtqueue_notify(vq->vq); if (timeout_jiffies) { timeout_ret = wait_for_completion_interruptible_timeout( &msg->responded, timeout_jiffies); if (!timeout_ret) return -ETIMEDOUT; else if (timeout_ret < 0) return (int)timeout_ret; } else { ret = wait_for_completion_interruptible(&msg->responded); if (ret) return ret; } if (msg->resp_actual_size < sizeof(struct virtio_rtc_resp_head)) return -EINVAL; ret = viortc_get_resp_errno(msg->resp); if (ret) return ret; /* * There is not yet a case where returning a short message would make * sense, so consider any deviation an error. */ if (msg->resp_actual_size != msg->resp_cap) return -EINVAL; return 0; } /* * common message handle macros for messages of different types */ /** * VIORTC_DECLARE_MSG_HDL_ONSTACK() - declare message handle on stack * @hdl: message handle name * @msg_id: message type id * @msg_req: message request type * @msg_resp: message response type */ #define VIORTC_DECLARE_MSG_HDL_ONSTACK(hdl, msg_id, msg_req, msg_resp) \ struct { \ struct viortc_msg *msg; \ msg_req *req; \ msg_resp *resp; \ unsigned int req_size; \ unsigned int resp_cap; \ u16 msg_type; \ } hdl = { \ NULL, NULL, NULL, sizeof(msg_req), sizeof(msg_resp), (msg_id), \ } /** * VIORTC_MSG() - extract message from message handle * @hdl: message handle * * Return: struct viortc_msg */ #define VIORTC_MSG(hdl) ((hdl).msg) /** * VIORTC_MSG_INIT() - initialize message handle * @hdl: message handle * @viortc: device data (struct viortc_dev *) * * Context: Process context. * Return: 0 on success, -ENOMEM otherwise. */ #define VIORTC_MSG_INIT(hdl, viortc) \ ({ \ typeof(hdl) *_hdl = &(hdl); \ \ _hdl->msg = viortc_msg_init((viortc), _hdl->msg_type, \ _hdl->req_size, _hdl->resp_cap); \ if (_hdl->msg) { \ _hdl->req = _hdl->msg->req; \ _hdl->resp = _hdl->msg->resp; \ } \ _hdl->msg ? 0 : -ENOMEM; \ }) /** * VIORTC_MSG_WRITE() - write a request message field * @hdl: message handle * @dest_member: request message field name * @src_ptr: pointer to data of compatible type * * Writes the field in little-endian format. */ #define VIORTC_MSG_WRITE(hdl, dest_member, src_ptr) \ do { \ typeof(hdl) _hdl = (hdl); \ typeof(src_ptr) _src_ptr = (src_ptr); \ \ /* Sanity check: must match the member's type */ \ typecheck(typeof(virtio_le_to_cpu(_hdl.req->dest_member)), \ *_src_ptr); \ \ _hdl.req->dest_member = \ virtio_cpu_to_le(*_src_ptr, _hdl.req->dest_member); \ } while (0) /** * VIORTC_MSG_READ() - read from a response message field * @hdl: message handle * @src_member: response message field name * @dest_ptr: pointer to data of compatible type * * Converts from little-endian format and writes to dest_ptr. */ #define VIORTC_MSG_READ(hdl, src_member, dest_ptr) \ do { \ typeof(dest_ptr) _dest_ptr = (dest_ptr); \ \ /* Sanity check: must match the member's type */ \ typecheck(typeof(virtio_le_to_cpu((hdl).resp->src_member)), \ *_dest_ptr); \ \ *_dest_ptr = virtio_le_to_cpu((hdl).resp->src_member); \ } while (0) /* * read requests */ /** timeout for clock readings, where timeouts are considered non-fatal */ #define VIORTC_MSG_READ_TIMEOUT secs_to_jiffies(60) /** * viortc_read() - VIRTIO_RTC_REQ_READ wrapper * @viortc: device data * @vio_clk_id: virtio_rtc clock id * @reading: clock reading [ns] * * Context: Process context. * Return: Zero on success, negative error code otherwise. */ int viortc_read(struct viortc_dev *viortc, u16 vio_clk_id, u64 *reading) { VIORTC_DECLARE_MSG_HDL_ONSTACK(hdl, VIRTIO_RTC_REQ_READ, struct virtio_rtc_req_read, struct virtio_rtc_resp_read); int ret; ret = VIORTC_MSG_INIT(hdl, viortc); if (ret) return ret; VIORTC_MSG_WRITE(hdl, clock_id, &vio_clk_id); ret = viortc_msg_xfer(&viortc->vqs[VIORTC_REQUESTQ], VIORTC_MSG(hdl), VIORTC_MSG_READ_TIMEOUT); if (ret) { dev_dbg(&viortc->vdev->dev, "%s: xfer returned %d\n", __func__, ret); goto out_release; } VIORTC_MSG_READ(hdl, clock_reading, reading); out_release: viortc_msg_release(VIORTC_MSG(hdl)); return ret; } /** * viortc_read_cross() - VIRTIO_RTC_REQ_READ_CROSS wrapper * @viortc: device data * @vio_clk_id: virtio_rtc clock id * @hw_counter: virtio_rtc HW counter type * @reading: clock reading [ns] * @cycles: HW counter cycles during clock reading * * Context: Process context. * Return: Zero on success, negative error code otherwise. */ int viortc_read_cross(struct viortc_dev *viortc, u16 vio_clk_id, u8 hw_counter, u64 *reading, u64 *cycles) { VIORTC_DECLARE_MSG_HDL_ONSTACK(hdl, VIRTIO_RTC_REQ_READ_CROSS, struct virtio_rtc_req_read_cross, struct virtio_rtc_resp_read_cross); int ret; ret = VIORTC_MSG_INIT(hdl, viortc); if (ret) return ret; VIORTC_MSG_WRITE(hdl, clock_id, &vio_clk_id); VIORTC_MSG_WRITE(hdl, hw_counter, &hw_counter); ret = viortc_msg_xfer(&viortc->vqs[VIORTC_REQUESTQ], VIORTC_MSG(hdl), VIORTC_MSG_READ_TIMEOUT); if (ret) { dev_dbg(&viortc->vdev->dev, "%s: xfer returned %d\n", __func__, ret); goto out_release; } VIORTC_MSG_READ(hdl, clock_reading, reading); VIORTC_MSG_READ(hdl, counter_cycles, cycles); out_release: viortc_msg_release(VIORTC_MSG(hdl)); return ret; } /* * control requests */ /** * viortc_cfg() - VIRTIO_RTC_REQ_CFG wrapper * @viortc: device data * @num_clocks: # of virtio_rtc clocks * * Context: Process context. * Return: Zero on success, negative error code otherwise. */ static int viortc_cfg(struct viortc_dev *viortc, u16 *num_clocks) { VIORTC_DECLARE_MSG_HDL_ONSTACK(hdl, VIRTIO_RTC_REQ_CFG, struct virtio_rtc_req_cfg, struct virtio_rtc_resp_cfg); int ret; ret = VIORTC_MSG_INIT(hdl, viortc); if (ret) return ret; ret = viortc_msg_xfer(&viortc->vqs[VIORTC_REQUESTQ], VIORTC_MSG(hdl), 0); if (ret) { dev_dbg(&viortc->vdev->dev, "%s: xfer returned %d\n", __func__, ret); goto out_release; } VIORTC_MSG_READ(hdl, num_clocks, num_clocks); out_release: viortc_msg_release(VIORTC_MSG(hdl)); return ret; } /** * viortc_clock_cap() - VIRTIO_RTC_REQ_CLOCK_CAP wrapper * @viortc: device data * @vio_clk_id: virtio_rtc clock id * @type: virtio_rtc clock type * @leap_second_smearing: virtio_rtc smearing variant * @flags: struct virtio_rtc_resp_clock_cap.flags * * Context: Process context. * Return: Zero on success, negative error code otherwise. */ static int viortc_clock_cap(struct viortc_dev *viortc, u16 vio_clk_id, u8 *type, u8 *leap_second_smearing, u8 *flags) { VIORTC_DECLARE_MSG_HDL_ONSTACK(hdl, VIRTIO_RTC_REQ_CLOCK_CAP, struct virtio_rtc_req_clock_cap, struct virtio_rtc_resp_clock_cap); int ret; ret = VIORTC_MSG_INIT(hdl, viortc); if (ret) return ret; VIORTC_MSG_WRITE(hdl, clock_id, &vio_clk_id); ret = viortc_msg_xfer(&viortc->vqs[VIORTC_REQUESTQ], VIORTC_MSG(hdl), 0); if (ret) { dev_dbg(&viortc->vdev->dev, "%s: xfer returned %d\n", __func__, ret); goto out_release; } VIORTC_MSG_READ(hdl, type, type); VIORTC_MSG_READ(hdl, leap_second_smearing, leap_second_smearing); VIORTC_MSG_READ(hdl, flags, flags); out_release: viortc_msg_release(VIORTC_MSG(hdl)); return ret; } /** * viortc_cross_cap() - VIRTIO_RTC_REQ_CROSS_CAP wrapper * @viortc: device data * @vio_clk_id: virtio_rtc clock id * @hw_counter: virtio_rtc HW counter type * @supported: xtstamping is supported for the vio_clk_id/hw_counter pair * * Context: Process context. * Return: Zero on success, negative error code otherwise. */ int viortc_cross_cap(struct viortc_dev *viortc, u16 vio_clk_id, u8 hw_counter, bool *supported) { VIORTC_DECLARE_MSG_HDL_ONSTACK(hdl, VIRTIO_RTC_REQ_CROSS_CAP, struct virtio_rtc_req_cross_cap, struct virtio_rtc_resp_cross_cap); u8 flags; int ret; ret = VIORTC_MSG_INIT(hdl, viortc); if (ret) return ret; VIORTC_MSG_WRITE(hdl, clock_id, &vio_clk_id); VIORTC_MSG_WRITE(hdl, hw_counter, &hw_counter); ret = viortc_msg_xfer(&viortc->vqs[VIORTC_REQUESTQ], VIORTC_MSG(hdl), 0); if (ret) { dev_dbg(&viortc->vdev->dev, "%s: xfer returned %d\n", __func__, ret); goto out_release; } VIORTC_MSG_READ(hdl, flags, &flags); *supported = !!(flags & VIRTIO_RTC_FLAG_CROSS_CAP); out_release: viortc_msg_release(VIORTC_MSG(hdl)); return ret; } /** * viortc_read_alarm() - VIRTIO_RTC_REQ_READ_ALARM wrapper * @viortc: device data * @vio_clk_id: virtio_rtc clock id * @alarm_time: alarm time in ns * @enabled: whether alarm is enabled * * Context: Process context. * Return: Zero on success, negative error code otherwise. */ int viortc_read_alarm(struct viortc_dev *viortc, u16 vio_clk_id, u64 *alarm_time, bool *enabled) { VIORTC_DECLARE_MSG_HDL_ONSTACK(hdl, VIRTIO_RTC_REQ_READ_ALARM, struct virtio_rtc_req_read_alarm, struct virtio_rtc_resp_read_alarm); u8 flags; int ret; ret = VIORTC_MSG_INIT(hdl, viortc); if (ret) return ret; VIORTC_MSG_WRITE(hdl, clock_id, &vio_clk_id); ret = viortc_msg_xfer(&viortc->vqs[VIORTC_REQUESTQ], VIORTC_MSG(hdl), 0); if (ret) { dev_dbg(&viortc->vdev->dev, "%s: xfer returned %d\n", __func__, ret); goto out_release; } VIORTC_MSG_READ(hdl, alarm_time, alarm_time); VIORTC_MSG_READ(hdl, flags, &flags); *enabled = !!(flags & VIRTIO_RTC_FLAG_ALARM_ENABLED); out_release: viortc_msg_release(VIORTC_MSG(hdl)); return ret; } /** * viortc_set_alarm() - VIRTIO_RTC_REQ_SET_ALARM wrapper * @viortc: device data * @vio_clk_id: virtio_rtc clock id * @alarm_time: alarm time in ns * @alarm_enable: enable or disable alarm * * Context: Process context. * Return: Zero on success, negative error code otherwise. */ int viortc_set_alarm(struct viortc_dev *viortc, u16 vio_clk_id, u64 alarm_time, bool alarm_enable) { VIORTC_DECLARE_MSG_HDL_ONSTACK(hdl, VIRTIO_RTC_REQ_SET_ALARM, struct virtio_rtc_req_set_alarm, struct virtio_rtc_resp_set_alarm); u8 flags = 0; int ret; ret = VIORTC_MSG_INIT(hdl, viortc); if (ret) return ret; if (alarm_enable) flags |= VIRTIO_RTC_FLAG_ALARM_ENABLED; VIORTC_MSG_WRITE(hdl, clock_id, &vio_clk_id); VIORTC_MSG_WRITE(hdl, alarm_time, &alarm_time); VIORTC_MSG_WRITE(hdl, flags, &flags); ret = viortc_msg_xfer(&viortc->vqs[VIORTC_REQUESTQ], VIORTC_MSG(hdl), 0); if (ret) { dev_dbg(&viortc->vdev->dev, "%s: xfer returned %d\n", __func__, ret); goto out_release; } out_release: viortc_msg_release(VIORTC_MSG(hdl)); return ret; } /** * viortc_set_alarm_enabled() - VIRTIO_RTC_REQ_SET_ALARM_ENABLED wrapper * @viortc: device data * @vio_clk_id: virtio_rtc clock id * @alarm_enable: enable or disable alarm * * Context: Process context. * Return: Zero on success, negative error code otherwise. */ int viortc_set_alarm_enabled(struct viortc_dev *viortc, u16 vio_clk_id, bool alarm_enable) { VIORTC_DECLARE_MSG_HDL_ONSTACK(hdl, VIRTIO_RTC_REQ_SET_ALARM_ENABLED, struct virtio_rtc_req_set_alarm_enabled, struct virtio_rtc_resp_set_alarm_enabled); u8 flags = 0; int ret; ret = VIORTC_MSG_INIT(hdl, viortc); if (ret) return ret; if (alarm_enable) flags |= VIRTIO_RTC_FLAG_ALARM_ENABLED; VIORTC_MSG_WRITE(hdl, clock_id, &vio_clk_id); VIORTC_MSG_WRITE(hdl, flags, &flags); ret = viortc_msg_xfer(&viortc->vqs[VIORTC_REQUESTQ], VIORTC_MSG(hdl), 0); if (ret) { dev_dbg(&viortc->vdev->dev, "%s: xfer returned %d\n", __func__, ret); goto out_release; } out_release: viortc_msg_release(VIORTC_MSG(hdl)); return ret; } /* * init, deinit */ /** * viortc_init_rtc_class_clock() - init and register a RTC class device * @viortc: device data * @vio_clk_id: virtio_rtc clock id * @clock_type: virtio_rtc clock type * @flags: struct virtio_rtc_resp_clock_cap.flags * * The clock must be a UTC-like clock. * * Context: Process context. * Return: Positive if registered, zero if not supported by configuration, * negative error code otherwise. */ static int viortc_init_rtc_class_clock(struct viortc_dev *viortc, u16 vio_clk_id, u8 clock_type, u8 flags) { struct virtio_device *vdev = viortc->vdev; struct viortc_class *viortc_class; struct device *dev = &vdev->dev; bool have_alarm; if (clock_type != VIRTIO_RTC_CLOCK_UTC_SMEARED) { dev_info(dev, "not creating RTC class device for clock %d, which may step on leap seconds\n", vio_clk_id); return 0; } if (viortc->viortc_class) { dev_warn_once(dev, "multiple UTC-like clocks are present, but creating only one RTC class device\n"); return 0; } have_alarm = viortc_alarms_supported(vdev) && !!(flags & VIRTIO_RTC_FLAG_ALARM_CAP); viortc_class = viortc_class_init(viortc, vio_clk_id, have_alarm, dev); if (IS_ERR(viortc_class)) return PTR_ERR(viortc_class); viortc->viortc_class = viortc_class; if (have_alarm) devm_device_init_wakeup(dev); return viortc_class_register(viortc_class) ?: 1; } /** * viortc_init_ptp_clock() - init and register PTP clock * @viortc: device data * @vio_clk_id: virtio_rtc clock id * @clock_type: virtio_rtc clock type * @leap_second_smearing: virtio_rtc leap second smearing * * Context: Process context. * Return: Positive if registered, zero if not supported by configuration, * negative error code otherwise. */ static int viortc_init_ptp_clock(struct viortc_dev *viortc, u16 vio_clk_id, u8 clock_type, u8 leap_second_smearing) { struct device *dev = &viortc->vdev->dev; char ptp_clock_name[PTP_CLOCK_NAME_LEN]; struct viortc_ptp_clock *vio_ptp; snprintf(ptp_clock_name, PTP_CLOCK_NAME_LEN, "Virtio PTP type %hhu/variant %hhu", clock_type, leap_second_smearing); vio_ptp = viortc_ptp_register(viortc, dev, vio_clk_id, ptp_clock_name); if (IS_ERR(vio_ptp)) { dev_err(dev, "failed to register PTP clock '%s'\n", ptp_clock_name); return PTR_ERR(vio_ptp); } viortc->clocks_to_unregister[vio_clk_id] = vio_ptp; return !!vio_ptp; } /** * viortc_init_clock() - init local representation of virtio_rtc clock * @viortc: device data * @vio_clk_id: virtio_rtc clock id * * Initializes PHC and/or RTC class device to represent virtio_rtc clock. * * Context: Process context. * Return: Zero on success, negative error code otherwise. */ static int viortc_init_clock(struct viortc_dev *viortc, u16 vio_clk_id) { u8 clock_type, leap_second_smearing, flags; bool is_exposed = false; int ret; ret = viortc_clock_cap(viortc, vio_clk_id, &clock_type, &leap_second_smearing, &flags); if (ret) return ret; if (IS_ENABLED(CONFIG_VIRTIO_RTC_CLASS) && (clock_type == VIRTIO_RTC_CLOCK_UTC || clock_type == VIRTIO_RTC_CLOCK_UTC_SMEARED || clock_type == VIRTIO_RTC_CLOCK_UTC_MAYBE_SMEARED)) { ret = viortc_init_rtc_class_clock(viortc, vio_clk_id, clock_type, flags); if (ret < 0) return ret; if (ret > 0) is_exposed = true; } if (IS_ENABLED(CONFIG_VIRTIO_RTC_PTP)) { ret = viortc_init_ptp_clock(viortc, vio_clk_id, clock_type, leap_second_smearing); if (ret < 0) return ret; if (ret > 0) is_exposed = true; } if (!is_exposed) dev_warn(&viortc->vdev->dev, "cannot expose clock %d (type %d, variant %d) to userspace\n", vio_clk_id, clock_type, leap_second_smearing); return 0; } /** * viortc_clocks_deinit() - unregister PHCs, stop RTC ops * @viortc: device data */ static void viortc_clocks_deinit(struct viortc_dev *viortc) { struct viortc_ptp_clock *vio_ptp; unsigned int i; for (i = 0; i < viortc->num_clocks; i++) { vio_ptp = viortc->clocks_to_unregister[i]; if (!vio_ptp) continue; viortc->clocks_to_unregister[i] = NULL; WARN_ON(viortc_ptp_unregister(vio_ptp, &viortc->vdev->dev)); } if (viortc->viortc_class) viortc_class_stop(viortc->viortc_class); } /** * viortc_clocks_init() - init local representations of virtio_rtc clocks * @viortc: device data * * Context: Process context. * Return: Zero on success, negative error code otherwise. */ static int viortc_clocks_init(struct viortc_dev *viortc) { u16 num_clocks; unsigned int i; int ret; ret = viortc_cfg(viortc, &num_clocks); if (ret) return ret; if (num_clocks < 1) { dev_err(&viortc->vdev->dev, "device reported 0 clocks\n"); return -ENODEV; } viortc->num_clocks = num_clocks; viortc->clocks_to_unregister = devm_kcalloc(&viortc->vdev->dev, num_clocks, sizeof(*viortc->clocks_to_unregister), GFP_KERNEL); if (!viortc->clocks_to_unregister) return -ENOMEM; for (i = 0; i < num_clocks; i++) { ret = viortc_init_clock(viortc, i); if (ret) goto err_deinit_clocks; } return 0; err_deinit_clocks: viortc_clocks_deinit(viortc); return ret; } /** * viortc_populate_vq() - populate alarmq with device-writable buffers * @viortc: device data * @viortc_vq: device specific data for virtqueue * @buf_cap: device-writable buffer size in bytes * @lock: lock queue during accesses * * Populates the alarmq with pre-allocated buffers. * * The caller is responsible for kicking the device. * * Context: Process context. * Return: Zero on success, negative error code otherwise. */ static int viortc_populate_vq(struct viortc_dev *viortc, struct viortc_vq *viortc_vq, u32 buf_cap, bool lock) { unsigned int num_elems, i; struct virtqueue *vq; unsigned long flags; void *buf; int ret; num_elems = viortc->num_alarmq_bufs; vq = viortc_vq->vq; for (i = 0; i < num_elems; i++) { buf = viortc->alarmq_bufs[i]; if (lock) { spin_lock_irqsave(&viortc_vq->lock, flags); ret = viortc_feed_vq(viortc, vq, buf, buf_cap, buf); spin_unlock_irqrestore(&viortc_vq->lock, flags); } else { ret = viortc_feed_vq(viortc, vq, buf, buf_cap, buf); } if (ret) return ret; } return 0; } /** * viortc_alloc_vq_bufs() - allocate alarmq buffers * @viortc: device data * @num_elems: # of buffers * @buf_cap: per-buffer device-writable bytes * * Context: Process context. * Return: Zero on success, negative error code otherwise. */ static int viortc_alloc_vq_bufs(struct viortc_dev *viortc, unsigned int num_elems, u32 buf_cap) { struct device *dev = &viortc->vdev->dev; void **buf_list; unsigned int i; void *buf; buf_list = devm_kcalloc(dev, num_elems, sizeof(*buf_list), GFP_KERNEL); if (!buf_list) return -ENOMEM; viortc->alarmq_bufs = buf_list; viortc->num_alarmq_bufs = num_elems; for (i = 0; i < num_elems; i++) { buf = devm_kzalloc(dev, buf_cap, GFP_KERNEL); if (!buf) return -ENOMEM; buf_list[i] = buf; } return 0; } /** * viortc_init_vqs() - init virtqueues * @viortc: device data * * Inits virtqueues and associated data. * * Context: Process context. * Return: Zero on success, negative error code otherwise. */ static int viortc_init_vqs(struct viortc_dev *viortc) { struct virtqueue *vqs[VIORTC_MAX_NR_QUEUES]; struct virtqueue_info vqs_info[] = { { "requestq", viortc_cb_requestq }, { "alarmq", viortc_cb_alarmq }, }; struct virtio_device *vdev = viortc->vdev; unsigned int num_elems; int nr_queues, ret; bool have_alarms; 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) return ret; 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; } /** * viortc_probe() - probe a virtio_rtc virtio device * @vdev: virtio device * * Context: Process context. * Return: Zero on success, negative error code otherwise. */ static int viortc_probe(struct virtio_device *vdev) { struct viortc_vq *alarm_viortc_vq; struct virtqueue *alarm_vq; struct viortc_dev *viortc; unsigned long flags; bool notify; int ret; viortc = devm_kzalloc(&vdev->dev, sizeof(*viortc), GFP_KERNEL); if (!viortc) return -ENOMEM; vdev->priv = viortc; viortc->vdev = vdev; ret = viortc_init_vqs(viortc); if (ret) return ret; virtio_device_ready(vdev); ret = viortc_clocks_init(viortc); if (ret) goto err_reset_vdev; if (viortc_alarms_supported(vdev)) { alarm_viortc_vq = &viortc->vqs[VIORTC_ALARMQ]; alarm_vq = alarm_viortc_vq->vq; ret = viortc_populate_vq(viortc, alarm_viortc_vq, VIORTC_ALARMQ_BUF_CAP, true); if (ret) goto err_deinit_clocks; spin_lock_irqsave(&alarm_viortc_vq->lock, flags); notify = virtqueue_kick_prepare(alarm_vq); spin_unlock_irqrestore(&alarm_viortc_vq->lock, flags); if (notify && !virtqueue_notify(alarm_vq)) { ret = -EIO; goto err_deinit_clocks; } } return 0; err_deinit_clocks: viortc_clocks_deinit(viortc); err_reset_vdev: virtio_reset_device(vdev); vdev->config->del_vqs(vdev); return ret; } /** * viortc_remove() - remove a virtio_rtc virtio device * @vdev: virtio device */ static void viortc_remove(struct virtio_device *vdev) { struct viortc_dev *viortc = vdev->priv; viortc_clocks_deinit(viortc); virtio_reset_device(vdev); vdev->config->del_vqs(vdev); } static int viortc_freeze(struct virtio_device *dev) { /* * Do not reset the device, so that the device may still wake up the * system through an alarmq notification. */ return 0; } static int viortc_restore(struct virtio_device *dev) { struct viortc_dev *viortc = dev->priv; struct viortc_vq *alarm_viortc_vq; struct virtqueue *alarm_vq; bool notify = false; int ret; ret = viortc_init_vqs(viortc); if (ret) return ret; alarm_viortc_vq = &viortc->vqs[VIORTC_ALARMQ]; alarm_vq = alarm_viortc_vq->vq; if (viortc_alarms_supported(dev)) { ret = viortc_populate_vq(viortc, alarm_viortc_vq, VIORTC_ALARMQ_BUF_CAP, false); if (ret) return ret; notify = virtqueue_kick_prepare(alarm_vq); } virtio_device_ready(dev); if (notify && !virtqueue_notify(alarm_vq)) ret = -EIO; return ret; } static unsigned int features[] = { #if IS_ENABLED(CONFIG_VIRTIO_RTC_CLASS) VIRTIO_RTC_F_ALARM, #endif }; static struct virtio_device_id id_table[] = { { VIRTIO_ID_CLOCK, VIRTIO_DEV_ANY_ID }, { 0 }, }; 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); MODULE_DESCRIPTION("Virtio RTC driver"); MODULE_AUTHOR("Qualcomm Innovation Center, Inc."); MODULE_LICENSE("GPL");