1419 lines · 35.1 KB · C
RawDownload
// 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 = {                                                              \

Lines 1–500 of 1419.