1408 lines
35 KiB
C
1408 lines
35 KiB
C
// 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 <linux/completion.h>
|
|
#include <linux/device.h>
|
|
#include <linux/module.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/virtio.h>
|
|
#include <linux/virtio_config.h>
|
|
#include <linux/virtio_ids.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,
|
|
};
|
|
|
|
/**
|
|
* 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");
|