summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGreg Kroah-Hartman <gregkh@google.com>2021-09-24 16:16:23 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2021-09-24 16:16:23 +0000
commit0ae1cf21e2192e23141539e565644a9841bf737a (patch)
tree217bfac655aa0d5334426b4c9f8c737d689a7b63
parent8190771d07e545070f8ad4d2af03473182ce405e (diff)
parentb9c7067933d35da686607575b3f8da5ba6220d4b (diff)
downloadvirtual-device-0ae1cf21e2192e23141539e565644a9841bf737a.tar.gz
Merge "Add virtio-snd" into android12-5.10
-rw-r--r--Kbuild2
-rw-r--r--uapi/linux/virtio_snd.h361
-rw-r--r--virtio_snd/Kbuild16
-rw-r--r--virtio_snd/virtio_card.c472
-rw-r--r--virtio_snd/virtio_card.h152
-rw-r--r--virtio_snd/virtio_chmap.c252
-rw-r--r--virtio_snd/virtio_ctl_msg.c206
-rw-r--r--virtio_snd/virtio_ctl_msg.h75
-rw-r--r--virtio_snd/virtio_dc.c377
-rw-r--r--virtio_snd/virtio_event.c127
-rw-r--r--virtio_snd/virtio_jack.c263
-rw-r--r--virtio_snd/virtio_opsy.h31
-rw-r--r--virtio_snd/virtio_opsy_ctl_msg.c156
-rw-r--r--virtio_snd/virtio_pcm.c620
-rw-r--r--virtio_snd/virtio_pcm.h125
-rw-r--r--virtio_snd/virtio_pcm_msg.c256
-rw-r--r--virtio_snd/virtio_pcm_ops.c385
-rw-r--r--virtio_snd/virtio_snd.h479
18 files changed, 4355 insertions, 0 deletions
diff --git a/Kbuild b/Kbuild
index 5f4a61a..060ac1e 100644
--- a/Kbuild
+++ b/Kbuild
@@ -1,5 +1,7 @@
obj-m += virtio_gpu/
+obj-m += virtio_snd/
+
obj-$(CONFIG_VIRT_WIFI) += wlan_simulation/
obj-${BUILD_GOLDFISH_DRIVERS} += goldfish_drivers/
diff --git a/uapi/linux/virtio_snd.h b/uapi/linux/virtio_snd.h
new file mode 100644
index 0000000..1ff6310
--- /dev/null
+++ b/uapi/linux/virtio_snd.h
@@ -0,0 +1,361 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/*
+ * Copyright (C) 2020 OpenSynergy GmbH
+ *
+ * This header is BSD licensed so anyone can use the definitions to
+ * implement compatible drivers/servers.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of OpenSynergy GmbH nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL IBM OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#ifndef VIRTIO_SND_IF_H
+#define VIRTIO_SND_IF_H
+
+#include <linux/virtio_types.h>
+
+/*******************************************************************************
+ * CONFIGURATION SPACE
+ */
+struct virtio_snd_config {
+ /* # of available physical jacks */
+ __le32 jacks;
+ /* # of available PCM streams */
+ __le32 streams;
+ /* # of available channel maps */
+ __le32 chmaps;
+};
+
+enum {
+ /* device virtqueue indexes */
+ VIRTIO_SND_VQ_CONTROL = 0,
+ VIRTIO_SND_VQ_EVENT,
+ VIRTIO_SND_VQ_TX,
+ VIRTIO_SND_VQ_RX,
+ /* # of device virtqueues */
+ VIRTIO_SND_VQ_MAX
+};
+
+/*******************************************************************************
+ * COMMON DEFINITIONS
+ */
+
+/* supported dataflow directions */
+enum {
+ VIRTIO_SND_D_OUTPUT = 0,
+ VIRTIO_SND_D_INPUT
+};
+
+enum {
+ /* jack control request types */
+ VIRTIO_SND_R_JACK_INFO = 1,
+ VIRTIO_SND_R_JACK_REMAP,
+
+ /* PCM control request types */
+ VIRTIO_SND_R_PCM_INFO = 0x0100,
+ VIRTIO_SND_R_PCM_SET_PARAMS,
+ VIRTIO_SND_R_PCM_PREPARE,
+ VIRTIO_SND_R_PCM_RELEASE,
+ VIRTIO_SND_R_PCM_START,
+ VIRTIO_SND_R_PCM_STOP,
+
+ /* channel map control request types */
+ VIRTIO_SND_R_CHMAP_INFO = 0x0200,
+
+ /* jack event types */
+ VIRTIO_SND_EVT_JACK_CONNECTED = 0x1000,
+ VIRTIO_SND_EVT_JACK_DISCONNECTED,
+
+ /* PCM event types */
+ VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED = 0x1100,
+ VIRTIO_SND_EVT_PCM_XRUN,
+
+ /* common status codes */
+ VIRTIO_SND_S_OK = 0x8000,
+ VIRTIO_SND_S_BAD_MSG,
+ VIRTIO_SND_S_NOT_SUPP,
+ VIRTIO_SND_S_IO_ERR
+};
+
+/* common header */
+struct virtio_snd_hdr {
+ __le32 code;
+};
+
+/* event notification */
+struct virtio_snd_event {
+ /* VIRTIO_SND_EVT_XXX */
+ struct virtio_snd_hdr hdr;
+ /* optional event data */
+ __le32 data;
+};
+
+/* common control request to query an item information */
+struct virtio_snd_query_info {
+ /* VIRTIO_SND_R_XXX_INFO */
+ struct virtio_snd_hdr hdr;
+ /* item start identifier */
+ __le32 start_id;
+ /* item count to query */
+ __le32 count;
+ /* item information size in bytes */
+ __le32 size;
+};
+
+/* common item information header */
+struct virtio_snd_info {
+ /* function group node id (High Definition Audio Specification 7.1.2) */
+ __le32 hda_fn_nid;
+};
+
+/*******************************************************************************
+ * JACK CONTROL MESSAGES
+ */
+struct virtio_snd_jack_hdr {
+ /* VIRTIO_SND_R_JACK_XXX */
+ struct virtio_snd_hdr hdr;
+ /* 0 ... virtio_snd_config::jacks - 1 */
+ __le32 jack_id;
+};
+
+/* supported jack features */
+enum {
+ VIRTIO_SND_JACK_F_REMAP = 0
+};
+
+struct virtio_snd_jack_info {
+ /* common header */
+ struct virtio_snd_info hdr;
+ /* supported feature bit map (1 << VIRTIO_SND_JACK_F_XXX) */
+ __le32 features;
+ /* pin configuration (High Definition Audio Specification 7.3.3.31) */
+ __le32 hda_reg_defconf;
+ /* pin capabilities (High Definition Audio Specification 7.3.4.9) */
+ __le32 hda_reg_caps;
+ /* current jack connection status (0: disconnected, 1: connected) */
+ __u8 connected;
+
+ __u8 padding[7];
+};
+
+/* jack remapping control request */
+struct virtio_snd_jack_remap {
+ /* .code = VIRTIO_SND_R_JACK_REMAP */
+ struct virtio_snd_jack_hdr hdr;
+ /* selected association number */
+ __le32 association;
+ /* selected sequence number */
+ __le32 sequence;
+};
+
+/*******************************************************************************
+ * PCM CONTROL MESSAGES
+ */
+struct virtio_snd_pcm_hdr {
+ /* VIRTIO_SND_R_PCM_XXX */
+ struct virtio_snd_hdr hdr;
+ /* 0 ... virtio_snd_config::streams - 1 */
+ __le32 stream_id;
+};
+
+/* supported PCM stream features */
+enum {
+ VIRTIO_SND_PCM_F_SHMEM_HOST = 0,
+ VIRTIO_SND_PCM_F_SHMEM_GUEST,
+ VIRTIO_SND_PCM_F_MSG_POLLING,
+ VIRTIO_SND_PCM_F_EVT_SHMEM_PERIODS,
+ VIRTIO_SND_PCM_F_EVT_XRUNS
+};
+
+/* supported PCM sample formats */
+enum {
+ /* analog formats (width / physical width) */
+ VIRTIO_SND_PCM_FMT_IMA_ADPCM = 0, /* 4 / 4 bits */
+ VIRTIO_SND_PCM_FMT_MU_LAW, /* 8 / 8 bits */
+ VIRTIO_SND_PCM_FMT_A_LAW, /* 8 / 8 bits */
+ VIRTIO_SND_PCM_FMT_S8, /* 8 / 8 bits */
+ VIRTIO_SND_PCM_FMT_U8, /* 8 / 8 bits */
+ VIRTIO_SND_PCM_FMT_S16, /* 16 / 16 bits */
+ VIRTIO_SND_PCM_FMT_U16, /* 16 / 16 bits */
+ VIRTIO_SND_PCM_FMT_S18_3, /* 18 / 24 bits */
+ VIRTIO_SND_PCM_FMT_U18_3, /* 18 / 24 bits */
+ VIRTIO_SND_PCM_FMT_S20_3, /* 20 / 24 bits */
+ VIRTIO_SND_PCM_FMT_U20_3, /* 20 / 24 bits */
+ VIRTIO_SND_PCM_FMT_S24_3, /* 24 / 24 bits */
+ VIRTIO_SND_PCM_FMT_U24_3, /* 24 / 24 bits */
+ VIRTIO_SND_PCM_FMT_S20, /* 20 / 32 bits */
+ VIRTIO_SND_PCM_FMT_U20, /* 20 / 32 bits */
+ VIRTIO_SND_PCM_FMT_S24, /* 24 / 32 bits */
+ VIRTIO_SND_PCM_FMT_U24, /* 24 / 32 bits */
+ VIRTIO_SND_PCM_FMT_S32, /* 32 / 32 bits */
+ VIRTIO_SND_PCM_FMT_U32, /* 32 / 32 bits */
+ VIRTIO_SND_PCM_FMT_FLOAT, /* 32 / 32 bits */
+ VIRTIO_SND_PCM_FMT_FLOAT64, /* 64 / 64 bits */
+ /* digital formats (width / physical width) */
+ VIRTIO_SND_PCM_FMT_DSD_U8, /* 8 / 8 bits */
+ VIRTIO_SND_PCM_FMT_DSD_U16, /* 16 / 16 bits */
+ VIRTIO_SND_PCM_FMT_DSD_U32, /* 32 / 32 bits */
+ VIRTIO_SND_PCM_FMT_IEC958_SUBFRAME /* 32 / 32 bits */
+};
+
+/* supported PCM frame rates */
+enum {
+ VIRTIO_SND_PCM_RATE_5512 = 0,
+ VIRTIO_SND_PCM_RATE_8000,
+ VIRTIO_SND_PCM_RATE_11025,
+ VIRTIO_SND_PCM_RATE_16000,
+ VIRTIO_SND_PCM_RATE_22050,
+ VIRTIO_SND_PCM_RATE_32000,
+ VIRTIO_SND_PCM_RATE_44100,
+ VIRTIO_SND_PCM_RATE_48000,
+ VIRTIO_SND_PCM_RATE_64000,
+ VIRTIO_SND_PCM_RATE_88200,
+ VIRTIO_SND_PCM_RATE_96000,
+ VIRTIO_SND_PCM_RATE_176400,
+ VIRTIO_SND_PCM_RATE_192000,
+ VIRTIO_SND_PCM_RATE_384000
+};
+
+struct virtio_snd_pcm_info {
+ /* common header */
+ struct virtio_snd_info hdr;
+ /* supported feature bit map (1 << VIRTIO_SND_PCM_F_XXX) */
+ __le32 features;
+ /* supported sample format bit map (1 << VIRTIO_SND_PCM_FMT_XXX) */
+ __le64 formats;
+ /* supported frame rate bit map (1 << VIRTIO_SND_PCM_RATE_XXX) */
+ __le64 rates;
+ /* dataflow direction (VIRTIO_SND_D_XXX) */
+ __u8 direction;
+ /* minimum # of supported channels */
+ __u8 channels_min;
+ /* maximum # of supported channels */
+ __u8 channels_max;
+
+ __u8 padding[5];
+};
+
+/* set PCM stream format */
+struct virtio_snd_pcm_set_params {
+ /* .code = VIRTIO_SND_R_PCM_SET_PARAMS */
+ struct virtio_snd_pcm_hdr hdr;
+ /* size of the hardware buffer */
+ __le32 buffer_bytes;
+ /* size of the hardware period */
+ __le32 period_bytes;
+ /* selected feature bit map (1 << VIRTIO_SND_PCM_F_XXX) */
+ __le32 features;
+ /* selected # of channels */
+ __u8 channels;
+ /* selected sample format (VIRTIO_SND_PCM_FMT_XXX) */
+ __u8 format;
+ /* selected frame rate (VIRTIO_SND_PCM_RATE_XXX) */
+ __u8 rate;
+
+ __u8 padding;
+};
+
+/*******************************************************************************
+ * PCM I/O MESSAGES
+ */
+
+/* I/O request header */
+struct virtio_snd_pcm_xfer {
+ /* 0 ... virtio_snd_config::streams - 1 */
+ __le32 stream_id;
+};
+
+/* I/O request status */
+struct virtio_snd_pcm_status {
+ /* VIRTIO_SND_S_XXX */
+ __le32 status;
+ /* current device latency */
+ __le32 latency_bytes;
+};
+
+/*******************************************************************************
+ * CHANNEL MAP CONTROL MESSAGES
+ */
+struct virtio_snd_chmap_hdr {
+ /* VIRTIO_SND_R_CHMAP_XXX */
+ struct virtio_snd_hdr hdr;
+ /* 0 ... virtio_snd_config::chmaps - 1 */
+ __le32 chmap_id;
+};
+
+/* standard channel position definition */
+enum {
+ VIRTIO_SND_CHMAP_NONE = 0, /* undefined */
+ VIRTIO_SND_CHMAP_NA, /* silent */
+ VIRTIO_SND_CHMAP_MONO, /* mono stream */
+ VIRTIO_SND_CHMAP_FL, /* front left */
+ VIRTIO_SND_CHMAP_FR, /* front right */
+ VIRTIO_SND_CHMAP_RL, /* rear left */
+ VIRTIO_SND_CHMAP_RR, /* rear right */
+ VIRTIO_SND_CHMAP_FC, /* front center */
+ VIRTIO_SND_CHMAP_LFE, /* low frequency (LFE) */
+ VIRTIO_SND_CHMAP_SL, /* side left */
+ VIRTIO_SND_CHMAP_SR, /* side right */
+ VIRTIO_SND_CHMAP_RC, /* rear center */
+ VIRTIO_SND_CHMAP_FLC, /* front left center */
+ VIRTIO_SND_CHMAP_FRC, /* front right center */
+ VIRTIO_SND_CHMAP_RLC, /* rear left center */
+ VIRTIO_SND_CHMAP_RRC, /* rear right center */
+ VIRTIO_SND_CHMAP_FLW, /* front left wide */
+ VIRTIO_SND_CHMAP_FRW, /* front right wide */
+ VIRTIO_SND_CHMAP_FLH, /* front left high */
+ VIRTIO_SND_CHMAP_FCH, /* front center high */
+ VIRTIO_SND_CHMAP_FRH, /* front right high */
+ VIRTIO_SND_CHMAP_TC, /* top center */
+ VIRTIO_SND_CHMAP_TFL, /* top front left */
+ VIRTIO_SND_CHMAP_TFR, /* top front right */
+ VIRTIO_SND_CHMAP_TFC, /* top front center */
+ VIRTIO_SND_CHMAP_TRL, /* top rear left */
+ VIRTIO_SND_CHMAP_TRR, /* top rear right */
+ VIRTIO_SND_CHMAP_TRC, /* top rear center */
+ VIRTIO_SND_CHMAP_TFLC, /* top front left center */
+ VIRTIO_SND_CHMAP_TFRC, /* top front right center */
+ VIRTIO_SND_CHMAP_TSL, /* top side left */
+ VIRTIO_SND_CHMAP_TSR, /* top side right */
+ VIRTIO_SND_CHMAP_LLFE, /* left LFE */
+ VIRTIO_SND_CHMAP_RLFE, /* right LFE */
+ VIRTIO_SND_CHMAP_BC, /* bottom center */
+ VIRTIO_SND_CHMAP_BLC, /* bottom left center */
+ VIRTIO_SND_CHMAP_BRC /* bottom right center */
+};
+
+/* maximum possible number of channels */
+#define VIRTIO_SND_CHMAP_MAX_SIZE 18
+
+struct virtio_snd_chmap_info {
+ /* common header */
+ struct virtio_snd_info hdr;
+ /* dataflow direction (VIRTIO_SND_D_XXX) */
+ __u8 direction;
+ /* # of valid channel position values */
+ __u8 channels;
+ /* channel position values (VIRTIO_SND_CHMAP_XXX) */
+ __u8 positions[VIRTIO_SND_CHMAP_MAX_SIZE];
+};
+
+#endif /* VIRTIO_SND_IF_H */
diff --git a/virtio_snd/Kbuild b/virtio_snd/Kbuild
new file mode 100644
index 0000000..fb6474b
--- /dev/null
+++ b/virtio_snd/Kbuild
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0
+
+virtio_snd-objs := \
+ virtio_card.o \
+ virtio_chmap.o \
+ virtio_ctl_msg.o \
+ virtio_dc.o \
+ virtio_event.o \
+ virtio_jack.o \
+ virtio_opsy_ctl_msg.o \
+ virtio_pcm.o \
+ virtio_pcm_msg.o \
+ virtio_pcm_ops.o
+
+obj-m += virtio_snd.o
+LINUXINCLUDE := -I$(srctree)/../common-modules/virtual-device/uapi ${LINUXINCLUDE}
diff --git a/virtio_snd/virtio_card.c b/virtio_snd/virtio_card.c
new file mode 100644
index 0000000..afa908a
--- /dev/null
+++ b/virtio_snd/virtio_card.c
@@ -0,0 +1,472 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Sound card driver for virtio
+ * Copyright (C) 2020 OpenSynergy GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/version.h>
+#include <linux/virtio_config.h>
+#include <sound/initval.h>
+
+#include "virtio_card.h"
+
+#ifndef VIRTIO_ID_SOUND
+#define VIRTIO_ID_SOUND 25
+#endif
+
+static int virtsnd_find_vqs(struct virtio_snd *snd)
+{
+ int rc;
+ int i;
+ struct virtio_device *vdev = snd->vdev;
+ vq_callback_t *callbacks[VIRTIO_SND_VQ_MAX] = { 0 };
+ const char *names[VIRTIO_SND_VQ_MAX] = {
+ "virtsnd-ctl", "virtsnd-event", "virtsnd-tx", "virtsnd-rx"
+ };
+ struct virtqueue *vqs[VIRTIO_SND_VQ_MAX] = { 0 };
+ unsigned int streams = 0;
+
+ callbacks[VIRTIO_SND_VQ_CONTROL] = virtsnd_ctl_notify_cb;
+ callbacks[VIRTIO_SND_VQ_EVENT] = virtsnd_event_notify_cb;
+
+ virtio_cread(vdev, struct virtio_snd_config, streams, &streams);
+ if (streams) {
+ callbacks[VIRTIO_SND_VQ_TX] = virtsnd_pcm_tx_notify_cb;
+ callbacks[VIRTIO_SND_VQ_RX] = virtsnd_pcm_rx_notify_cb;
+ }
+
+#if KERNEL_VERSION(4, 12, 0) <= LINUX_VERSION_CODE
+ rc = virtio_find_vqs(vdev, VIRTIO_SND_VQ_MAX, vqs, callbacks, names,
+ NULL);
+#else
+ rc = vdev->config->find_vqs(vdev, VIRTIO_SND_VQ_MAX, vqs, callbacks,
+ names);
+#endif
+ if (rc) {
+ dev_err(&vdev->dev, "Failed to initialize virtqueues");
+ return rc;
+ }
+
+ for (i = 0; i < VIRTIO_SND_VQ_MAX; ++i) {
+ /*
+ * By default, disable callbacks for all queues except the
+ * control queue, since the device must be fully initialized
+ * first.
+ */
+ if (i != VIRTIO_SND_VQ_CONTROL)
+ virtqueue_disable_cb(vqs[i]);
+
+ snd->queues[i].vqueue = vqs[i];
+ }
+
+ rc = virtsnd_event_populate(snd);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
+static void virtsnd_enable_vqs(struct virtio_snd *snd)
+{
+ struct virtio_device *vdev = snd->vdev;
+ struct virtqueue *vqueue;
+
+ vqueue = snd->queues[VIRTIO_SND_VQ_EVENT].vqueue;
+ if (!virtqueue_enable_cb(vqueue))
+ virtsnd_event_notify_cb(vqueue);
+
+ if (snd->nsubstreams) {
+ vqueue = snd->queues[VIRTIO_SND_VQ_TX].vqueue;
+ if (!virtqueue_enable_cb(vqueue))
+ dev_warn(&vdev->dev,
+ "Suspicious notification in the TX queue");
+ vqueue = snd->queues[VIRTIO_SND_VQ_RX].vqueue;
+ if (!virtqueue_enable_cb(vqueue))
+ dev_warn(&vdev->dev,
+ "Suspicious notification in the RX queue");
+ }
+}
+
+static void virtsnd_disable_vqs(struct virtio_snd *snd)
+{
+ int i;
+ unsigned long flags;
+
+ for (i = 0; i < VIRTIO_SND_VQ_MAX; ++i) {
+ struct virtio_snd_queue *queue = &snd->queues[i];
+
+ spin_lock_irqsave(&queue->lock, flags);
+ virtqueue_disable_cb(queue->vqueue);
+ queue->vqueue = NULL;
+ spin_unlock_irqrestore(&queue->lock, flags);
+ }
+}
+
+static void virtsnd_flush_vqs(struct virtio_snd *snd)
+{
+ struct virtio_device *vdev = snd->vdev;
+
+ if (!list_empty(&snd->ctl_msgs)) {
+ struct virtio_snd_queue *queue = virtsnd_control_queue(snd);
+ unsigned long flags;
+ struct virtio_snd_msg *msg;
+ struct virtio_snd_msg *next;
+
+ spin_lock_irqsave(&queue->lock, flags);
+ list_for_each_entry_safe(msg, next, &snd->ctl_msgs, list) {
+ struct virtio_snd_hdr *response =
+ sg_virt(&msg->sg_response);
+
+ list_del(&msg->list);
+
+ response->code = cpu_to_virtio32(vdev,
+ VIRTIO_SND_S_IO_ERR);
+
+ complete(&msg->notify);
+
+ virtsnd_ctl_msg_unref(vdev, msg);
+ }
+ spin_unlock_irqrestore(&queue->lock, flags);
+ }
+
+ if (snd->event_msgs)
+ devm_kfree(&vdev->dev, snd->event_msgs);
+
+ snd->event_msgs = NULL;
+}
+
+static void virtsnd_reset_fn(struct work_struct *work)
+{
+ struct virtio_snd *snd =
+ container_of(work, struct virtio_snd, reset_work);
+ struct virtio_device *vdev = snd->vdev;
+ struct device *dev = &vdev->dev;
+ int rc;
+
+ dev_info(dev, "Sound device needs reset");
+
+ rc = dev->bus->remove(dev);
+ if (rc)
+ dev_warn(dev, "bus->remove() failed: %d", rc);
+
+ rc = dev->bus->probe(dev);
+ if (rc)
+ dev_err(dev, "bus->probe() failed: %d", rc);
+}
+
+static int virtsnd_card_info(struct virtio_snd *snd)
+{
+ if (VIRTIO_HAS_OPSY_EXTENSION(snd, DEV_EXT_INFO)) {
+ int code;
+
+ code = virtsnd_ctl_alsa_card_info(snd);
+ if (!code || code != -EOPNOTSUPP)
+ return code;
+ }
+
+ strlcpy(snd->card->id, "viosnd", sizeof(snd->card->id));
+ strlcpy(snd->card->driver, "virtio_snd", sizeof(snd->card->driver));
+ strlcpy(snd->card->shortname, "VIOSND", sizeof(snd->card->shortname));
+ strlcpy(snd->card->longname, "VirtIO Sound Card",
+ sizeof(snd->card->longname));
+
+ return 0;
+}
+
+static int virtsnd_build_devs(struct virtio_snd *snd)
+{
+ static struct snd_device_ops ops = { 0 };
+ struct virtio_device *vdev = snd->vdev;
+ int rc;
+
+ /* query supported OPSY extensions */
+ if (virtio_has_feature(vdev, VIRTIO_SND_F_OPSY_EXT)) {
+ rc = virtsnd_ctl_query_opsy_extensions(snd);
+ if (rc)
+ return rc;
+ }
+
+ rc = snd_card_new(&vdev->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
+ THIS_MODULE, 0, &snd->card);
+ if (rc < 0)
+ return rc;
+
+ snd->card->private_data = snd;
+
+ rc = virtsnd_card_info(snd);
+ if (rc)
+ return rc;
+
+ rc = snd_device_new(snd->card, SNDRV_DEV_LOWLEVEL, snd, &ops);
+ if (rc < 0)
+ return rc;
+
+ rc = virtsnd_jack_parse_cfg(snd);
+ if (rc)
+ return rc;
+
+ rc = virtsnd_pcm_parse_cfg(snd);
+ if (rc)
+ return rc;
+
+ rc = virtsnd_chmap_parse_cfg(snd);
+ if (rc)
+ return rc;
+
+ if (VIRTIO_HAS_OPSY_EXTENSION(snd, DEV_CTLS)) {
+ rc = virtsnd_dc_parse_cfg(snd);
+ if (rc)
+ return rc;
+ }
+
+ if (snd->njacks) {
+ rc = virtsnd_jack_build_devs(snd);
+ if (rc)
+ return rc;
+ }
+
+ if (snd->nsubstreams) {
+ rc = virtsnd_pcm_build_devs(snd);
+ if (rc)
+ return rc;
+ }
+
+ rc = virtsnd_chmap_build_devs(snd);
+ if (rc)
+ return rc;
+
+ return snd_card_register(snd->card);
+}
+
+static int virtsnd_validate(struct virtio_device *vdev)
+{
+ if (!vdev->config->get) {
+ dev_err(&vdev->dev, "Config access disabled");
+ return -EINVAL;
+ }
+
+ if (virtsnd_pcm_validate(vdev))
+ return -EINVAL;
+
+ return 0;
+}
+
+static void virtsnd_remove(struct virtio_device *vdev)
+{
+ struct virtio_snd *snd = vdev->priv;
+ struct virtio_pcm *pcm;
+ struct virtio_pcm *pcm_next;
+
+ virtsnd_disable_vqs(snd);
+
+ virtsnd_flush_vqs(snd);
+
+ if (snd->card)
+ snd_card_free(snd->card);
+
+ vdev->config->reset(vdev);
+ vdev->config->del_vqs(vdev);
+
+ list_for_each_entry_safe(pcm, pcm_next, &snd->pcm_list, list) {
+ unsigned int i;
+
+ list_del(&pcm->list);
+
+ for (i = 0; i < ARRAY_SIZE(pcm->streams); ++i) {
+ struct virtio_pcm_stream *stream = &pcm->streams[i];
+
+ if (stream->substreams)
+ devm_kfree(&vdev->dev, stream->substreams);
+ if (stream->chmaps)
+ devm_kfree(&vdev->dev, stream->chmaps);
+ }
+
+ devm_kfree(&vdev->dev, pcm);
+ }
+
+ if (snd->jacks)
+ devm_kfree(&vdev->dev, snd->jacks);
+
+ if (snd->substreams)
+ devm_kfree(&vdev->dev, snd->substreams);
+
+ if (snd->chmaps)
+ devm_kfree(&vdev->dev, snd->chmaps);
+
+ snd->card = NULL;
+ snd->jacks = NULL;
+ snd->njacks = 0;
+ snd->substreams = NULL;
+ snd->nsubstreams = 0;
+ snd->chmaps = NULL;
+ snd->nchmaps = 0;
+}
+
+static int virtsnd_probe(struct virtio_device *vdev)
+{
+ int rc;
+ unsigned int i;
+ struct virtio_snd *snd = vdev->priv;
+
+ /*
+ * if we got here because the NEEDS_RESET status was set, we do not need
+ * to create the structure of the device.
+ */
+ if (!snd) {
+ snd = devm_kzalloc(&vdev->dev, sizeof(*snd), GFP_KERNEL);
+ if (!snd)
+ return -ENOMEM;
+
+ snd->vdev = vdev;
+ INIT_WORK(&snd->reset_work, virtsnd_reset_fn);
+ INIT_LIST_HEAD(&snd->ctl_msgs);
+ INIT_LIST_HEAD(&snd->pcm_list);
+
+ vdev->priv = snd;
+
+ for (i = 0; i < VIRTIO_SND_VQ_MAX; ++i)
+ spin_lock_init(&snd->queues[i].lock);
+ }
+
+ rc = virtsnd_find_vqs(snd);
+ if (rc)
+ goto on_failure;
+
+ virtio_device_ready(vdev);
+
+ rc = virtsnd_build_devs(snd);
+ if (rc)
+ goto on_failure;
+
+ virtsnd_enable_vqs(snd);
+
+on_failure:
+ if (rc)
+ virtsnd_remove(vdev);
+
+ return rc;
+}
+
+static void virtsnd_config_changed(struct virtio_device *vdev)
+{
+ struct virtio_snd *snd = vdev->priv;
+ unsigned int status = vdev->config->get_status(vdev);
+
+ if (status & VIRTIO_CONFIG_S_NEEDS_RESET)
+ schedule_work(&snd->reset_work);
+ else
+ dev_warn(&vdev->dev, "Sound device configuration was changed");
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int virtsnd_freeze(struct virtio_device *vdev)
+{
+ struct virtio_snd *snd = vdev->priv;
+
+ virtsnd_disable_vqs(snd);
+
+ virtsnd_flush_vqs(snd);
+
+ vdev->config->reset(vdev);
+ vdev->config->del_vqs(vdev);
+
+ return 0;
+}
+
+static int virtsnd_restore(struct virtio_device *vdev)
+{
+ struct virtio_snd *snd = vdev->priv;
+ int rc;
+
+ rc = virtsnd_find_vqs(snd);
+ if (rc)
+ return rc;
+
+ virtio_device_ready(vdev);
+
+ /* If the configuration has been changed, reset the device. */
+ if (virtsnd_jack_check_cfg(snd))
+ goto on_reset;
+
+ if (virtsnd_pcm_check_cfg(snd))
+ goto on_reset;
+
+ if (virtsnd_chmap_check_cfg(snd))
+ goto on_reset;
+
+ /* If the configuration has not been changed, continue as usual. */
+ virtsnd_enable_vqs(snd);
+
+ if (snd->nsubstreams) {
+ rc = virtsnd_pcm_restore(snd);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+
+on_reset:
+ dev_warn(&vdev->dev, "configuration has changed -> reset device\n");
+
+ virtsnd_disable_vqs(snd);
+
+ schedule_work(&snd->reset_work);
+
+ return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static struct virtio_device_id id_table[] = {
+ { VIRTIO_ID_SOUND, VIRTIO_DEV_ANY_ID },
+ { 0 },
+};
+
+static unsigned int features[] = {
+ VIRTIO_SND_F_OPSY_EXT
+};
+
+static struct virtio_driver virtsnd_driver = {
+ .driver.name = KBUILD_MODNAME,
+ .driver.owner = THIS_MODULE,
+ .id_table = id_table,
+ .feature_table = features,
+ .feature_table_size = ARRAY_SIZE(features),
+ .validate = virtsnd_validate,
+ .probe = virtsnd_probe,
+ .remove = virtsnd_remove,
+ .config_changed = virtsnd_config_changed,
+#ifdef CONFIG_PM_SLEEP
+ .freeze = virtsnd_freeze,
+ .restore = virtsnd_restore,
+#endif
+};
+
+static int __init init(void)
+{
+ return register_virtio_driver(&virtsnd_driver);
+}
+module_init(init);
+
+static void __exit fini(void)
+{
+ unregister_virtio_driver(&virtsnd_driver);
+}
+module_exit(fini);
+
+MODULE_DEVICE_TABLE(virtio, id_table);
+MODULE_DESCRIPTION("Virtio sound card driver");
+MODULE_LICENSE("GPL");
diff --git a/virtio_snd/virtio_card.h b/virtio_snd/virtio_card.h
new file mode 100644
index 0000000..7b9a354
--- /dev/null
+++ b/virtio_snd/virtio_card.h
@@ -0,0 +1,152 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Sound card driver for virtio
+ * Copyright (C) 2020 OpenSynergy GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef VIRTIO_SND_CARD_H
+#define VIRTIO_SND_CARD_H
+
+#include <linux/virtio.h>
+#include <sound/core.h>
+
+#include "virtio_snd.h"
+#include "virtio_ctl_msg.h"
+#include "virtio_pcm.h"
+
+#include "virtio_opsy.h"
+
+struct virtio_jack;
+struct virtio_pcm_substream;
+struct virtio_kctl_ctx;
+
+/**
+ * struct virtio_snd_queue - Virtqueue wrapper structure.
+ * @lock: Used to synchronize access to a virtqueue.
+ * @vqueue: Pointer to underlying virtqueue structure.
+ */
+struct virtio_snd_queue {
+ spinlock_t lock;
+ struct virtqueue *vqueue;
+};
+
+/**
+ * struct virtio_snd - Virtio sound card device representation.
+ * @vdev: Underlying virtio device.
+ * @queues: Virtqueue wrappers.
+ * @card: Kernel sound card device.
+ * @pcm_list: List of virtio PCM devices.
+ * @jacks: Virtio jacks.
+ * @njacks: Number of jacks.
+ * @substreams: Virtio PCM substreams.
+ * @nsubstreams: Number of PCM stream.
+ * @extensions: Supported OpSy extension bit map (1 << VIRTIO_SND_OPSY_F_XXX).
+ */
+struct virtio_snd {
+ struct virtio_device *vdev;
+ struct virtio_snd_queue queues[VIRTIO_SND_VQ_MAX];
+ struct work_struct reset_work;
+ struct snd_card *card;
+ struct list_head ctl_msgs;
+ struct virtio_snd_event *event_msgs;
+ struct list_head pcm_list;
+ struct virtio_jack *jacks;
+ unsigned int njacks;
+ struct virtio_pcm_substream *substreams;
+ unsigned int nsubstreams;
+ struct virtio_snd_chmap_info *chmaps;
+ unsigned int nchmaps;
+ u32 extensions;
+ /* --- OpenSynergy extensions --------------------------------------- */
+ struct virtio_kctl_ctx *kctl_ctx;
+ struct work_struct kctl_work;
+};
+
+static inline void
+virtsnd_strlcpy(char *dst, const char *src, size_t max_size)
+{
+ strlcpy(dst, src, max_size);
+ dst[max_size - 1] = 0;
+}
+
+static inline struct virtio_snd_queue *
+virtsnd_control_queue(struct virtio_snd *snd)
+{
+ return &snd->queues[VIRTIO_SND_VQ_CONTROL];
+}
+
+static inline struct virtio_snd_queue *
+virtsnd_event_queue(struct virtio_snd *snd)
+{
+ return &snd->queues[VIRTIO_SND_VQ_EVENT];
+}
+
+static inline struct virtio_snd_queue *
+virtsnd_tx_queue(struct virtio_snd *snd)
+{
+ return &snd->queues[VIRTIO_SND_VQ_TX];
+}
+
+static inline struct virtio_snd_queue *
+virtsnd_rx_queue(struct virtio_snd *snd)
+{
+ return &snd->queues[VIRTIO_SND_VQ_RX];
+}
+
+static inline struct virtio_snd_queue *
+virtsnd_pcm_queue(struct virtio_pcm_substream *substream)
+{
+ if (substream->direction == SNDRV_PCM_STREAM_PLAYBACK)
+ return virtsnd_tx_queue(substream->snd);
+ else
+ return virtsnd_rx_queue(substream->snd);
+}
+
+/*
+ * event related functions:
+ */
+int virtsnd_event_populate(struct virtio_snd *snd);
+
+void virtsnd_event_notify_cb(struct virtqueue *vqueue);
+
+/*
+ * jack related functions:
+ */
+int virtsnd_jack_parse_cfg(struct virtio_snd *snd);
+
+int virtsnd_jack_check_cfg(struct virtio_snd *snd);
+
+int virtsnd_jack_build_devs(struct virtio_snd *snd);
+
+void virtsnd_jack_event(struct virtio_snd *snd,
+ struct virtio_snd_event *event);
+
+/*
+ * channel map related functions:
+ */
+int virtsnd_chmap_parse_cfg(struct virtio_snd *snd);
+
+int virtsnd_chmap_check_cfg(struct virtio_snd *snd);
+
+int virtsnd_chmap_build_devs(struct virtio_snd *snd);
+
+/*
+ * device controls related functions:
+ */
+int virtsnd_dc_parse_cfg(struct virtio_snd *vsnd);
+
+void virtsnd_dc_event(struct virtio_snd *vsnd, struct virtio_snd_event *event);
+
+#endif /* VIRTIO_SND_CARD_H */
diff --git a/virtio_snd/virtio_chmap.c b/virtio_snd/virtio_chmap.c
new file mode 100644
index 0000000..17dd8ae
--- /dev/null
+++ b/virtio_snd/virtio_chmap.c
@@ -0,0 +1,252 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Sound card driver for virtio
+ * Copyright (C) 2020 OpenSynergy GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/virtio_config.h>
+
+#include "virtio_card.h"
+
+static const u8 g_v2a_position_map[] = {
+ [VIRTIO_SND_CHMAP_NONE] = SNDRV_CHMAP_UNKNOWN,
+ [VIRTIO_SND_CHMAP_NA] = SNDRV_CHMAP_NA,
+ [VIRTIO_SND_CHMAP_MONO] = SNDRV_CHMAP_MONO,
+ [VIRTIO_SND_CHMAP_FL] = SNDRV_CHMAP_FL,
+ [VIRTIO_SND_CHMAP_FR] = SNDRV_CHMAP_FR,
+ [VIRTIO_SND_CHMAP_RL] = SNDRV_CHMAP_RL,
+ [VIRTIO_SND_CHMAP_RR] = SNDRV_CHMAP_RR,
+ [VIRTIO_SND_CHMAP_FC] = SNDRV_CHMAP_FC,
+ [VIRTIO_SND_CHMAP_LFE] = SNDRV_CHMAP_LFE,
+ [VIRTIO_SND_CHMAP_SL] = SNDRV_CHMAP_SL,
+ [VIRTIO_SND_CHMAP_SR] = SNDRV_CHMAP_SR,
+ [VIRTIO_SND_CHMAP_RC] = SNDRV_CHMAP_RC,
+ [VIRTIO_SND_CHMAP_FLC] = SNDRV_CHMAP_FLC,
+ [VIRTIO_SND_CHMAP_FRC] = SNDRV_CHMAP_FRC,
+ [VIRTIO_SND_CHMAP_RLC] = SNDRV_CHMAP_RLC,
+ [VIRTIO_SND_CHMAP_RRC] = SNDRV_CHMAP_RRC,
+ [VIRTIO_SND_CHMAP_FLW] = SNDRV_CHMAP_FLW,
+ [VIRTIO_SND_CHMAP_FRW] = SNDRV_CHMAP_FRW,
+ [VIRTIO_SND_CHMAP_FLH] = SNDRV_CHMAP_FLH,
+ [VIRTIO_SND_CHMAP_FCH] = SNDRV_CHMAP_FCH,
+ [VIRTIO_SND_CHMAP_FRH] = SNDRV_CHMAP_FRH,
+ [VIRTIO_SND_CHMAP_TC] = SNDRV_CHMAP_TC,
+ [VIRTIO_SND_CHMAP_TFL] = SNDRV_CHMAP_TFL,
+ [VIRTIO_SND_CHMAP_TFR] = SNDRV_CHMAP_TFR,
+ [VIRTIO_SND_CHMAP_TFC] = SNDRV_CHMAP_TFC,
+ [VIRTIO_SND_CHMAP_TRL] = SNDRV_CHMAP_TRL,
+ [VIRTIO_SND_CHMAP_TRR] = SNDRV_CHMAP_TRR,
+ [VIRTIO_SND_CHMAP_TRC] = SNDRV_CHMAP_TRC,
+ [VIRTIO_SND_CHMAP_TFLC] = SNDRV_CHMAP_TFLC,
+ [VIRTIO_SND_CHMAP_TFRC] = SNDRV_CHMAP_TFRC,
+ [VIRTIO_SND_CHMAP_TSL] = SNDRV_CHMAP_TSL,
+ [VIRTIO_SND_CHMAP_TSR] = SNDRV_CHMAP_TSR,
+ [VIRTIO_SND_CHMAP_LLFE] = SNDRV_CHMAP_LLFE,
+ [VIRTIO_SND_CHMAP_RLFE] = SNDRV_CHMAP_RLFE,
+ [VIRTIO_SND_CHMAP_BC] = SNDRV_CHMAP_BC,
+ [VIRTIO_SND_CHMAP_BLC] = SNDRV_CHMAP_BLC,
+ [VIRTIO_SND_CHMAP_BRC] = SNDRV_CHMAP_BRC
+};
+
+int virtsnd_chmap_parse_cfg(struct virtio_snd *snd)
+{
+ struct virtio_device *vdev = snd->vdev;
+ int rc;
+ struct virtio_pcm *pcm;
+ struct virtio_pcm_stream *stream;
+ unsigned int i;
+
+ virtio_cread(vdev, struct virtio_snd_config, chmaps, &snd->nchmaps);
+ if (!snd->nchmaps)
+ return 0;
+
+ snd->chmaps = devm_kcalloc(&vdev->dev, snd->nchmaps,
+ sizeof(*snd->chmaps), GFP_KERNEL);
+ if (!snd->chmaps)
+ return -ENOMEM;
+
+ rc = virtsnd_ctl_query_info(snd, VIRTIO_SND_R_CHMAP_INFO, 0,
+ snd->nchmaps, sizeof(*snd->chmaps),
+ snd->chmaps);
+ if (rc)
+ return rc;
+
+ /* Count the number of channel maps per each pcm/stream. */
+ for (i = 0; i < snd->nchmaps; ++i) {
+ struct virtio_snd_chmap_info *info = &snd->chmaps[i];
+ unsigned int nid = le32_to_cpu(info->hdr.hda_fn_nid);
+
+ pcm = virtsnd_pcm_find_or_create(snd, nid);
+ if (IS_ERR(pcm))
+ return PTR_ERR(pcm);
+
+ switch (info->direction) {
+ case VIRTIO_SND_D_OUTPUT: {
+ stream = &pcm->streams[SNDRV_PCM_STREAM_PLAYBACK];
+ break;
+ }
+ case VIRTIO_SND_D_INPUT: {
+ stream = &pcm->streams[SNDRV_PCM_STREAM_CAPTURE];
+ break;
+ }
+ default: {
+ dev_err(&vdev->dev,
+ "chmap #%u: unknown direction (%u)", i,
+ info->direction);
+ return -EINVAL;
+ }
+ }
+
+ stream->nchmaps++;
+ }
+
+ return 0;
+}
+
+int virtsnd_chmap_check_cfg(struct virtio_snd *snd)
+{
+ struct virtio_device *vdev = snd->vdev;
+ int rc;
+ struct virtio_snd_chmap_info *info;
+ unsigned int i;
+
+ virtio_cread(vdev, struct virtio_snd_config, chmaps, &i);
+ if (snd->nchmaps != i) {
+ dev_warn(&vdev->dev,
+ "config: number of channel maps has changed (%u->%u)",
+ snd->nchmaps, i);
+ return -EINVAL;
+ }
+
+ if (!snd->chmaps)
+ return 0;
+
+ info = devm_kcalloc(&vdev->dev, snd->nchmaps, sizeof(*info),
+ GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ rc = virtsnd_ctl_query_info(snd, VIRTIO_SND_R_CHMAP_INFO, 0,
+ snd->nchmaps, sizeof(*info), info);
+ if (rc)
+ goto on_failure;
+
+ for (i = 0; i < snd->nchmaps; ++i)
+ if (memcmp(&snd->chmaps[i], &info[i], sizeof(*info)) != 0) {
+ dev_warn(&vdev->dev,
+ "config: channel map #%u has changed", i);
+ rc = -EINVAL;
+ break;
+ }
+
+on_failure:
+ devm_kfree(&vdev->dev, info);
+
+ return rc;
+}
+
+static int virtsnd_chmap_add_ctls(struct snd_pcm *pcm, int direction,
+ struct virtio_pcm_stream *stream)
+{
+ unsigned int i;
+ int max_channels = 0;
+
+ for (i = 0; i < stream->nchmaps; i++)
+ if (max_channels < stream->chmaps[i].channels)
+ max_channels = stream->chmaps[i].channels;
+
+ return snd_pcm_add_chmap_ctls(pcm, direction, stream->chmaps,
+ max_channels, 0, NULL);
+}
+
+int virtsnd_chmap_build_devs(struct virtio_snd *snd)
+{
+ struct virtio_device *vdev = snd->vdev;
+ struct virtio_pcm *pcm;
+ struct virtio_pcm_stream *stream;
+ unsigned int i;
+ int rc;
+
+ /* Allocate channel map elements per each pcm/stream. */
+ list_for_each_entry(pcm, &snd->pcm_list, list) {
+ for (i = 0; i < ARRAY_SIZE(pcm->streams); ++i) {
+ stream = &pcm->streams[i];
+
+ if (!stream->nchmaps)
+ continue;
+
+ stream->chmaps = devm_kcalloc(&vdev->dev,
+ stream->nchmaps + 1,
+ sizeof(*stream->chmaps),
+ GFP_KERNEL);
+ if (!stream->chmaps)
+ return -ENOMEM;
+
+ stream->nchmaps = 0;
+ }
+ }
+
+ /* Initialize channel maps per each pcm/stream. */
+ for (i = 0; i < snd->nchmaps; ++i) {
+ struct virtio_snd_chmap_info *info = &snd->chmaps[i];
+ unsigned int nid = le32_to_cpu(info->hdr.hda_fn_nid);
+ unsigned int channels = info->channels;
+ unsigned int ch;
+ struct snd_pcm_chmap_elem *chmap;
+
+ pcm = virtsnd_pcm_find(snd, nid);
+ if (IS_ERR(pcm))
+ return PTR_ERR(pcm);
+
+ if (info->direction == VIRTIO_SND_D_OUTPUT)
+ stream = &pcm->streams[SNDRV_PCM_STREAM_PLAYBACK];
+ else
+ stream = &pcm->streams[SNDRV_PCM_STREAM_CAPTURE];
+
+ chmap = &stream->chmaps[stream->nchmaps++];
+
+ if (channels > ARRAY_SIZE(chmap->map))
+ channels = ARRAY_SIZE(chmap->map);
+
+ chmap->channels = channels;
+
+ for (ch = 0; ch < channels; ++ch) {
+ u8 position = info->positions[ch];
+
+ if (position >= ARRAY_SIZE(g_v2a_position_map))
+ return -EINVAL;
+
+ chmap->map[ch] = g_v2a_position_map[position];
+ }
+ }
+
+ list_for_each_entry(pcm, &snd->pcm_list, list) {
+ if (!pcm->pcm)
+ continue;
+
+ for (i = 0; i < ARRAY_SIZE(pcm->streams); ++i) {
+ stream = &pcm->streams[i];
+
+ if (!stream->nchmaps)
+ continue;
+
+ rc = virtsnd_chmap_add_ctls(pcm->pcm, i, stream);
+ if (rc)
+ return rc;
+ }
+ }
+
+ return 0;
+}
diff --git a/virtio_snd/virtio_ctl_msg.c b/virtio_snd/virtio_ctl_msg.c
new file mode 100644
index 0000000..4b743a5
--- /dev/null
+++ b/virtio_snd/virtio_ctl_msg.c
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Sound card driver for virtio
+ * Copyright (C) 2020 OpenSynergy GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/moduleparam.h>
+#include <linux/virtio_config.h>
+
+#include "virtio_card.h"
+#include "virtio_ctl_msg.h"
+
+static int msg_timeout_ms = 1000;
+module_param(msg_timeout_ms, int, 0644);
+MODULE_PARM_DESC(msg_timeout_ms, "Message completion timeout in milliseconds");
+
+struct virtio_snd_msg *virtsnd_ctl_msg_alloc(struct virtio_device *vdev,
+ size_t request_size,
+ size_t response_size, gfp_t gfp)
+{
+ struct virtio_snd_msg *msg;
+
+ msg = devm_kzalloc(&vdev->dev,
+ sizeof(*msg) + request_size + response_size,
+ gfp);
+ if (!msg)
+ return ERR_PTR(-ENOMEM);
+
+ sg_init_one(&msg->sg_request, (u8 *)msg + sizeof(*msg), request_size);
+ sg_init_one(&msg->sg_response, (u8 *)msg + sizeof(*msg) + request_size,
+ response_size);
+
+ init_completion(&msg->notify);
+ atomic_set(&msg->ref_count, 1);
+
+ return msg;
+}
+
+int virtsnd_ctl_msg_send(struct virtio_snd *snd, struct virtio_snd_msg *msg)
+{
+ int rc;
+ struct virtio_snd_queue *queue = virtsnd_control_queue(snd);
+ struct scatterlist *psgs[4];
+ unsigned int out_nsgs = 1;
+ unsigned int in_nsgs = 1;
+ unsigned int nsgs = 0;
+ bool notify = false;
+ unsigned long flags;
+
+ psgs[nsgs++] = &msg->sg_request;
+ if (msg->sg_request_ext) {
+ psgs[nsgs++] = msg->sg_request_ext;
+ out_nsgs++;
+ }
+ psgs[nsgs++] = &msg->sg_response;
+ if (msg->sg_response_ext) {
+ psgs[nsgs++] = msg->sg_response_ext;
+ in_nsgs++;
+ }
+
+ spin_lock_irqsave(&queue->lock, flags);
+ if (queue->vqueue)
+ rc = virtqueue_add_sgs(queue->vqueue, psgs, out_nsgs, in_nsgs,
+ msg, GFP_ATOMIC);
+ else
+ rc = -EIO;
+ if (!rc) {
+ notify = virtqueue_kick_prepare(queue->vqueue);
+ list_add_tail(&msg->list, &snd->ctl_msgs);
+ }
+ spin_unlock_irqrestore(&queue->lock, flags);
+
+ if (rc)
+ goto on_failure;
+
+ if (notify)
+ if (!virtqueue_notify(queue->vqueue))
+ goto on_failure;
+
+ return 0;
+
+on_failure:
+ virtsnd_ctl_msg_unref(snd->vdev, msg);
+
+ return -EIO;
+}
+
+int virtsnd_ctl_msg_send_sync(struct virtio_snd *snd,
+ struct virtio_snd_msg *msg)
+{
+ int code;
+ struct virtio_device *vdev = snd->vdev;
+ unsigned int js = msecs_to_jiffies(msg_timeout_ms);
+ struct virtio_snd_hdr *response;
+
+ virtsnd_ctl_msg_ref(vdev, msg);
+
+ code = virtsnd_ctl_msg_send(snd, msg);
+ if (code)
+ goto on_failure;
+
+ code = wait_for_completion_interruptible_timeout(&msg->notify, js);
+ if (code <= 0) {
+ if (!code) {
+ dev_err(&vdev->dev, "control message timeout");
+ code = -EIO;
+ }
+
+ goto on_failure;
+ }
+
+ response = sg_virt(&msg->sg_response);
+
+ switch (le32_to_cpu(response->code)) {
+ case VIRTIO_SND_S_OK:
+ code = 0;
+ break;
+ case VIRTIO_SND_S_BAD_MSG:
+ code = -EINVAL;
+ break;
+ case VIRTIO_SND_S_NOT_SUPP:
+ code = -EOPNOTSUPP;
+ break;
+ case VIRTIO_SND_S_IO_ERR:
+ code = -EIO;
+ break;
+ default:
+ code = -EPERM;
+ break;
+ }
+
+on_failure:
+ virtsnd_ctl_msg_unref(vdev, msg);
+
+ return code;
+}
+
+int virtsnd_ctl_query_info(struct virtio_snd *snd, int command, int start_id,
+ int count, size_t size, void *info)
+{
+ struct virtio_device *vdev = snd->vdev;
+ struct virtio_snd_msg *msg;
+ struct virtio_snd_query_info *query;
+ struct scatterlist sg_response_ext;
+
+ msg = virtsnd_ctl_msg_alloc(vdev, sizeof(*query),
+ sizeof(struct virtio_snd_hdr), GFP_KERNEL);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ query = sg_virt(&msg->sg_request);
+ query->hdr.code = cpu_to_virtio32(vdev, command);
+ query->start_id = cpu_to_virtio32(vdev, start_id);
+ query->count = cpu_to_virtio32(vdev, count);
+ query->size = cpu_to_virtio32(vdev, size);
+
+ sg_init_one(&sg_response_ext, info, count * size);
+ msg->sg_response_ext = &sg_response_ext;
+
+ return virtsnd_ctl_msg_send_sync(snd, msg);
+}
+
+void virtsnd_ctl_notify_cb(struct virtqueue *vqueue)
+{
+ struct virtio_snd *snd = vqueue->vdev->priv;
+ struct virtio_snd_queue *queue = virtsnd_control_queue(snd);
+ unsigned long flags;
+
+ spin_lock_irqsave(&queue->lock, flags);
+ while (queue->vqueue) {
+ virtqueue_disable_cb(queue->vqueue);
+
+ for (;;) {
+ struct virtio_snd_msg *msg;
+ u32 length;
+
+ msg = virtqueue_get_buf(queue->vqueue, &length);
+ if (!msg)
+ break;
+
+ list_del(&msg->list);
+ complete(&msg->notify);
+
+ virtsnd_ctl_msg_unref(snd->vdev, msg);
+ }
+
+ if (unlikely(virtqueue_is_broken(queue->vqueue)))
+ break;
+
+ if (virtqueue_enable_cb(queue->vqueue))
+ break;
+ }
+ spin_unlock_irqrestore(&queue->lock, flags);
+}
diff --git a/virtio_snd/virtio_ctl_msg.h b/virtio_snd/virtio_ctl_msg.h
new file mode 100644
index 0000000..8a38bad
--- /dev/null
+++ b/virtio_snd/virtio_ctl_msg.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Sound card driver for virtio
+ * Copyright (C) 2020 OpenSynergy GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef VIRTIO_SND_MSG_H
+#define VIRTIO_SND_MSG_H
+
+#include <linux/atomic.h>
+#include <linux/virtio.h>
+
+struct virtio_snd;
+
+/**
+ * struct virtio_snd_msg - Device message common representation.
+ * @sg_request: Scattergather element containing a device request (header).
+ * @sg_request_ext: Scattergather element containing optinal request payload.
+ * @sg_response: Scattergather element containing a device response (status).
+ * @sg_response_ext: Scattergather element containing optinal response payload.
+ * @notify: Request completed notification.
+ * @ref_count: Reference count used to manage a message lifetime.
+ */
+struct virtio_snd_msg {
+/* public: */
+ struct scatterlist sg_request;
+ struct scatterlist *sg_request_ext;
+ struct scatterlist sg_response;
+ struct scatterlist *sg_response_ext;
+/* private: internal use only */
+ struct list_head list;
+ struct completion notify;
+ atomic_t ref_count;
+};
+
+static inline void virtsnd_ctl_msg_ref(struct virtio_device *vdev,
+ struct virtio_snd_msg *msg)
+{
+ atomic_inc(&msg->ref_count);
+}
+
+static inline void virtsnd_ctl_msg_unref(struct virtio_device *vdev,
+ struct virtio_snd_msg *msg)
+{
+ if (!atomic_dec_return(&msg->ref_count))
+ devm_kfree(&vdev->dev, msg);
+}
+
+struct virtio_snd_msg *virtsnd_ctl_msg_alloc(struct virtio_device *vdev,
+ size_t request_size,
+ size_t response_size, gfp_t gfp);
+
+int virtsnd_ctl_msg_send(struct virtio_snd *snd, struct virtio_snd_msg *msg);
+
+int virtsnd_ctl_msg_send_sync(struct virtio_snd *snd,
+ struct virtio_snd_msg *msg);
+
+int virtsnd_ctl_query_info(struct virtio_snd *snd, int command, int start_id,
+ int count, size_t size, void *info);
+
+void virtsnd_ctl_notify_cb(struct virtqueue *vqueue);
+
+#endif /* VIRTIO_SND_MSG_H */
diff --git a/virtio_snd/virtio_dc.c b/virtio_snd/virtio_dc.c
new file mode 100644
index 0000000..7bc91d8
--- /dev/null
+++ b/virtio_snd/virtio_dc.c
@@ -0,0 +1,377 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Sound card driver for virtio
+ * Copyright (C) 2020 OpenSynergy GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include <sound/control.h>
+#include <linux/virtio_config.h>
+
+#include "virtio_card.h"
+
+/**
+ * struct virtio_kctl - virtio device control representation.
+ * @kctl: Kernel device control.
+ * @info: Device control information.
+ * @enum_values: Values for the ENUMERATED control type.
+ */
+struct virtio_kctl {
+ struct snd_kcontrol *kctl;
+ struct virtio_snd_dc_info *info;
+ struct virtio_snd_dc_enum_value *enum_values;
+};
+
+/**
+ * struct virtio_kctl_ctx - device control context.
+ * @events_enabled: Event handling state.
+ * @kctls: Virtio device controls.
+ * @nkctls: Number of device controls.
+ */
+struct virtio_kctl_ctx {
+ atomic_t events_enabled;
+ struct virtio_kctl *kctls;
+ unsigned int nkctls;
+};
+
+/**
+ * struct virtio_snd_dc_info - virtio device control information.
+ * @events_enabled: Event handling state.
+ * @hdr: Common virtio item information header.
+ * @elem_info: ALSA element information (uapi/sound/asound.h).
+ */
+struct virtio_snd_dc_info {
+ struct virtio_snd_info hdr;
+ struct snd_ctl_elem_info elem_info;
+};
+
+static int virtsnd_dc_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct virtio_snd *snd = kcontrol->private_data;
+ struct virtio_kctl_ctx *ctx = snd->kctl_ctx;
+ struct virtio_kctl *kctl = &ctx->kctls[kcontrol->private_value];
+ struct snd_ctl_elem_info *info = &kctl->info->elem_info;
+
+ if (info->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED) {
+ unsigned int item = uinfo->value.enumerated.item;
+
+ if (item >= info->value.enumerated.items)
+ return -EINVAL;
+
+ strlcpy(info->value.enumerated.name,
+ kctl->enum_values[item].name,
+ sizeof(info->value.enumerated.name));
+ }
+
+ memcpy(uinfo, info, sizeof(*uinfo));
+
+ uinfo->id = info->id;
+
+ return 0;
+}
+
+static int virtsnd_dc_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct virtio_snd *snd = kcontrol->private_data;
+ struct virtio_device *vdev = snd->vdev;
+ struct virtio_snd_msg *msg;
+ struct virtio_snd_dc_hdr *hdr;
+ unsigned int subcid = snd_ctl_get_ioff(kcontrol, &ucontrol->id);
+ struct scatterlist sg;
+
+ msg = virtsnd_ctl_msg_alloc(vdev, sizeof(*hdr),
+ sizeof(struct virtio_snd_hdr), GFP_KERNEL);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ hdr = sg_virt(&msg->sg_request);
+ hdr->hdr.code = cpu_to_virtio32(vdev, VIRTIO_SND_R_DC_READ);
+ hdr->control_id = cpu_to_virtio16(vdev, kcontrol->private_value);
+ hdr->subcontrol_id = cpu_to_virtio16(vdev, subcid);
+
+ sg_init_one(&sg, ucontrol, sizeof(*ucontrol));
+ msg->sg_response_ext = &sg;
+
+ return virtsnd_ctl_msg_send_sync(snd, msg);
+}
+
+static int virtsnd_dc_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct virtio_snd *snd = kcontrol->private_data;
+ struct virtio_device *vdev = snd->vdev;
+ struct virtio_snd_msg *msg;
+ struct virtio_snd_dc_hdr *hdr;
+ unsigned int subcid = snd_ctl_get_ioff(kcontrol, &ucontrol->id);
+ struct scatterlist sg;
+
+ msg = virtsnd_ctl_msg_alloc(vdev, sizeof(*hdr),
+ sizeof(struct virtio_snd_hdr), GFP_KERNEL);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ hdr = sg_virt(&msg->sg_request);
+ hdr->hdr.code = cpu_to_virtio32(vdev, VIRTIO_SND_R_DC_WRITE);
+ hdr->control_id = cpu_to_virtio16(vdev, kcontrol->private_value);
+ hdr->subcontrol_id = cpu_to_virtio16(vdev, subcid);
+
+ sg_init_one(&sg, ucontrol, sizeof(*ucontrol));
+ msg->sg_request_ext = &sg;
+
+ return virtsnd_ctl_msg_send_sync(snd, msg);
+}
+
+static int virtsnd_dc_tlv_op(struct snd_kcontrol *kcontrol, int op_flag,
+ unsigned int size, unsigned int *utlv)
+{
+ struct virtio_snd *snd = kcontrol->private_data;
+ struct virtio_device *vdev = snd->vdev;
+ struct virtio_snd_msg *msg;
+ struct virtio_snd_dc_hdr *hdr;
+ unsigned int cmd = 0;
+ unsigned int *tlv = NULL;
+ struct scatterlist sg_request_ext;
+ struct scatterlist sg_response_ext;
+ int code;
+
+ switch (op_flag) {
+ case SNDRV_CTL_TLV_OP_READ: {
+ cmd = VIRTIO_SND_R_DC_TLV_READ;
+ break;
+ }
+ case SNDRV_CTL_TLV_OP_WRITE: {
+ cmd = VIRTIO_SND_R_DC_TLV_WRITE;
+ break;
+ }
+ case SNDRV_CTL_TLV_OP_CMD: {
+ cmd = VIRTIO_SND_R_DC_TLV_COMMAND;
+ break;
+ }
+ default: {
+ return -EINVAL;
+ }
+ }
+
+ msg = virtsnd_ctl_msg_alloc(vdev, sizeof(*hdr),
+ sizeof(struct virtio_snd_hdr), GFP_KERNEL);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ hdr = sg_virt(&msg->sg_request);
+ hdr->hdr.code = cpu_to_virtio32(vdev, cmd);
+ hdr->control_id = cpu_to_virtio16(vdev, kcontrol->private_value);
+
+ tlv = devm_kzalloc(&vdev->dev, size, GFP_KERNEL);
+ if (!tlv)
+ return -ENOMEM;
+
+ if (cmd == VIRTIO_SND_R_DC_TLV_READ) {
+ sg_init_one(&sg_response_ext, tlv, size);
+ msg->sg_response_ext = &sg_response_ext;
+ } else {
+ if (copy_from_user(tlv, utlv, size)) {
+ code = -EFAULT;
+ goto on_failure;
+ }
+
+ sg_init_one(&sg_request_ext, tlv, size);
+ msg->sg_request_ext = &sg_request_ext;
+ }
+
+ code = virtsnd_ctl_msg_send_sync(snd, msg);
+ if (!code)
+ if (cmd == VIRTIO_SND_R_DC_TLV_READ)
+ if (copy_to_user(utlv, tlv, size))
+ code = -EFAULT;
+
+on_failure:
+ devm_kfree(&vdev->dev, tlv);
+
+ return code;
+}
+
+static int virtsnd_dc_query_enum_info(struct virtio_snd *snd, unsigned int cid,
+ unsigned int nvalues)
+{
+ struct virtio_kctl_ctx *ctx = snd->kctl_ctx;
+ struct virtio_device *vdev = snd->vdev;
+ struct virtio_kctl *kctl = &ctx->kctls[cid];
+ struct virtio_snd_msg *msg;
+ struct virtio_snd_dc_hdr *hdr;
+ struct virtio_snd_dc_enum_value *values;
+ struct scatterlist sg_response_ext;
+ int code;
+
+ values = devm_kcalloc(&vdev->dev, nvalues, sizeof(*values), GFP_KERNEL);
+ if (!values)
+ return -ENOMEM;
+
+ msg = virtsnd_ctl_msg_alloc(vdev, sizeof(*hdr),
+ sizeof(struct virtio_snd_hdr), GFP_KERNEL);
+ if (IS_ERR(msg)) {
+ devm_kfree(&vdev->dev, values);
+ return PTR_ERR(msg);
+ }
+
+ hdr = sg_virt(&msg->sg_request);
+ hdr->hdr.code = cpu_to_virtio32(vdev, VIRTIO_SND_R_DC_ENUM_INFO);
+ hdr->control_id = cpu_to_virtio16(vdev, cid);
+
+ sg_init_one(&sg_response_ext, values, nvalues * sizeof(*values));
+ msg->sg_response_ext = &sg_response_ext;
+
+ code = virtsnd_ctl_msg_send_sync(snd, msg);
+ if (code) {
+ dev_warn(&vdev->dev,
+ "Failed to query enumerated information: %d\n",
+ code);
+ devm_kfree(&vdev->dev, values);
+ return code;
+ }
+
+ kctl->enum_values = values;
+
+ return 0;
+}
+
+static void virtsnd_dc_work(struct work_struct *work)
+{
+ struct virtio_snd *snd =
+ container_of(work, struct virtio_snd, kctl_work);
+ struct virtio_kctl_ctx *ctx = snd->kctl_ctx;
+ struct virtio_device *vdev = snd->vdev;
+ struct virtio_snd_dc_info *info;
+ unsigned int i;
+ unsigned int tlv_mask = SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+ SNDRV_CTL_ELEM_ACCESS_TLV_WRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND;
+ int code;
+
+ info = devm_kcalloc(&vdev->dev, ctx->nkctls, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return;
+
+ code = virtsnd_ctl_query_info(snd, VIRTIO_SND_R_DC_INFO, 0, ctx->nkctls,
+ sizeof(*info), info);
+ if (code) {
+ dev_warn(&vdev->dev,
+ "Failed to query control element information: %d\n",
+ code);
+ devm_kfree(&vdev->dev, info);
+ return;
+ }
+
+ for (i = 0; i < ctx->nkctls; ++i) {
+ struct virtio_kctl *kctl = &ctx->kctls[i];
+ struct snd_ctl_elem_info *elem_info = &info[i].elem_info;
+ struct snd_kcontrol_new kctl_new;
+
+ kctl->info = &info[i];
+
+ if (elem_info->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED) {
+ unsigned int nvalues =
+ elem_info->value.enumerated.items;
+
+ code = virtsnd_dc_query_enum_info(snd, i, nvalues);
+ if (code)
+ continue;
+ }
+
+ memset(&kctl_new, 0, sizeof(kctl_new));
+
+ kctl_new.iface = elem_info->id.iface;
+ if (kctl_new.iface == SNDRV_CTL_ELEM_IFACE_PCM)
+ kctl_new.device = le32_to_cpu(info[i].hdr.hda_fn_nid);
+
+ kctl_new.name = elem_info->id.name;
+ kctl_new.index = elem_info->id.index;
+
+ elem_info->access &= ~(SNDRV_CTL_ELEM_ACCESS_LOCK |
+ SNDRV_CTL_ELEM_ACCESS_OWNER |
+ SNDRV_CTL_ELEM_ACCESS_USER);
+ if (elem_info->access & tlv_mask) {
+ elem_info->access |= SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK;
+ kctl_new.tlv.c = virtsnd_dc_tlv_op;
+ }
+
+ kctl_new.access = elem_info->access;
+
+ kctl_new.info = virtsnd_dc_info;
+ kctl_new.get = virtsnd_dc_get;
+ kctl_new.put = virtsnd_dc_put;
+
+ kctl->kctl = snd_ctl_new1(&kctl_new, snd);
+ if (!kctl->kctl) {
+ dev_warn(&vdev->dev,
+ "Failed to create a control [#%u]\n", i);
+ continue;
+ }
+
+ kctl->kctl->private_value = i;
+
+ code = snd_ctl_add(snd->card, kctl->kctl);
+ if (code)
+ dev_warn(&vdev->dev,
+ "Failed to add a control [#%u]: %d\n", i,
+ code);
+ }
+
+ atomic_set(&ctx->events_enabled, 1);
+}
+
+int virtsnd_dc_parse_cfg(struct virtio_snd *snd)
+{
+ struct virtio_device *vdev = snd->vdev;
+ struct virtio_kctl_ctx *ctx;
+ unsigned int nkctls;
+
+ virtio_cread(vdev, struct virtio_snd_config, controls, &nkctls);
+ if (!nkctls)
+ return 0;
+
+ ctx = devm_kzalloc(&vdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->nkctls = nkctls;
+ ctx->kctls = devm_kcalloc(&vdev->dev, nkctls, sizeof(*ctx->kctls),
+ GFP_KERNEL);
+ if (!ctx->kctls)
+ return -ENOMEM;
+
+ snd->kctl_ctx = ctx;
+ INIT_WORK(&snd->kctl_work, virtsnd_dc_work);
+
+ schedule_work(&snd->kctl_work);
+
+ return 0;
+}
+
+void virtsnd_dc_event(struct virtio_snd *snd, struct virtio_snd_event *event)
+{
+ struct virtio_kctl_ctx *ctx = snd->kctl_ctx;
+ struct virtio_snd_dc_event *dce =
+ (struct virtio_snd_dc_event *)event;
+ struct virtio_kctl *kctl;
+ unsigned int cid = le16_to_cpu(dce->control_id);
+
+ if (!atomic_read(&ctx->events_enabled) || cid >= ctx->nkctls)
+ return;
+
+ kctl = &ctx->kctls[cid];
+
+ snd_ctl_notify(snd->card, le16_to_cpu(dce->mask), &kctl->kctl->id);
+}
diff --git a/virtio_snd/virtio_event.c b/virtio_snd/virtio_event.c
new file mode 100644
index 0000000..22ef6ce
--- /dev/null
+++ b/virtio_snd/virtio_event.c
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Sound card driver for virtio
+ * Copyright (C) 2020 OpenSynergy GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "virtio_card.h"
+
+static void virtsnd_event_dispatch(struct virtio_snd *snd,
+ struct virtio_snd_event *event)
+{
+ switch (le32_to_cpu(event->hdr.code)) {
+ case VIRTIO_SND_EVT_JACK_CONNECTED:
+ case VIRTIO_SND_EVT_JACK_DISCONNECTED: {
+ virtsnd_jack_event(snd, event);
+ break;
+ }
+ case VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED:
+ case VIRTIO_SND_EVT_PCM_XRUN: {
+ virtsnd_pcm_event(snd, event);
+ break;
+ }
+ case VIRTIO_SND_EVT_DC_NOTIFY: {
+ virtsnd_dc_event(snd, event);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+}
+
+static int virtsnd_event_send(struct virtqueue *vqueue,
+ struct virtio_snd_event *event, bool notify,
+ gfp_t gfp)
+{
+ int rc;
+ struct scatterlist sg;
+ struct scatterlist *psgs[1] = { &sg };
+
+ /* reset event content */
+ memset(event, 0, sizeof(*event));
+
+ sg_init_one(&sg, event, sizeof(*event));
+
+ rc = virtqueue_add_sgs(vqueue, psgs, 0, 1, event, gfp);
+ if (rc)
+ return rc;
+
+ if (notify)
+ if (virtqueue_kick_prepare(vqueue))
+ if (!virtqueue_notify(vqueue))
+ return -EIO;
+
+ return 0;
+}
+
+int virtsnd_event_populate(struct virtio_snd *snd)
+{
+ struct virtio_device *vdev = snd->vdev;
+ struct virtqueue *vqueue = virtsnd_event_queue(snd)->vqueue;
+ unsigned int nevents;
+ unsigned int i;
+
+ nevents = virtqueue_get_vring_size(vqueue);
+
+ snd->event_msgs = devm_kcalloc(&vdev->dev, nevents,
+ sizeof(*snd->event_msgs), GFP_KERNEL);
+ if (!snd->event_msgs)
+ return -ENOMEM;
+
+ for (i = 0; i < nevents; ++i) {
+ int rc;
+
+ rc = virtsnd_event_send(vqueue, &snd->event_msgs[i],
+ false, GFP_KERNEL);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+void virtsnd_event_notify_cb(struct virtqueue *vqueue)
+{
+ struct virtio_snd *snd = vqueue->vdev->priv;
+ struct virtio_snd_queue *queue = virtsnd_event_queue(snd);
+ unsigned long flags;
+
+ spin_lock_irqsave(&queue->lock, flags);
+ while (queue->vqueue) {
+ virtqueue_disable_cb(queue->vqueue);
+
+ for (;;) {
+ struct virtio_snd_event *event;
+ u32 length;
+
+ event = virtqueue_get_buf(queue->vqueue, &length);
+ if (!event)
+ break;
+
+ virtsnd_event_dispatch(snd, event);
+
+ virtsnd_event_send(queue->vqueue, event, true,
+ GFP_ATOMIC);
+ }
+
+ if (unlikely(virtqueue_is_broken(queue->vqueue)))
+ break;
+
+ if (virtqueue_enable_cb(queue->vqueue))
+ break;
+ }
+ spin_unlock_irqrestore(&queue->lock, flags);
+}
diff --git a/virtio_snd/virtio_jack.c b/virtio_snd/virtio_jack.c
new file mode 100644
index 0000000..fdc855c
--- /dev/null
+++ b/virtio_snd/virtio_jack.c
@@ -0,0 +1,263 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Sound card driver for virtio
+ * Copyright (C) 2020 OpenSynergy GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/virtio_config.h>
+#include <sound/jack.h>
+#include <sound/hda_verbs.h>
+
+#include "virtio_card.h"
+
+/**
+ * struct virtio_jack - Virtio jack representation.
+ * @jack: Kernel jack control.
+ * @nid: Functional group node identifier.
+ * @features: Jack virtio feature bit map (1 << VIRTIO_SND_JACK_F_XXX).
+ * @defconf: Pin default configuration value.
+ * @caps: Pin capabilities value.
+ * @connected: Current jack connection status.
+ * @type: Kernel jack type (SND_JACK_XXX).
+ */
+struct virtio_jack {
+ struct snd_jack *jack;
+ unsigned int nid;
+ unsigned int features;
+ unsigned int defconf;
+ unsigned int caps;
+ bool connected;
+ int type;
+};
+
+static const char *virtsnd_jack_get_label(struct virtio_jack *jack)
+{
+ unsigned int defconf = jack->defconf;
+ unsigned int device =
+ (defconf & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT;
+ unsigned int location =
+ (defconf & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT;
+
+ switch (device) {
+ case AC_JACK_LINE_OUT:
+ return "Line Out";
+ case AC_JACK_SPEAKER:
+ return "Speaker";
+ case AC_JACK_HP_OUT:
+ return "Headphone";
+ case AC_JACK_CD:
+ return "CD";
+ case AC_JACK_SPDIF_OUT:
+ case AC_JACK_DIG_OTHER_OUT:
+ if (location == AC_JACK_LOC_HDMI)
+ return "HDMI Out";
+ else
+ return "SPDIF Out";
+ case AC_JACK_LINE_IN:
+ return "Line";
+ case AC_JACK_AUX:
+ return "Aux";
+ case AC_JACK_MIC_IN:
+ return "Mic";
+ case AC_JACK_SPDIF_IN:
+ return "SPDIF In";
+ case AC_JACK_DIG_OTHER_IN:
+ return "Digital In";
+ default:
+ return "Misc";
+ }
+}
+
+static int virtsnd_jack_get_type(struct virtio_jack *jack)
+{
+ unsigned int defconf = jack->defconf;
+ unsigned int device =
+ (defconf & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT;
+
+ switch (device) {
+ case AC_JACK_LINE_OUT:
+ case AC_JACK_SPEAKER:
+ return SND_JACK_LINEOUT;
+ case AC_JACK_HP_OUT:
+ return SND_JACK_HEADPHONE;
+ case AC_JACK_SPDIF_OUT:
+ case AC_JACK_DIG_OTHER_OUT:
+ return SND_JACK_AVOUT;
+ case AC_JACK_MIC_IN:
+ return SND_JACK_MICROPHONE;
+ default:
+ return SND_JACK_LINEIN;
+ }
+}
+
+int virtsnd_jack_parse_cfg(struct virtio_snd *snd)
+{
+ struct virtio_device *vdev = snd->vdev;
+ int code;
+ unsigned int i;
+ struct virtio_snd_jack_info *info;
+
+ virtio_cread(vdev, struct virtio_snd_config, jacks, &snd->njacks);
+ if (!snd->njacks)
+ return 0;
+
+ snd->jacks = devm_kcalloc(&vdev->dev, snd->njacks, sizeof(*snd->jacks),
+ GFP_KERNEL);
+ if (!snd->jacks)
+ return -ENOMEM;
+
+ info = devm_kcalloc(&vdev->dev, snd->njacks, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ code = virtsnd_ctl_query_info(snd, VIRTIO_SND_R_JACK_INFO, 0,
+ snd->njacks, sizeof(*info), info);
+ if (code)
+ return code;
+
+ for (i = 0; i < snd->njacks; ++i) {
+ struct virtio_jack *jack = &snd->jacks[i];
+ struct virtio_pcm *pcm;
+
+ jack->nid = le32_to_cpu(info[i].hdr.hda_fn_nid);
+ jack->features = le32_to_cpu(info[i].features);
+ jack->defconf = le32_to_cpu(info[i].hda_reg_defconf);
+ jack->caps = le32_to_cpu(info[i].hda_reg_caps);
+ jack->connected = info[i].connected;
+
+ pcm = virtsnd_pcm_find_or_create(snd, jack->nid);
+ if (IS_ERR(pcm))
+ return PTR_ERR(pcm);
+ }
+
+ devm_kfree(&vdev->dev, info);
+
+ return 0;
+}
+
+static int virtsnd_jack_check_entity_cfg(struct virtio_jack *jack,
+ struct virtio_snd_jack_info *info)
+{
+ if (jack->nid != le32_to_cpu(info->hdr.hda_fn_nid))
+ return -EINVAL;
+ if (jack->defconf != le32_to_cpu(info->hda_reg_defconf))
+ return -EINVAL;
+
+ jack->features = le32_to_cpu(info->features);
+ jack->caps = le32_to_cpu(info->hda_reg_caps);
+ jack->connected = info->connected;
+
+ return 0;
+}
+
+int virtsnd_jack_check_cfg(struct virtio_snd *snd)
+{
+ struct virtio_device *vdev = snd->vdev;
+ int rc;
+ unsigned int i;
+ struct virtio_snd_jack_info *info;
+
+ virtio_cread(vdev, struct virtio_snd_config, jacks, &i);
+ if (snd->njacks != i) {
+ dev_warn(&vdev->dev,
+ "config: number of jacks has changed (%u->%u)",
+ snd->njacks, i);
+ return -EINVAL;
+ }
+
+ if (!snd->njacks)
+ return 0;
+
+ info = devm_kcalloc(&vdev->dev, snd->njacks, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ rc = virtsnd_ctl_query_info(snd, VIRTIO_SND_R_JACK_INFO, 0,
+ snd->njacks, sizeof(*info), info);
+ if (rc)
+ goto on_failure;
+
+ for (i = 0; i < snd->njacks; ++i) {
+ struct virtio_jack *jack = &snd->jacks[i];
+
+ rc = virtsnd_jack_check_entity_cfg(jack, &info[i]);
+ if (rc) {
+ dev_warn(&vdev->dev,
+ "config: jack#%u configuration has changed", i);
+ break;
+ }
+
+ snd_jack_report(jack->jack, jack->connected ? jack->type : 0);
+ }
+
+on_failure:
+ devm_kfree(&vdev->dev, info);
+
+ return rc;
+}
+
+int virtsnd_jack_build_devs(struct virtio_snd *snd)
+{
+ unsigned int i;
+ int code;
+
+ for (i = 0; i < snd->njacks; ++i) {
+ struct virtio_jack *jack = &snd->jacks[i];
+
+ jack->type = virtsnd_jack_get_type(jack);
+
+ code = snd_jack_new(snd->card, virtsnd_jack_get_label(jack),
+ jack->type, &jack->jack, true, true);
+ if (code)
+ return code;
+
+ if (!jack->jack)
+ continue;
+
+ jack->jack->private_data = jack;
+
+ snd_jack_report(jack->jack,
+ jack->connected ? jack->type : 0);
+ }
+
+ return 0;
+}
+
+void virtsnd_jack_event(struct virtio_snd *snd, struct virtio_snd_event *event)
+{
+ unsigned int jack_id = le32_to_cpu(event->data);
+ struct virtio_jack *jack;
+
+ if (jack_id >= snd->njacks)
+ return;
+
+ jack = &snd->jacks[jack_id];
+
+ switch (le32_to_cpu(event->hdr.code)) {
+ case VIRTIO_SND_EVT_JACK_CONNECTED: {
+ jack->connected = true;
+ break;
+ }
+ case VIRTIO_SND_EVT_JACK_DISCONNECTED: {
+ jack->connected = false;
+ break;
+ }
+ default: {
+ return;
+ }
+ }
+
+ snd_jack_report(jack->jack, jack->connected ? jack->type : 0);
+}
diff --git a/virtio_snd/virtio_opsy.h b/virtio_snd/virtio_opsy.h
new file mode 100644
index 0000000..fd8ecee
--- /dev/null
+++ b/virtio_snd/virtio_opsy.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Sound card driver for virtio
+ * Copyright (C) 2020 OpenSynergy GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef VIRTIO_SND_OPSY_H
+#define VIRTIO_SND_OPSY_H
+
+#define VIRTIO_HAS_OPSY_EXTENSION(_snd_, _extension_) \
+ ((_snd_)->extensions & (1U << (VIRTIO_SND_OPSY_F_ ## _extension_)))
+
+int virtsnd_ctl_query_opsy_extensions(struct virtio_snd *snd);
+
+int virtsnd_ctl_alsa_card_info(struct virtio_snd *snd);
+
+int virtsnd_ctl_alsa_pcm_info(struct virtio_snd *snd, struct snd_pcm *pcm);
+
+#endif /* VIRTIO_SND_OPSY_H */
diff --git a/virtio_snd/virtio_opsy_ctl_msg.c b/virtio_snd/virtio_opsy_ctl_msg.c
new file mode 100644
index 0000000..8aa5e12
--- /dev/null
+++ b/virtio_snd/virtio_opsy_ctl_msg.c
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Sound card driver for virtio
+ * Copyright (C) 2020 OpenSynergy GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/virtio_config.h>
+
+#include "virtio_card.h"
+
+int virtsnd_ctl_query_opsy_extensions(struct virtio_snd *snd)
+{
+ struct virtio_device *vdev = snd->vdev;
+ struct virtio_snd_msg *msg;
+ struct virtio_snd_hdr *hdr;
+ struct virtio_snd_opsy_info *info;
+ int code;
+
+ msg = virtsnd_ctl_msg_alloc(vdev, sizeof(*hdr), sizeof(*info),
+ GFP_KERNEL);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ virtsnd_ctl_msg_ref(vdev, msg);
+
+ hdr = sg_virt(&msg->sg_request);
+ hdr->code = cpu_to_virtio32(vdev, VIRTIO_SND_R_OPSY_INFO);
+
+ code = virtsnd_ctl_msg_send_sync(snd, msg);
+ if (code)
+ return code;
+
+ info = sg_virt(&msg->sg_response);
+
+ snd->extensions = le32_to_cpu(info->extensions);
+
+ if (VIRTIO_HAS_OPSY_EXTENSION(snd, DEV_EXT_INFO))
+ dev_info(&vdev->dev,
+ "OpSy extension: ALSA extended device information\n");
+ if (VIRTIO_HAS_OPSY_EXTENSION(snd, DEV_CTLS))
+ dev_info(&vdev->dev, "OpSy extension: ALSA device controls\n");
+
+ virtsnd_ctl_msg_unref(vdev, msg);
+
+ return 0;
+}
+
+int virtsnd_ctl_alsa_card_info(struct virtio_snd *snd)
+{
+ struct virtio_device *vdev = snd->vdev;
+ struct virtio_snd_msg *msg;
+ struct virtio_snd_hdr *hdr;
+ struct virtio_snd_alsa_card_info *info = NULL;
+ struct scatterlist sg_response_ext;
+ int code;
+
+ msg = virtsnd_ctl_msg_alloc(vdev, sizeof(*hdr), sizeof(*hdr),
+ GFP_KERNEL);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ virtsnd_ctl_msg_ref(vdev, msg);
+
+ info = devm_kzalloc(&vdev->dev, sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ code = -ENOMEM;
+ goto on_failure;
+ }
+
+ hdr = sg_virt(&msg->sg_request);
+ hdr->code = cpu_to_virtio32(vdev, VIRTIO_SND_R_ALSA_CARD_INFO);
+
+ sg_init_one(&sg_response_ext, info, sizeof(*info));
+ msg->sg_response_ext = &sg_response_ext;
+
+ code = virtsnd_ctl_msg_send_sync(snd, msg);
+ if (code)
+ goto on_failure;
+
+ virtsnd_strlcpy(snd->card->id, info->id, sizeof(snd->card->id));
+ virtsnd_strlcpy(snd->card->driver, info->driver,
+ sizeof(snd->card->driver));
+ virtsnd_strlcpy(snd->card->shortname, info->name,
+ sizeof(snd->card->shortname));
+ virtsnd_strlcpy(snd->card->longname, info->longname,
+ sizeof(snd->card->longname));
+ virtsnd_strlcpy(snd->card->mixername, info->mixername,
+ sizeof(snd->card->mixername));
+ virtsnd_strlcpy(snd->card->components, info->components,
+ sizeof(snd->card->components));
+
+on_failure:
+ if (info)
+ devm_kfree(&vdev->dev, info);
+
+ virtsnd_ctl_msg_unref(vdev, msg);
+
+ return code;
+}
+
+int virtsnd_ctl_alsa_pcm_info(struct virtio_snd *snd, struct snd_pcm *pcm)
+{
+ struct virtio_device *vdev = snd->vdev;
+ struct virtio_snd_msg *msg;
+ struct virtio_snd_alsa_query_pcm_info *hdr;
+ struct virtio_snd_alsa_pcm_info *info = NULL;
+ struct scatterlist sg_response_ext;
+ int code;
+
+ msg = virtsnd_ctl_msg_alloc(vdev, sizeof(*hdr),
+ sizeof(struct virtio_snd_hdr), GFP_KERNEL);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ virtsnd_ctl_msg_ref(vdev, msg);
+
+ info = devm_kzalloc(&vdev->dev, sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ code = -ENOMEM;
+ goto on_failure;
+ }
+
+ hdr = sg_virt(&msg->sg_request);
+ hdr->hdr.code = cpu_to_virtio32(vdev, VIRTIO_SND_R_ALSA_PCM_INFO);
+ hdr->hda_fn_nid = cpu_to_virtio32(vdev, pcm->device);
+
+ sg_init_one(&sg_response_ext, info, sizeof(*info));
+ msg->sg_response_ext = &sg_response_ext;
+
+ code = virtsnd_ctl_msg_send_sync(snd, msg);
+ if (code)
+ goto on_failure;
+
+ virtsnd_strlcpy(pcm->id, info->id, sizeof(pcm->id));
+ virtsnd_strlcpy(pcm->name, info->name, sizeof(pcm->name));
+
+on_failure:
+ if (info)
+ devm_kfree(&vdev->dev, info);
+
+ virtsnd_ctl_msg_unref(vdev, msg);
+
+ return 0;
+}
diff --git a/virtio_snd/virtio_pcm.c b/virtio_snd/virtio_pcm.c
new file mode 100644
index 0000000..4c699b5
--- /dev/null
+++ b/virtio_snd/virtio_pcm.c
@@ -0,0 +1,620 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Sound card driver for virtio
+ * Copyright (C) 2020 OpenSynergy GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/moduleparam.h>
+#include <linux/virtio_config.h>
+
+#include "virtio_card.h"
+
+static unsigned int pcm_buffer_ms = 160;
+module_param(pcm_buffer_ms, uint, 0644);
+MODULE_PARM_DESC(pcm_buffer_ms, "PCM substream buffer time in milliseconds");
+
+static unsigned int pcm_periods_min = 2;
+module_param(pcm_periods_min, uint, 0644);
+MODULE_PARM_DESC(pcm_periods_min, "Minimum number of PCM periods");
+
+static unsigned int pcm_periods_max = 16;
+module_param(pcm_periods_max, uint, 0644);
+MODULE_PARM_DESC(pcm_periods_max, "Maximum number of PCM periods");
+
+static unsigned int pcm_period_ms_min = 10;
+module_param(pcm_period_ms_min, uint, 0644);
+MODULE_PARM_DESC(pcm_period_ms_min, "Minimum PCM period time in milliseconds");
+
+static unsigned int pcm_period_ms_max = 80;
+module_param(pcm_period_ms_max, uint, 0644);
+MODULE_PARM_DESC(pcm_period_ms_max, "Maximum PCM period time in milliseconds");
+
+static const unsigned int g_v2a_format_map[] = {
+ [VIRTIO_SND_PCM_FMT_IMA_ADPCM] = SNDRV_PCM_FORMAT_IMA_ADPCM,
+ [VIRTIO_SND_PCM_FMT_MU_LAW] = SNDRV_PCM_FORMAT_MU_LAW,
+ [VIRTIO_SND_PCM_FMT_A_LAW] = SNDRV_PCM_FORMAT_A_LAW,
+ [VIRTIO_SND_PCM_FMT_S8] = SNDRV_PCM_FORMAT_S8,
+ [VIRTIO_SND_PCM_FMT_U8] = SNDRV_PCM_FORMAT_U8,
+ [VIRTIO_SND_PCM_FMT_S16] = SNDRV_PCM_FORMAT_S16_LE,
+ [VIRTIO_SND_PCM_FMT_U16] = SNDRV_PCM_FORMAT_U16_LE,
+ [VIRTIO_SND_PCM_FMT_S18_3] = SNDRV_PCM_FORMAT_S18_3LE,
+ [VIRTIO_SND_PCM_FMT_U18_3] = SNDRV_PCM_FORMAT_U18_3LE,
+ [VIRTIO_SND_PCM_FMT_S20_3] = SNDRV_PCM_FORMAT_S20_3LE,
+ [VIRTIO_SND_PCM_FMT_U20_3] = SNDRV_PCM_FORMAT_U20_3LE,
+ [VIRTIO_SND_PCM_FMT_S24_3] = SNDRV_PCM_FORMAT_S24_3LE,
+ [VIRTIO_SND_PCM_FMT_U24_3] = SNDRV_PCM_FORMAT_U24_3LE,
+#ifdef SNDRV_PCM_FORMAT_S20
+ [VIRTIO_SND_PCM_FMT_S20] = SNDRV_PCM_FORMAT_S20_LE,
+#endif
+#ifdef SNDRV_PCM_FORMAT_U20
+ [VIRTIO_SND_PCM_FMT_U20] = SNDRV_PCM_FORMAT_U20_LE,
+#endif
+ [VIRTIO_SND_PCM_FMT_S24] = SNDRV_PCM_FORMAT_S24_LE,
+ [VIRTIO_SND_PCM_FMT_U24] = SNDRV_PCM_FORMAT_U24_LE,
+ [VIRTIO_SND_PCM_FMT_S32] = SNDRV_PCM_FORMAT_S32_LE,
+ [VIRTIO_SND_PCM_FMT_U32] = SNDRV_PCM_FORMAT_U32_LE,
+ [VIRTIO_SND_PCM_FMT_FLOAT] = SNDRV_PCM_FORMAT_FLOAT_LE,
+ [VIRTIO_SND_PCM_FMT_FLOAT64] = SNDRV_PCM_FORMAT_FLOAT64_LE,
+ [VIRTIO_SND_PCM_FMT_DSD_U8] = SNDRV_PCM_FORMAT_DSD_U8,
+ [VIRTIO_SND_PCM_FMT_DSD_U16] = SNDRV_PCM_FORMAT_DSD_U16_LE,
+ [VIRTIO_SND_PCM_FMT_DSD_U32] = SNDRV_PCM_FORMAT_DSD_U32_LE,
+ [VIRTIO_SND_PCM_FMT_IEC958_SUBFRAME] =
+ SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE
+};
+
+struct virtsnd_v2a_rate {
+ unsigned int alsa_bit;
+ unsigned int rate;
+};
+
+static const struct virtsnd_v2a_rate g_v2a_rate_map[] = {
+ [VIRTIO_SND_PCM_RATE_5512] = { SNDRV_PCM_RATE_5512, 5512 },
+ [VIRTIO_SND_PCM_RATE_8000] = { SNDRV_PCM_RATE_8000, 8000 },
+ [VIRTIO_SND_PCM_RATE_11025] = { SNDRV_PCM_RATE_11025, 11025 },
+ [VIRTIO_SND_PCM_RATE_16000] = { SNDRV_PCM_RATE_16000, 16000 },
+ [VIRTIO_SND_PCM_RATE_22050] = { SNDRV_PCM_RATE_22050, 22050 },
+ [VIRTIO_SND_PCM_RATE_32000] = { SNDRV_PCM_RATE_32000, 32000 },
+ [VIRTIO_SND_PCM_RATE_44100] = { SNDRV_PCM_RATE_44100, 44100 },
+ [VIRTIO_SND_PCM_RATE_48000] = { SNDRV_PCM_RATE_48000, 48000 },
+ [VIRTIO_SND_PCM_RATE_64000] = { SNDRV_PCM_RATE_64000, 64000 },
+ [VIRTIO_SND_PCM_RATE_88200] = { SNDRV_PCM_RATE_88200, 88200 },
+ [VIRTIO_SND_PCM_RATE_96000] = { SNDRV_PCM_RATE_96000, 96000 },
+ [VIRTIO_SND_PCM_RATE_176400] = { SNDRV_PCM_RATE_176400, 176400 },
+ [VIRTIO_SND_PCM_RATE_192000] = { SNDRV_PCM_RATE_192000, 192000 }
+};
+
+static int virtsnd_pcm_build_hw(struct virtio_pcm_substream *substream,
+ struct virtio_snd_pcm_info *info)
+{
+ struct virtio_device *vdev = substream->snd->vdev;
+ unsigned int i;
+ u64 values;
+ size_t sample_max = 0;
+ size_t sample_min = 0;
+
+ substream->features = le32_to_cpu(info->features);
+
+ /*
+ * TODO: set SNDRV_PCM_INFO_{BATCH,BLOCK_TRANSFER} if device supports
+ * only message-based transport.
+ */
+ substream->hw.info =
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_BATCH |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_PAUSE;
+
+ if (!info->channels_min || info->channels_min > info->channels_max) {
+ dev_err(&vdev->dev,
+ "SID %u: invalid channel range [%u %u]", substream->sid,
+ info->channels_min, info->channels_max);
+ return -EINVAL;
+ }
+
+ substream->hw.channels_min = info->channels_min;
+ substream->hw.channels_max = info->channels_max;
+
+ values = le64_to_cpu(info->formats);
+
+ for (i = 0; i < ARRAY_SIZE(g_v2a_format_map); ++i)
+ if (values & (1ULL << i)) {
+ unsigned int alsa_fmt = g_v2a_format_map[i];
+ int bytes = snd_pcm_format_physical_width(alsa_fmt) / 8;
+
+ if (!sample_min || sample_min > bytes)
+ sample_min = bytes;
+
+ if (sample_max < bytes)
+ sample_max = bytes;
+
+ substream->hw.formats |= (1ULL << alsa_fmt);
+ }
+
+ if (!substream->hw.formats) {
+ dev_err(&vdev->dev,
+ "SID %u: no supported PCM sample formats found",
+ substream->sid);
+ return -EINVAL;
+ }
+
+ values = le64_to_cpu(info->rates);
+
+ for (i = 0; i < ARRAY_SIZE(g_v2a_rate_map); ++i)
+ if (values & (1ULL << i)) {
+ if (!substream->hw.rate_min ||
+ substream->hw.rate_min > g_v2a_rate_map[i].rate)
+ substream->hw.rate_min = g_v2a_rate_map[i].rate;
+
+ if (substream->hw.rate_max < g_v2a_rate_map[i].rate)
+ substream->hw.rate_max = g_v2a_rate_map[i].rate;
+
+ substream->hw.rates |= g_v2a_rate_map[i].alsa_bit;
+ }
+
+ if (!substream->hw.rates) {
+ dev_err(&vdev->dev,
+ "SID %u: no supported PCM frame rates found",
+ substream->sid);
+ return -EINVAL;
+ }
+
+ substream->hw.periods_min = pcm_periods_min;
+ substream->hw.periods_max = pcm_periods_max;
+
+ /*
+ * We must ensure that there is enough space in the buffer to store
+ * pcm_buffer_ms ms for the combination (Cmax, Smax, Rmax), where:
+ * Cmax = maximum supported number of channels,
+ * Smax = maximum supported sample size in bytes,
+ * Rmax = maximum supported frame rate.
+ */
+ substream->hw.buffer_bytes_max =
+ sample_max * substream->hw.channels_max * pcm_buffer_ms *
+ (substream->hw.rate_max / MSEC_PER_SEC);
+
+ /* Align the buffer size to the page size */
+ substream->hw.buffer_bytes_max =
+ (substream->hw.buffer_bytes_max + PAGE_SIZE - 1) & -PAGE_SIZE;
+
+ /*
+ * We must ensure that the minimum period size is enough to store
+ * pcm_period_ms_min ms for the combination (Cmin, Smin, Rmin), where:
+ * Cmin = minimum supported number of channels,
+ * Smin = minimum supported sample size in bytes,
+ * Rmin = minimum supported frame rate.
+ */
+ substream->hw.period_bytes_min =
+ sample_min * substream->hw.channels_min * pcm_period_ms_min *
+ (substream->hw.rate_min / MSEC_PER_SEC);
+
+ /*
+ * We must ensure that the maximum period size is enough to store
+ * pcm_period_ms_max ms for the combination (Cmax, Smax, Rmax).
+ */
+ substream->hw.period_bytes_max =
+ sample_max * substream->hw.channels_max * pcm_period_ms_max *
+ (substream->hw.rate_max / MSEC_PER_SEC);
+
+ return 0;
+}
+
+static void virtsnd_pcm_prealloc_pages(struct virtio_pcm_substream *substream)
+{
+ struct snd_pcm_substream *ksubstream = substream->substream;
+ size_t size = substream->hw.buffer_bytes_max;
+ struct device *data = snd_dma_continuous_data(GFP_KERNEL);
+
+ snd_pcm_lib_preallocate_pages(ksubstream,
+ SNDRV_DMA_TYPE_CONTINUOUS, data,
+ size, size);
+}
+
+struct virtio_pcm *virtsnd_pcm_find(struct virtio_snd *snd, unsigned int nid)
+{
+ struct virtio_pcm *pcm;
+
+ list_for_each_entry(pcm, &snd->pcm_list, list)
+ if (pcm->nid == nid)
+ return pcm;
+
+ return ERR_PTR(-ENOENT);
+}
+
+struct virtio_pcm *virtsnd_pcm_find_or_create(struct virtio_snd *snd,
+ unsigned int nid)
+{
+ struct virtio_device *vdev = snd->vdev;
+ struct virtio_pcm *pcm;
+
+ pcm = virtsnd_pcm_find(snd, nid);
+ if (!IS_ERR(pcm))
+ return pcm;
+
+ pcm = devm_kzalloc(&vdev->dev, sizeof(*pcm), GFP_KERNEL);
+ if (!pcm)
+ return ERR_PTR(-ENOMEM);
+
+ pcm->nid = nid;
+ list_add_tail(&pcm->list, &snd->pcm_list);
+
+ return pcm;
+}
+
+int virtsnd_pcm_validate(struct virtio_device *vdev)
+{
+ if (pcm_periods_min < 2 || pcm_periods_min > pcm_periods_max) {
+ dev_err(&vdev->dev,
+ "invalid range [%u %u] of the number of PCM periods",
+ pcm_periods_min, pcm_periods_max);
+ return -EINVAL;
+ }
+
+ if (!pcm_period_ms_min || pcm_period_ms_min > pcm_period_ms_max) {
+ dev_err(&vdev->dev,
+ "invalid range [%u %u] of the size of the PCM period",
+ pcm_period_ms_min, pcm_period_ms_max);
+ return -EINVAL;
+ }
+
+ if (pcm_buffer_ms < pcm_periods_min * pcm_period_ms_min) {
+ dev_err(&vdev->dev,
+ "pcm_buffer_ms(=%u) value cannot be < %u ms",
+ pcm_buffer_ms, pcm_periods_min * pcm_period_ms_min);
+ return -EINVAL;
+ }
+
+ if (pcm_period_ms_max > pcm_buffer_ms / 2) {
+ dev_err(&vdev->dev,
+ "pcm_period_ms_max(=%u) value cannot be > %u ms",
+ pcm_period_ms_max, pcm_buffer_ms / 2);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int virtsnd_pcm_parse_cfg(struct virtio_snd *snd)
+{
+ struct virtio_device *vdev = snd->vdev;
+ struct virtio_snd_pcm_info *info;
+ unsigned int i;
+ int rc;
+
+ virtio_cread(vdev, struct virtio_snd_config, streams,
+ &snd->nsubstreams);
+ if (!snd->nsubstreams)
+ return 0;
+
+ snd->substreams = devm_kcalloc(&vdev->dev, snd->nsubstreams,
+ sizeof(*snd->substreams), GFP_KERNEL);
+ if (!snd->substreams)
+ return -ENOMEM;
+
+ info = devm_kcalloc(&vdev->dev, snd->nsubstreams, sizeof(*info),
+ GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ rc = virtsnd_ctl_query_info(snd, VIRTIO_SND_R_PCM_INFO, 0,
+ snd->nsubstreams, sizeof(*info), info);
+ if (rc)
+ return rc;
+
+ for (i = 0; i < snd->nsubstreams; ++i) {
+ struct virtio_pcm_substream *substream = &snd->substreams[i];
+ struct virtio_pcm *pcm;
+
+ substream->snd = snd;
+ substream->sid = i;
+ init_waitqueue_head(&substream->msg_empty);
+
+ rc = virtsnd_pcm_build_hw(substream, &info[i]);
+ if (rc)
+ return rc;
+
+ substream->nid = le32_to_cpu(info[i].hdr.hda_fn_nid);
+
+ pcm = virtsnd_pcm_find_or_create(snd, substream->nid);
+ if (IS_ERR(pcm))
+ return PTR_ERR(pcm);
+
+ switch (info[i].direction) {
+ case VIRTIO_SND_D_OUTPUT: {
+ substream->direction = SNDRV_PCM_STREAM_PLAYBACK;
+ break;
+ }
+ case VIRTIO_SND_D_INPUT: {
+ substream->direction = SNDRV_PCM_STREAM_CAPTURE;
+ break;
+ }
+ default: {
+ dev_err(&vdev->dev, "SID %u: unknown direction (%u)",
+ substream->sid, info[i].direction);
+ return -EINVAL;
+ }
+ }
+
+ pcm->streams[substream->direction].nsubstreams++;
+ }
+
+ devm_kfree(&vdev->dev, info);
+
+ return 0;
+}
+
+static int virtsnd_pcm_check_entity_cfg(struct virtio_pcm_substream *substream,
+ struct virtio_snd_pcm_info *info)
+{
+ unsigned int i;
+ u64 values;
+ u64 formats = 0;
+ u64 rates = 0;
+ bool changed = false;
+ int rc;
+
+ if (substream->nid != le32_to_cpu(info->hdr.hda_fn_nid))
+ return -EINVAL;
+ if (substream->direction != info->direction)
+ return -EINVAL;
+
+ if (substream->features != le32_to_cpu(info->features))
+ changed = true;
+ if (substream->hw.channels_min != info->channels_min)
+ changed = true;
+ if (substream->hw.channels_max != info->channels_max)
+ changed = true;
+
+ values = le64_to_cpu(info->formats);
+
+ for (i = 0; i < ARRAY_SIZE(g_v2a_format_map); ++i)
+ if (values & (1ULL << i))
+ formats |= (1ULL << g_v2a_format_map[i]);
+
+ if (substream->hw.formats != formats)
+ changed = true;
+
+ values = le64_to_cpu(info->rates);
+
+ for (i = 0; i < ARRAY_SIZE(g_v2a_rate_map); ++i)
+ if (values & (1ULL << i))
+ rates |= g_v2a_rate_map[i].alsa_bit;
+
+ if (substream->hw.rates != rates)
+ changed = true;
+
+ if (changed) {
+ struct snd_pcm_runtime *runtime = substream->substream->runtime;
+ size_t buffer_bytes_max = substream->hw.buffer_bytes_max;
+
+ if (runtime && runtime->status &&
+ runtime->status->state != SNDRV_PCM_STATE_OPEN)
+ return -EINVAL;
+
+ rc = virtsnd_pcm_build_hw(substream, info);
+ if (rc)
+ return rc;
+
+ if (buffer_bytes_max < substream->hw.buffer_bytes_max)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int virtsnd_pcm_check_cfg(struct virtio_snd *snd)
+{
+ struct virtio_device *vdev = snd->vdev;
+ struct virtio_snd_pcm_info *info;
+ unsigned int i;
+ int rc;
+
+ virtio_cread(vdev, struct virtio_snd_config, streams, &i);
+ if (snd->nsubstreams != i) {
+ dev_warn(&vdev->dev,
+ "config: number of streams has changed (%u->%u)",
+ snd->nsubstreams, i);
+ return -EINVAL;
+ }
+
+ if (!snd->nsubstreams)
+ return 0;
+
+ info = devm_kcalloc(&vdev->dev, snd->nsubstreams, sizeof(*info),
+ GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ rc = virtsnd_ctl_query_info(snd, VIRTIO_SND_R_PCM_INFO, 0,
+ snd->nsubstreams, sizeof(*info), info);
+ if (rc)
+ goto on_failure;
+
+ for (i = 0; i < snd->nsubstreams; ++i) {
+ rc = virtsnd_pcm_check_entity_cfg(&snd->substreams[i],
+ &info[i]);
+ if (rc) {
+ dev_warn(&vdev->dev,
+ "config: stream#%u configuration has changed",
+ i);
+ break;
+ }
+ }
+
+on_failure:
+ devm_kfree(&vdev->dev, info);
+
+ return rc;
+}
+
+static int virtsnd_pcm_info(struct virtio_snd *snd, struct snd_pcm *pcm)
+{
+ if (VIRTIO_HAS_OPSY_EXTENSION(snd, DEV_EXT_INFO)) {
+ int code;
+
+ code = virtsnd_ctl_alsa_pcm_info(snd, pcm);
+ if (!code || code != -EOPNOTSUPP)
+ return code;
+ }
+
+ strlcpy(pcm->name, "VirtIO PCM", sizeof(pcm->name));
+
+ return 0;
+}
+
+int virtsnd_pcm_build_devs(struct virtio_snd *snd)
+{
+ struct virtio_device *vdev = snd->vdev;
+ struct virtio_pcm *pcm;
+ unsigned int i;
+ int code;
+
+ list_for_each_entry(pcm, &snd->pcm_list, list) {
+ unsigned int npbs =
+ pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].nsubstreams;
+ unsigned int ncps =
+ pcm->streams[SNDRV_PCM_STREAM_CAPTURE].nsubstreams;
+
+ if (!npbs && !ncps)
+ continue;
+
+ code = snd_pcm_new(snd->card, "virtio_snd", pcm->nid, npbs,
+ ncps, &pcm->pcm);
+ if (code) {
+ dev_err(&vdev->dev, "snd_pcm_new[%u] failed: %d",
+ pcm->nid, code);
+ return code;
+ }
+
+ code = virtsnd_pcm_info(snd, pcm->pcm);
+ if (code)
+ return code;
+
+ pcm->pcm->info_flags = 0;
+ pcm->pcm->dev_class = SNDRV_PCM_CLASS_GENERIC;
+ pcm->pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
+
+ pcm->pcm->private_data = pcm;
+
+ for (i = 0; i < ARRAY_SIZE(pcm->streams); ++i) {
+ struct virtio_pcm_stream *stream = &pcm->streams[i];
+
+ if (!stream->nsubstreams)
+ continue;
+
+ stream->substreams =
+ devm_kcalloc(&vdev->dev,
+ stream->nsubstreams,
+ sizeof(*stream->substreams),
+ GFP_KERNEL);
+ if (!stream->substreams)
+ return -ENOMEM;
+
+ stream->nsubstreams = 0;
+ }
+ }
+
+ for (i = 0; i < snd->nsubstreams; ++i) {
+ struct virtio_pcm_substream *substream = &snd->substreams[i];
+ struct virtio_pcm_stream *stream;
+
+ pcm = virtsnd_pcm_find(snd, substream->nid);
+ if (IS_ERR(pcm))
+ return PTR_ERR(pcm);
+
+ stream = &pcm->streams[substream->direction];
+ stream->substreams[stream->nsubstreams++] = substream;
+ }
+
+ list_for_each_entry(pcm, &snd->pcm_list, list)
+ for (i = 0; i < ARRAY_SIZE(pcm->streams); ++i) {
+ struct virtio_pcm_stream *stream = &pcm->streams[i];
+ struct snd_pcm_str *kstream;
+ struct snd_pcm_substream *ksubstream;
+
+ if (!stream->nsubstreams)
+ continue;
+
+ kstream = &pcm->pcm->streams[i];
+ ksubstream = kstream->substream;
+
+ while (ksubstream) {
+ struct virtio_pcm_substream *substream =
+ stream->substreams[ksubstream->number];
+
+ substream->substream = ksubstream;
+ ksubstream = ksubstream->next;
+
+ virtsnd_pcm_prealloc_pages(substream);
+ }
+
+ snd_pcm_set_ops(pcm->pcm, i, &virtsnd_pcm_ops);
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+int virtsnd_pcm_restore(struct virtio_snd *snd)
+{
+ unsigned int i;
+
+ for (i = 0; i < snd->nsubstreams; ++i) {
+ struct snd_pcm_substream *substream =
+ snd->substreams[i].substream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int rc;
+
+ if (!runtime || !runtime->status ||
+ runtime->status->state != SNDRV_PCM_STATE_SUSPENDED)
+ continue;
+
+ rc = substream->ops->hw_params(substream, NULL);
+ if (rc)
+ return rc;
+
+ rc = substream->ops->prepare(substream);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+void virtsnd_pcm_event(struct virtio_snd *snd, struct virtio_snd_event *event)
+{
+ struct virtio_pcm_substream *substream;
+ unsigned int sid = le32_to_cpu(event->data);
+
+ if (sid >= snd->nsubstreams)
+ return;
+
+ substream = &snd->substreams[sid];
+
+ switch (le32_to_cpu(event->hdr.code)) {
+ case VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED: {
+ /* TODO: deal with shmem elapsed period */
+ break;
+ }
+ case VIRTIO_SND_EVT_PCM_XRUN: {
+ if (atomic_read(&substream->xfer_enabled))
+ atomic_set(&substream->xfer_xrun, 1);
+ break;
+ }
+ }
+}
diff --git a/virtio_snd/virtio_pcm.h b/virtio_snd/virtio_pcm.h
new file mode 100644
index 0000000..eb0a03a
--- /dev/null
+++ b/virtio_snd/virtio_pcm.h
@@ -0,0 +1,125 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Sound card driver for virtio
+ * Copyright (C) 2020 OpenSynergy GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef VIRTIO_SND_PCM_H
+#define VIRTIO_SND_PCM_H
+
+#include <linux/atomic.h>
+#include <linux/virtio_config.h>
+#include <sound/pcm.h>
+
+struct virtio_pcm;
+struct virtio_pcm_msg;
+
+/**
+ * struct virtio_pcm_substream - virtio PCM substream representation.
+ * @snd: Virtio sound card device.
+ * @nid: Functional group node identifier.
+ * @sid: Stream identifier.
+ * @direction: Stream data flow direction (VIRTIO_SND_D_XXX).
+ * @features: Stream virtio feature bit map (1 << VIRTIO_SND_PCM_F_XXX).
+ * @substream: Kernel substream.
+ * @hw: Kernel substream hardware descriptor.
+ * @hw_ptr: Substream hardware pointer value.
+ * @xfer_enabled: Data transfer state.
+ * @xfer_draining: Data draining state.
+ * @xfer_xrun: Data underflow/overflow state.
+ * @msg_list: Pending I/O message list.
+ * @msg_empty: Notify when msg_list is empty.
+ */
+struct virtio_pcm_substream {
+ struct virtio_snd *snd;
+ unsigned int nid;
+ unsigned int sid;
+ u32 direction;
+ u32 features;
+ struct snd_pcm_substream *substream;
+ struct snd_pcm_hardware hw;
+ atomic_t hw_ptr;
+ atomic_t xfer_enabled;
+ atomic_t xfer_xrun;
+ struct virtio_pcm_msg *msgs;
+ int msg_last_enqueued;
+ atomic_t msg_count;
+ wait_queue_head_t msg_empty;
+};
+
+/**
+ * struct virtio_pcm_stream - virtio PCM stream representation.
+ * @substreams: Virtio substreams belonging to the stream.
+ * @nsubstreams: Number of substreams.
+ * @chmaps: Kernel channel maps belonging to the stream.
+ * @nchmaps: Number of channel maps.
+ */
+struct virtio_pcm_stream {
+ struct virtio_pcm_substream **substreams;
+ unsigned int nsubstreams;
+ struct snd_pcm_chmap_elem *chmaps;
+ unsigned int nchmaps;
+};
+
+/**
+ * struct virtio_pcm - virtio PCM device representation.
+ * @list: PCM list entry.
+ * @nid: Functional group node identifier.
+ * @pcm: Kernel PCM device.
+ * @streams: Virtio streams (playback and capture).
+ */
+struct virtio_pcm {
+ struct list_head list;
+ unsigned int nid;
+ struct snd_pcm *pcm;
+ struct virtio_pcm_stream streams[SNDRV_PCM_STREAM_LAST + 1];
+};
+
+extern const struct snd_pcm_ops virtsnd_pcm_ops;
+
+int virtsnd_pcm_validate(struct virtio_device *vdev);
+
+int virtsnd_pcm_parse_cfg(struct virtio_snd *snd);
+
+int virtsnd_pcm_check_cfg(struct virtio_snd *snd);
+
+int virtsnd_pcm_build_devs(struct virtio_snd *snd);
+
+#ifdef CONFIG_PM_SLEEP
+int virtsnd_pcm_restore(struct virtio_snd *snd);
+#endif /* CONFIG_PM_SLEEP */
+
+void virtsnd_pcm_event(struct virtio_snd *snd, struct virtio_snd_event *event);
+
+void virtsnd_pcm_tx_notify_cb(struct virtqueue *vqueue);
+
+void virtsnd_pcm_rx_notify_cb(struct virtqueue *vqueue);
+
+struct virtio_pcm *virtsnd_pcm_find(struct virtio_snd *snd, unsigned int nid);
+
+struct virtio_pcm *virtsnd_pcm_find_or_create(struct virtio_snd *snd,
+ unsigned int nid);
+
+struct virtio_snd_msg *
+virtsnd_pcm_ctl_msg_alloc(struct virtio_pcm_substream *substream,
+ unsigned int command, gfp_t gfp);
+
+int virtsnd_pcm_msg_alloc(struct virtio_pcm_substream *substream,
+ unsigned int nmsg, u8 *dma_area,
+ unsigned int period_bytes);
+
+int virtsnd_pcm_msg_send(struct virtio_pcm_substream *substream);
+
+#endif /* VIRTIO_SND_PCM_H */
diff --git a/virtio_snd/virtio_pcm_msg.c b/virtio_snd/virtio_pcm_msg.c
new file mode 100644
index 0000000..6b7839e
--- /dev/null
+++ b/virtio_snd/virtio_pcm_msg.c
@@ -0,0 +1,256 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Sound card driver for virtio
+ * Copyright (C) 2020 OpenSynergy GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include <sound/pcm_params.h>
+
+#include "virtio_card.h"
+
+/**
+ * enum pcm_msg_sg_index - Scatter-gather element indexes for an I/O message
+ * @PCM_MSG_SG_XFER: Element containing a virtio_snd_pcm_xfer structure
+ * @PCM_MSG_SG_DATA: Element containing a data buffer
+ * @PCM_MSG_SG_STATUS: Element containing a virtio_snd_pcm_status structure
+ * @PCM_MSG_SG_MAX: The maximum number of elements in the scatter-gather table
+ *
+ * These values are used as the index of the scatter-gather table.
+ */
+enum pcm_msg_sg_index {
+ PCM_MSG_SG_XFER = 0,
+ PCM_MSG_SG_DATA,
+ PCM_MSG_SG_STATUS,
+ PCM_MSG_SG_MAX
+};
+
+/**
+ * struct virtio_pcm_msg - I/O message representation
+ * @list: Pending I/O message list entry
+ * @stream: Pointer to virtio PCM stream structure
+ * @xfer: I/O message header payload
+ * @status: I/O message status payload
+ * @one_shot_data: if the message should not be resent to the device, the field
+ * contains a pointer to the optional payload that should be
+ * released after completion
+ * @sgs: I/O message payload scatter-gather table
+ */
+struct virtio_pcm_msg {
+ struct virtio_pcm_substream *substream;
+ struct virtio_snd_pcm_xfer xfer;
+ struct virtio_snd_pcm_status status;
+ struct scatterlist sgs[PCM_MSG_SG_MAX];
+};
+
+int virtsnd_pcm_msg_alloc(struct virtio_pcm_substream *substream,
+ unsigned int nmsg, u8 *dma_area,
+ unsigned int period_bytes)
+{
+ struct virtio_device *vdev = substream->snd->vdev;
+ unsigned int i;
+
+ if (substream->msgs)
+ devm_kfree(&vdev->dev, substream->msgs);
+
+ substream->msgs = devm_kcalloc(&vdev->dev, nmsg,
+ sizeof(*substream->msgs), GFP_KERNEL);
+ if (!substream->msgs)
+ return -ENOMEM;
+
+ for (i = 0; i < nmsg; ++i) {
+ struct virtio_pcm_msg *msg = &substream->msgs[i];
+
+ msg->substream = substream;
+
+ sg_init_table(msg->sgs, PCM_MSG_SG_MAX);
+ sg_init_one(&msg->sgs[PCM_MSG_SG_XFER], &msg->xfer,
+ sizeof(msg->xfer));
+ sg_init_one(&msg->sgs[PCM_MSG_SG_DATA],
+ dma_area + period_bytes * i, period_bytes);
+ sg_init_one(&msg->sgs[PCM_MSG_SG_STATUS], &msg->status,
+ sizeof(msg->status));
+ }
+
+ return 0;
+}
+
+int virtsnd_pcm_msg_send(struct virtio_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->substream->runtime;
+ struct virtio_snd *snd = substream->snd;
+ struct virtio_device *vdev = snd->vdev;
+ struct virtqueue *vqueue = virtsnd_pcm_queue(substream)->vqueue;
+ int i;
+ int n;
+ bool notify = false;
+
+ if (!vqueue)
+ return -EIO;
+
+ i = (substream->msg_last_enqueued + 1) % runtime->periods;
+ n = runtime->periods - atomic_read(&substream->msg_count);
+
+ for (; n; --n, i = (i + 1) % runtime->periods) {
+ struct virtio_pcm_msg *msg = &substream->msgs[i];
+ struct scatterlist *psgs[PCM_MSG_SG_MAX] = {
+ [PCM_MSG_SG_XFER] = &msg->sgs[PCM_MSG_SG_XFER],
+ [PCM_MSG_SG_DATA] = &msg->sgs[PCM_MSG_SG_DATA],
+ [PCM_MSG_SG_STATUS] = &msg->sgs[PCM_MSG_SG_STATUS]
+ };
+ int rc;
+
+ msg->xfer.stream_id = cpu_to_virtio32(vdev, substream->sid);
+ memset(&msg->status, 0, sizeof(msg->status));
+
+ atomic_inc(&substream->msg_count);
+
+ if (substream->direction == SNDRV_PCM_STREAM_PLAYBACK)
+ rc = virtqueue_add_sgs(vqueue, psgs, 2, 1, msg,
+ GFP_ATOMIC);
+ else
+ rc = virtqueue_add_sgs(vqueue, psgs, 1, 2, msg,
+ GFP_ATOMIC);
+
+ if (rc) {
+ atomic_dec(&substream->msg_count);
+ return -EIO;
+ }
+
+ substream->msg_last_enqueued = i;
+ }
+
+ if (!(substream->features & (1U << VIRTIO_SND_PCM_F_MSG_POLLING)))
+ notify = virtqueue_kick_prepare(vqueue);
+
+ if (notify)
+ if (!virtqueue_notify(vqueue))
+ return -EIO;
+
+ return 0;
+}
+
+static void virtsnd_pcm_msg_complete(struct virtio_pcm_msg *msg, size_t size)
+{
+ struct virtio_pcm_substream *substream = msg->substream;
+ struct snd_pcm_runtime *runtime = substream->substream->runtime;
+ snd_pcm_uframes_t hw_ptr;
+
+ /* TODO: propagate an error to upper layer? */
+ if (le32_to_cpu(msg->status.status) != VIRTIO_SND_S_OK)
+ return;
+
+ hw_ptr = (snd_pcm_uframes_t)atomic_read(&substream->hw_ptr);
+
+ if (substream->direction == SNDRV_PCM_STREAM_PLAYBACK) {
+ hw_ptr += runtime->period_size;
+ } else {
+ if (size > sizeof(struct virtio_snd_pcm_status))
+ size -= sizeof(struct virtio_snd_pcm_status);
+ else
+ /* TODO: propagate an error to upper layer? */
+ return;
+
+ hw_ptr += bytes_to_frames(runtime, size);
+ }
+
+ atomic_set(&substream->hw_ptr, (u32)(hw_ptr % runtime->buffer_size));
+ atomic_set(&substream->xfer_xrun, 0);
+
+ runtime->delay =
+ bytes_to_frames(runtime,
+ le32_to_cpu(msg->status.latency_bytes));
+
+ snd_pcm_period_elapsed(substream->substream);
+}
+
+static inline void virtsnd_pcm_notify_cb(struct virtio_snd_queue *queue)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&queue->lock, flags);
+ while (queue->vqueue) {
+ virtqueue_disable_cb(queue->vqueue);
+
+ for (;;) {
+ struct virtio_pcm_substream *substream;
+ struct virtio_pcm_msg *msg;
+ unsigned int msg_count;
+ u32 length;
+
+ msg = virtqueue_get_buf(queue->vqueue, &length);
+ if (!msg)
+ break;
+
+ substream = msg->substream;
+
+ msg_count = atomic_dec_return(&substream->msg_count);
+
+ if (atomic_read(&substream->xfer_enabled)) {
+ virtsnd_pcm_msg_complete(msg, length);
+ virtsnd_pcm_msg_send(substream);
+ } else if (!msg_count) {
+ wake_up_all(&substream->msg_empty);
+ }
+ }
+
+ if (unlikely(virtqueue_is_broken(queue->vqueue)))
+ break;
+
+ if (virtqueue_enable_cb(queue->vqueue))
+ break;
+ }
+ spin_unlock_irqrestore(&queue->lock, flags);
+}
+
+void virtsnd_pcm_tx_notify_cb(struct virtqueue *vqueue)
+{
+ struct virtio_snd *snd = vqueue->vdev->priv;
+
+ virtsnd_pcm_notify_cb(virtsnd_tx_queue(snd));
+}
+
+void virtsnd_pcm_rx_notify_cb(struct virtqueue *vqueue)
+{
+ struct virtio_snd *snd = vqueue->vdev->priv;
+
+ virtsnd_pcm_notify_cb(virtsnd_rx_queue(snd));
+}
+
+struct virtio_snd_msg *
+virtsnd_pcm_ctl_msg_alloc(struct virtio_pcm_substream *substream,
+ unsigned int command, gfp_t gfp)
+{
+ struct virtio_device *vdev = substream->snd->vdev;
+ size_t request_size = sizeof(struct virtio_snd_pcm_hdr);
+ size_t response_size = sizeof(struct virtio_snd_hdr);
+ struct virtio_snd_msg *msg;
+
+ switch (command) {
+ case VIRTIO_SND_R_PCM_SET_PARAMS: {
+ request_size = sizeof(struct virtio_snd_pcm_set_params);
+ break;
+ }
+ }
+
+ msg = virtsnd_ctl_msg_alloc(vdev, request_size, response_size, gfp);
+ if (!IS_ERR(msg)) {
+ struct virtio_snd_pcm_hdr *hdr = sg_virt(&msg->sg_request);
+
+ hdr->hdr.code = cpu_to_virtio32(vdev, command);
+ hdr->stream_id = cpu_to_virtio32(vdev, substream->sid);
+ }
+
+ return msg;
+}
diff --git a/virtio_snd/virtio_pcm_ops.c b/virtio_snd/virtio_pcm_ops.c
new file mode 100644
index 0000000..0743a7b
--- /dev/null
+++ b/virtio_snd/virtio_pcm_ops.c
@@ -0,0 +1,385 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Sound card driver for virtio
+ * Copyright (C) 2020 OpenSynergy GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include <sound/pcm_params.h>
+
+#include "virtio_card.h"
+
+struct virtsnd_a2v_format {
+ unsigned int alsa_bit;
+ unsigned int vio_bit;
+};
+
+static const struct virtsnd_a2v_format g_a2v_format_map[] = {
+ { SNDRV_PCM_FORMAT_IMA_ADPCM, VIRTIO_SND_PCM_FMT_IMA_ADPCM },
+ { SNDRV_PCM_FORMAT_MU_LAW, VIRTIO_SND_PCM_FMT_MU_LAW },
+ { SNDRV_PCM_FORMAT_A_LAW, VIRTIO_SND_PCM_FMT_A_LAW },
+ { SNDRV_PCM_FORMAT_S8, VIRTIO_SND_PCM_FMT_S8 },
+ { SNDRV_PCM_FORMAT_U8, VIRTIO_SND_PCM_FMT_U8 },
+ { SNDRV_PCM_FORMAT_S16_LE, VIRTIO_SND_PCM_FMT_S16 },
+ { SNDRV_PCM_FORMAT_U16_LE, VIRTIO_SND_PCM_FMT_U16 },
+ { SNDRV_PCM_FORMAT_S18_3LE, VIRTIO_SND_PCM_FMT_S18_3 },
+ { SNDRV_PCM_FORMAT_U18_3LE, VIRTIO_SND_PCM_FMT_U18_3 },
+ { SNDRV_PCM_FORMAT_S20_3LE, VIRTIO_SND_PCM_FMT_S20_3 },
+ { SNDRV_PCM_FORMAT_U20_3LE, VIRTIO_SND_PCM_FMT_U20_3 },
+ { SNDRV_PCM_FORMAT_S24_3LE, VIRTIO_SND_PCM_FMT_S24_3 },
+ { SNDRV_PCM_FORMAT_U24_3LE, VIRTIO_SND_PCM_FMT_U24_3 },
+#ifdef SNDRV_PCM_FORMAT_S20
+ { SNDRV_PCM_FORMAT_S20_LE, VIRTIO_SND_PCM_FMT_S20 },
+#endif
+#ifdef SNDRV_PCM_FORMAT_U20
+ { SNDRV_PCM_FORMAT_U20_LE, VIRTIO_SND_PCM_FMT_U20 },
+#endif
+ { SNDRV_PCM_FORMAT_S24_LE, VIRTIO_SND_PCM_FMT_S24 },
+ { SNDRV_PCM_FORMAT_U24_LE, VIRTIO_SND_PCM_FMT_U24 },
+ { SNDRV_PCM_FORMAT_S32_LE, VIRTIO_SND_PCM_FMT_S32 },
+ { SNDRV_PCM_FORMAT_U32_LE, VIRTIO_SND_PCM_FMT_U32 },
+ { SNDRV_PCM_FORMAT_FLOAT_LE, VIRTIO_SND_PCM_FMT_FLOAT },
+ { SNDRV_PCM_FORMAT_FLOAT64_LE, VIRTIO_SND_PCM_FMT_FLOAT64 },
+ { SNDRV_PCM_FORMAT_DSD_U8, VIRTIO_SND_PCM_FMT_DSD_U8 },
+ { SNDRV_PCM_FORMAT_DSD_U16_LE, VIRTIO_SND_PCM_FMT_DSD_U16 },
+ { SNDRV_PCM_FORMAT_DSD_U32_LE, VIRTIO_SND_PCM_FMT_DSD_U32 },
+ { SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE,
+ VIRTIO_SND_PCM_FMT_IEC958_SUBFRAME }
+};
+
+struct virtsnd_a2v_rate {
+ unsigned int rate;
+ unsigned int vio_bit;
+};
+
+static const struct virtsnd_a2v_rate g_a2v_rate_map[] = {
+ { 5512, VIRTIO_SND_PCM_RATE_5512 },
+ { 8000, VIRTIO_SND_PCM_RATE_8000 },
+ { 11025, VIRTIO_SND_PCM_RATE_11025 },
+ { 16000, VIRTIO_SND_PCM_RATE_16000 },
+ { 22050, VIRTIO_SND_PCM_RATE_22050 },
+ { 32000, VIRTIO_SND_PCM_RATE_32000 },
+ { 44100, VIRTIO_SND_PCM_RATE_44100 },
+ { 48000, VIRTIO_SND_PCM_RATE_48000 },
+ { 64000, VIRTIO_SND_PCM_RATE_64000 },
+ { 88200, VIRTIO_SND_PCM_RATE_88200 },
+ { 96000, VIRTIO_SND_PCM_RATE_96000 },
+ { 176400, VIRTIO_SND_PCM_RATE_176400 },
+ { 192000, VIRTIO_SND_PCM_RATE_192000 }
+};
+
+static inline bool virtsnd_pcm_released(struct virtio_pcm_substream *substream)
+{
+ return atomic_read(&substream->msg_count) == 0;
+}
+
+static int virtsnd_pcm_release(struct virtio_pcm_substream *substream)
+{
+ struct virtio_snd *snd = substream->snd;
+ struct virtio_snd_msg *msg;
+ int rc;
+
+ msg = virtsnd_pcm_ctl_msg_alloc(substream, VIRTIO_SND_R_PCM_RELEASE,
+ GFP_KERNEL);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ rc = virtsnd_ctl_msg_send_sync(snd, msg);
+ if (!rc)
+ rc = wait_event_interruptible(substream->msg_empty,
+ virtsnd_pcm_released(substream));
+
+ return rc;
+}
+
+static int virtsnd_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct virtio_pcm *pcm = snd_pcm_substream_chip(substream);
+ struct virtio_pcm_substream *ss = NULL;
+
+ if (pcm) {
+ switch (substream->stream) {
+ case SNDRV_PCM_STREAM_PLAYBACK:
+ case SNDRV_PCM_STREAM_CAPTURE: {
+ struct virtio_pcm_stream *stream =
+ &pcm->streams[substream->stream];
+
+ if (substream->number < stream->nsubstreams)
+ ss = stream->substreams[substream->number];
+ break;
+ }
+ }
+ }
+
+ if (!ss)
+ return -EBADFD;
+
+ substream->runtime->hw = ss->hw;
+ substream->private_data = ss;
+
+ return 0;
+}
+
+static int virtsnd_pcm_close(struct snd_pcm_substream *substream)
+{
+ return 0;
+}
+
+static int virtsnd_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ snd_pcm_state_t state;
+ unsigned long flags;
+ struct virtio_pcm_substream *ss = snd_pcm_substream_chip(substream);
+ struct virtio_device *vdev = ss->snd->vdev;
+ struct virtio_snd_msg *msg;
+ struct virtio_snd_pcm_set_params *request;
+ snd_pcm_format_t format;
+ unsigned int channels;
+ unsigned int rate;
+ unsigned int buffer_bytes;
+ unsigned int period_bytes;
+ unsigned int periods;
+ unsigned int i;
+ int vformat = -1;
+ int vrate = -1;
+ int rc;
+
+ snd_pcm_stream_lock_irqsave(substream, flags);
+ state = substream->runtime->status->state;
+ snd_pcm_stream_unlock_irqrestore(substream, flags);
+
+ if (state != SNDRV_PCM_STATE_SUSPENDED) {
+ /*
+ * If we got here after ops->trigger() was called, the queue may
+ * still contain messages. In this case, we need to release the
+ * substream first.
+ */
+ if (atomic_read(&ss->msg_count)) {
+ rc = virtsnd_pcm_release(ss);
+ if (rc)
+ return rc;
+ }
+ }
+
+ /* Set hardware parameters in device */
+ if (hw_params) {
+ format = params_format(hw_params);
+ channels = params_channels(hw_params);
+ rate = params_rate(hw_params);
+ buffer_bytes = params_buffer_bytes(hw_params);
+ period_bytes = params_period_bytes(hw_params);
+ periods = params_periods(hw_params);
+ } else {
+ format = runtime->format;
+ channels = runtime->channels;
+ rate = runtime->rate;
+ buffer_bytes = frames_to_bytes(runtime, runtime->buffer_size);
+ period_bytes = frames_to_bytes(runtime, runtime->period_size);
+ periods = runtime->periods;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(g_a2v_format_map); ++i)
+ if (g_a2v_format_map[i].alsa_bit == format) {
+ vformat = g_a2v_format_map[i].vio_bit;
+
+ break;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(g_a2v_rate_map); ++i)
+ if (g_a2v_rate_map[i].rate == rate) {
+ vrate = g_a2v_rate_map[i].vio_bit;
+
+ break;
+ }
+
+ if (vformat == -1 || vrate == -1)
+ return -EINVAL;
+
+ msg = virtsnd_pcm_ctl_msg_alloc(ss, VIRTIO_SND_R_PCM_SET_PARAMS,
+ GFP_KERNEL);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ request = sg_virt(&msg->sg_request);
+
+ request->buffer_bytes = cpu_to_virtio32(vdev, buffer_bytes);
+ request->period_bytes = cpu_to_virtio32(vdev, period_bytes);
+ request->channels = channels;
+ request->format = vformat;
+ request->rate = vrate;
+
+ if (ss->features & (1U << VIRTIO_SND_PCM_F_MSG_POLLING))
+ request->features |=
+ cpu_to_virtio32(vdev,
+ 1U << VIRTIO_SND_PCM_F_MSG_POLLING);
+
+ if (ss->features & (1U << VIRTIO_SND_PCM_F_EVT_XRUNS))
+ request->features |=
+ cpu_to_virtio32(vdev,
+ 1U << VIRTIO_SND_PCM_F_EVT_XRUNS);
+
+ rc = virtsnd_ctl_msg_send_sync(ss->snd, msg);
+ if (rc)
+ return rc;
+
+ /* If the buffer was already allocated earlier, do nothing. */
+ if (runtime->dma_area)
+ return 0;
+
+ /* Allocate hardware buffer */
+ rc = snd_pcm_lib_malloc_pages(substream, buffer_bytes);
+ if (rc < 0)
+ return rc;
+
+ /* Allocate and initialize I/O messages */
+ rc = virtsnd_pcm_msg_alloc(ss, periods, runtime->dma_area,
+ period_bytes);
+ if (rc)
+ snd_pcm_lib_free_pages(substream);
+
+ return rc;
+}
+
+static int virtsnd_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct virtio_pcm_substream *ss = snd_pcm_substream_chip(substream);
+ int rc;
+
+ rc = virtsnd_pcm_release(ss);
+
+ /*
+ * Even if we failed to send the RELEASE message or wait for the queue
+ * flush to complete, we can safely delete the buffer. Because after
+ * receiving the STOP command, the device must stop all I/O message
+ * processing. If there are still pending messages in the queue, the
+ * next ops->hw_params() call should deal with this.
+ */
+ snd_pcm_lib_free_pages(substream);
+
+ return rc;
+}
+
+static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct virtio_pcm_substream *ss = snd_pcm_substream_chip(substream);
+ snd_pcm_state_t state;
+ struct virtio_snd_msg *msg;
+ unsigned long flags;
+ int rc;
+
+ snd_pcm_stream_lock_irqsave(substream, flags);
+ state = substream->runtime->status->state;
+ snd_pcm_stream_unlock_irqrestore(substream, flags);
+
+ if (state != SNDRV_PCM_STATE_SUSPENDED) {
+ struct virtio_snd_queue *queue = virtsnd_pcm_queue(ss);
+
+ /*
+ * If we got here after ops->trigger() was called, the queue may
+ * still contain messages. In this case, we need to reset the
+ * substream first.
+ */
+ if (atomic_read(&ss->msg_count)) {
+ rc = virtsnd_pcm_hw_params(substream, NULL);
+ if (rc)
+ return rc;
+ }
+
+ spin_lock_irqsave(&queue->lock, flags);
+ ss->msg_last_enqueued = -1;
+ spin_unlock_irqrestore(&queue->lock, flags);
+
+ atomic_set(&ss->hw_ptr, 0);
+ }
+
+ atomic_set(&ss->xfer_xrun, 0);
+ atomic_set(&ss->msg_count, 0);
+
+ msg = virtsnd_pcm_ctl_msg_alloc(ss, VIRTIO_SND_R_PCM_PREPARE,
+ GFP_KERNEL);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ return virtsnd_ctl_msg_send_sync(ss->snd, msg);
+}
+
+static int virtsnd_pcm_trigger(struct snd_pcm_substream *substream, int command)
+{
+ struct virtio_pcm_substream *ss = snd_pcm_substream_chip(substream);
+ struct virtio_snd *snd = ss->snd;
+ struct virtio_snd_queue *queue = virtsnd_pcm_queue(ss);
+ struct virtio_snd_msg *msg;
+
+ switch (command) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME: {
+ int rc;
+
+ spin_lock(&queue->lock);
+ rc = virtsnd_pcm_msg_send(ss);
+ spin_unlock(&queue->lock);
+ if (rc)
+ return rc;
+
+ atomic_set(&ss->xfer_enabled, 1);
+
+ msg = virtsnd_pcm_ctl_msg_alloc(ss, VIRTIO_SND_R_PCM_START,
+ GFP_ATOMIC);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ return virtsnd_ctl_msg_send(snd, msg);
+ }
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND: {
+ atomic_set(&ss->xfer_enabled, 0);
+
+ msg = virtsnd_pcm_ctl_msg_alloc(ss, VIRTIO_SND_R_PCM_STOP,
+ GFP_ATOMIC);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ return virtsnd_ctl_msg_send(snd, msg);
+ }
+ default: {
+ return -EINVAL;
+ }
+ }
+}
+
+static snd_pcm_uframes_t
+virtsnd_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct virtio_pcm_substream *ss = snd_pcm_substream_chip(substream);
+
+ if (atomic_read(&ss->xfer_xrun))
+ return SNDRV_PCM_POS_XRUN;
+
+ return (snd_pcm_uframes_t)atomic_read(&ss->hw_ptr);
+}
+
+const struct snd_pcm_ops virtsnd_pcm_ops = {
+ .open = virtsnd_pcm_open,
+ .close = virtsnd_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = virtsnd_pcm_hw_params,
+ .hw_free = virtsnd_pcm_hw_free,
+ .prepare = virtsnd_pcm_prepare,
+ .trigger = virtsnd_pcm_trigger,
+ .pointer = virtsnd_pcm_pointer,
+};
diff --git a/virtio_snd/virtio_snd.h b/virtio_snd/virtio_snd.h
new file mode 100644
index 0000000..e5dc8aa
--- /dev/null
+++ b/virtio_snd/virtio_snd.h
@@ -0,0 +1,479 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/*
+ * Copyright (C) 2020 OpenSynergy GmbH
+ *
+ * This header is BSD licensed so anyone can use the definitions to
+ * implement compatible drivers/servers.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of OpenSynergy GmbH nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL IBM OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#ifndef VIRTIO_SND_IF_H
+#define VIRTIO_SND_IF_H
+
+#include <linux/virtio_types.h>
+
+/*******************************************************************************
+ * DEVICE FEATURES
+ */
+enum {
+ /* OpenSynergy extensions support */
+ VIRTIO_SND_F_OPSY_EXT = 0xf
+};
+
+/*******************************************************************************
+ * CONFIGURATION SPACE
+ */
+struct virtio_snd_config {
+ /* # of available physical jacks */
+ __le32 jacks;
+ /* # of available PCM streams */
+ __le32 streams;
+ /* # of available channel maps */
+ __le32 chmaps;
+ /* # of available device controls */
+ __le32 controls;
+};
+
+enum {
+ /* device virtqueue indexes */
+ VIRTIO_SND_VQ_CONTROL = 0,
+ VIRTIO_SND_VQ_EVENT,
+ VIRTIO_SND_VQ_TX,
+ VIRTIO_SND_VQ_RX,
+ /* a number of device virtqueues */
+ VIRTIO_SND_VQ_MAX
+};
+
+/*******************************************************************************
+ * COMMON DEFINITIONS
+ */
+
+/* supported dataflow directions */
+enum {
+ VIRTIO_SND_D_OUTPUT = 0,
+ VIRTIO_SND_D_INPUT
+};
+
+enum {
+ /* jack control request types */
+ VIRTIO_SND_R_JACK_INFO = 1,
+ VIRTIO_SND_R_JACK_REMAP,
+
+ /* PCM control request types */
+ VIRTIO_SND_R_PCM_INFO = 0x0100,
+ VIRTIO_SND_R_PCM_SET_PARAMS,
+ VIRTIO_SND_R_PCM_PREPARE,
+ VIRTIO_SND_R_PCM_RELEASE,
+ VIRTIO_SND_R_PCM_START,
+ VIRTIO_SND_R_PCM_STOP,
+
+ /* channel map control request types */
+ VIRTIO_SND_R_CHMAP_INFO = 0x0200,
+
+ /* jack event types */
+ VIRTIO_SND_EVT_JACK_CONNECTED = 0x1000,
+ VIRTIO_SND_EVT_JACK_DISCONNECTED,
+
+ /* PCM event types */
+ VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED = 0x1100,
+ VIRTIO_SND_EVT_PCM_XRUN,
+
+ /* common status codes */
+ VIRTIO_SND_S_OK = 0x8000,
+ VIRTIO_SND_S_BAD_MSG,
+ VIRTIO_SND_S_NOT_SUPP,
+ VIRTIO_SND_S_IO_ERR,
+
+ /* --- OpenSynergy extensions --------------------------------------- */
+ VIRTIO_SND_R_OPSY_INFO = 0xf000,
+
+ /* ALSA extended device information */
+ VIRTIO_SND_R_ALSA_CARD_INFO = 0xf100,
+ VIRTIO_SND_R_ALSA_PCM_INFO,
+
+ /* ALSA device controls */
+ VIRTIO_SND_R_DC_INFO = 0xf200,
+ VIRTIO_SND_R_DC_ENUM_INFO,
+ VIRTIO_SND_R_DC_READ,
+ VIRTIO_SND_R_DC_WRITE,
+ VIRTIO_SND_R_DC_TLV_READ,
+ VIRTIO_SND_R_DC_TLV_WRITE,
+ VIRTIO_SND_R_DC_TLV_COMMAND,
+
+ /* ALSA device control events */
+ VIRTIO_SND_EVT_DC_NOTIFY = 0xff00,
+};
+
+/* common header */
+struct virtio_snd_hdr {
+ __le32 code;
+};
+
+/* event notification */
+struct virtio_snd_event {
+ /* VIRTIO_SND_EVT_XXX */
+ struct virtio_snd_hdr hdr;
+ /* optional event data */
+ __le32 data;
+};
+
+/* common control request to query an item information */
+struct virtio_snd_query_info {
+ /* VIRTIO_SND_R_XXX_INFO */
+ struct virtio_snd_hdr hdr;
+ /* item start identifier */
+ __le32 start_id;
+ /* item count to query */
+ __le32 count;
+ /* item information size in bytes */
+ __le32 size;
+};
+
+/* common item information header */
+struct virtio_snd_info {
+ /* function group node id (High Definition Audio Specification 7.1.2) */
+ __le32 hda_fn_nid;
+};
+
+/*******************************************************************************
+ * JACK CONTROL MESSAGES
+ */
+struct virtio_snd_jack_hdr {
+ /* VIRTIO_SND_R_JACK_XXX */
+ struct virtio_snd_hdr hdr;
+ /* 0 ... virtio_snd_config::jacks - 1 */
+ __le32 jack_id;
+};
+
+/* supported jack features */
+enum {
+ VIRTIO_SND_JACK_F_REMAP = 0
+};
+
+struct virtio_snd_jack_info {
+ /* common header */
+ struct virtio_snd_info hdr;
+ /* supported feature bit map (1 << VIRTIO_SND_JACK_F_XXX) */
+ __le32 features;
+ /* pin configuration (High Definition Audio Specification 7.3.3.31) */
+ __le32 hda_reg_defconf;
+ /* pin capabilities (High Definition Audio Specification 7.3.4.9) */
+ __le32 hda_reg_caps;
+ /* current jack connection status (0: disconnected, 1: connected) */
+ __u8 connected;
+
+ __u8 padding[7];
+};
+
+/* jack remapping control request */
+struct virtio_snd_jack_remap {
+ /* .code = VIRTIO_SND_R_JACK_REMAP */
+ struct virtio_snd_jack_hdr hdr;
+ /* selected association number */
+ __le32 association;
+ /* selected sequence number */
+ __le32 sequence;
+};
+
+/*******************************************************************************
+ * PCM CONTROL MESSAGES
+ */
+struct virtio_snd_pcm_hdr {
+ /* VIRTIO_SND_R_PCM_XXX */
+ struct virtio_snd_hdr hdr;
+ /* 0 ... virtio_snd_config::streams - 1 */
+ __le32 stream_id;
+};
+
+/* supported PCM stream features */
+enum {
+ VIRTIO_SND_PCM_F_SHMEM_HOST = 0,
+ VIRTIO_SND_PCM_F_SHMEM_GUEST,
+ VIRTIO_SND_PCM_F_MSG_POLLING,
+ VIRTIO_SND_PCM_F_EVT_SHMEM_PERIODS,
+ VIRTIO_SND_PCM_F_EVT_XRUNS
+};
+
+/* supported PCM sample formats */
+enum {
+ /* analog formats (width / physical width) */
+ VIRTIO_SND_PCM_FMT_IMA_ADPCM = 0, /* 4 / 4 bits */
+ VIRTIO_SND_PCM_FMT_MU_LAW, /* 8 / 8 bits */
+ VIRTIO_SND_PCM_FMT_A_LAW, /* 8 / 8 bits */
+ VIRTIO_SND_PCM_FMT_S8, /* 8 / 8 bits */
+ VIRTIO_SND_PCM_FMT_U8, /* 8 / 8 bits */
+ VIRTIO_SND_PCM_FMT_S16, /* 16 / 16 bits */
+ VIRTIO_SND_PCM_FMT_U16, /* 16 / 16 bits */
+ VIRTIO_SND_PCM_FMT_S18_3, /* 18 / 24 bits */
+ VIRTIO_SND_PCM_FMT_U18_3, /* 18 / 24 bits */
+ VIRTIO_SND_PCM_FMT_S20_3, /* 20 / 24 bits */
+ VIRTIO_SND_PCM_FMT_U20_3, /* 20 / 24 bits */
+ VIRTIO_SND_PCM_FMT_S24_3, /* 24 / 24 bits */
+ VIRTIO_SND_PCM_FMT_U24_3, /* 24 / 24 bits */
+ VIRTIO_SND_PCM_FMT_S20, /* 20 / 32 bits */
+ VIRTIO_SND_PCM_FMT_U20, /* 20 / 32 bits */
+ VIRTIO_SND_PCM_FMT_S24, /* 24 / 32 bits */
+ VIRTIO_SND_PCM_FMT_U24, /* 24 / 32 bits */
+ VIRTIO_SND_PCM_FMT_S32, /* 32 / 32 bits */
+ VIRTIO_SND_PCM_FMT_U32, /* 32 / 32 bits */
+ VIRTIO_SND_PCM_FMT_FLOAT, /* 32 / 32 bits */
+ VIRTIO_SND_PCM_FMT_FLOAT64, /* 64 / 64 bits */
+ /* digital formats (width / physical width) */
+ VIRTIO_SND_PCM_FMT_DSD_U8, /* 8 / 8 bits */
+ VIRTIO_SND_PCM_FMT_DSD_U16, /* 16 / 16 bits */
+ VIRTIO_SND_PCM_FMT_DSD_U32, /* 32 / 32 bits */
+ VIRTIO_SND_PCM_FMT_IEC958_SUBFRAME /* 32 / 32 bits */
+};
+
+/* supported PCM frame rates */
+enum {
+ VIRTIO_SND_PCM_RATE_5512 = 0,
+ VIRTIO_SND_PCM_RATE_8000,
+ VIRTIO_SND_PCM_RATE_11025,
+ VIRTIO_SND_PCM_RATE_16000,
+ VIRTIO_SND_PCM_RATE_22050,
+ VIRTIO_SND_PCM_RATE_32000,
+ VIRTIO_SND_PCM_RATE_44100,
+ VIRTIO_SND_PCM_RATE_48000,
+ VIRTIO_SND_PCM_RATE_64000,
+ VIRTIO_SND_PCM_RATE_88200,
+ VIRTIO_SND_PCM_RATE_96000,
+ VIRTIO_SND_PCM_RATE_176400,
+ VIRTIO_SND_PCM_RATE_192000,
+ VIRTIO_SND_PCM_RATE_384000
+};
+
+struct virtio_snd_pcm_info {
+ /* common header */
+ struct virtio_snd_info hdr;
+ /* supported feature bit map (1 << VIRTIO_SND_PCM_F_XXX) */
+ __le32 features;
+ /* supported sample format bit map (1 << VIRTIO_SND_PCM_FMT_XXX) */
+ __le64 formats;
+ /* supported frame rate bit map (1 << VIRTIO_SND_PCM_RATE_XXX) */
+ __le64 rates;
+ /* dataflow direction (VIRTIO_SND_D_XXX) */
+ __u8 direction;
+ /* minimum # of supported channels */
+ __u8 channels_min;
+ /* maximum # of supported channels */
+ __u8 channels_max;
+
+ __u8 padding[5];
+};
+
+/* set PCM stream format */
+struct virtio_snd_pcm_set_params {
+ /* .code = VIRTIO_SND_R_PCM_SET_PARAMS */
+ struct virtio_snd_pcm_hdr hdr;
+ /* size of the hardware buffer */
+ __le32 buffer_bytes;
+ /* size of the hardware period */
+ __le32 period_bytes;
+ /* selected feature bit map (1 << VIRTIO_SND_PCM_F_XXX) */
+ __le32 features;
+ /* selected # of channels */
+ __u8 channels;
+ /* selected sample format (VIRTIO_SND_PCM_FMT_XXX) */
+ __u8 format;
+ /* selected frame rate (VIRTIO_SND_PCM_RATE_XXX) */
+ __u8 rate;
+
+ __u8 padding;
+};
+
+/*******************************************************************************
+ * PCM I/O MESSAGES
+ */
+
+/* I/O request header */
+struct virtio_snd_pcm_xfer {
+ /* 0 ... virtio_snd_config::streams - 1 */
+ __le32 stream_id;
+};
+
+/* I/O request status */
+struct virtio_snd_pcm_status {
+ /* VIRTIO_SND_S_XXX */
+ __le32 status;
+ /* current device latency */
+ __le32 latency_bytes;
+};
+
+/*******************************************************************************
+ * CHANNEL MAP CONTROL MESSAGES
+ */
+struct virtio_snd_chmap_hdr {
+ /* VIRTIO_SND_R_CHMAP_XXX */
+ struct virtio_snd_hdr hdr;
+ /* 0 ... virtio_snd_config::chmaps - 1 */
+ __le32 chmap_id;
+};
+
+/* standard channel position definition */
+enum {
+ VIRTIO_SND_CHMAP_NONE = 0, /* undefined */
+ VIRTIO_SND_CHMAP_NA, /* silent */
+ VIRTIO_SND_CHMAP_MONO, /* mono stream */
+ VIRTIO_SND_CHMAP_FL, /* front left */
+ VIRTIO_SND_CHMAP_FR, /* front right */
+ VIRTIO_SND_CHMAP_RL, /* rear left */
+ VIRTIO_SND_CHMAP_RR, /* rear right */
+ VIRTIO_SND_CHMAP_FC, /* front center */
+ VIRTIO_SND_CHMAP_LFE, /* low frequency (LFE) */
+ VIRTIO_SND_CHMAP_SL, /* side left */
+ VIRTIO_SND_CHMAP_SR, /* side right */
+ VIRTIO_SND_CHMAP_RC, /* rear center */
+ VIRTIO_SND_CHMAP_FLC, /* front left center */
+ VIRTIO_SND_CHMAP_FRC, /* front right center */
+ VIRTIO_SND_CHMAP_RLC, /* rear left center */
+ VIRTIO_SND_CHMAP_RRC, /* rear right center */
+ VIRTIO_SND_CHMAP_FLW, /* front left wide */
+ VIRTIO_SND_CHMAP_FRW, /* front right wide */
+ VIRTIO_SND_CHMAP_FLH, /* front left high */
+ VIRTIO_SND_CHMAP_FCH, /* front center high */
+ VIRTIO_SND_CHMAP_FRH, /* front right high */
+ VIRTIO_SND_CHMAP_TC, /* top center */
+ VIRTIO_SND_CHMAP_TFL, /* top front left */
+ VIRTIO_SND_CHMAP_TFR, /* top front right */
+ VIRTIO_SND_CHMAP_TFC, /* top front center */
+ VIRTIO_SND_CHMAP_TRL, /* top rear left */
+ VIRTIO_SND_CHMAP_TRR, /* top rear right */
+ VIRTIO_SND_CHMAP_TRC, /* top rear center */
+ VIRTIO_SND_CHMAP_TFLC, /* top front left center */
+ VIRTIO_SND_CHMAP_TFRC, /* top front right center */
+ VIRTIO_SND_CHMAP_TSL, /* top side left */
+ VIRTIO_SND_CHMAP_TSR, /* top side right */
+ VIRTIO_SND_CHMAP_LLFE, /* left LFE */
+ VIRTIO_SND_CHMAP_RLFE, /* right LFE */
+ VIRTIO_SND_CHMAP_BC, /* bottom center */
+ VIRTIO_SND_CHMAP_BLC, /* bottom left center */
+ VIRTIO_SND_CHMAP_BRC /* bottom right center */
+};
+
+/* maximum possible number of channels */
+#define VIRTIO_SND_CHMAP_MAX_SIZE 18
+
+struct virtio_snd_chmap_info {
+ /* common header */
+ struct virtio_snd_info hdr;
+ /* dataflow direction (VIRTIO_SND_D_XXX) */
+ __u8 direction;
+ /* # of valid channel position values */
+ __u8 channels;
+ /* channel position values (VIRTIO_SND_CHMAP_XXX) */
+ __u8 positions[VIRTIO_SND_CHMAP_MAX_SIZE];
+};
+
+/*******************************************************************************
+ * OPENSYNERGY EXTENSIONS
+ */
+enum {
+ /* ALSA extended device information support */
+ VIRTIO_SND_OPSY_F_DEV_EXT_INFO = 0,
+ /* ALSA device controls support */
+ VIRTIO_SND_OPSY_F_DEV_CTLS
+};
+
+struct virtio_snd_opsy_info {
+ /* VIRTIO_SND_S_XXX */
+ struct virtio_snd_hdr hdr;
+ /* supported extension bit map (1 << VIRTIO_SND_OPSY_F_XXX) */
+ __le32 extensions;
+};
+
+/*******************************************************************************
+ * ALSA EXTENDED DEVICE INFORMATION
+ */
+struct virtio_snd_alsa_card_info {
+ /* ID string of soundcard */
+ __u8 id[16];
+ /* Driver name */
+ __u8 driver[16];
+ /* short name of soundcard */
+ __u8 name[32];
+ /* name + info text about soundcard */
+ __u8 longname[80];
+ /* visual mixer identification */
+ __u8 mixername[80];
+ /* card components delimited with space */
+ __u8 components[128];
+};
+
+struct virtio_snd_alsa_query_pcm_info {
+ /* .code = VIRTIO_SND_R_ALSA_PCM_INFO */
+ struct virtio_snd_hdr hdr;
+ /* function group node id */
+ __le32 hda_fn_nid;
+};
+
+struct virtio_snd_alsa_pcm_info {
+ /* ID string of PCM device */
+ __u8 id[64];
+ /* name of PCM device */
+ __u8 name[80];
+};
+
+/*******************************************************************************
+ * ALSA DEVICE CONTROLS
+ */
+struct virtio_snd_dc_hdr {
+ /* VIRTIO_SND_R_DC_XXX */
+ struct virtio_snd_hdr hdr;
+ /* 0 ... virtio_snd_config::controls - 1 */
+ __le16 control_id;
+ /* subcontrol identifier */
+ __le16 subcontrol_id;
+};
+
+/*
+ * The following structure is de facto part of the protocol, but cannot be
+ * defined here because it contains the snd_ctl_elem_info body. It represents
+ * information about a device control in response to the VIRTIO_SND_R_DC_INFO
+ * command.
+ *
+ * struct virtio_snd_dc_info {
+ * struct virtio_snd_info hdr;
+ * struct snd_ctl_elem_info elem_info;
+ * };
+ */
+
+#define VIRTIO_SND_DC_NAME_MAXLEN 64
+
+struct virtio_snd_dc_enum_value {
+ /* null-terminated ASCII value name */
+ __u8 name[VIRTIO_SND_DC_NAME_MAXLEN];
+};
+
+struct virtio_snd_dc_event {
+ /* VIRTIO_SND_EVT_DC_XXX */
+ struct virtio_snd_hdr hdr;
+ /* 0 ... virtio_snd_config::controls - 1 */
+ __le16 control_id;
+ /* event mask (SNDRV_CTL_EVENT_MASK_XXX) */
+ __le16 mask;
+};
+
+#endif /* VIRTIO_SND_IF_H */