162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * virtio-snd: Virtio sound device 462306a36Sopenharmony_ci * Copyright (C) 2021 OpenSynergy GmbH 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci#include <linux/moduleparam.h> 762306a36Sopenharmony_ci#include <linux/virtio_config.h> 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include "virtio_card.h" 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci/** 1262306a36Sopenharmony_ci * struct virtio_snd_msg - Control message. 1362306a36Sopenharmony_ci * @sg_request: Scattergather list containing a device request (header). 1462306a36Sopenharmony_ci * @sg_response: Scattergather list containing a device response (status). 1562306a36Sopenharmony_ci * @list: Pending message list entry. 1662306a36Sopenharmony_ci * @notify: Request completed notification. 1762306a36Sopenharmony_ci * @ref_count: Reference count used to manage a message lifetime. 1862306a36Sopenharmony_ci */ 1962306a36Sopenharmony_cistruct virtio_snd_msg { 2062306a36Sopenharmony_ci struct scatterlist sg_request; 2162306a36Sopenharmony_ci struct scatterlist sg_response; 2262306a36Sopenharmony_ci struct list_head list; 2362306a36Sopenharmony_ci struct completion notify; 2462306a36Sopenharmony_ci refcount_t ref_count; 2562306a36Sopenharmony_ci}; 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci/** 2862306a36Sopenharmony_ci * virtsnd_ctl_msg_ref() - Increment reference counter for the message. 2962306a36Sopenharmony_ci * @msg: Control message. 3062306a36Sopenharmony_ci * 3162306a36Sopenharmony_ci * Context: Any context. 3262306a36Sopenharmony_ci */ 3362306a36Sopenharmony_civoid virtsnd_ctl_msg_ref(struct virtio_snd_msg *msg) 3462306a36Sopenharmony_ci{ 3562306a36Sopenharmony_ci refcount_inc(&msg->ref_count); 3662306a36Sopenharmony_ci} 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci/** 3962306a36Sopenharmony_ci * virtsnd_ctl_msg_unref() - Decrement reference counter for the message. 4062306a36Sopenharmony_ci * @msg: Control message. 4162306a36Sopenharmony_ci * 4262306a36Sopenharmony_ci * The message will be freed when the ref_count value is 0. 4362306a36Sopenharmony_ci * 4462306a36Sopenharmony_ci * Context: Any context. 4562306a36Sopenharmony_ci */ 4662306a36Sopenharmony_civoid virtsnd_ctl_msg_unref(struct virtio_snd_msg *msg) 4762306a36Sopenharmony_ci{ 4862306a36Sopenharmony_ci if (refcount_dec_and_test(&msg->ref_count)) 4962306a36Sopenharmony_ci kfree(msg); 5062306a36Sopenharmony_ci} 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci/** 5362306a36Sopenharmony_ci * virtsnd_ctl_msg_request() - Get a pointer to the request header. 5462306a36Sopenharmony_ci * @msg: Control message. 5562306a36Sopenharmony_ci * 5662306a36Sopenharmony_ci * Context: Any context. 5762306a36Sopenharmony_ci */ 5862306a36Sopenharmony_civoid *virtsnd_ctl_msg_request(struct virtio_snd_msg *msg) 5962306a36Sopenharmony_ci{ 6062306a36Sopenharmony_ci return sg_virt(&msg->sg_request); 6162306a36Sopenharmony_ci} 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci/** 6462306a36Sopenharmony_ci * virtsnd_ctl_msg_response() - Get a pointer to the response header. 6562306a36Sopenharmony_ci * @msg: Control message. 6662306a36Sopenharmony_ci * 6762306a36Sopenharmony_ci * Context: Any context. 6862306a36Sopenharmony_ci */ 6962306a36Sopenharmony_civoid *virtsnd_ctl_msg_response(struct virtio_snd_msg *msg) 7062306a36Sopenharmony_ci{ 7162306a36Sopenharmony_ci return sg_virt(&msg->sg_response); 7262306a36Sopenharmony_ci} 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci/** 7562306a36Sopenharmony_ci * virtsnd_ctl_msg_alloc() - Allocate and initialize a control message. 7662306a36Sopenharmony_ci * @request_size: Size of request header. 7762306a36Sopenharmony_ci * @response_size: Size of response header. 7862306a36Sopenharmony_ci * @gfp: Kernel flags for memory allocation. 7962306a36Sopenharmony_ci * 8062306a36Sopenharmony_ci * The message will be automatically freed when the ref_count value is 0. 8162306a36Sopenharmony_ci * 8262306a36Sopenharmony_ci * Context: Any context. May sleep if @gfp flags permit. 8362306a36Sopenharmony_ci * Return: Allocated message on success, NULL on failure. 8462306a36Sopenharmony_ci */ 8562306a36Sopenharmony_cistruct virtio_snd_msg *virtsnd_ctl_msg_alloc(size_t request_size, 8662306a36Sopenharmony_ci size_t response_size, gfp_t gfp) 8762306a36Sopenharmony_ci{ 8862306a36Sopenharmony_ci struct virtio_snd_msg *msg; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci if (!request_size || !response_size) 9162306a36Sopenharmony_ci return NULL; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci msg = kzalloc(sizeof(*msg) + request_size + response_size, gfp); 9462306a36Sopenharmony_ci if (!msg) 9562306a36Sopenharmony_ci return NULL; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci sg_init_one(&msg->sg_request, (u8 *)msg + sizeof(*msg), request_size); 9862306a36Sopenharmony_ci sg_init_one(&msg->sg_response, (u8 *)msg + sizeof(*msg) + request_size, 9962306a36Sopenharmony_ci response_size); 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci INIT_LIST_HEAD(&msg->list); 10262306a36Sopenharmony_ci init_completion(&msg->notify); 10362306a36Sopenharmony_ci /* This reference is dropped in virtsnd_ctl_msg_complete(). */ 10462306a36Sopenharmony_ci refcount_set(&msg->ref_count, 1); 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci return msg; 10762306a36Sopenharmony_ci} 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci/** 11062306a36Sopenharmony_ci * virtsnd_ctl_msg_send() - Send a control message. 11162306a36Sopenharmony_ci * @snd: VirtIO sound device. 11262306a36Sopenharmony_ci * @msg: Control message. 11362306a36Sopenharmony_ci * @out_sgs: Additional sg-list to attach to the request header (may be NULL). 11462306a36Sopenharmony_ci * @in_sgs: Additional sg-list to attach to the response header (may be NULL). 11562306a36Sopenharmony_ci * @nowait: Flag indicating whether to wait for completion. 11662306a36Sopenharmony_ci * 11762306a36Sopenharmony_ci * Context: Any context. Takes and releases the control queue spinlock. 11862306a36Sopenharmony_ci * May sleep if @nowait is false. 11962306a36Sopenharmony_ci * Return: 0 on success, -errno on failure. 12062306a36Sopenharmony_ci */ 12162306a36Sopenharmony_ciint virtsnd_ctl_msg_send(struct virtio_snd *snd, struct virtio_snd_msg *msg, 12262306a36Sopenharmony_ci struct scatterlist *out_sgs, 12362306a36Sopenharmony_ci struct scatterlist *in_sgs, bool nowait) 12462306a36Sopenharmony_ci{ 12562306a36Sopenharmony_ci struct virtio_device *vdev = snd->vdev; 12662306a36Sopenharmony_ci struct virtio_snd_queue *queue = virtsnd_control_queue(snd); 12762306a36Sopenharmony_ci unsigned int js = msecs_to_jiffies(virtsnd_msg_timeout_ms); 12862306a36Sopenharmony_ci struct virtio_snd_hdr *request = virtsnd_ctl_msg_request(msg); 12962306a36Sopenharmony_ci struct virtio_snd_hdr *response = virtsnd_ctl_msg_response(msg); 13062306a36Sopenharmony_ci unsigned int nouts = 0; 13162306a36Sopenharmony_ci unsigned int nins = 0; 13262306a36Sopenharmony_ci struct scatterlist *psgs[4]; 13362306a36Sopenharmony_ci bool notify = false; 13462306a36Sopenharmony_ci unsigned long flags; 13562306a36Sopenharmony_ci int rc; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci virtsnd_ctl_msg_ref(msg); 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci /* Set the default status in case the message was canceled. */ 14062306a36Sopenharmony_ci response->code = cpu_to_le32(VIRTIO_SND_S_IO_ERR); 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci psgs[nouts++] = &msg->sg_request; 14362306a36Sopenharmony_ci if (out_sgs) 14462306a36Sopenharmony_ci psgs[nouts++] = out_sgs; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci psgs[nouts + nins++] = &msg->sg_response; 14762306a36Sopenharmony_ci if (in_sgs) 14862306a36Sopenharmony_ci psgs[nouts + nins++] = in_sgs; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci spin_lock_irqsave(&queue->lock, flags); 15162306a36Sopenharmony_ci rc = virtqueue_add_sgs(queue->vqueue, psgs, nouts, nins, msg, 15262306a36Sopenharmony_ci GFP_ATOMIC); 15362306a36Sopenharmony_ci if (!rc) { 15462306a36Sopenharmony_ci notify = virtqueue_kick_prepare(queue->vqueue); 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci list_add_tail(&msg->list, &snd->ctl_msgs); 15762306a36Sopenharmony_ci } 15862306a36Sopenharmony_ci spin_unlock_irqrestore(&queue->lock, flags); 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci if (rc) { 16162306a36Sopenharmony_ci dev_err(&vdev->dev, "failed to send control message (0x%08x)\n", 16262306a36Sopenharmony_ci le32_to_cpu(request->code)); 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci /* 16562306a36Sopenharmony_ci * Since in this case virtsnd_ctl_msg_complete() will not be 16662306a36Sopenharmony_ci * called, it is necessary to decrement the reference count. 16762306a36Sopenharmony_ci */ 16862306a36Sopenharmony_ci virtsnd_ctl_msg_unref(msg); 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci goto on_exit; 17162306a36Sopenharmony_ci } 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci if (notify) 17462306a36Sopenharmony_ci virtqueue_notify(queue->vqueue); 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci if (nowait) 17762306a36Sopenharmony_ci goto on_exit; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci rc = wait_for_completion_interruptible_timeout(&msg->notify, js); 18062306a36Sopenharmony_ci if (rc <= 0) { 18162306a36Sopenharmony_ci if (!rc) { 18262306a36Sopenharmony_ci dev_err(&vdev->dev, 18362306a36Sopenharmony_ci "control message (0x%08x) timeout\n", 18462306a36Sopenharmony_ci le32_to_cpu(request->code)); 18562306a36Sopenharmony_ci rc = -ETIMEDOUT; 18662306a36Sopenharmony_ci } 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci goto on_exit; 18962306a36Sopenharmony_ci } 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci switch (le32_to_cpu(response->code)) { 19262306a36Sopenharmony_ci case VIRTIO_SND_S_OK: 19362306a36Sopenharmony_ci rc = 0; 19462306a36Sopenharmony_ci break; 19562306a36Sopenharmony_ci case VIRTIO_SND_S_NOT_SUPP: 19662306a36Sopenharmony_ci rc = -EOPNOTSUPP; 19762306a36Sopenharmony_ci break; 19862306a36Sopenharmony_ci case VIRTIO_SND_S_IO_ERR: 19962306a36Sopenharmony_ci rc = -EIO; 20062306a36Sopenharmony_ci break; 20162306a36Sopenharmony_ci default: 20262306a36Sopenharmony_ci rc = -EINVAL; 20362306a36Sopenharmony_ci break; 20462306a36Sopenharmony_ci } 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_cion_exit: 20762306a36Sopenharmony_ci virtsnd_ctl_msg_unref(msg); 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci return rc; 21062306a36Sopenharmony_ci} 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci/** 21362306a36Sopenharmony_ci * virtsnd_ctl_msg_complete() - Complete a control message. 21462306a36Sopenharmony_ci * @msg: Control message. 21562306a36Sopenharmony_ci * 21662306a36Sopenharmony_ci * Context: Any context. Expects the control queue spinlock to be held by 21762306a36Sopenharmony_ci * caller. 21862306a36Sopenharmony_ci */ 21962306a36Sopenharmony_civoid virtsnd_ctl_msg_complete(struct virtio_snd_msg *msg) 22062306a36Sopenharmony_ci{ 22162306a36Sopenharmony_ci list_del(&msg->list); 22262306a36Sopenharmony_ci complete(&msg->notify); 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci virtsnd_ctl_msg_unref(msg); 22562306a36Sopenharmony_ci} 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci/** 22862306a36Sopenharmony_ci * virtsnd_ctl_msg_cancel_all() - Cancel all pending control messages. 22962306a36Sopenharmony_ci * @snd: VirtIO sound device. 23062306a36Sopenharmony_ci * 23162306a36Sopenharmony_ci * Context: Any context. 23262306a36Sopenharmony_ci */ 23362306a36Sopenharmony_civoid virtsnd_ctl_msg_cancel_all(struct virtio_snd *snd) 23462306a36Sopenharmony_ci{ 23562306a36Sopenharmony_ci struct virtio_snd_queue *queue = virtsnd_control_queue(snd); 23662306a36Sopenharmony_ci unsigned long flags; 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci spin_lock_irqsave(&queue->lock, flags); 23962306a36Sopenharmony_ci while (!list_empty(&snd->ctl_msgs)) { 24062306a36Sopenharmony_ci struct virtio_snd_msg *msg = 24162306a36Sopenharmony_ci list_first_entry(&snd->ctl_msgs, struct virtio_snd_msg, 24262306a36Sopenharmony_ci list); 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci virtsnd_ctl_msg_complete(msg); 24562306a36Sopenharmony_ci } 24662306a36Sopenharmony_ci spin_unlock_irqrestore(&queue->lock, flags); 24762306a36Sopenharmony_ci} 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci/** 25062306a36Sopenharmony_ci * virtsnd_ctl_query_info() - Query the item configuration from the device. 25162306a36Sopenharmony_ci * @snd: VirtIO sound device. 25262306a36Sopenharmony_ci * @command: Control request code (VIRTIO_SND_R_XXX_INFO). 25362306a36Sopenharmony_ci * @start_id: Item start identifier. 25462306a36Sopenharmony_ci * @count: Item count to query. 25562306a36Sopenharmony_ci * @size: Item information size in bytes. 25662306a36Sopenharmony_ci * @info: Buffer for storing item information. 25762306a36Sopenharmony_ci * 25862306a36Sopenharmony_ci * Context: Any context that permits to sleep. 25962306a36Sopenharmony_ci * Return: 0 on success, -errno on failure. 26062306a36Sopenharmony_ci */ 26162306a36Sopenharmony_ciint virtsnd_ctl_query_info(struct virtio_snd *snd, int command, int start_id, 26262306a36Sopenharmony_ci int count, size_t size, void *info) 26362306a36Sopenharmony_ci{ 26462306a36Sopenharmony_ci struct virtio_snd_msg *msg; 26562306a36Sopenharmony_ci struct virtio_snd_query_info *query; 26662306a36Sopenharmony_ci struct scatterlist sg; 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci msg = virtsnd_ctl_msg_alloc(sizeof(*query), 26962306a36Sopenharmony_ci sizeof(struct virtio_snd_hdr), GFP_KERNEL); 27062306a36Sopenharmony_ci if (!msg) 27162306a36Sopenharmony_ci return -ENOMEM; 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci query = virtsnd_ctl_msg_request(msg); 27462306a36Sopenharmony_ci query->hdr.code = cpu_to_le32(command); 27562306a36Sopenharmony_ci query->start_id = cpu_to_le32(start_id); 27662306a36Sopenharmony_ci query->count = cpu_to_le32(count); 27762306a36Sopenharmony_ci query->size = cpu_to_le32(size); 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci sg_init_one(&sg, info, count * size); 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci return virtsnd_ctl_msg_send(snd, msg, NULL, &sg, false); 28262306a36Sopenharmony_ci} 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci/** 28562306a36Sopenharmony_ci * virtsnd_ctl_notify_cb() - Process all completed control messages. 28662306a36Sopenharmony_ci * @vqueue: Underlying control virtqueue. 28762306a36Sopenharmony_ci * 28862306a36Sopenharmony_ci * This callback function is called upon a vring interrupt request from the 28962306a36Sopenharmony_ci * device. 29062306a36Sopenharmony_ci * 29162306a36Sopenharmony_ci * Context: Interrupt context. Takes and releases the control queue spinlock. 29262306a36Sopenharmony_ci */ 29362306a36Sopenharmony_civoid virtsnd_ctl_notify_cb(struct virtqueue *vqueue) 29462306a36Sopenharmony_ci{ 29562306a36Sopenharmony_ci struct virtio_snd *snd = vqueue->vdev->priv; 29662306a36Sopenharmony_ci struct virtio_snd_queue *queue = virtsnd_control_queue(snd); 29762306a36Sopenharmony_ci struct virtio_snd_msg *msg; 29862306a36Sopenharmony_ci u32 length; 29962306a36Sopenharmony_ci unsigned long flags; 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci spin_lock_irqsave(&queue->lock, flags); 30262306a36Sopenharmony_ci do { 30362306a36Sopenharmony_ci virtqueue_disable_cb(vqueue); 30462306a36Sopenharmony_ci while ((msg = virtqueue_get_buf(vqueue, &length))) 30562306a36Sopenharmony_ci virtsnd_ctl_msg_complete(msg); 30662306a36Sopenharmony_ci if (unlikely(virtqueue_is_broken(vqueue))) 30762306a36Sopenharmony_ci break; 30862306a36Sopenharmony_ci } while (!virtqueue_enable_cb(vqueue)); 30962306a36Sopenharmony_ci spin_unlock_irqrestore(&queue->lock, flags); 31062306a36Sopenharmony_ci} 311