linux/sound/virtio/virtio_ctl_msg.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * virtio-snd: Virtio sound device
   4 * Copyright (C) 2021 OpenSynergy GmbH
   5 */
   6#include <linux/moduleparam.h>
   7#include <linux/virtio_config.h>
   8
   9#include "virtio_card.h"
  10
  11/**
  12 * struct virtio_snd_msg - Control message.
  13 * @sg_request: Scattergather list containing a device request (header).
  14 * @sg_response: Scattergather list containing a device response (status).
  15 * @list: Pending message list entry.
  16 * @notify: Request completed notification.
  17 * @ref_count: Reference count used to manage a message lifetime.
  18 */
  19struct virtio_snd_msg {
  20        struct scatterlist sg_request;
  21        struct scatterlist sg_response;
  22        struct list_head list;
  23        struct completion notify;
  24        refcount_t ref_count;
  25};
  26
  27/**
  28 * virtsnd_ctl_msg_ref() - Increment reference counter for the message.
  29 * @msg: Control message.
  30 *
  31 * Context: Any context.
  32 */
  33void virtsnd_ctl_msg_ref(struct virtio_snd_msg *msg)
  34{
  35        refcount_inc(&msg->ref_count);
  36}
  37
  38/**
  39 * virtsnd_ctl_msg_unref() - Decrement reference counter for the message.
  40 * @msg: Control message.
  41 *
  42 * The message will be freed when the ref_count value is 0.
  43 *
  44 * Context: Any context.
  45 */
  46void virtsnd_ctl_msg_unref(struct virtio_snd_msg *msg)
  47{
  48        if (refcount_dec_and_test(&msg->ref_count))
  49                kfree(msg);
  50}
  51
  52/**
  53 * virtsnd_ctl_msg_request() - Get a pointer to the request header.
  54 * @msg: Control message.
  55 *
  56 * Context: Any context.
  57 */
  58void *virtsnd_ctl_msg_request(struct virtio_snd_msg *msg)
  59{
  60        return sg_virt(&msg->sg_request);
  61}
  62
  63/**
  64 * virtsnd_ctl_msg_response() - Get a pointer to the response header.
  65 * @msg: Control message.
  66 *
  67 * Context: Any context.
  68 */
  69void *virtsnd_ctl_msg_response(struct virtio_snd_msg *msg)
  70{
  71        return sg_virt(&msg->sg_response);
  72}
  73
  74/**
  75 * virtsnd_ctl_msg_alloc() - Allocate and initialize a control message.
  76 * @request_size: Size of request header.
  77 * @response_size: Size of response header.
  78 * @gfp: Kernel flags for memory allocation.
  79 *
  80 * The message will be automatically freed when the ref_count value is 0.
  81 *
  82 * Context: Any context. May sleep if @gfp flags permit.
  83 * Return: Allocated message on success, NULL on failure.
  84 */
  85struct virtio_snd_msg *virtsnd_ctl_msg_alloc(size_t request_size,
  86                                             size_t response_size, gfp_t gfp)
  87{
  88        struct virtio_snd_msg *msg;
  89
  90        if (!request_size || !response_size)
  91                return NULL;
  92
  93        msg = kzalloc(sizeof(*msg) + request_size + response_size, gfp);
  94        if (!msg)
  95                return NULL;
  96
  97        sg_init_one(&msg->sg_request, (u8 *)msg + sizeof(*msg), request_size);
  98        sg_init_one(&msg->sg_response, (u8 *)msg + sizeof(*msg) + request_size,
  99                    response_size);
 100
 101        INIT_LIST_HEAD(&msg->list);
 102        init_completion(&msg->notify);
 103        /* This reference is dropped in virtsnd_ctl_msg_complete(). */
 104        refcount_set(&msg->ref_count, 1);
 105
 106        return msg;
 107}
 108
 109/**
 110 * virtsnd_ctl_msg_send() - Send a control message.
 111 * @snd: VirtIO sound device.
 112 * @msg: Control message.
 113 * @out_sgs: Additional sg-list to attach to the request header (may be NULL).
 114 * @in_sgs: Additional sg-list to attach to the response header (may be NULL).
 115 * @nowait: Flag indicating whether to wait for completion.
 116 *
 117 * Context: Any context. Takes and releases the control queue spinlock.
 118 *          May sleep if @nowait is false.
 119 * Return: 0 on success, -errno on failure.
 120 */
 121int virtsnd_ctl_msg_send(struct virtio_snd *snd, struct virtio_snd_msg *msg,
 122                         struct scatterlist *out_sgs,
 123                         struct scatterlist *in_sgs, bool nowait)
 124{
 125        struct virtio_device *vdev = snd->vdev;
 126        struct virtio_snd_queue *queue = virtsnd_control_queue(snd);
 127        unsigned int js = msecs_to_jiffies(virtsnd_msg_timeout_ms);
 128        struct virtio_snd_hdr *request = virtsnd_ctl_msg_request(msg);
 129        struct virtio_snd_hdr *response = virtsnd_ctl_msg_response(msg);
 130        unsigned int nouts = 0;
 131        unsigned int nins = 0;
 132        struct scatterlist *psgs[4];
 133        bool notify = false;
 134        unsigned long flags;
 135        int rc;
 136
 137        virtsnd_ctl_msg_ref(msg);
 138
 139        /* Set the default status in case the message was canceled. */
 140        response->code = cpu_to_le32(VIRTIO_SND_S_IO_ERR);
 141
 142        psgs[nouts++] = &msg->sg_request;
 143        if (out_sgs)
 144                psgs[nouts++] = out_sgs;
 145
 146        psgs[nouts + nins++] = &msg->sg_response;
 147        if (in_sgs)
 148                psgs[nouts + nins++] = in_sgs;
 149
 150        spin_lock_irqsave(&queue->lock, flags);
 151        rc = virtqueue_add_sgs(queue->vqueue, psgs, nouts, nins, msg,
 152                               GFP_ATOMIC);
 153        if (!rc) {
 154                notify = virtqueue_kick_prepare(queue->vqueue);
 155
 156                list_add_tail(&msg->list, &snd->ctl_msgs);
 157        }
 158        spin_unlock_irqrestore(&queue->lock, flags);
 159
 160        if (rc) {
 161                dev_err(&vdev->dev, "failed to send control message (0x%08x)\n",
 162                        le32_to_cpu(request->code));
 163
 164                /*
 165                 * Since in this case virtsnd_ctl_msg_complete() will not be
 166                 * called, it is necessary to decrement the reference count.
 167                 */
 168                virtsnd_ctl_msg_unref(msg);
 169
 170                goto on_exit;
 171        }
 172
 173        if (notify)
 174                virtqueue_notify(queue->vqueue);
 175
 176        if (nowait)
 177                goto on_exit;
 178
 179        rc = wait_for_completion_interruptible_timeout(&msg->notify, js);
 180        if (rc <= 0) {
 181                if (!rc) {
 182                        dev_err(&vdev->dev,
 183                                "control message (0x%08x) timeout\n",
 184                                le32_to_cpu(request->code));
 185                        rc = -ETIMEDOUT;
 186                }
 187
 188                goto on_exit;
 189        }
 190
 191        switch (le32_to_cpu(response->code)) {
 192        case VIRTIO_SND_S_OK:
 193                rc = 0;
 194                break;
 195        case VIRTIO_SND_S_NOT_SUPP:
 196                rc = -EOPNOTSUPP;
 197                break;
 198        case VIRTIO_SND_S_IO_ERR:
 199                rc = -EIO;
 200                break;
 201        default:
 202                rc = -EINVAL;
 203                break;
 204        }
 205
 206on_exit:
 207        virtsnd_ctl_msg_unref(msg);
 208
 209        return rc;
 210}
 211
 212/**
 213 * virtsnd_ctl_msg_complete() - Complete a control message.
 214 * @msg: Control message.
 215 *
 216 * Context: Any context. Expects the control queue spinlock to be held by
 217 *          caller.
 218 */
 219void virtsnd_ctl_msg_complete(struct virtio_snd_msg *msg)
 220{
 221        list_del(&msg->list);
 222        complete(&msg->notify);
 223
 224        virtsnd_ctl_msg_unref(msg);
 225}
 226
 227/**
 228 * virtsnd_ctl_msg_cancel_all() - Cancel all pending control messages.
 229 * @snd: VirtIO sound device.
 230 *
 231 * Context: Any context.
 232 */
 233void virtsnd_ctl_msg_cancel_all(struct virtio_snd *snd)
 234{
 235        struct virtio_snd_queue *queue = virtsnd_control_queue(snd);
 236        unsigned long flags;
 237
 238        spin_lock_irqsave(&queue->lock, flags);
 239        while (!list_empty(&snd->ctl_msgs)) {
 240                struct virtio_snd_msg *msg =
 241                        list_first_entry(&snd->ctl_msgs, struct virtio_snd_msg,
 242                                         list);
 243
 244                virtsnd_ctl_msg_complete(msg);
 245        }
 246        spin_unlock_irqrestore(&queue->lock, flags);
 247}
 248
 249/**
 250 * virtsnd_ctl_query_info() - Query the item configuration from the device.
 251 * @snd: VirtIO sound device.
 252 * @command: Control request code (VIRTIO_SND_R_XXX_INFO).
 253 * @start_id: Item start identifier.
 254 * @count: Item count to query.
 255 * @size: Item information size in bytes.
 256 * @info: Buffer for storing item information.
 257 *
 258 * Context: Any context that permits to sleep.
 259 * Return: 0 on success, -errno on failure.
 260 */
 261int virtsnd_ctl_query_info(struct virtio_snd *snd, int command, int start_id,
 262                           int count, size_t size, void *info)
 263{
 264        struct virtio_snd_msg *msg;
 265        struct virtio_snd_query_info *query;
 266        struct scatterlist sg;
 267
 268        msg = virtsnd_ctl_msg_alloc(sizeof(*query),
 269                                    sizeof(struct virtio_snd_hdr), GFP_KERNEL);
 270        if (!msg)
 271                return -ENOMEM;
 272
 273        query = virtsnd_ctl_msg_request(msg);
 274        query->hdr.code = cpu_to_le32(command);
 275        query->start_id = cpu_to_le32(start_id);
 276        query->count = cpu_to_le32(count);
 277        query->size = cpu_to_le32(size);
 278
 279        sg_init_one(&sg, info, count * size);
 280
 281        return virtsnd_ctl_msg_send(snd, msg, NULL, &sg, false);
 282}
 283
 284/**
 285 * virtsnd_ctl_notify_cb() - Process all completed control messages.
 286 * @vqueue: Underlying control virtqueue.
 287 *
 288 * This callback function is called upon a vring interrupt request from the
 289 * device.
 290 *
 291 * Context: Interrupt context. Takes and releases the control queue spinlock.
 292 */
 293void virtsnd_ctl_notify_cb(struct virtqueue *vqueue)
 294{
 295        struct virtio_snd *snd = vqueue->vdev->priv;
 296        struct virtio_snd_queue *queue = virtsnd_control_queue(snd);
 297        struct virtio_snd_msg *msg;
 298        u32 length;
 299        unsigned long flags;
 300
 301        spin_lock_irqsave(&queue->lock, flags);
 302        do {
 303                virtqueue_disable_cb(vqueue);
 304                while ((msg = virtqueue_get_buf(vqueue, &length)))
 305                        virtsnd_ctl_msg_complete(msg);
 306                if (unlikely(virtqueue_is_broken(vqueue)))
 307                        break;
 308        } while (!virtqueue_enable_cb(vqueue));
 309        spin_unlock_irqrestore(&queue->lock, flags);
 310}
 311