[PATCH 0/7] ALSA: add virtio sound driver
This series implements a driver part of the virtio sound device specification v8 [1].
The driver supports PCM playback and capture substreams, jack and channel map controls. A message-based transport is used to write/read PCM frames to/from a device.
The series is based (and was actually tested) on Linus's master branch [2], on top of
commit 1e2a199f6ccd ("Merge tag 'spi-fix-v5.11-rc4' of ...")
As a device part was used OpenSynergy proprietary implementation.
Any comments are very welcome.
[1] https://lists.oasis-open.org/archives/virtio-dev/202003/msg00185.html [2] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
Anton Yakovlev (7): uapi: virtio_ids: add a sound device type ID from OASIS spec uapi: virtio_snd: add the sound device header file ALSA: virtio: add virtio sound driver ALSA: virtio: handling control and I/O messages for the PCM device ALSA: virtio: PCM substream operators ALSA: virtio: introduce jack support ALSA: virtio: introduce device suspend/resume support
MAINTAINERS | 8 + include/uapi/linux/virtio_ids.h | 1 + include/uapi/linux/virtio_snd.h | 361 +++++++++++++++++++ sound/Kconfig | 2 + sound/Makefile | 3 +- sound/virtio/Kconfig | 10 + sound/virtio/Makefile | 13 + sound/virtio/virtio_card.c | 593 ++++++++++++++++++++++++++++++++ sound/virtio/virtio_card.h | 121 +++++++ sound/virtio/virtio_chmap.c | 237 +++++++++++++ sound/virtio/virtio_ctl_msg.c | 293 ++++++++++++++++ sound/virtio/virtio_ctl_msg.h | 122 +++++++ sound/virtio/virtio_jack.c | 255 ++++++++++++++ sound/virtio/virtio_pcm.c | 582 +++++++++++++++++++++++++++++++ sound/virtio/virtio_pcm.h | 132 +++++++ sound/virtio/virtio_pcm_msg.c | 317 +++++++++++++++++ sound/virtio/virtio_pcm_ops.c | 524 ++++++++++++++++++++++++++++ 17 files changed, 3573 insertions(+), 1 deletion(-) create mode 100644 include/uapi/linux/virtio_snd.h create mode 100644 sound/virtio/Kconfig create mode 100644 sound/virtio/Makefile create mode 100644 sound/virtio/virtio_card.c create mode 100644 sound/virtio/virtio_card.h create mode 100644 sound/virtio/virtio_chmap.c create mode 100644 sound/virtio/virtio_ctl_msg.c create mode 100644 sound/virtio/virtio_ctl_msg.h create mode 100644 sound/virtio/virtio_jack.c create mode 100644 sound/virtio/virtio_pcm.c create mode 100644 sound/virtio/virtio_pcm.h create mode 100644 sound/virtio/virtio_pcm_msg.c create mode 100644 sound/virtio/virtio_pcm_ops.c
The OASIS virtio spec defines a sound device type ID that is not present in the header yet.
Signed-off-by: Anton Yakovlev anton.yakovlev@opensynergy.com --- include/uapi/linux/virtio_ids.h | 1 + 1 file changed, 1 insertion(+)
diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h index bc1c0621f5ed..029a2e07a7f9 100644 --- a/include/uapi/linux/virtio_ids.h +++ b/include/uapi/linux/virtio_ids.h @@ -51,6 +51,7 @@ #define VIRTIO_ID_PSTORE 22 /* virtio pstore device */ #define VIRTIO_ID_IOMMU 23 /* virtio IOMMU */ #define VIRTIO_ID_MEM 24 /* virtio mem */ +#define VIRTIO_ID_SOUND 25 /* virtio sound */ #define VIRTIO_ID_FS 26 /* virtio filesystem */ #define VIRTIO_ID_PMEM 27 /* virtio pmem */ #define VIRTIO_ID_MAC80211_HWSIM 29 /* virtio mac80211-hwsim */
The file contains the definitions for the sound device from the OASIS virtio spec.
Signed-off-by: Anton Yakovlev anton.yakovlev@opensynergy.com --- MAINTAINERS | 6 + include/uapi/linux/virtio_snd.h | 361 ++++++++++++++++++++++++++++++++ 2 files changed, 367 insertions(+) create mode 100644 include/uapi/linux/virtio_snd.h
diff --git a/MAINTAINERS b/MAINTAINERS index 00836f6452f0..6dfd59eafe82 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18936,6 +18936,12 @@ W: https://virtio-mem.gitlab.io/ F: drivers/virtio/virtio_mem.c F: include/uapi/linux/virtio_mem.h
+VIRTIO SOUND DRIVER +M: Anton Yakovlev anton.yakovlev@opensynergy.com +L: virtualization@lists.linux-foundation.org +S: Maintained +F: include/uapi/linux/virtio_snd.h + VIRTUAL BOX GUEST DEVICE DRIVER M: Hans de Goede hdegoede@redhat.com M: Arnd Bergmann arnd@arndb.de diff --git a/include/uapi/linux/virtio_snd.h b/include/uapi/linux/virtio_snd.h new file mode 100644 index 000000000000..1ff6310e54d6 --- /dev/null +++ b/include/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 */
On Wed, Jan 20, 2021 at 01:36:30AM +0100, Anton Yakovlev wrote:
The file contains the definitions for the sound device from the OASIS virtio spec.
Signed-off-by: Anton Yakovlev anton.yakovlev@opensynergy.com
MAINTAINERS | 6 + include/uapi/linux/virtio_snd.h | 361 ++++++++++++++++++++++++++++++++ 2 files changed, 367 insertions(+) create mode 100644 include/uapi/linux/virtio_snd.h
diff --git a/MAINTAINERS b/MAINTAINERS index 00836f6452f0..6dfd59eafe82 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18936,6 +18936,12 @@ W: https://virtio-mem.gitlab.io/ F: drivers/virtio/virtio_mem.c F: include/uapi/linux/virtio_mem.h
+VIRTIO SOUND DRIVER +M: Anton Yakovlev anton.yakovlev@opensynergy.com +L: virtualization@lists.linux-foundation.org +S: Maintained +F: include/uapi/linux/virtio_snd.h
VIRTUAL BOX GUEST DEVICE DRIVER M: Hans de Goede hdegoede@redhat.com M: Arnd Bergmann arnd@arndb.de
You want sound/virtio here too, right? I'd just squash this with the next patch in series.
diff --git a/include/uapi/linux/virtio_snd.h b/include/uapi/linux/virtio_snd.h new file mode 100644 index 000000000000..1ff6310e54d6 --- /dev/null +++ b/include/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:
- Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- 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.
- 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 */
2.30.0
On Wed, Jan 20, 2021 at 03:19:55AM -0500, Michael S. Tsirkin wrote:
On Wed, Jan 20, 2021 at 01:36:30AM +0100, Anton Yakovlev wrote:
The file contains the definitions for the sound device from the OASIS virtio spec.
Signed-off-by: Anton Yakovlev anton.yakovlev@opensynergy.com
MAINTAINERS | 6 + include/uapi/linux/virtio_snd.h | 361 ++++++++++++++++++++++++++++++++ 2 files changed, 367 insertions(+) create mode 100644 include/uapi/linux/virtio_snd.h
diff --git a/MAINTAINERS b/MAINTAINERS index 00836f6452f0..6dfd59eafe82 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18936,6 +18936,12 @@ W: https://virtio-mem.gitlab.io/ F: drivers/virtio/virtio_mem.c F: include/uapi/linux/virtio_mem.h
+VIRTIO SOUND DRIVER +M: Anton Yakovlev anton.yakovlev@opensynergy.com +L: virtualization@lists.linux-foundation.org +S: Maintained +F: include/uapi/linux/virtio_snd.h
VIRTUAL BOX GUEST DEVICE DRIVER M: Hans de Goede hdegoede@redhat.com M: Arnd Bergmann arnd@arndb.de
You want sound/virtio here too, right? I'd just squash this with the next patch in series.
I meant just the MAINTAINERS part. Not a big deal, admittedly ...
diff --git a/include/uapi/linux/virtio_snd.h b/include/uapi/linux/virtio_snd.h new file mode 100644 index 000000000000..1ff6310e54d6 --- /dev/null +++ b/include/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:
- Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- 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.
- 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 */
2.30.0
Hello, Michael.
Thanks you for your comments!
On 20.01.2021 09:19, Michael S. Tsirkin wrote:
CAUTION: This email originated from outside of the organization. Do not click links or open attachments unless you recognize the sender and know the content is safe.
On Wed, Jan 20, 2021 at 01:36:30AM +0100, Anton Yakovlev wrote:
The file contains the definitions for the sound device from the OASIS virtio spec.
Signed-off-by: Anton Yakovlev anton.yakovlev@opensynergy.com
MAINTAINERS | 6 + include/uapi/linux/virtio_snd.h | 361 ++++++++++++++++++++++++++++++++ 2 files changed, 367 insertions(+) create mode 100644 include/uapi/linux/virtio_snd.h
diff --git a/MAINTAINERS b/MAINTAINERS index 00836f6452f0..6dfd59eafe82 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18936,6 +18936,12 @@ W: https://virtio-mem.gitlab.io/ F: drivers/virtio/virtio_mem.c F: include/uapi/linux/virtio_mem.h
+VIRTIO SOUND DRIVER +M: Anton Yakovlev anton.yakovlev@opensynergy.com +L: virtualization@lists.linux-foundation.org +S: Maintained +F: include/uapi/linux/virtio_snd.h
- VIRTUAL BOX GUEST DEVICE DRIVER M: Hans de Goede hdegoede@redhat.com M: Arnd Bergmann arnd@arndb.de
You want sound/virtio here too, right? I'd just squash this with the next patch in series.
Yes, I squashed these two in v2 and added you to the MAINTAINERS.
diff --git a/include/uapi/linux/virtio_snd.h b/include/uapi/linux/virtio_snd.h new file mode 100644 index 000000000000..1ff6310e54d6 --- /dev/null +++ b/include/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:
- Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- 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.
- 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 */
2.30.0
On Wed, Jan 20, 2021 at 01:36:30AM +0100, Anton Yakovlev wrote:
The file contains the definitions for the sound device from the OASIS virtio spec.
Signed-off-by: Anton Yakovlev anton.yakovlev@opensynergy.com
MAINTAINERS | 6 + include/uapi/linux/virtio_snd.h | 361 ++++++++++++++++++++++++++++++++ 2 files changed, 367 insertions(+) create mode 100644 include/uapi/linux/virtio_snd.h
diff --git a/MAINTAINERS b/MAINTAINERS index 00836f6452f0..6dfd59eafe82 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18936,6 +18936,12 @@ W: https://virtio-mem.gitlab.io/ F: drivers/virtio/virtio_mem.c F: include/uapi/linux/virtio_mem.h
+VIRTIO SOUND DRIVER +M: Anton Yakovlev anton.yakovlev@opensynergy.com +L: virtualization@lists.linux-foundation.org +S: Maintained +F: include/uapi/linux/virtio_snd.h
VIRTUAL BOX GUEST DEVICE DRIVER M: Hans de Goede hdegoede@redhat.com M: Arnd Bergmann arnd@arndb.de
Who's merging this driver me? If so pls add mst@redhat.com so I'm copied on patches.
diff --git a/include/uapi/linux/virtio_snd.h b/include/uapi/linux/virtio_snd.h new file mode 100644 index 000000000000..1ff6310e54d6 --- /dev/null +++ b/include/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:
- Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- 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.
- 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 */
2.30.0
Introduce skeleton of the virtio sound driver. The driver implements the virtio sound device specification, which has become part of the virtio standard.
Initial initialization of the device, virtqueues and creation of an empty ALSA sound device. Also, handling DEVICE_NEEDS_RESET device status.
Signed-off-by: Anton Yakovlev anton.yakovlev@opensynergy.com --- MAINTAINERS | 2 + sound/Kconfig | 2 + sound/Makefile | 3 +- sound/virtio/Kconfig | 10 + sound/virtio/Makefile | 9 + sound/virtio/virtio_card.c | 473 ++++++++++++++++++++++++++++++ sound/virtio/virtio_card.h | 92 ++++++ sound/virtio/virtio_ctl_msg.c | 293 +++++++++++++++++++ sound/virtio/virtio_ctl_msg.h | 122 ++++++++ sound/virtio/virtio_pcm.c | 536 ++++++++++++++++++++++++++++++++++ sound/virtio/virtio_pcm.h | 89 ++++++ 11 files changed, 1630 insertions(+), 1 deletion(-) create mode 100644 sound/virtio/Kconfig create mode 100644 sound/virtio/Makefile create mode 100644 sound/virtio/virtio_card.c create mode 100644 sound/virtio/virtio_card.h create mode 100644 sound/virtio/virtio_ctl_msg.c create mode 100644 sound/virtio/virtio_ctl_msg.h create mode 100644 sound/virtio/virtio_pcm.c create mode 100644 sound/virtio/virtio_pcm.h
diff --git a/MAINTAINERS b/MAINTAINERS index 6dfd59eafe82..8a0e9f04402f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18939,8 +18939,10 @@ F: include/uapi/linux/virtio_mem.h VIRTIO SOUND DRIVER M: Anton Yakovlev anton.yakovlev@opensynergy.com L: virtualization@lists.linux-foundation.org +L: alsa-devel@alsa-project.org (moderated for non-subscribers) S: Maintained F: include/uapi/linux/virtio_snd.h +F: sound/virtio/*
VIRTUAL BOX GUEST DEVICE DRIVER M: Hans de Goede hdegoede@redhat.com diff --git a/sound/Kconfig b/sound/Kconfig index 36785410fbe1..e56d96d2b11c 100644 --- a/sound/Kconfig +++ b/sound/Kconfig @@ -99,6 +99,8 @@ source "sound/synth/Kconfig"
source "sound/xen/Kconfig"
+source "sound/virtio/Kconfig" + endif # SND
endif # !UML diff --git a/sound/Makefile b/sound/Makefile index 797ecdcd35e2..04ef04b1168f 100644 --- a/sound/Makefile +++ b/sound/Makefile @@ -5,7 +5,8 @@ obj-$(CONFIG_SOUND) += soundcore.o obj-$(CONFIG_DMASOUND) += oss/dmasound/ obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ sh/ synth/ usb/ \ - firewire/ sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/ hda/ x86/ xen/ + firewire/ sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/ hda/ x86/ xen/ \ + virtio/ obj-$(CONFIG_SND_AOA) += aoa/
# This one must be compilable even if sound is configured out diff --git a/sound/virtio/Kconfig b/sound/virtio/Kconfig new file mode 100644 index 000000000000..094cba24ee5b --- /dev/null +++ b/sound/virtio/Kconfig @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Sound card driver for virtio + +config SND_VIRTIO + tristate "Virtio sound driver" + depends on VIRTIO + select SND_PCM + select SND_JACK + help + This is the virtual sound driver for virtio. Say Y or M. diff --git a/sound/virtio/Makefile b/sound/virtio/Makefile new file mode 100644 index 000000000000..69162a545a41 --- /dev/null +++ b/sound/virtio/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0+ + +obj-$(CONFIG_SND_VIRTIO) += virtio_snd.o + +virtio_snd-objs := \ + virtio_card.o \ + virtio_ctl_msg.o \ + virtio_pcm.o + diff --git a/sound/virtio/virtio_card.c b/sound/virtio/virtio_card.c new file mode 100644 index 000000000000..293d497f24e7 --- /dev/null +++ b/sound/virtio/virtio_card.c @@ -0,0 +1,473 @@ +// 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/virtio_config.h> +#include <sound/initval.h> +#include <uapi/linux/virtio_ids.h> + +#include "virtio_card.h" + +int msg_timeout_ms = MSEC_PER_SEC; +module_param(msg_timeout_ms, int, 0644); +MODULE_PARM_DESC(msg_timeout_ms, "Message completion timeout in milliseconds"); + +static int virtsnd_probe(struct virtio_device *vdev); +static void virtsnd_remove(struct virtio_device *vdev); + +/** + * virtsnd_event_send() - Add an event to the event queue. + * @vqueue: Underlying event virtqueue. + * @event: Event. + * @notify: Indicates whether or not to send a notification to the device. + * @gfp: Kernel flags for memory allocation. + * + * Context: Any context. + * Return: 0 on success, -errno on failure. + */ +static int virtsnd_event_send(struct virtqueue *vqueue, + struct virtio_snd_event *event, bool notify, + gfp_t gfp) +{ + struct scatterlist sg; + struct scatterlist *psgs[1] = { &sg }; + int rc; + + /* 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; +} + +/** + * virtsnd_event_notify_cb() - Dispatch all reported events from the event queue. + * @vqueue: Underlying event virtqueue. + * + * This callback function is called upon a vring interrupt request from the + * device. + * + * Context: Interrupt context. + */ +static 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; + + switch (le32_to_cpu(event->hdr.code)) { + case VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED: + case VIRTIO_SND_EVT_PCM_XRUN: { + virtsnd_pcm_event(snd, event); + break; + } + default: { + break; + } + } + + 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); +} + +/** + * virtsnd_find_vqs() - Enumerate and initialize all virtqueues. + * @snd: VirtIO sound device. + * + * After calling this function, interrupts are allowed only for the control + * queue, all other queues are disabled. + * + * Context: Any context. + * Return: 0 on success, -errno on failure. + */ +static int virtsnd_find_vqs(struct virtio_snd *snd) +{ + struct virtio_device *vdev = snd->vdev; + vq_callback_t *callbacks[VIRTIO_SND_VQ_MAX] = { 0 }; + const char *names[VIRTIO_SND_VQ_MAX] = { + [VIRTIO_SND_VQ_CONTROL] = "virtsnd-ctl", + [VIRTIO_SND_VQ_EVENT] = "virtsnd-event", + [VIRTIO_SND_VQ_TX] = "virtsnd-tx", + [VIRTIO_SND_VQ_RX] = "virtsnd-rx" + }; + struct virtqueue *vqs[VIRTIO_SND_VQ_MAX] = { 0 }; + unsigned int i; + unsigned int n = 0; + int rc; + + callbacks[VIRTIO_SND_VQ_CONTROL] = virtsnd_ctl_notify_cb; + callbacks[VIRTIO_SND_VQ_EVENT] = virtsnd_event_notify_cb; + + rc = virtio_find_vqs(vdev, VIRTIO_SND_VQ_MAX, vqs, callbacks, names, + NULL); + if (rc) { + dev_err(&vdev->dev, "failed to initialize virtqueues\n"); + 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]; + } + + /* Allocate events and populate the event queue */ + n = virtqueue_get_vring_size(vqs[VIRTIO_SND_VQ_EVENT]); + + snd->event_msgs = devm_kcalloc(&vdev->dev, n, sizeof(*snd->event_msgs), + GFP_KERNEL); + if (!snd->event_msgs) + return -ENOMEM; + + for (i = 0; i < n; ++i) { + rc = virtsnd_event_send(vqs[VIRTIO_SND_VQ_EVENT], + &snd->event_msgs[i], false, GFP_KERNEL); + if (rc) + return rc; + } + + return 0; +} + +/** + * virtsnd_enable_vqs() - Enable the event, tx and rx virtqueues. + * @snd: VirtIO sound device. + * + * Context: Any context. + */ +static void virtsnd_enable_vqs(struct virtio_snd *snd) +{ + struct virtqueue *vqueue; + + vqueue = snd->queues[VIRTIO_SND_VQ_EVENT].vqueue; + if (!virtqueue_enable_cb(vqueue)) + virtsnd_event_notify_cb(vqueue); +} + +/** + * virtsnd_disable_vqs() - Disable all virtqueues. + * @snd: VirtIO sound device. + * + * Also free all allocated events and control messages. + * + * Context: Any context. + */ +static void virtsnd_disable_vqs(struct virtio_snd *snd) +{ + struct virtio_device *vdev = snd->vdev; + unsigned 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); + /* Prohibit the use of the queue */ + if (queue->vqueue) + virtqueue_disable_cb(queue->vqueue); + queue->vqueue = NULL; + /* Cancel all pending requests for the control queue */ + if (i == VIRTIO_SND_VQ_CONTROL) { + struct virtio_snd_msg *msg; + struct virtio_snd_msg *next; + + list_for_each_entry_safe(msg, next, &snd->ctl_msgs, + list) + virtsnd_ctl_msg_complete(snd, msg); + } + spin_unlock_irqrestore(&queue->lock, flags); + } + + if (snd->event_msgs) + devm_kfree(&vdev->dev, snd->event_msgs); + + snd->event_msgs = NULL; +} + +/** + * virtsnd_reset_fn() - Kernel worker's function to reset the device. + * @work: Reset device work. + * + * Context: Process context. + */ +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\n"); + + /* + * It seems that the only way to properly reset the device is to remove + * and re-create the ALSA sound card device. + */ + virtsnd_remove(vdev); + + rc = virtsnd_probe(vdev); + if (rc) + dev_err(dev, "re-probe() failed: %d\n", rc); +} + +/** + * virtsnd_build_devs() - Read configuration and build ALSA devices. + * @snd: VirtIO sound device. + * + * Context: Any context that permits to sleep. + * Return: 0 on success, -errno on failure. + */ +static int virtsnd_build_devs(struct virtio_snd *snd) +{ + struct virtio_device *vdev = snd->vdev; + int 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; + + strscpy(snd->card->id, "viosnd", sizeof(snd->card->id)); + strscpy(snd->card->driver, "virtio_snd", sizeof(snd->card->driver)); + strscpy(snd->card->shortname, "VIOSND", sizeof(snd->card->shortname)); + strscpy(snd->card->longname, "VirtIO Sound Card", + sizeof(snd->card->longname)); + + rc = virtsnd_pcm_parse_cfg(snd); + if (rc) + return rc; + + if (snd->nsubstreams) { + rc = virtsnd_pcm_build_devs(snd); + if (rc) + return rc; + } + + return snd_card_register(snd->card); +} + +/** + * virtsnd_validate() - Validate if the device can be started. + * @vdev: VirtIO parent device. + * + * Context: Any context. + * Return: 0 on success, -EINVAL on failure. + */ +static int virtsnd_validate(struct virtio_device *vdev) +{ + if (!vdev->config->get) { + dev_err(&vdev->dev, "configuration access disabled\n"); + return -EINVAL; + } + + if (!msg_timeout_ms) { + dev_err(&vdev->dev, "msg_timeout_ms value cannot be zero\n"); + return -EINVAL; + } + + if (virtsnd_pcm_validate(vdev)) + return -EINVAL; + + return 0; +} + +/** + * virtsnd_probe() - Create and initialize the device. + * @vdev: VirtIO parent device. + * + * Context: Any context that permits to sleep. + * Return: 0 on success, -errno on failure. + */ +static int virtsnd_probe(struct virtio_device *vdev) +{ + struct virtio_snd *snd; + unsigned int i; + int rc; + + 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; +} + +/** + * virtsnd_remove() - Remove VirtIO and ALSA devices. + * @vdev: VirtIO parent device. + * + * Context: Any context that permits to sleep. + */ +static void virtsnd_remove(struct virtio_device *vdev) +{ + struct virtio_snd *snd = vdev->priv; + struct virtio_pcm *pcm; + struct virtio_pcm *pcm_next; + + if (!snd) + return; + + virtsnd_disable_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); + } + + devm_kfree(&vdev->dev, pcm); + } + + if (snd->substreams) + devm_kfree(&vdev->dev, snd->substreams); + + devm_kfree(&vdev->dev, snd); + + vdev->priv = NULL; +} + +/** + * virtsnd_config_changed() - Handle configuration change notification. + * @vdev: VirtIO parent device. + * + * This callback function is called upon a configuration change interrupt + * request from the device. Currently only used to handle NEEDS_RESET device + * status. + * + * Context: Interrupt context. + */ +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\n"); +} + +static const struct virtio_device_id id_table[] = { + { VIRTIO_ID_SOUND, VIRTIO_DEV_ANY_ID }, + { 0 }, +}; + +static struct virtio_driver virtsnd_driver = { + .driver.name = KBUILD_MODNAME, + .driver.owner = THIS_MODULE, + .id_table = id_table, + .validate = virtsnd_validate, + .probe = virtsnd_probe, + .remove = virtsnd_remove, + .config_changed = virtsnd_config_changed, +}; + +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/sound/virtio/virtio_card.h b/sound/virtio/virtio_card.h new file mode 100644 index 000000000000..be6651a6aaf8 --- /dev/null +++ b/sound/virtio/virtio_card.h @@ -0,0 +1,92 @@ +/* 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 <uapi/linux/virtio_snd.h> + +#include "virtio_ctl_msg.h" +#include "virtio_pcm.h" + +struct virtio_pcm_substream; + +/** + * struct virtio_snd_queue - Virtqueue wrapper structure. + * @lock: Used to synchronize access to a virtqueue. + * @vqueue: Underlying virtqueue. + */ +struct virtio_snd_queue { + spinlock_t lock; + struct virtqueue *vqueue; +}; + +/** + * struct virtio_snd - VirtIO sound card device. + * @vdev: Underlying virtio device. + * @queues: Virtqueue wrappers. + * @reset_work: Reset device work. + * @card: ALSA sound card. + * @ctl_msgs: Pending control request list. + * @event_msgs: Device events. + * @pcm_list: VirtIO PCM device list. + * @substreams: VirtIO PCM substreams. + * @nsubstreams: Number of PCM substreams. + */ +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_pcm_substream *substreams; + unsigned int nsubstreams; +}; + +/* Message completion timeout in milliseconds (module parameter). */ +extern int msg_timeout_ms; + +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]; +} + +#endif /* VIRTIO_SND_CARD_H */ diff --git a/sound/virtio/virtio_ctl_msg.c b/sound/virtio/virtio_ctl_msg.c new file mode 100644 index 000000000000..c1701756bc32 --- /dev/null +++ b/sound/virtio/virtio_ctl_msg.c @@ -0,0 +1,293 @@ +// 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" + +/** + * virtsnd_ctl_msg_alloc_ext() - Allocate and initialize a control message. + * @vdev: VirtIO parent device. + * @request_size: Size of request header (pointed to by sg_request field). + * @response_size: Size of response header (pointed to by sg_response field). + * @sgs: Additional data to attach to the message (may be NULL). + * @out_sgs: Number of scattergather elements to attach to the request header. + * @in_sgs: Number of scattergather elements to attach to the response header. + * @gfp: Kernel flags for memory allocation. + * + * The message will be automatically freed when the ref_count value is 0. + * + * Context: Any context. May sleep if @gfp flags permit. + * Return: Allocated message on success, ERR_PTR(-errno) on failure. + */ +struct virtio_snd_msg *virtsnd_ctl_msg_alloc_ext(struct virtio_device *vdev, + size_t request_size, + size_t response_size, + struct scatterlist *sgs, + unsigned int out_sgs, + unsigned int in_sgs, gfp_t gfp) +{ + struct virtio_snd_msg *msg; + size_t msg_size = + sizeof(*msg) + (1 + out_sgs + 1 + in_sgs) * sizeof(*msg->sgs); + unsigned int i; + + msg = devm_kzalloc(&vdev->dev, msg_size + request_size + response_size, + gfp); + if (!msg) + return ERR_PTR(-ENOMEM); + + sg_init_one(&msg->sg_request, (u8 *)msg + msg_size, request_size); + sg_init_one(&msg->sg_response, (u8 *)msg + msg_size + request_size, + response_size); + + INIT_LIST_HEAD(&msg->list); + init_completion(&msg->notify); + atomic_set(&msg->ref_count, 1); + + msg->sgs[msg->out_sgs++] = &msg->sg_request; + if (sgs) + for (i = 0; i < out_sgs; ++i) + msg->sgs[msg->out_sgs++] = &sgs[i]; + + msg->sgs[msg->out_sgs + msg->in_sgs++] = &msg->sg_response; + if (sgs) + for (i = out_sgs; i < out_sgs + in_sgs; ++i) + msg->sgs[msg->out_sgs + msg->in_sgs++] = &sgs[i]; + + return msg; +} + +/** + * virtsnd_ctl_msg_send() - Send an (asynchronous) control message. + * @snd: VirtIO sound device. + * @msg: Control message. + * + * If a message is failed to be enqueued, it will be deleted. If message content + * is still needed, the caller must additionally to virtsnd_ctl_msg_ref/unref() + * it. + * + * Context: Any context. Takes and releases the control queue spinlock. + * Return: 0 on success, -errno on failure. + */ +int virtsnd_ctl_msg_send(struct virtio_snd *snd, struct virtio_snd_msg *msg) +{ + struct virtio_device *vdev = snd->vdev; + struct virtio_snd_queue *queue = virtsnd_control_queue(snd); + struct virtio_snd_hdr *response = sg_virt(&msg->sg_response); + bool notify = false; + unsigned long flags; + int rc = -EIO; + + /* Set the default status in case the message was not sent or was + * canceled. + */ + response->code = cpu_to_virtio32(vdev, VIRTIO_SND_S_IO_ERR); + + spin_lock_irqsave(&queue->lock, flags); + if (queue->vqueue) { + rc = virtqueue_add_sgs(queue->vqueue, msg->sgs, msg->out_sgs, + msg->in_sgs, msg, GFP_ATOMIC); + if (!rc) { + notify = virtqueue_kick_prepare(queue->vqueue); + list_add_tail(&msg->list, &snd->ctl_msgs); + } + } + spin_unlock_irqrestore(&queue->lock, flags); + + if (!rc) { + if (!notify || virtqueue_notify(queue->vqueue)) + return 0; + + spin_lock_irqsave(&queue->lock, flags); + list_del(&msg->list); + spin_unlock_irqrestore(&queue->lock, flags); + } + + virtsnd_ctl_msg_unref(snd->vdev, msg); + + return -EIO; +} + +/** + * virtsnd_ctl_msg_send_sync() - Send a (synchronous) control message. + * @snd: VirtIO sound device. + * @msg: Control message. + * + * After returning from this function, the message will be deleted. If message + * content is still needed, the caller must additionally to + * virtsnd_ctl_msg_ref/unref() it. + * + * The msg_timeout_ms module parameter defines the message completion timeout. + * If the message is not completed within this time, the function will return an + * error. + * + * Context: Any context. Takes and releases the control queue spinlock. + * Return: 0 on success, -errno on failure. + * + * The return value is a message status code (VIRTIO_SND_S_XXX) converted to an + * appropriate -errno value. + */ +int virtsnd_ctl_msg_send_sync(struct virtio_snd *snd, + struct virtio_snd_msg *msg) +{ + struct virtio_device *vdev = snd->vdev; + unsigned int js = msecs_to_jiffies(msg_timeout_ms); + struct virtio_snd_hdr *response; + int rc; + + virtsnd_ctl_msg_ref(vdev, msg); + + rc = virtsnd_ctl_msg_send(snd, msg); + if (rc) + goto on_failure; + + rc = wait_for_completion_interruptible_timeout(&msg->notify, js); + if (rc <= 0) { + if (!rc) { + struct virtio_snd_hdr *request = + sg_virt(&msg->sg_request); + + dev_err(&vdev->dev, + "control message (0x%08x) timeout\n", + le32_to_cpu(request->code)); + rc = -EIO; + } + + goto on_failure; + } + + response = sg_virt(&msg->sg_response); + + switch (le32_to_cpu(response->code)) { + case VIRTIO_SND_S_OK: + rc = 0; + break; + case VIRTIO_SND_S_BAD_MSG: + rc = -EINVAL; + break; + case VIRTIO_SND_S_NOT_SUPP: + rc = -EOPNOTSUPP; + break; + case VIRTIO_SND_S_IO_ERR: + rc = -EIO; + break; + default: + rc = -EPERM; + break; + } + +on_failure: + virtsnd_ctl_msg_unref(vdev, msg); + + return rc; +} + +/** + * virtsnd_ctl_msg_complete() - Complete a control message. + * @snd: VirtIO sound device. + * @msg: Control message. + * + * Context: Any context. + */ +void virtsnd_ctl_msg_complete(struct virtio_snd *snd, + struct virtio_snd_msg *msg) +{ + list_del(&msg->list); + complete(&msg->notify); + + virtsnd_ctl_msg_unref(snd->vdev, msg); +} + +/** + * virtsnd_ctl_query_info() - Query the item configuration from the device. + * @snd: VirtIO sound device. + * @command: Control request code (VIRTIO_SND_R_XXX_INFO). + * @start_id: Item start identifier. + * @count: Item count to query. + * @size: Item information size in bytes. + * @info: Buffer for storing item information. + * + * Context: Any context that permits to sleep. + * Return: 0 on success, -errno on failure. + */ +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; + + sg_init_one(&sg, info, count * size); + + msg = virtsnd_ctl_msg_alloc_ext(vdev, sizeof(*query), + sizeof(struct virtio_snd_hdr), &sg, 0, + 1, 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); + + return virtsnd_ctl_msg_send_sync(snd, msg); +} + +/** + * virtsnd_ctl_notify_cb() - Process all completed control messages. + * @vqueue: Underlying control virtqueue. + * + * This callback function is called upon a vring interrupt request from the + * device. + * + * Context: Interrupt context. Takes and releases the control queue spinlock. + */ +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; + + virtsnd_ctl_msg_complete(snd, 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/sound/virtio/virtio_ctl_msg.h b/sound/virtio/virtio_ctl_msg.h new file mode 100644 index 000000000000..0f8de8f2fd2d --- /dev/null +++ b/sound/virtio/virtio_ctl_msg.h @@ -0,0 +1,122 @@ +/* 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 - Control message. + * @sg_request: Scattergather element containing a device request (header). + * @sg_response: Scattergather element containing a device response (status). + * @list: Pending message list entry. + * @notify: Request completed notification. + * @ref_count: Reference count used to manage a message lifetime. + * @out_sgs: Number of read-only sg elements in the sgs array. + * @in_sgs: Number of write-only sg elements in the sgs array. + * @sgs: Array of sg elements to add to the control virtqueue. + */ +struct virtio_snd_msg { +/* public: */ + struct scatterlist sg_request; + struct scatterlist sg_response; +/* private: internal use only */ + struct list_head list; + struct completion notify; + atomic_t ref_count; + unsigned int out_sgs; + unsigned int in_sgs; + struct scatterlist *sgs[0]; +}; + +/** + * virtsnd_ctl_msg_ref() - Increment reference counter for the message. + * @vdev: VirtIO parent device. + * @msg: Control message. + * + * Context: Any context. + */ +static inline void virtsnd_ctl_msg_ref(struct virtio_device *vdev, + struct virtio_snd_msg *msg) +{ + atomic_inc(&msg->ref_count); +} + +/** + * virtsnd_ctl_msg_unref() - Decrement reference counter for the message. + * @vdev: VirtIO parent device. + * @msg: Control message. + * + * The message will be freed when the ref_count value is 0. + * + * Context: Any context. + */ +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_ext(struct virtio_device *vdev, + size_t request_size, + size_t response_size, + struct scatterlist *sgs, + unsigned int out_sgs, + unsigned int in_sgs, + gfp_t gfp); + +/** + * virtsnd_ctl_msg_alloc() - Simplified control message allocation. + * @vdev: VirtIO parent device. + * @request_size: Size of request header (pointed to by sg_request field). + * @response_size: Size of response header (pointed to by sg_response field). + * @gfp: Kernel flags for memory allocation. + * + * The message will be automatically freed when the ref_count value is 0. + * + * Context: Any context. May sleep if @gfp flags permit. + * Return: Allocated message on success, ERR_PTR(-errno) on failure. + */ +static inline +struct virtio_snd_msg *virtsnd_ctl_msg_alloc(struct virtio_device *vdev, + size_t request_size, + size_t response_size, gfp_t gfp) +{ + return virtsnd_ctl_msg_alloc_ext(vdev, request_size, response_size, + NULL, 0, 0, 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); + +void virtsnd_ctl_msg_complete(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/sound/virtio/virtio_pcm.c b/sound/virtio/virtio_pcm.c new file mode 100644 index 000000000000..036990b7b78a --- /dev/null +++ b/sound/virtio/virtio_pcm.c @@ -0,0 +1,536 @@ +// 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"); + +/* Map for converting VirtIO format to ALSA format. */ +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, + [VIRTIO_SND_PCM_FMT_S20] = SNDRV_PCM_FORMAT_S20_LE, + [VIRTIO_SND_PCM_FMT_U20] = SNDRV_PCM_FORMAT_U20_LE, + [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 +}; + +/* Map for converting VirtIO frame rate to ALSA frame rate. */ +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 } +}; + +/** + * virtsnd_pcm_build_hw() - Parse substream config and build HW descriptor. + * @substream: VirtIO substream. + * @info: VirtIO substream information entry. + * + * Context: Any context. + * Return: 0 on success, -EINVAL if configuration is invalid. + */ +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; + + if (!info->channels_min || info->channels_min > info->channels_max) { + dev_err(&vdev->dev, + "SID %u: invalid channel range [%u %u]\n", + 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); + + substream->hw.formats = 0; + + 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\n", + substream->sid); + return -EINVAL; + } + + values = le64_to_cpu(info->rates); + + substream->hw.rates = 0; + + 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\n", + 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; +} + +/** + * virtsnd_pcm_prealloc_pages() - Preallocate substream hardware buffer. + * @substream: VirtIO substream. + * + * Context: Any context that permits to sleep. + * Return: 0 on success, -errno on failure. + */ +static int 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); + + /* + * We just allocate a CONTINUOUS buffer as it should work in any setup. + * + * If there is a need to use DEV(_XXX), then add this case here and + * (probably) update the related source code in other places. + */ + snd_pcm_lib_preallocate_pages(ksubstream, SNDRV_DMA_TYPE_CONTINUOUS, + data, size, size); + + return 0; +} + +/** + * virtsnd_pcm_find() - Find the PCM device for the specified node ID. + * @snd: VirtIO sound device. + * @nid: Function node ID. + * + * Context: Any context. + * Return: a pointer to the PCM device or ERR_PTR(-ENOENT). + */ +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); +} + +/** + * virtsnd_pcm_find_or_create() - Find or create the PCM device for the + * specified node ID. + * @snd: VirtIO sound device. + * @nid: Function node ID. + * + * Context: Any context that permits to sleep. + * Return: a pointer to the PCM device or ERR_PTR(-errno). + */ +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; +} + +/** + * virtsnd_pcm_validate() - Validate if the device can be started. + * @vdev: VirtIO parent device. + * + * Context: Any context. + * Return: 0 on success, -EINVAL on failure. + */ +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\n", + 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\n", + 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\n", + 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\n", + pcm_period_ms_max, pcm_buffer_ms / 2); + return -EINVAL; + } + + return 0; +} + +/** + * virtsnd_pcm_parse_cfg() - Parse the stream configuration. + * @snd: VirtIO sound device. + * + * This function is called during initial device initialization. + * + * Context: Any context that permits to sleep. + * Return: 0 on success, -errno on failure. + */ +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; + + 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)\n", + substream->sid, info[i].direction); + return -EINVAL; + } + } + + pcm->streams[substream->direction].nsubstreams++; + } + + devm_kfree(&vdev->dev, info); + + return 0; +} + +/** + * virtsnd_pcm_build_devs() - Build ALSA PCM devices. + * @snd: VirtIO sound device. + * + * Context: Any context that permits to sleep. + * Return: 0 on success, -errno on failure. + */ +int virtsnd_pcm_build_devs(struct virtio_snd *snd) +{ + struct virtio_device *vdev = snd->vdev; + struct virtio_pcm *pcm; + unsigned int i; + int rc; + + 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; + + rc = snd_pcm_new(snd->card, "virtio_snd", pcm->nid, npbs, ncps, + &pcm->pcm); + if (rc) { + dev_err(&vdev->dev, "snd_pcm_new[%u] failed: %d\n", + pcm->nid, rc); + return rc; + } + + pcm->pcm->info_flags = 0; + pcm->pcm->dev_class = SNDRV_PCM_CLASS_GENERIC; + pcm->pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX; + strscpy(pcm->pcm->name, "VirtIO PCM", sizeof(pcm->pcm->name)); + + 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; + + rc = virtsnd_pcm_prealloc_pages(substream); + if (rc) + return rc; + } + } + + return 0; +} + +/** + * virtsnd_pcm_event() - Handle the PCM device event notification. + * @snd: VirtIO sound device. + * @event: VirtIO sound event. + * + * Context: Interrupt context. + */ +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: { + break; + } + } +} diff --git a/sound/virtio/virtio_pcm.h b/sound/virtio/virtio_pcm.h new file mode 100644 index 000000000000..73fb4d9dc524 --- /dev/null +++ b/sound/virtio/virtio_pcm.h @@ -0,0 +1,89 @@ +/* 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_substream - VirtIO PCM substream. + * @snd: VirtIO sound device. + * @nid: Function group node identifier. + * @sid: Stream identifier. + * @direction: Stream data flow direction (SNDRV_PCM_STREAM_XXX). + * @features: Stream VirtIO feature bit map (1 << VIRTIO_SND_PCM_F_XXX). + * @substream: Kernel ALSA substream. + * @hw: Kernel ALSA substream hardware descriptor. + */ +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; +}; + +/** + * struct virtio_pcm_stream - VirtIO PCM stream. + * @substreams: Virtio substreams belonging to the stream. + * @nsubstreams: Number of substreams. + */ +struct virtio_pcm_stream { + struct virtio_pcm_substream **substreams; + unsigned int nsubstreams; +}; + +/** + * struct virtio_pcm - VirtIO PCM device. + * @list: PCM list entry. + * @nid: Function group node identifier. + * @pcm: Kernel PCM device. + * @streams: VirtIO PCM 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]; +}; + +int virtsnd_pcm_validate(struct virtio_device *vdev); + +int virtsnd_pcm_parse_cfg(struct virtio_snd *snd); + +int virtsnd_pcm_build_devs(struct virtio_snd *snd); + +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); + +#endif /* VIRTIO_SND_PCM_H */
On Wed, Jan 20, 2021 at 01:36:31AM +0100, Anton Yakovlev wrote:
Introduce skeleton of the virtio sound driver. The driver implements the virtio sound device specification, which has become part of the virtio standard.
Initial initialization of the device, virtqueues and creation of an empty ALSA sound device. Also, handling DEVICE_NEEDS_RESET device status.
Signed-off-by: Anton Yakovlev anton.yakovlev@opensynergy.com
MAINTAINERS | 2 + sound/Kconfig | 2 + sound/Makefile | 3 +- sound/virtio/Kconfig | 10 + sound/virtio/Makefile | 9 + sound/virtio/virtio_card.c | 473 ++++++++++++++++++++++++++++++ sound/virtio/virtio_card.h | 92 ++++++ sound/virtio/virtio_ctl_msg.c | 293 +++++++++++++++++++ sound/virtio/virtio_ctl_msg.h | 122 ++++++++ sound/virtio/virtio_pcm.c | 536 ++++++++++++++++++++++++++++++++++ sound/virtio/virtio_pcm.h | 89 ++++++ 11 files changed, 1630 insertions(+), 1 deletion(-) create mode 100644 sound/virtio/Kconfig create mode 100644 sound/virtio/Makefile create mode 100644 sound/virtio/virtio_card.c create mode 100644 sound/virtio/virtio_card.h create mode 100644 sound/virtio/virtio_ctl_msg.c create mode 100644 sound/virtio/virtio_ctl_msg.h create mode 100644 sound/virtio/virtio_pcm.c create mode 100644 sound/virtio/virtio_pcm.h
diff --git a/MAINTAINERS b/MAINTAINERS index 6dfd59eafe82..8a0e9f04402f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18939,8 +18939,10 @@ F: include/uapi/linux/virtio_mem.h VIRTIO SOUND DRIVER M: Anton Yakovlev anton.yakovlev@opensynergy.com L: virtualization@lists.linux-foundation.org +L: alsa-devel@alsa-project.org (moderated for non-subscribers) S: Maintained F: include/uapi/linux/virtio_snd.h +F: sound/virtio/*
VIRTUAL BOX GUEST DEVICE DRIVER M: Hans de Goede hdegoede@redhat.com diff --git a/sound/Kconfig b/sound/Kconfig index 36785410fbe1..e56d96d2b11c 100644 --- a/sound/Kconfig +++ b/sound/Kconfig @@ -99,6 +99,8 @@ source "sound/synth/Kconfig"
source "sound/xen/Kconfig"
+source "sound/virtio/Kconfig"
endif # SND
endif # !UML diff --git a/sound/Makefile b/sound/Makefile index 797ecdcd35e2..04ef04b1168f 100644 --- a/sound/Makefile +++ b/sound/Makefile @@ -5,7 +5,8 @@ obj-$(CONFIG_SOUND) += soundcore.o obj-$(CONFIG_DMASOUND) += oss/dmasound/ obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ sh/ synth/ usb/ \
- firewire/ sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/ hda/ x86/ xen/
- firewire/ sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/ hda/ x86/ xen/ \
- virtio/
obj-$(CONFIG_SND_AOA) += aoa/
# This one must be compilable even if sound is configured out diff --git a/sound/virtio/Kconfig b/sound/virtio/Kconfig new file mode 100644 index 000000000000..094cba24ee5b --- /dev/null +++ b/sound/virtio/Kconfig @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Sound card driver for virtio
+config SND_VIRTIO
- tristate "Virtio sound driver"
- depends on VIRTIO
- select SND_PCM
- select SND_JACK
- help
This is the virtual sound driver for virtio. Say Y or M.
diff --git a/sound/virtio/Makefile b/sound/virtio/Makefile new file mode 100644 index 000000000000..69162a545a41 --- /dev/null +++ b/sound/virtio/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0+
+obj-$(CONFIG_SND_VIRTIO) += virtio_snd.o
+virtio_snd-objs := \
- virtio_card.o \
- virtio_ctl_msg.o \
- virtio_pcm.o
diff --git a/sound/virtio/virtio_card.c b/sound/virtio/virtio_card.c new file mode 100644 index 000000000000..293d497f24e7 --- /dev/null +++ b/sound/virtio/virtio_card.c @@ -0,0 +1,473 @@ +// 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/virtio_config.h> +#include <sound/initval.h> +#include <uapi/linux/virtio_ids.h>
+#include "virtio_card.h"
+int msg_timeout_ms = MSEC_PER_SEC; +module_param(msg_timeout_ms, int, 0644); +MODULE_PARM_DESC(msg_timeout_ms, "Message completion timeout in milliseconds");
+static int virtsnd_probe(struct virtio_device *vdev); +static void virtsnd_remove(struct virtio_device *vdev);
+/**
- virtsnd_event_send() - Add an event to the event queue.
- @vqueue: Underlying event virtqueue.
- @event: Event.
- @notify: Indicates whether or not to send a notification to the device.
- @gfp: Kernel flags for memory allocation.
- Context: Any context.
- Return: 0 on success, -errno on failure.
- */
+static int virtsnd_event_send(struct virtqueue *vqueue,
struct virtio_snd_event *event, bool notify,
gfp_t gfp)
+{
- struct scatterlist sg;
- struct scatterlist *psgs[1] = { &sg };
- int rc;
- /* 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;
+}
+/**
- virtsnd_event_notify_cb() - Dispatch all reported events from the event queue.
- @vqueue: Underlying event virtqueue.
- This callback function is called upon a vring interrupt request from the
- device.
- Context: Interrupt context.
- */
+static 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;
switch (le32_to_cpu(event->hdr.code)) {
case VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED:
case VIRTIO_SND_EVT_PCM_XRUN: {
virtsnd_pcm_event(snd, event);
break;
}
default: {
break;
}
}
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);
+}
+/**
- virtsnd_find_vqs() - Enumerate and initialize all virtqueues.
- @snd: VirtIO sound device.
- After calling this function, interrupts are allowed only for the control
- queue, all other queues are disabled.
- Context: Any context.
- Return: 0 on success, -errno on failure.
- */
+static int virtsnd_find_vqs(struct virtio_snd *snd) +{
- struct virtio_device *vdev = snd->vdev;
- vq_callback_t *callbacks[VIRTIO_SND_VQ_MAX] = { 0 };
- const char *names[VIRTIO_SND_VQ_MAX] = {
[VIRTIO_SND_VQ_CONTROL] = "virtsnd-ctl",
[VIRTIO_SND_VQ_EVENT] = "virtsnd-event",
[VIRTIO_SND_VQ_TX] = "virtsnd-tx",
[VIRTIO_SND_VQ_RX] = "virtsnd-rx"
- };
- struct virtqueue *vqs[VIRTIO_SND_VQ_MAX] = { 0 };
- unsigned int i;
- unsigned int n = 0;
- int rc;
- callbacks[VIRTIO_SND_VQ_CONTROL] = virtsnd_ctl_notify_cb;
- callbacks[VIRTIO_SND_VQ_EVENT] = virtsnd_event_notify_cb;
- rc = virtio_find_vqs(vdev, VIRTIO_SND_VQ_MAX, vqs, callbacks, names,
NULL);
- if (rc) {
dev_err(&vdev->dev, "failed to initialize virtqueues\n");
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];
- }
- /* Allocate events and populate the event queue */
- n = virtqueue_get_vring_size(vqs[VIRTIO_SND_VQ_EVENT]);
- snd->event_msgs = devm_kcalloc(&vdev->dev, n, sizeof(*snd->event_msgs),
GFP_KERNEL);
- if (!snd->event_msgs)
return -ENOMEM;
- for (i = 0; i < n; ++i) {
rc = virtsnd_event_send(vqs[VIRTIO_SND_VQ_EVENT],
&snd->event_msgs[i], false, GFP_KERNEL);
if (rc)
return rc;
- }
- return 0;
+}
+/**
- virtsnd_enable_vqs() - Enable the event, tx and rx virtqueues.
- @snd: VirtIO sound device.
- Context: Any context.
- */
+static void virtsnd_enable_vqs(struct virtio_snd *snd) +{
- struct virtqueue *vqueue;
- vqueue = snd->queues[VIRTIO_SND_VQ_EVENT].vqueue;
- if (!virtqueue_enable_cb(vqueue))
virtsnd_event_notify_cb(vqueue);
+}
+/**
- virtsnd_disable_vqs() - Disable all virtqueues.
- @snd: VirtIO sound device.
- Also free all allocated events and control messages.
- Context: Any context.
- */
+static void virtsnd_disable_vqs(struct virtio_snd *snd) +{
- struct virtio_device *vdev = snd->vdev;
- unsigned 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);
/* Prohibit the use of the queue */
if (queue->vqueue)
virtqueue_disable_cb(queue->vqueue);
queue->vqueue = NULL;
/* Cancel all pending requests for the control queue */
if (i == VIRTIO_SND_VQ_CONTROL) {
struct virtio_snd_msg *msg;
struct virtio_snd_msg *next;
list_for_each_entry_safe(msg, next, &snd->ctl_msgs,
list)
virtsnd_ctl_msg_complete(snd, msg);
}
spin_unlock_irqrestore(&queue->lock, flags);
- }
- if (snd->event_msgs)
devm_kfree(&vdev->dev, snd->event_msgs);
- snd->event_msgs = NULL;
+}
+/**
- virtsnd_reset_fn() - Kernel worker's function to reset the device.
- @work: Reset device work.
- Context: Process context.
- */
+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\n");
- /*
* It seems that the only way to properly reset the device is to remove
* and re-create the ALSA sound card device.
*/
- virtsnd_remove(vdev);
- rc = virtsnd_probe(vdev);
- if (rc)
dev_err(dev, "re-probe() failed: %d\n", rc);
+}
+/**
- virtsnd_build_devs() - Read configuration and build ALSA devices.
- @snd: VirtIO sound device.
- Context: Any context that permits to sleep.
- Return: 0 on success, -errno on failure.
- */
+static int virtsnd_build_devs(struct virtio_snd *snd) +{
- struct virtio_device *vdev = snd->vdev;
- int 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;
- strscpy(snd->card->id, "viosnd", sizeof(snd->card->id));
- strscpy(snd->card->driver, "virtio_snd", sizeof(snd->card->driver));
- strscpy(snd->card->shortname, "VIOSND", sizeof(snd->card->shortname));
- strscpy(snd->card->longname, "VirtIO Sound Card",
sizeof(snd->card->longname));
- rc = virtsnd_pcm_parse_cfg(snd);
- if (rc)
return rc;
- if (snd->nsubstreams) {
rc = virtsnd_pcm_build_devs(snd);
if (rc)
return rc;
- }
- return snd_card_register(snd->card);
+}
+/**
- virtsnd_validate() - Validate if the device can be started.
- @vdev: VirtIO parent device.
- Context: Any context.
- Return: 0 on success, -EINVAL on failure.
- */
+static int virtsnd_validate(struct virtio_device *vdev) +{
- if (!vdev->config->get) {
dev_err(&vdev->dev, "configuration access disabled\n");
return -EINVAL;
- }
- if (!msg_timeout_ms) {
dev_err(&vdev->dev, "msg_timeout_ms value cannot be zero\n");
return -EINVAL;
- }
- if (virtsnd_pcm_validate(vdev))
return -EINVAL;
I think you also need VIRTIO_F_VERSION_1.
- return 0;
+}
+/**
- virtsnd_probe() - Create and initialize the device.
- @vdev: VirtIO parent device.
- Context: Any context that permits to sleep.
- Return: 0 on success, -errno on failure.
- */
+static int virtsnd_probe(struct virtio_device *vdev) +{
- struct virtio_snd *snd;
- unsigned int i;
- int rc;
- 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);
Shouldn't virtsnd_enable_vqs happen first? Looks like after virtsnd_build_devs Linux can already try to make noise ...
+on_failure:
- if (rc)
virtsnd_remove(vdev);
- return rc;
+}
+/**
- virtsnd_remove() - Remove VirtIO and ALSA devices.
- @vdev: VirtIO parent device.
- Context: Any context that permits to sleep.
- */
+static void virtsnd_remove(struct virtio_device *vdev) +{
- struct virtio_snd *snd = vdev->priv;
- struct virtio_pcm *pcm;
- struct virtio_pcm *pcm_next;
- if (!snd)
return;
- virtsnd_disable_vqs(snd);
Here Linux can still try to use the device, so all the cleanup we are doing is too early?
- 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);
}
devm_kfree(&vdev->dev, pcm);
- }
- if (snd->substreams)
devm_kfree(&vdev->dev, snd->substreams);
- devm_kfree(&vdev->dev, snd);
- vdev->priv = NULL;
+}
+/**
- virtsnd_config_changed() - Handle configuration change notification.
- @vdev: VirtIO parent device.
- This callback function is called upon a configuration change interrupt
- request from the device. Currently only used to handle NEEDS_RESET device
- status.
- Context: Interrupt context.
- */
+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\n");
+}
+static const struct virtio_device_id id_table[] = {
- { VIRTIO_ID_SOUND, VIRTIO_DEV_ANY_ID },
- { 0 },
+};
+static struct virtio_driver virtsnd_driver = {
- .driver.name = KBUILD_MODNAME,
- .driver.owner = THIS_MODULE,
- .id_table = id_table,
- .validate = virtsnd_validate,
- .probe = virtsnd_probe,
- .remove = virtsnd_remove,
- .config_changed = virtsnd_config_changed,
+};
+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/sound/virtio/virtio_card.h b/sound/virtio/virtio_card.h new file mode 100644 index 000000000000..be6651a6aaf8 --- /dev/null +++ b/sound/virtio/virtio_card.h @@ -0,0 +1,92 @@ +/* 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 <uapi/linux/virtio_snd.h>
+#include "virtio_ctl_msg.h" +#include "virtio_pcm.h"
+struct virtio_pcm_substream;
+/**
- struct virtio_snd_queue - Virtqueue wrapper structure.
- @lock: Used to synchronize access to a virtqueue.
- @vqueue: Underlying virtqueue.
- */
+struct virtio_snd_queue {
- spinlock_t lock;
- struct virtqueue *vqueue;
+};
+/**
- struct virtio_snd - VirtIO sound card device.
- @vdev: Underlying virtio device.
- @queues: Virtqueue wrappers.
- @reset_work: Reset device work.
- @card: ALSA sound card.
- @ctl_msgs: Pending control request list.
- @event_msgs: Device events.
- @pcm_list: VirtIO PCM device list.
- @substreams: VirtIO PCM substreams.
- @nsubstreams: Number of PCM substreams.
- */
+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_pcm_substream *substreams;
- unsigned int nsubstreams;
+};
+/* Message completion timeout in milliseconds (module parameter). */ +extern int msg_timeout_ms;
+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];
+}
+#endif /* VIRTIO_SND_CARD_H */ diff --git a/sound/virtio/virtio_ctl_msg.c b/sound/virtio/virtio_ctl_msg.c new file mode 100644 index 000000000000..c1701756bc32 --- /dev/null +++ b/sound/virtio/virtio_ctl_msg.c @@ -0,0 +1,293 @@ +// 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"
+/**
- virtsnd_ctl_msg_alloc_ext() - Allocate and initialize a control message.
- @vdev: VirtIO parent device.
- @request_size: Size of request header (pointed to by sg_request field).
- @response_size: Size of response header (pointed to by sg_response field).
- @sgs: Additional data to attach to the message (may be NULL).
- @out_sgs: Number of scattergather elements to attach to the request header.
- @in_sgs: Number of scattergather elements to attach to the response header.
- @gfp: Kernel flags for memory allocation.
- The message will be automatically freed when the ref_count value is 0.
- Context: Any context. May sleep if @gfp flags permit.
- Return: Allocated message on success, ERR_PTR(-errno) on failure.
- */
+struct virtio_snd_msg *virtsnd_ctl_msg_alloc_ext(struct virtio_device *vdev,
size_t request_size,
size_t response_size,
struct scatterlist *sgs,
unsigned int out_sgs,
unsigned int in_sgs, gfp_t gfp)
+{
- struct virtio_snd_msg *msg;
- size_t msg_size =
sizeof(*msg) + (1 + out_sgs + 1 + in_sgs) * sizeof(*msg->sgs);
- unsigned int i;
- msg = devm_kzalloc(&vdev->dev, msg_size + request_size + response_size,
gfp);
- if (!msg)
return ERR_PTR(-ENOMEM);
- sg_init_one(&msg->sg_request, (u8 *)msg + msg_size, request_size);
- sg_init_one(&msg->sg_response, (u8 *)msg + msg_size + request_size,
response_size);
- INIT_LIST_HEAD(&msg->list);
- init_completion(&msg->notify);
- atomic_set(&msg->ref_count, 1);
- msg->sgs[msg->out_sgs++] = &msg->sg_request;
- if (sgs)
for (i = 0; i < out_sgs; ++i)
msg->sgs[msg->out_sgs++] = &sgs[i];
- msg->sgs[msg->out_sgs + msg->in_sgs++] = &msg->sg_response;
- if (sgs)
for (i = out_sgs; i < out_sgs + in_sgs; ++i)
msg->sgs[msg->out_sgs + msg->in_sgs++] = &sgs[i];
- return msg;
+}
+/**
- virtsnd_ctl_msg_send() - Send an (asynchronous) control message.
- @snd: VirtIO sound device.
- @msg: Control message.
- If a message is failed to be enqueued, it will be deleted. If message content
- is still needed, the caller must additionally to virtsnd_ctl_msg_ref/unref()
- it.
- Context: Any context. Takes and releases the control queue spinlock.
- Return: 0 on success, -errno on failure.
- */
+int virtsnd_ctl_msg_send(struct virtio_snd *snd, struct virtio_snd_msg *msg) +{
- struct virtio_device *vdev = snd->vdev;
- struct virtio_snd_queue *queue = virtsnd_control_queue(snd);
- struct virtio_snd_hdr *response = sg_virt(&msg->sg_response);
- bool notify = false;
- unsigned long flags;
- int rc = -EIO;
- /* Set the default status in case the message was not sent or was
* canceled.
*/
- response->code = cpu_to_virtio32(vdev, VIRTIO_SND_S_IO_ERR);
- spin_lock_irqsave(&queue->lock, flags);
- if (queue->vqueue) {
rc = virtqueue_add_sgs(queue->vqueue, msg->sgs, msg->out_sgs,
msg->in_sgs, msg, GFP_ATOMIC);
if (!rc) {
notify = virtqueue_kick_prepare(queue->vqueue);
list_add_tail(&msg->list, &snd->ctl_msgs);
}
- }
- spin_unlock_irqrestore(&queue->lock, flags);
- if (!rc) {
if (!notify || virtqueue_notify(queue->vqueue))
return 0;
spin_lock_irqsave(&queue->lock, flags);
list_del(&msg->list);
spin_unlock_irqrestore(&queue->lock, flags);
- }
- virtsnd_ctl_msg_unref(snd->vdev, msg);
- return -EIO;
+}
+/**
- virtsnd_ctl_msg_send_sync() - Send a (synchronous) control message.
- @snd: VirtIO sound device.
- @msg: Control message.
- After returning from this function, the message will be deleted. If message
- content is still needed, the caller must additionally to
- virtsnd_ctl_msg_ref/unref() it.
- The msg_timeout_ms module parameter defines the message completion timeout.
- If the message is not completed within this time, the function will return an
- error.
- Context: Any context. Takes and releases the control queue spinlock.
- Return: 0 on success, -errno on failure.
- The return value is a message status code (VIRTIO_SND_S_XXX) converted to an
- appropriate -errno value.
- */
+int virtsnd_ctl_msg_send_sync(struct virtio_snd *snd,
struct virtio_snd_msg *msg)
+{
- struct virtio_device *vdev = snd->vdev;
- unsigned int js = msecs_to_jiffies(msg_timeout_ms);
- struct virtio_snd_hdr *response;
- int rc;
- virtsnd_ctl_msg_ref(vdev, msg);
- rc = virtsnd_ctl_msg_send(snd, msg);
- if (rc)
goto on_failure;
- rc = wait_for_completion_interruptible_timeout(&msg->notify, js);
- if (rc <= 0) {
if (!rc) {
struct virtio_snd_hdr *request =
sg_virt(&msg->sg_request);
dev_err(&vdev->dev,
"control message (0x%08x) timeout\n",
le32_to_cpu(request->code));
rc = -EIO;
}
goto on_failure;
- }
- response = sg_virt(&msg->sg_response);
- switch (le32_to_cpu(response->code)) {
- case VIRTIO_SND_S_OK:
rc = 0;
break;
- case VIRTIO_SND_S_BAD_MSG:
rc = -EINVAL;
break;
- case VIRTIO_SND_S_NOT_SUPP:
rc = -EOPNOTSUPP;
break;
- case VIRTIO_SND_S_IO_ERR:
rc = -EIO;
break;
- default:
rc = -EPERM;
break;
- }
+on_failure:
- virtsnd_ctl_msg_unref(vdev, msg);
- return rc;
+}
+/**
- virtsnd_ctl_msg_complete() - Complete a control message.
- @snd: VirtIO sound device.
- @msg: Control message.
- Context: Any context.
- */
+void virtsnd_ctl_msg_complete(struct virtio_snd *snd,
struct virtio_snd_msg *msg)
+{
- list_del(&msg->list);
- complete(&msg->notify);
- virtsnd_ctl_msg_unref(snd->vdev, msg);
+}
+/**
- virtsnd_ctl_query_info() - Query the item configuration from the device.
- @snd: VirtIO sound device.
- @command: Control request code (VIRTIO_SND_R_XXX_INFO).
- @start_id: Item start identifier.
- @count: Item count to query.
- @size: Item information size in bytes.
- @info: Buffer for storing item information.
- Context: Any context that permits to sleep.
- Return: 0 on success, -errno on failure.
- */
+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;
- sg_init_one(&sg, info, count * size);
- msg = virtsnd_ctl_msg_alloc_ext(vdev, sizeof(*query),
sizeof(struct virtio_snd_hdr), &sg, 0,
1, 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);
- return virtsnd_ctl_msg_send_sync(snd, msg);
+}
+/**
- virtsnd_ctl_notify_cb() - Process all completed control messages.
- @vqueue: Underlying control virtqueue.
- This callback function is called upon a vring interrupt request from the
- device.
- Context: Interrupt context. Takes and releases the control queue spinlock.
- */
+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;
virtsnd_ctl_msg_complete(snd, 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/sound/virtio/virtio_ctl_msg.h b/sound/virtio/virtio_ctl_msg.h new file mode 100644 index 000000000000..0f8de8f2fd2d --- /dev/null +++ b/sound/virtio/virtio_ctl_msg.h @@ -0,0 +1,122 @@ +/* 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 - Control message.
- @sg_request: Scattergather element containing a device request (header).
- @sg_response: Scattergather element containing a device response (status).
- @list: Pending message list entry.
- @notify: Request completed notification.
- @ref_count: Reference count used to manage a message lifetime.
- @out_sgs: Number of read-only sg elements in the sgs array.
- @in_sgs: Number of write-only sg elements in the sgs array.
- @sgs: Array of sg elements to add to the control virtqueue.
- */
+struct virtio_snd_msg { +/* public: */
- struct scatterlist sg_request;
- struct scatterlist sg_response;
+/* private: internal use only */
- struct list_head list;
- struct completion notify;
- atomic_t ref_count;
- unsigned int out_sgs;
- unsigned int in_sgs;
- struct scatterlist *sgs[0];
+};
+/**
- virtsnd_ctl_msg_ref() - Increment reference counter for the message.
- @vdev: VirtIO parent device.
- @msg: Control message.
- Context: Any context.
- */
+static inline void virtsnd_ctl_msg_ref(struct virtio_device *vdev,
struct virtio_snd_msg *msg)
+{
- atomic_inc(&msg->ref_count);
+}
+/**
- virtsnd_ctl_msg_unref() - Decrement reference counter for the message.
- @vdev: VirtIO parent device.
- @msg: Control message.
- The message will be freed when the ref_count value is 0.
- Context: Any context.
- */
+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_ext(struct virtio_device *vdev,
size_t request_size,
size_t response_size,
struct scatterlist *sgs,
unsigned int out_sgs,
unsigned int in_sgs,
gfp_t gfp);
+/**
- virtsnd_ctl_msg_alloc() - Simplified control message allocation.
- @vdev: VirtIO parent device.
- @request_size: Size of request header (pointed to by sg_request field).
- @response_size: Size of response header (pointed to by sg_response field).
- @gfp: Kernel flags for memory allocation.
- The message will be automatically freed when the ref_count value is 0.
- Context: Any context. May sleep if @gfp flags permit.
- Return: Allocated message on success, ERR_PTR(-errno) on failure.
- */
+static inline +struct virtio_snd_msg *virtsnd_ctl_msg_alloc(struct virtio_device *vdev,
size_t request_size,
size_t response_size, gfp_t gfp)
+{
- return virtsnd_ctl_msg_alloc_ext(vdev, request_size, response_size,
NULL, 0, 0, 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);
+void virtsnd_ctl_msg_complete(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/sound/virtio/virtio_pcm.c b/sound/virtio/virtio_pcm.c new file mode 100644 index 000000000000..036990b7b78a --- /dev/null +++ b/sound/virtio/virtio_pcm.c @@ -0,0 +1,536 @@ +// 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");
+/* Map for converting VirtIO format to ALSA format. */ +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,
- [VIRTIO_SND_PCM_FMT_S20] = SNDRV_PCM_FORMAT_S20_LE,
- [VIRTIO_SND_PCM_FMT_U20] = SNDRV_PCM_FORMAT_U20_LE,
- [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
+};
+/* Map for converting VirtIO frame rate to ALSA frame rate. */ +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 }
+};
+/**
- virtsnd_pcm_build_hw() - Parse substream config and build HW descriptor.
- @substream: VirtIO substream.
- @info: VirtIO substream information entry.
- Context: Any context.
- Return: 0 on success, -EINVAL if configuration is invalid.
- */
+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;
- if (!info->channels_min || info->channels_min > info->channels_max) {
dev_err(&vdev->dev,
"SID %u: invalid channel range [%u %u]\n",
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);
- substream->hw.formats = 0;
- 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\n",
substream->sid);
return -EINVAL;
- }
- values = le64_to_cpu(info->rates);
- substream->hw.rates = 0;
- 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\n",
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;
+}
+/**
- virtsnd_pcm_prealloc_pages() - Preallocate substream hardware buffer.
- @substream: VirtIO substream.
- Context: Any context that permits to sleep.
- Return: 0 on success, -errno on failure.
- */
+static int 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);
- /*
* We just allocate a CONTINUOUS buffer as it should work in any setup.
*
* If there is a need to use DEV(_XXX), then add this case here and
* (probably) update the related source code in other places.
*/
- snd_pcm_lib_preallocate_pages(ksubstream, SNDRV_DMA_TYPE_CONTINUOUS,
data, size, size);
- return 0;
+}
+/**
- virtsnd_pcm_find() - Find the PCM device for the specified node ID.
- @snd: VirtIO sound device.
- @nid: Function node ID.
- Context: Any context.
- Return: a pointer to the PCM device or ERR_PTR(-ENOENT).
- */
+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);
+}
+/**
- virtsnd_pcm_find_or_create() - Find or create the PCM device for the
specified node ID.
- @snd: VirtIO sound device.
- @nid: Function node ID.
- Context: Any context that permits to sleep.
- Return: a pointer to the PCM device or ERR_PTR(-errno).
- */
+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;
+}
+/**
- virtsnd_pcm_validate() - Validate if the device can be started.
- @vdev: VirtIO parent device.
- Context: Any context.
- Return: 0 on success, -EINVAL on failure.
- */
+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\n",
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\n",
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\n",
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\n",
pcm_period_ms_max, pcm_buffer_ms / 2);
return -EINVAL;
- }
- return 0;
+}
+/**
- virtsnd_pcm_parse_cfg() - Parse the stream configuration.
- @snd: VirtIO sound device.
- This function is called during initial device initialization.
- Context: Any context that permits to sleep.
- Return: 0 on success, -errno on failure.
- */
+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;
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)\n",
substream->sid, info[i].direction);
return -EINVAL;
}
}
pcm->streams[substream->direction].nsubstreams++;
- }
- devm_kfree(&vdev->dev, info);
- return 0;
+}
+/**
- virtsnd_pcm_build_devs() - Build ALSA PCM devices.
- @snd: VirtIO sound device.
- Context: Any context that permits to sleep.
- Return: 0 on success, -errno on failure.
- */
+int virtsnd_pcm_build_devs(struct virtio_snd *snd) +{
- struct virtio_device *vdev = snd->vdev;
- struct virtio_pcm *pcm;
- unsigned int i;
- int rc;
- 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;
rc = snd_pcm_new(snd->card, "virtio_snd", pcm->nid, npbs, ncps,
&pcm->pcm);
if (rc) {
dev_err(&vdev->dev, "snd_pcm_new[%u] failed: %d\n",
pcm->nid, rc);
return rc;
}
pcm->pcm->info_flags = 0;
pcm->pcm->dev_class = SNDRV_PCM_CLASS_GENERIC;
pcm->pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
strscpy(pcm->pcm->name, "VirtIO PCM", sizeof(pcm->pcm->name));
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;
rc = virtsnd_pcm_prealloc_pages(substream);
if (rc)
return rc;
}
}
- return 0;
+}
+/**
- virtsnd_pcm_event() - Handle the PCM device event notification.
- @snd: VirtIO sound device.
- @event: VirtIO sound event.
- Context: Interrupt context.
- */
+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: {
break;
- }
- }
+} diff --git a/sound/virtio/virtio_pcm.h b/sound/virtio/virtio_pcm.h new file mode 100644 index 000000000000..73fb4d9dc524 --- /dev/null +++ b/sound/virtio/virtio_pcm.h @@ -0,0 +1,89 @@ +/* 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_substream - VirtIO PCM substream.
- @snd: VirtIO sound device.
- @nid: Function group node identifier.
- @sid: Stream identifier.
- @direction: Stream data flow direction (SNDRV_PCM_STREAM_XXX).
- @features: Stream VirtIO feature bit map (1 << VIRTIO_SND_PCM_F_XXX).
- @substream: Kernel ALSA substream.
- @hw: Kernel ALSA substream hardware descriptor.
- */
+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;
+};
+/**
- struct virtio_pcm_stream - VirtIO PCM stream.
- @substreams: Virtio substreams belonging to the stream.
- @nsubstreams: Number of substreams.
- */
+struct virtio_pcm_stream {
- struct virtio_pcm_substream **substreams;
- unsigned int nsubstreams;
+};
+/**
- struct virtio_pcm - VirtIO PCM device.
- @list: PCM list entry.
- @nid: Function group node identifier.
- @pcm: Kernel PCM device.
- @streams: VirtIO PCM 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];
+};
+int virtsnd_pcm_validate(struct virtio_device *vdev);
+int virtsnd_pcm_parse_cfg(struct virtio_snd *snd);
+int virtsnd_pcm_build_devs(struct virtio_snd *snd);
+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);
+#endif /* VIRTIO_SND_PCM_H */
2.30.0
On 20.01.2021 09:26, Michael S. Tsirkin wrote:
CAUTION: This email originated from outside of the organization. Do not click links or open attachments unless you recognize the sender and know the content is safe.
On Wed, Jan 20, 2021 at 01:36:31AM +0100, Anton Yakovlev wrote:
Introduce skeleton of the virtio sound driver. The driver implements the virtio sound device specification, which has become part of the virtio standard.
Initial initialization of the device, virtqueues and creation of an empty ALSA sound device. Also, handling DEVICE_NEEDS_RESET device status.
Signed-off-by: Anton Yakovlev anton.yakovlev@opensynergy.com
MAINTAINERS | 2 + sound/Kconfig | 2 + sound/Makefile | 3 +- sound/virtio/Kconfig | 10 + sound/virtio/Makefile | 9 + sound/virtio/virtio_card.c | 473 ++++++++++++++++++++++++++++++ sound/virtio/virtio_card.h | 92 ++++++ sound/virtio/virtio_ctl_msg.c | 293 +++++++++++++++++++ sound/virtio/virtio_ctl_msg.h | 122 ++++++++ sound/virtio/virtio_pcm.c | 536 ++++++++++++++++++++++++++++++++++ sound/virtio/virtio_pcm.h | 89 ++++++ 11 files changed, 1630 insertions(+), 1 deletion(-) create mode 100644 sound/virtio/Kconfig create mode 100644 sound/virtio/Makefile create mode 100644 sound/virtio/virtio_card.c create mode 100644 sound/virtio/virtio_card.h create mode 100644 sound/virtio/virtio_ctl_msg.c create mode 100644 sound/virtio/virtio_ctl_msg.h create mode 100644 sound/virtio/virtio_pcm.c create mode 100644 sound/virtio/virtio_pcm.h
diff --git a/MAINTAINERS b/MAINTAINERS index 6dfd59eafe82..8a0e9f04402f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18939,8 +18939,10 @@ F: include/uapi/linux/virtio_mem.h VIRTIO SOUND DRIVER M: Anton Yakovlev anton.yakovlev@opensynergy.com L: virtualization@lists.linux-foundation.org +L: alsa-devel@alsa-project.org (moderated for non-subscribers) S: Maintained F: include/uapi/linux/virtio_snd.h +F: sound/virtio/*
VIRTUAL BOX GUEST DEVICE DRIVER M: Hans de Goede hdegoede@redhat.com diff --git a/sound/Kconfig b/sound/Kconfig index 36785410fbe1..e56d96d2b11c 100644 --- a/sound/Kconfig +++ b/sound/Kconfig @@ -99,6 +99,8 @@ source "sound/synth/Kconfig"
source "sound/xen/Kconfig"
+source "sound/virtio/Kconfig"
endif # SND
endif # !UML
diff --git a/sound/Makefile b/sound/Makefile index 797ecdcd35e2..04ef04b1168f 100644 --- a/sound/Makefile +++ b/sound/Makefile @@ -5,7 +5,8 @@ obj-$(CONFIG_SOUND) += soundcore.o obj-$(CONFIG_DMASOUND) += oss/dmasound/ obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ sh/ synth/ usb/ \
firewire/ sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/ hda/ x86/ xen/
firewire/ sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/ hda/ x86/ xen/ \
virtio/
obj-$(CONFIG_SND_AOA) += aoa/
# This one must be compilable even if sound is configured out
diff --git a/sound/virtio/Kconfig b/sound/virtio/Kconfig new file mode 100644 index 000000000000..094cba24ee5b --- /dev/null +++ b/sound/virtio/Kconfig @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Sound card driver for virtio
+config SND_VIRTIO
tristate "Virtio sound driver"
depends on VIRTIO
select SND_PCM
select SND_JACK
help
This is the virtual sound driver for virtio. Say Y or M.
diff --git a/sound/virtio/Makefile b/sound/virtio/Makefile new file mode 100644 index 000000000000..69162a545a41 --- /dev/null +++ b/sound/virtio/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0+
+obj-$(CONFIG_SND_VIRTIO) += virtio_snd.o
+virtio_snd-objs := \
virtio_card.o \
virtio_ctl_msg.o \
virtio_pcm.o
diff --git a/sound/virtio/virtio_card.c b/sound/virtio/virtio_card.c new file mode 100644 index 000000000000..293d497f24e7 --- /dev/null +++ b/sound/virtio/virtio_card.c @@ -0,0 +1,473 @@ +// 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/virtio_config.h> +#include <sound/initval.h> +#include <uapi/linux/virtio_ids.h>
+#include "virtio_card.h"
+int msg_timeout_ms = MSEC_PER_SEC; +module_param(msg_timeout_ms, int, 0644); +MODULE_PARM_DESC(msg_timeout_ms, "Message completion timeout in milliseconds");
+static int virtsnd_probe(struct virtio_device *vdev); +static void virtsnd_remove(struct virtio_device *vdev);
+/**
- virtsnd_event_send() - Add an event to the event queue.
- @vqueue: Underlying event virtqueue.
- @event: Event.
- @notify: Indicates whether or not to send a notification to the device.
- @gfp: Kernel flags for memory allocation.
- Context: Any context.
- Return: 0 on success, -errno on failure.
- */
+static int virtsnd_event_send(struct virtqueue *vqueue,
struct virtio_snd_event *event, bool notify,
gfp_t gfp)
+{
struct scatterlist sg;
struct scatterlist *psgs[1] = { &sg };
int rc;
/* 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;
+}
+/**
- virtsnd_event_notify_cb() - Dispatch all reported events from the event queue.
- @vqueue: Underlying event virtqueue.
- This callback function is called upon a vring interrupt request from the
- device.
- Context: Interrupt context.
- */
+static 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;
switch (le32_to_cpu(event->hdr.code)) {
case VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED:
case VIRTIO_SND_EVT_PCM_XRUN: {
virtsnd_pcm_event(snd, event);
break;
}
default: {
break;
}
}
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);
+}
+/**
- virtsnd_find_vqs() - Enumerate and initialize all virtqueues.
- @snd: VirtIO sound device.
- After calling this function, interrupts are allowed only for the control
- queue, all other queues are disabled.
- Context: Any context.
- Return: 0 on success, -errno on failure.
- */
+static int virtsnd_find_vqs(struct virtio_snd *snd) +{
struct virtio_device *vdev = snd->vdev;
vq_callback_t *callbacks[VIRTIO_SND_VQ_MAX] = { 0 };
const char *names[VIRTIO_SND_VQ_MAX] = {
[VIRTIO_SND_VQ_CONTROL] = "virtsnd-ctl",
[VIRTIO_SND_VQ_EVENT] = "virtsnd-event",
[VIRTIO_SND_VQ_TX] = "virtsnd-tx",
[VIRTIO_SND_VQ_RX] = "virtsnd-rx"
};
struct virtqueue *vqs[VIRTIO_SND_VQ_MAX] = { 0 };
unsigned int i;
unsigned int n = 0;
int rc;
callbacks[VIRTIO_SND_VQ_CONTROL] = virtsnd_ctl_notify_cb;
callbacks[VIRTIO_SND_VQ_EVENT] = virtsnd_event_notify_cb;
rc = virtio_find_vqs(vdev, VIRTIO_SND_VQ_MAX, vqs, callbacks, names,
NULL);
if (rc) {
dev_err(&vdev->dev, "failed to initialize virtqueues\n");
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];
}
/* Allocate events and populate the event queue */
n = virtqueue_get_vring_size(vqs[VIRTIO_SND_VQ_EVENT]);
snd->event_msgs = devm_kcalloc(&vdev->dev, n, sizeof(*snd->event_msgs),
GFP_KERNEL);
if (!snd->event_msgs)
return -ENOMEM;
for (i = 0; i < n; ++i) {
rc = virtsnd_event_send(vqs[VIRTIO_SND_VQ_EVENT],
&snd->event_msgs[i], false, GFP_KERNEL);
if (rc)
return rc;
}
return 0;
+}
+/**
- virtsnd_enable_vqs() - Enable the event, tx and rx virtqueues.
- @snd: VirtIO sound device.
- Context: Any context.
- */
+static void virtsnd_enable_vqs(struct virtio_snd *snd) +{
struct virtqueue *vqueue;
vqueue = snd->queues[VIRTIO_SND_VQ_EVENT].vqueue;
if (!virtqueue_enable_cb(vqueue))
virtsnd_event_notify_cb(vqueue);
+}
+/**
- virtsnd_disable_vqs() - Disable all virtqueues.
- @snd: VirtIO sound device.
- Also free all allocated events and control messages.
- Context: Any context.
- */
+static void virtsnd_disable_vqs(struct virtio_snd *snd) +{
struct virtio_device *vdev = snd->vdev;
unsigned 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);
/* Prohibit the use of the queue */
if (queue->vqueue)
virtqueue_disable_cb(queue->vqueue);
queue->vqueue = NULL;
/* Cancel all pending requests for the control queue */
if (i == VIRTIO_SND_VQ_CONTROL) {
struct virtio_snd_msg *msg;
struct virtio_snd_msg *next;
list_for_each_entry_safe(msg, next, &snd->ctl_msgs,
list)
virtsnd_ctl_msg_complete(snd, msg);
}
spin_unlock_irqrestore(&queue->lock, flags);
}
if (snd->event_msgs)
devm_kfree(&vdev->dev, snd->event_msgs);
snd->event_msgs = NULL;
+}
+/**
- virtsnd_reset_fn() - Kernel worker's function to reset the device.
- @work: Reset device work.
- Context: Process context.
- */
+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\n");
/*
* It seems that the only way to properly reset the device is to remove
* and re-create the ALSA sound card device.
*/
virtsnd_remove(vdev);
rc = virtsnd_probe(vdev);
if (rc)
dev_err(dev, "re-probe() failed: %d\n", rc);
+}
+/**
- virtsnd_build_devs() - Read configuration and build ALSA devices.
- @snd: VirtIO sound device.
- Context: Any context that permits to sleep.
- Return: 0 on success, -errno on failure.
- */
+static int virtsnd_build_devs(struct virtio_snd *snd) +{
struct virtio_device *vdev = snd->vdev;
int 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;
strscpy(snd->card->id, "viosnd", sizeof(snd->card->id));
strscpy(snd->card->driver, "virtio_snd", sizeof(snd->card->driver));
strscpy(snd->card->shortname, "VIOSND", sizeof(snd->card->shortname));
strscpy(snd->card->longname, "VirtIO Sound Card",
sizeof(snd->card->longname));
rc = virtsnd_pcm_parse_cfg(snd);
if (rc)
return rc;
if (snd->nsubstreams) {
rc = virtsnd_pcm_build_devs(snd);
if (rc)
return rc;
}
return snd_card_register(snd->card);
+}
+/**
- virtsnd_validate() - Validate if the device can be started.
- @vdev: VirtIO parent device.
- Context: Any context.
- Return: 0 on success, -EINVAL on failure.
- */
+static int virtsnd_validate(struct virtio_device *vdev) +{
if (!vdev->config->get) {
dev_err(&vdev->dev, "configuration access disabled\n");
return -EINVAL;
}
if (!msg_timeout_ms) {
dev_err(&vdev->dev, "msg_timeout_ms value cannot be zero\n");
return -EINVAL;
}
if (virtsnd_pcm_validate(vdev))
return -EINVAL;
I think you also need VIRTIO_F_VERSION_1.
Added additional check in v2.
return 0;
+}
+/**
- virtsnd_probe() - Create and initialize the device.
- @vdev: VirtIO parent device.
- Context: Any context that permits to sleep.
- Return: 0 on success, -errno on failure.
- */
+static int virtsnd_probe(struct virtio_device *vdev) +{
struct virtio_snd *snd;
unsigned int i;
int rc;
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);
Shouldn't virtsnd_enable_vqs happen first? Looks like after virtsnd_build_devs Linux can already try to make noise ...
Since only the event virtqueue must be disabled before calling virtsnd_build_devs, I've simplified the code here in v2. The other three virtqueues are now always enabled.
+on_failure:
if (rc)
virtsnd_remove(vdev);
return rc;
+}
+/**
- virtsnd_remove() - Remove VirtIO and ALSA devices.
- @vdev: VirtIO parent device.
- Context: Any context that permits to sleep.
- */
+static void virtsnd_remove(struct virtio_device *vdev) +{
struct virtio_snd *snd = vdev->priv;
struct virtio_pcm *pcm;
struct virtio_pcm *pcm_next;
if (!snd)
return;
virtsnd_disable_vqs(snd);
Here Linux can still try to use the device, so all the cleanup we are doing is too early?
We can get here also because of the DEVICE_NEEDS_RESET status. In this case, there may be pending synchronous control requests. This means that a subsequent call to snd_card_free may take a significant amount of time (until the message timed out). So it is best to disable the virtqueues and cancel any pending messages first.
By the way, could you take a look into the reset worker function in v2: [2/9] virtio_card.c:virtsnd_reset_fn() I put there additional comments. The question is, is the current solution acceptable? The code itself works fine though.
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);
}
devm_kfree(&vdev->dev, pcm);
}
if (snd->substreams)
devm_kfree(&vdev->dev, snd->substreams);
devm_kfree(&vdev->dev, snd);
vdev->priv = NULL;
+}
+/**
- virtsnd_config_changed() - Handle configuration change notification.
- @vdev: VirtIO parent device.
- This callback function is called upon a configuration change interrupt
- request from the device. Currently only used to handle NEEDS_RESET device
- status.
- Context: Interrupt context.
- */
+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\n");
+}
+static const struct virtio_device_id id_table[] = {
{ VIRTIO_ID_SOUND, VIRTIO_DEV_ANY_ID },
{ 0 },
+};
+static struct virtio_driver virtsnd_driver = {
.driver.name = KBUILD_MODNAME,
.driver.owner = THIS_MODULE,
.id_table = id_table,
.validate = virtsnd_validate,
.probe = virtsnd_probe,
.remove = virtsnd_remove,
.config_changed = virtsnd_config_changed,
+};
+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/sound/virtio/virtio_card.h b/sound/virtio/virtio_card.h new file mode 100644 index 000000000000..be6651a6aaf8 --- /dev/null +++ b/sound/virtio/virtio_card.h @@ -0,0 +1,92 @@ +/* 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 <uapi/linux/virtio_snd.h>
+#include "virtio_ctl_msg.h" +#include "virtio_pcm.h"
+struct virtio_pcm_substream;
+/**
- struct virtio_snd_queue - Virtqueue wrapper structure.
- @lock: Used to synchronize access to a virtqueue.
- @vqueue: Underlying virtqueue.
- */
+struct virtio_snd_queue {
spinlock_t lock;
struct virtqueue *vqueue;
+};
+/**
- struct virtio_snd - VirtIO sound card device.
- @vdev: Underlying virtio device.
- @queues: Virtqueue wrappers.
- @reset_work: Reset device work.
- @card: ALSA sound card.
- @ctl_msgs: Pending control request list.
- @event_msgs: Device events.
- @pcm_list: VirtIO PCM device list.
- @substreams: VirtIO PCM substreams.
- @nsubstreams: Number of PCM substreams.
- */
+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_pcm_substream *substreams;
unsigned int nsubstreams;
+};
+/* Message completion timeout in milliseconds (module parameter). */ +extern int msg_timeout_ms;
+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];
+}
+#endif /* VIRTIO_SND_CARD_H */ diff --git a/sound/virtio/virtio_ctl_msg.c b/sound/virtio/virtio_ctl_msg.c new file mode 100644 index 000000000000..c1701756bc32 --- /dev/null +++ b/sound/virtio/virtio_ctl_msg.c @@ -0,0 +1,293 @@ +// 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"
+/**
- virtsnd_ctl_msg_alloc_ext() - Allocate and initialize a control message.
- @vdev: VirtIO parent device.
- @request_size: Size of request header (pointed to by sg_request field).
- @response_size: Size of response header (pointed to by sg_response field).
- @sgs: Additional data to attach to the message (may be NULL).
- @out_sgs: Number of scattergather elements to attach to the request header.
- @in_sgs: Number of scattergather elements to attach to the response header.
- @gfp: Kernel flags for memory allocation.
- The message will be automatically freed when the ref_count value is 0.
- Context: Any context. May sleep if @gfp flags permit.
- Return: Allocated message on success, ERR_PTR(-errno) on failure.
- */
+struct virtio_snd_msg *virtsnd_ctl_msg_alloc_ext(struct virtio_device *vdev,
size_t request_size,
size_t response_size,
struct scatterlist *sgs,
unsigned int out_sgs,
unsigned int in_sgs, gfp_t gfp)
+{
struct virtio_snd_msg *msg;
size_t msg_size =
sizeof(*msg) + (1 + out_sgs + 1 + in_sgs) * sizeof(*msg->sgs);
unsigned int i;
msg = devm_kzalloc(&vdev->dev, msg_size + request_size + response_size,
gfp);
if (!msg)
return ERR_PTR(-ENOMEM);
sg_init_one(&msg->sg_request, (u8 *)msg + msg_size, request_size);
sg_init_one(&msg->sg_response, (u8 *)msg + msg_size + request_size,
response_size);
INIT_LIST_HEAD(&msg->list);
init_completion(&msg->notify);
atomic_set(&msg->ref_count, 1);
msg->sgs[msg->out_sgs++] = &msg->sg_request;
if (sgs)
for (i = 0; i < out_sgs; ++i)
msg->sgs[msg->out_sgs++] = &sgs[i];
msg->sgs[msg->out_sgs + msg->in_sgs++] = &msg->sg_response;
if (sgs)
for (i = out_sgs; i < out_sgs + in_sgs; ++i)
msg->sgs[msg->out_sgs + msg->in_sgs++] = &sgs[i];
return msg;
+}
+/**
- virtsnd_ctl_msg_send() - Send an (asynchronous) control message.
- @snd: VirtIO sound device.
- @msg: Control message.
- If a message is failed to be enqueued, it will be deleted. If message content
- is still needed, the caller must additionally to virtsnd_ctl_msg_ref/unref()
- it.
- Context: Any context. Takes and releases the control queue spinlock.
- Return: 0 on success, -errno on failure.
- */
+int virtsnd_ctl_msg_send(struct virtio_snd *snd, struct virtio_snd_msg *msg) +{
struct virtio_device *vdev = snd->vdev;
struct virtio_snd_queue *queue = virtsnd_control_queue(snd);
struct virtio_snd_hdr *response = sg_virt(&msg->sg_response);
bool notify = false;
unsigned long flags;
int rc = -EIO;
/* Set the default status in case the message was not sent or was
* canceled.
*/
response->code = cpu_to_virtio32(vdev, VIRTIO_SND_S_IO_ERR);
spin_lock_irqsave(&queue->lock, flags);
if (queue->vqueue) {
rc = virtqueue_add_sgs(queue->vqueue, msg->sgs, msg->out_sgs,
msg->in_sgs, msg, GFP_ATOMIC);
if (!rc) {
notify = virtqueue_kick_prepare(queue->vqueue);
list_add_tail(&msg->list, &snd->ctl_msgs);
}
}
spin_unlock_irqrestore(&queue->lock, flags);
if (!rc) {
if (!notify || virtqueue_notify(queue->vqueue))
return 0;
spin_lock_irqsave(&queue->lock, flags);
list_del(&msg->list);
spin_unlock_irqrestore(&queue->lock, flags);
}
virtsnd_ctl_msg_unref(snd->vdev, msg);
return -EIO;
+}
+/**
- virtsnd_ctl_msg_send_sync() - Send a (synchronous) control message.
- @snd: VirtIO sound device.
- @msg: Control message.
- After returning from this function, the message will be deleted. If message
- content is still needed, the caller must additionally to
- virtsnd_ctl_msg_ref/unref() it.
- The msg_timeout_ms module parameter defines the message completion timeout.
- If the message is not completed within this time, the function will return an
- error.
- Context: Any context. Takes and releases the control queue spinlock.
- Return: 0 on success, -errno on failure.
- The return value is a message status code (VIRTIO_SND_S_XXX) converted to an
- appropriate -errno value.
- */
+int virtsnd_ctl_msg_send_sync(struct virtio_snd *snd,
struct virtio_snd_msg *msg)
+{
struct virtio_device *vdev = snd->vdev;
unsigned int js = msecs_to_jiffies(msg_timeout_ms);
struct virtio_snd_hdr *response;
int rc;
virtsnd_ctl_msg_ref(vdev, msg);
rc = virtsnd_ctl_msg_send(snd, msg);
if (rc)
goto on_failure;
rc = wait_for_completion_interruptible_timeout(&msg->notify, js);
if (rc <= 0) {
if (!rc) {
struct virtio_snd_hdr *request =
sg_virt(&msg->sg_request);
dev_err(&vdev->dev,
"control message (0x%08x) timeout\n",
le32_to_cpu(request->code));
rc = -EIO;
}
goto on_failure;
}
response = sg_virt(&msg->sg_response);
switch (le32_to_cpu(response->code)) {
case VIRTIO_SND_S_OK:
rc = 0;
break;
case VIRTIO_SND_S_BAD_MSG:
rc = -EINVAL;
break;
case VIRTIO_SND_S_NOT_SUPP:
rc = -EOPNOTSUPP;
break;
case VIRTIO_SND_S_IO_ERR:
rc = -EIO;
break;
default:
rc = -EPERM;
break;
}
+on_failure:
virtsnd_ctl_msg_unref(vdev, msg);
return rc;
+}
+/**
- virtsnd_ctl_msg_complete() - Complete a control message.
- @snd: VirtIO sound device.
- @msg: Control message.
- Context: Any context.
- */
+void virtsnd_ctl_msg_complete(struct virtio_snd *snd,
struct virtio_snd_msg *msg)
+{
list_del(&msg->list);
complete(&msg->notify);
virtsnd_ctl_msg_unref(snd->vdev, msg);
+}
+/**
- virtsnd_ctl_query_info() - Query the item configuration from the device.
- @snd: VirtIO sound device.
- @command: Control request code (VIRTIO_SND_R_XXX_INFO).
- @start_id: Item start identifier.
- @count: Item count to query.
- @size: Item information size in bytes.
- @info: Buffer for storing item information.
- Context: Any context that permits to sleep.
- Return: 0 on success, -errno on failure.
- */
+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;
sg_init_one(&sg, info, count * size);
msg = virtsnd_ctl_msg_alloc_ext(vdev, sizeof(*query),
sizeof(struct virtio_snd_hdr), &sg, 0,
1, 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);
return virtsnd_ctl_msg_send_sync(snd, msg);
+}
+/**
- virtsnd_ctl_notify_cb() - Process all completed control messages.
- @vqueue: Underlying control virtqueue.
- This callback function is called upon a vring interrupt request from the
- device.
- Context: Interrupt context. Takes and releases the control queue spinlock.
- */
+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;
virtsnd_ctl_msg_complete(snd, 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/sound/virtio/virtio_ctl_msg.h b/sound/virtio/virtio_ctl_msg.h new file mode 100644 index 000000000000..0f8de8f2fd2d --- /dev/null +++ b/sound/virtio/virtio_ctl_msg.h @@ -0,0 +1,122 @@ +/* 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 - Control message.
- @sg_request: Scattergather element containing a device request (header).
- @sg_response: Scattergather element containing a device response (status).
- @list: Pending message list entry.
- @notify: Request completed notification.
- @ref_count: Reference count used to manage a message lifetime.
- @out_sgs: Number of read-only sg elements in the sgs array.
- @in_sgs: Number of write-only sg elements in the sgs array.
- @sgs: Array of sg elements to add to the control virtqueue.
- */
+struct virtio_snd_msg { +/* public: */
struct scatterlist sg_request;
struct scatterlist sg_response;
+/* private: internal use only */
struct list_head list;
struct completion notify;
atomic_t ref_count;
unsigned int out_sgs;
unsigned int in_sgs;
struct scatterlist *sgs[0];
+};
+/**
- virtsnd_ctl_msg_ref() - Increment reference counter for the message.
- @vdev: VirtIO parent device.
- @msg: Control message.
- Context: Any context.
- */
+static inline void virtsnd_ctl_msg_ref(struct virtio_device *vdev,
struct virtio_snd_msg *msg)
+{
atomic_inc(&msg->ref_count);
+}
+/**
- virtsnd_ctl_msg_unref() - Decrement reference counter for the message.
- @vdev: VirtIO parent device.
- @msg: Control message.
- The message will be freed when the ref_count value is 0.
- Context: Any context.
- */
+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_ext(struct virtio_device *vdev,
size_t request_size,
size_t response_size,
struct scatterlist *sgs,
unsigned int out_sgs,
unsigned int in_sgs,
gfp_t gfp);
+/**
- virtsnd_ctl_msg_alloc() - Simplified control message allocation.
- @vdev: VirtIO parent device.
- @request_size: Size of request header (pointed to by sg_request field).
- @response_size: Size of response header (pointed to by sg_response field).
- @gfp: Kernel flags for memory allocation.
- The message will be automatically freed when the ref_count value is 0.
- Context: Any context. May sleep if @gfp flags permit.
- Return: Allocated message on success, ERR_PTR(-errno) on failure.
- */
+static inline +struct virtio_snd_msg *virtsnd_ctl_msg_alloc(struct virtio_device *vdev,
size_t request_size,
size_t response_size, gfp_t gfp)
+{
return virtsnd_ctl_msg_alloc_ext(vdev, request_size, response_size,
NULL, 0, 0, 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);
+void virtsnd_ctl_msg_complete(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/sound/virtio/virtio_pcm.c b/sound/virtio/virtio_pcm.c new file mode 100644 index 000000000000..036990b7b78a --- /dev/null +++ b/sound/virtio/virtio_pcm.c @@ -0,0 +1,536 @@ +// 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");
+/* Map for converting VirtIO format to ALSA format. */ +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,
[VIRTIO_SND_PCM_FMT_S20] = SNDRV_PCM_FORMAT_S20_LE,
[VIRTIO_SND_PCM_FMT_U20] = SNDRV_PCM_FORMAT_U20_LE,
[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
+};
+/* Map for converting VirtIO frame rate to ALSA frame rate. */ +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 }
+};
+/**
- virtsnd_pcm_build_hw() - Parse substream config and build HW descriptor.
- @substream: VirtIO substream.
- @info: VirtIO substream information entry.
- Context: Any context.
- Return: 0 on success, -EINVAL if configuration is invalid.
- */
+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;
if (!info->channels_min || info->channels_min > info->channels_max) {
dev_err(&vdev->dev,
"SID %u: invalid channel range [%u %u]\n",
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);
substream->hw.formats = 0;
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\n",
substream->sid);
return -EINVAL;
}
values = le64_to_cpu(info->rates);
substream->hw.rates = 0;
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\n",
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;
+}
+/**
- virtsnd_pcm_prealloc_pages() - Preallocate substream hardware buffer.
- @substream: VirtIO substream.
- Context: Any context that permits to sleep.
- Return: 0 on success, -errno on failure.
- */
+static int 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);
/*
* We just allocate a CONTINUOUS buffer as it should work in any setup.
*
* If there is a need to use DEV(_XXX), then add this case here and
* (probably) update the related source code in other places.
*/
snd_pcm_lib_preallocate_pages(ksubstream, SNDRV_DMA_TYPE_CONTINUOUS,
data, size, size);
return 0;
+}
+/**
- virtsnd_pcm_find() - Find the PCM device for the specified node ID.
- @snd: VirtIO sound device.
- @nid: Function node ID.
- Context: Any context.
- Return: a pointer to the PCM device or ERR_PTR(-ENOENT).
- */
+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);
+}
+/**
- virtsnd_pcm_find_or_create() - Find or create the PCM device for the
specified node ID.
- @snd: VirtIO sound device.
- @nid: Function node ID.
- Context: Any context that permits to sleep.
- Return: a pointer to the PCM device or ERR_PTR(-errno).
- */
+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;
+}
+/**
- virtsnd_pcm_validate() - Validate if the device can be started.
- @vdev: VirtIO parent device.
- Context: Any context.
- Return: 0 on success, -EINVAL on failure.
- */
+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\n",
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\n",
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\n",
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\n",
pcm_period_ms_max, pcm_buffer_ms / 2);
return -EINVAL;
}
return 0;
+}
+/**
- virtsnd_pcm_parse_cfg() - Parse the stream configuration.
- @snd: VirtIO sound device.
- This function is called during initial device initialization.
- Context: Any context that permits to sleep.
- Return: 0 on success, -errno on failure.
- */
+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;
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)\n",
substream->sid, info[i].direction);
return -EINVAL;
}
}
pcm->streams[substream->direction].nsubstreams++;
}
devm_kfree(&vdev->dev, info);
return 0;
+}
+/**
- virtsnd_pcm_build_devs() - Build ALSA PCM devices.
- @snd: VirtIO sound device.
- Context: Any context that permits to sleep.
- Return: 0 on success, -errno on failure.
- */
+int virtsnd_pcm_build_devs(struct virtio_snd *snd) +{
struct virtio_device *vdev = snd->vdev;
struct virtio_pcm *pcm;
unsigned int i;
int rc;
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;
rc = snd_pcm_new(snd->card, "virtio_snd", pcm->nid, npbs, ncps,
&pcm->pcm);
if (rc) {
dev_err(&vdev->dev, "snd_pcm_new[%u] failed: %d\n",
pcm->nid, rc);
return rc;
}
pcm->pcm->info_flags = 0;
pcm->pcm->dev_class = SNDRV_PCM_CLASS_GENERIC;
pcm->pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
strscpy(pcm->pcm->name, "VirtIO PCM", sizeof(pcm->pcm->name));
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;
rc = virtsnd_pcm_prealloc_pages(substream);
if (rc)
return rc;
}
}
return 0;
+}
+/**
- virtsnd_pcm_event() - Handle the PCM device event notification.
- @snd: VirtIO sound device.
- @event: VirtIO sound event.
- Context: Interrupt context.
- */
+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: {
break;
}
}
+} diff --git a/sound/virtio/virtio_pcm.h b/sound/virtio/virtio_pcm.h new file mode 100644 index 000000000000..73fb4d9dc524 --- /dev/null +++ b/sound/virtio/virtio_pcm.h @@ -0,0 +1,89 @@ +/* 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_substream - VirtIO PCM substream.
- @snd: VirtIO sound device.
- @nid: Function group node identifier.
- @sid: Stream identifier.
- @direction: Stream data flow direction (SNDRV_PCM_STREAM_XXX).
- @features: Stream VirtIO feature bit map (1 << VIRTIO_SND_PCM_F_XXX).
- @substream: Kernel ALSA substream.
- @hw: Kernel ALSA substream hardware descriptor.
- */
+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;
+};
+/**
- struct virtio_pcm_stream - VirtIO PCM stream.
- @substreams: Virtio substreams belonging to the stream.
- @nsubstreams: Number of substreams.
- */
+struct virtio_pcm_stream {
struct virtio_pcm_substream **substreams;
unsigned int nsubstreams;
+};
+/**
- struct virtio_pcm - VirtIO PCM device.
- @list: PCM list entry.
- @nid: Function group node identifier.
- @pcm: Kernel PCM device.
- @streams: VirtIO PCM 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];
+};
+int virtsnd_pcm_validate(struct virtio_device *vdev);
+int virtsnd_pcm_parse_cfg(struct virtio_snd *snd);
+int virtsnd_pcm_build_devs(struct virtio_snd *snd);
+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);
+#endif /* VIRTIO_SND_PCM_H */
2.30.0
The driver implements a message-based transport for I/O substream operations. Before the start of the substream, the hardware buffer is sliced into I/O messages, the number of which is equal to the current number of periods. The size of each message is equal to the current size of one period.
I/O messages are organized in an ordered queue. The completion of the I/O message indicates an expired period (the only exception is the end of the stream for the capture substream). Upon completion, the message is automatically re-added to the end of the queue.
When an I/O message is completed, the hw_ptr value is incremented unconditionally (to ensure that the hw_ptr always correctly reflects the state of the messages in the virtqueue). Due to its asynchronous nature, a message can be completed when the runtime structure no longer exists. For this reason, the values from this structure are cached in the virtio substream, which are required to calculate the new value of the hw_ptr.
Signed-off-by: Anton Yakovlev anton.yakovlev@opensynergy.com --- sound/virtio/Makefile | 3 +- sound/virtio/virtio_card.c | 33 ++++ sound/virtio/virtio_card.h | 9 + sound/virtio/virtio_pcm.c | 3 + sound/virtio/virtio_pcm.h | 31 ++++ sound/virtio/virtio_pcm_msg.c | 317 ++++++++++++++++++++++++++++++++++ 6 files changed, 395 insertions(+), 1 deletion(-) create mode 100644 sound/virtio/virtio_pcm_msg.c
diff --git a/sound/virtio/Makefile b/sound/virtio/Makefile index 69162a545a41..626af3cc3ed7 100644 --- a/sound/virtio/Makefile +++ b/sound/virtio/Makefile @@ -5,5 +5,6 @@ obj-$(CONFIG_SND_VIRTIO) += virtio_snd.o virtio_snd-objs := \ virtio_card.o \ virtio_ctl_msg.o \ - virtio_pcm.o + virtio_pcm.o \ + virtio_pcm_msg.o
diff --git a/sound/virtio/virtio_card.c b/sound/virtio/virtio_card.c index 293d497f24e7..dc703fc662f5 100644 --- a/sound/virtio/virtio_card.c +++ b/sound/virtio/virtio_card.c @@ -145,6 +145,12 @@ static int virtsnd_find_vqs(struct virtio_snd *snd) 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, &n); + if (n) { + callbacks[VIRTIO_SND_VQ_TX] = virtsnd_pcm_tx_notify_cb; + callbacks[VIRTIO_SND_VQ_RX] = virtsnd_pcm_rx_notify_cb; + } + rc = virtio_find_vqs(vdev, VIRTIO_SND_VQ_MAX, vqs, callbacks, names, NULL); if (rc) { @@ -186,15 +192,42 @@ static int virtsnd_find_vqs(struct virtio_snd *snd) * virtsnd_enable_vqs() - Enable the event, tx and rx virtqueues. * @snd: VirtIO sound device. * + * The tx queue is enabled only if the device supports playback stream(s). + * + * The rx queue is enabled only if the device supports capture stream(s). + * * Context: Any context. */ static void virtsnd_enable_vqs(struct virtio_snd *snd) { + struct virtio_device *vdev = snd->vdev; struct virtqueue *vqueue; + struct virtio_pcm *pcm; + unsigned int npbs = 0; + unsigned int ncps = 0;
vqueue = snd->queues[VIRTIO_SND_VQ_EVENT].vqueue; if (!virtqueue_enable_cb(vqueue)) virtsnd_event_notify_cb(vqueue); + + list_for_each_entry(pcm, &snd->pcm_list, list) { + npbs += pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].nsubstreams; + ncps += pcm->streams[SNDRV_PCM_STREAM_CAPTURE].nsubstreams; + } + + if (npbs) { + vqueue = snd->queues[VIRTIO_SND_VQ_TX].vqueue; + if (!virtqueue_enable_cb(vqueue)) + dev_warn(&vdev->dev, + "suspicious notification in the TX queue\n"); + } + + if (ncps) { + vqueue = snd->queues[VIRTIO_SND_VQ_RX].vqueue; + if (!virtqueue_enable_cb(vqueue)) + dev_warn(&vdev->dev, + "suspicious notification in the RX queue\n"); + } }
/** diff --git a/sound/virtio/virtio_card.h b/sound/virtio/virtio_card.h index be6651a6aaf8..b11c09984882 100644 --- a/sound/virtio/virtio_card.h +++ b/sound/virtio/virtio_card.h @@ -89,4 +89,13 @@ 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); +} + #endif /* VIRTIO_SND_CARD_H */ diff --git a/sound/virtio/virtio_pcm.c b/sound/virtio/virtio_pcm.c index 036990b7b78a..1ab50dcc88c8 100644 --- a/sound/virtio/virtio_pcm.c +++ b/sound/virtio/virtio_pcm.c @@ -376,6 +376,7 @@ int virtsnd_pcm_parse_cfg(struct virtio_snd *snd)
substream->snd = snd; substream->sid = i; + init_waitqueue_head(&substream->msg_empty);
rc = virtsnd_pcm_build_hw(substream, &info[i]); if (rc) @@ -530,6 +531,8 @@ void virtsnd_pcm_event(struct virtio_snd *snd, struct virtio_snd_event *event) break; } case VIRTIO_SND_EVT_PCM_XRUN: { + if (atomic_read(&substream->xfer_enabled)) + atomic_set(&substream->xfer_xrun, 1); break; } } diff --git a/sound/virtio/virtio_pcm.h b/sound/virtio/virtio_pcm.h index 73fb4d9dc524..d011b7e1d18d 100644 --- a/sound/virtio/virtio_pcm.h +++ b/sound/virtio/virtio_pcm.h @@ -24,6 +24,7 @@ #include <sound/pcm.h>
struct virtio_pcm; +struct virtio_pcm_msg;
/** * struct virtio_pcm_substream - VirtIO PCM substream. @@ -34,6 +35,16 @@ struct virtio_pcm; * @features: Stream VirtIO feature bit map (1 << VIRTIO_SND_PCM_F_XXX). * @substream: Kernel ALSA substream. * @hw: Kernel ALSA substream hardware descriptor. + * @frame_bytes: Current frame size in bytes. + * @period_size: Current period size in frames. + * @buffer_size: Current buffer size in frames. + * @hw_ptr: Substream hardware pointer value in frames [0 ... buffer_size). + * @xfer_enabled: Data transfer state (0 - off, 1 - on). + * @xfer_xrun: Data underflow/overflow state (0 - no xrun, 1 - xrun). + * @msgs: I/O messages. + * @msg_last_enqueued: Index of the last I/O message added to the virtqueue. + * @msg_count: Number of pending I/O messages in the virtqueue. + * @msg_empty: Notify when msg_count is zero. */ struct virtio_pcm_substream { struct virtio_snd *snd; @@ -43,6 +54,16 @@ struct virtio_pcm_substream { u32 features; struct snd_pcm_substream *substream; struct snd_pcm_hardware hw; + unsigned int frame_bytes; + snd_pcm_uframes_t period_size; + snd_pcm_uframes_t buffer_size; + 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; };
/** @@ -86,4 +107,14 @@ 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/sound/virtio/virtio_pcm_msg.c b/sound/virtio/virtio_pcm_msg.c new file mode 100644 index 000000000000..cfbe5935527a --- /dev/null +++ b/sound/virtio/virtio_pcm_msg.c @@ -0,0 +1,317 @@ +// 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 payload 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 - VirtIO I/O message. + * @substream: VirtIO PCM substream. + * @xfer: Request header payload. + * @status: Response header payload. + * @sgs: 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]; +}; + +/** + * virtsnd_pcm_msg_alloc() - Allocate I/O messages. + * @substream: VirtIO PCM substream. + * @nmsg: Number of messages (equal to the number of periods). + * @dma_area: Pointer to used audio buffer. + * @period_bytes: Period (message payload) size. + * + * The function slices the buffer into nmsg parts (each with the size of + * period_bytes), and creates nmsg corresponding I/O messages. + * + * Context: Any context that permits to sleep. + * Return: 0 on success, -ENOMEM on failure. + */ +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; +} + +/** + * virtsnd_pcm_msg_send() - Send asynchronous I/O messages. + * @substream: VirtIO PCM substream. + * + * All messages are organized in an ordered circular list. Each time the + * function is called, all currently non-enqueued messages are added to the + * virtqueue. For this, the function keeps track of two values: + * + * msg_last_enqueued = index of the last enqueued message, + * msg_count = # of pending messages in the virtqueue. + * + * Context: Any context. + * Return: 0 on success, -EIO on failure. + */ +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; +} + +/** + * virtsnd_pcm_msg_complete() - Complete an I/O message. + * @msg: I/O message. + * @size: Number of bytes written. + * + * Completion of the message means the elapsed period: + * - update hardware pointer, + * - update latency value, + * - kick the upper layer. + * + * Context: Interrupt context. + */ +static void virtsnd_pcm_msg_complete(struct virtio_pcm_msg *msg, size_t size) +{ + struct virtio_pcm_substream *substream = msg->substream; + snd_pcm_uframes_t hw_ptr; + unsigned int msg_count; + + hw_ptr = (snd_pcm_uframes_t)atomic_read(&substream->hw_ptr); + + /* + * If the capture substream returned an incorrect status, then just + * increase the hw_ptr by the period size. + */ + if (substream->direction == SNDRV_PCM_STREAM_PLAYBACK || + size <= sizeof(msg->status)) { + hw_ptr += substream->period_size; + } else { + size -= sizeof(msg->status); + hw_ptr += size / substream->frame_bytes; + } + + atomic_set(&substream->hw_ptr, (u32)(hw_ptr % substream->buffer_size)); + atomic_set(&substream->xfer_xrun, 0); + + msg_count = atomic_dec_return(&substream->msg_count); + + if (atomic_read(&substream->xfer_enabled)) { + struct snd_pcm_runtime *runtime = substream->substream->runtime; + + runtime->delay = + bytes_to_frames(runtime, + le32_to_cpu(msg->status.latency_bytes)); + + snd_pcm_period_elapsed(substream->substream); + + virtsnd_pcm_msg_send(substream); + } else if (!msg_count) { + wake_up_all(&substream->msg_empty); + } +} + +/** + * virtsnd_pcm_notify_cb() - Process all completed I/O messages. + * @vqueue: Underlying tx/rx virtqueue. + * + * If transmission is allowed, then each completed message is immediately placed + * back at the end of the queue. + * + * Context: Interrupt context. Takes and releases the tx/rx queue spinlock. + */ +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_msg *msg; + u32 length; + + msg = virtqueue_get_buf(queue->vqueue, &length); + if (!msg) + break; + + virtsnd_pcm_msg_complete(msg, length); + } + + if (unlikely(virtqueue_is_broken(queue->vqueue))) + break; + + if (virtqueue_enable_cb(queue->vqueue)) + break; + } + spin_unlock_irqrestore(&queue->lock, flags); +} + +/** + * virtsnd_pcm_tx_notify_cb() - Process all completed TX messages. + * @vqueue: Underlying tx virtqueue. + * + * Context: Interrupt context. + */ +void virtsnd_pcm_tx_notify_cb(struct virtqueue *vqueue) +{ + struct virtio_snd *snd = vqueue->vdev->priv; + + virtsnd_pcm_notify_cb(virtsnd_tx_queue(snd)); +} + +/** + * virtsnd_pcm_rx_notify_cb() - Process all completed RX messages. + * @vqueue: Underlying rx virtqueue. + * + * Context: Interrupt context. + */ +void virtsnd_pcm_rx_notify_cb(struct virtqueue *vqueue) +{ + struct virtio_snd *snd = vqueue->vdev->priv; + + virtsnd_pcm_notify_cb(virtsnd_rx_queue(snd)); +} + +/** + * virtsnd_pcm_ctl_msg_alloc() - Allocate and initialize the PCM device control + * message for the specified substream. + * @substream: VirtIO PCM substream. + * @command: Control request code (VIRTIO_SND_R_PCM_XXX). + * @gfp: Kernel flags for memory allocation. + * + * Context: Any context. May sleep if @gfp flags permit. + * Return: Allocated message on success, ERR_PTR(-errno) on failure. + */ +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; +}
On Wed, Jan 20, 2021 at 01:36:32AM +0100, Anton Yakovlev wrote:
The driver implements a message-based transport for I/O substream operations. Before the start of the substream, the hardware buffer is sliced into I/O messages, the number of which is equal to the current number of periods. The size of each message is equal to the current size of one period.
I/O messages are organized in an ordered queue. The completion of the I/O message indicates an expired period (the only exception is the end of the stream for the capture substream). Upon completion, the message is automatically re-added to the end of the queue.
When an I/O message is completed, the hw_ptr value is incremented unconditionally (to ensure that the hw_ptr always correctly reflects the state of the messages in the virtqueue). Due to its asynchronous nature, a message can be completed when the runtime structure no longer exists. For this reason, the values from this structure are cached in the virtio substream, which are required to calculate the new value of the hw_ptr.
Signed-off-by: Anton Yakovlev anton.yakovlev@opensynergy.com
sound/virtio/Makefile | 3 +- sound/virtio/virtio_card.c | 33 ++++ sound/virtio/virtio_card.h | 9 + sound/virtio/virtio_pcm.c | 3 + sound/virtio/virtio_pcm.h | 31 ++++ sound/virtio/virtio_pcm_msg.c | 317 ++++++++++++++++++++++++++++++++++ 6 files changed, 395 insertions(+), 1 deletion(-) create mode 100644 sound/virtio/virtio_pcm_msg.c
diff --git a/sound/virtio/Makefile b/sound/virtio/Makefile index 69162a545a41..626af3cc3ed7 100644 --- a/sound/virtio/Makefile +++ b/sound/virtio/Makefile @@ -5,5 +5,6 @@ obj-$(CONFIG_SND_VIRTIO) += virtio_snd.o virtio_snd-objs := \ virtio_card.o \ virtio_ctl_msg.o \
- virtio_pcm.o
- virtio_pcm.o \
- virtio_pcm_msg.o
diff --git a/sound/virtio/virtio_card.c b/sound/virtio/virtio_card.c index 293d497f24e7..dc703fc662f5 100644 --- a/sound/virtio/virtio_card.c +++ b/sound/virtio/virtio_card.c @@ -145,6 +145,12 @@ static int virtsnd_find_vqs(struct virtio_snd *snd) 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, &n);
- if (n) {
callbacks[VIRTIO_SND_VQ_TX] = virtsnd_pcm_tx_notify_cb;
callbacks[VIRTIO_SND_VQ_RX] = virtsnd_pcm_rx_notify_cb;
- }
- rc = virtio_find_vqs(vdev, VIRTIO_SND_VQ_MAX, vqs, callbacks, names, NULL); if (rc) {
@@ -186,15 +192,42 @@ static int virtsnd_find_vqs(struct virtio_snd *snd)
- virtsnd_enable_vqs() - Enable the event, tx and rx virtqueues.
- @snd: VirtIO sound device.
- The tx queue is enabled only if the device supports playback stream(s).
- The rx queue is enabled only if the device supports capture stream(s).
*/
- Context: Any context.
static void virtsnd_enable_vqs(struct virtio_snd *snd) {
struct virtio_device *vdev = snd->vdev; struct virtqueue *vqueue;
struct virtio_pcm *pcm;
unsigned int npbs = 0;
unsigned int ncps = 0;
vqueue = snd->queues[VIRTIO_SND_VQ_EVENT].vqueue; if (!virtqueue_enable_cb(vqueue)) virtsnd_event_notify_cb(vqueue);
list_for_each_entry(pcm, &snd->pcm_list, list) {
npbs += pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].nsubstreams;
ncps += pcm->streams[SNDRV_PCM_STREAM_CAPTURE].nsubstreams;
}
if (npbs) {
vqueue = snd->queues[VIRTIO_SND_VQ_TX].vqueue;
if (!virtqueue_enable_cb(vqueue))
dev_warn(&vdev->dev,
"suspicious notification in the TX queue\n");
}
if (ncps) {
vqueue = snd->queues[VIRTIO_SND_VQ_RX].vqueue;
if (!virtqueue_enable_cb(vqueue))
dev_warn(&vdev->dev,
"suspicious notification in the RX queue\n");
}
Not sure how all this prevents use of same vq from multiple threads ... And why are we sure there are no buffers yet? If that is because nothing yet happened, then I'd also like to point out that a vq starts out with callbacks enabled, so you don't need to do that first thing ...
}
/** diff --git a/sound/virtio/virtio_card.h b/sound/virtio/virtio_card.h index be6651a6aaf8..b11c09984882 100644 --- a/sound/virtio/virtio_card.h +++ b/sound/virtio/virtio_card.h @@ -89,4 +89,13 @@ 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);
+}
#endif /* VIRTIO_SND_CARD_H */ diff --git a/sound/virtio/virtio_pcm.c b/sound/virtio/virtio_pcm.c index 036990b7b78a..1ab50dcc88c8 100644 --- a/sound/virtio/virtio_pcm.c +++ b/sound/virtio/virtio_pcm.c @@ -376,6 +376,7 @@ int virtsnd_pcm_parse_cfg(struct virtio_snd *snd)
substream->snd = snd; substream->sid = i;
init_waitqueue_head(&substream->msg_empty);
rc = virtsnd_pcm_build_hw(substream, &info[i]); if (rc)
@@ -530,6 +531,8 @@ void virtsnd_pcm_event(struct virtio_snd *snd, struct virtio_snd_event *event) break; } case VIRTIO_SND_EVT_PCM_XRUN: {
if (atomic_read(&substream->xfer_enabled))
break; } }atomic_set(&substream->xfer_xrun, 1);
diff --git a/sound/virtio/virtio_pcm.h b/sound/virtio/virtio_pcm.h index 73fb4d9dc524..d011b7e1d18d 100644 --- a/sound/virtio/virtio_pcm.h +++ b/sound/virtio/virtio_pcm.h @@ -24,6 +24,7 @@ #include <sound/pcm.h>
struct virtio_pcm; +struct virtio_pcm_msg;
/**
- struct virtio_pcm_substream - VirtIO PCM substream.
@@ -34,6 +35,16 @@ struct virtio_pcm;
- @features: Stream VirtIO feature bit map (1 << VIRTIO_SND_PCM_F_XXX).
- @substream: Kernel ALSA substream.
- @hw: Kernel ALSA substream hardware descriptor.
- @frame_bytes: Current frame size in bytes.
- @period_size: Current period size in frames.
- @buffer_size: Current buffer size in frames.
- @hw_ptr: Substream hardware pointer value in frames [0 ... buffer_size).
- @xfer_enabled: Data transfer state (0 - off, 1 - on).
- @xfer_xrun: Data underflow/overflow state (0 - no xrun, 1 - xrun).
- @msgs: I/O messages.
- @msg_last_enqueued: Index of the last I/O message added to the virtqueue.
- @msg_count: Number of pending I/O messages in the virtqueue.
*/
- @msg_empty: Notify when msg_count is zero.
struct virtio_pcm_substream { struct virtio_snd *snd; @@ -43,6 +54,16 @@ struct virtio_pcm_substream { u32 features; struct snd_pcm_substream *substream; struct snd_pcm_hardware hw;
- unsigned int frame_bytes;
- snd_pcm_uframes_t period_size;
- snd_pcm_uframes_t buffer_size;
- 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;
};
/** @@ -86,4 +107,14 @@ 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/sound/virtio/virtio_pcm_msg.c b/sound/virtio/virtio_pcm_msg.c new file mode 100644 index 000000000000..cfbe5935527a --- /dev/null +++ b/sound/virtio/virtio_pcm_msg.c @@ -0,0 +1,317 @@ +// 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 payload 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 - VirtIO I/O message.
- @substream: VirtIO PCM substream.
- @xfer: Request header payload.
- @status: Response header payload.
- @sgs: 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];
+};
+/**
- virtsnd_pcm_msg_alloc() - Allocate I/O messages.
- @substream: VirtIO PCM substream.
- @nmsg: Number of messages (equal to the number of periods).
- @dma_area: Pointer to used audio buffer.
- @period_bytes: Period (message payload) size.
- The function slices the buffer into nmsg parts (each with the size of
- period_bytes), and creates nmsg corresponding I/O messages.
- Context: Any context that permits to sleep.
- Return: 0 on success, -ENOMEM on failure.
- */
+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;
+}
+/**
- virtsnd_pcm_msg_send() - Send asynchronous I/O messages.
- @substream: VirtIO PCM substream.
- All messages are organized in an ordered circular list. Each time the
- function is called, all currently non-enqueued messages are added to the
- virtqueue. For this, the function keeps track of two values:
- msg_last_enqueued = index of the last enqueued message,
- msg_count = # of pending messages in the virtqueue.
- Context: Any context.
- Return: 0 on success, -EIO on failure.
- */
+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;
+}
+/**
- virtsnd_pcm_msg_complete() - Complete an I/O message.
- @msg: I/O message.
- @size: Number of bytes written.
- Completion of the message means the elapsed period:
- update hardware pointer,
- update latency value,
- kick the upper layer.
- Context: Interrupt context.
- */
+static void virtsnd_pcm_msg_complete(struct virtio_pcm_msg *msg, size_t size) +{
- struct virtio_pcm_substream *substream = msg->substream;
- snd_pcm_uframes_t hw_ptr;
- unsigned int msg_count;
- hw_ptr = (snd_pcm_uframes_t)atomic_read(&substream->hw_ptr);
- /*
* If the capture substream returned an incorrect status, then just
* increase the hw_ptr by the period size.
*/
- if (substream->direction == SNDRV_PCM_STREAM_PLAYBACK ||
size <= sizeof(msg->status)) {
hw_ptr += substream->period_size;
- } else {
size -= sizeof(msg->status);
hw_ptr += size / substream->frame_bytes;
- }
- atomic_set(&substream->hw_ptr, (u32)(hw_ptr % substream->buffer_size));
- atomic_set(&substream->xfer_xrun, 0);
- msg_count = atomic_dec_return(&substream->msg_count);
- if (atomic_read(&substream->xfer_enabled)) {
struct snd_pcm_runtime *runtime = substream->substream->runtime;
runtime->delay =
bytes_to_frames(runtime,
le32_to_cpu(msg->status.latency_bytes));
snd_pcm_period_elapsed(substream->substream);
virtsnd_pcm_msg_send(substream);
- } else if (!msg_count) {
wake_up_all(&substream->msg_empty);
- }
+}
+/**
- virtsnd_pcm_notify_cb() - Process all completed I/O messages.
- @vqueue: Underlying tx/rx virtqueue.
- If transmission is allowed, then each completed message is immediately placed
- back at the end of the queue.
- Context: Interrupt context. Takes and releases the tx/rx queue spinlock.
- */
+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_msg *msg;
u32 length;
msg = virtqueue_get_buf(queue->vqueue, &length);
if (!msg)
break;
virtsnd_pcm_msg_complete(msg, length);
}
if (unlikely(virtqueue_is_broken(queue->vqueue)))
break;
if (virtqueue_enable_cb(queue->vqueue))
break;
- }
- spin_unlock_irqrestore(&queue->lock, flags);
+}
+/**
- virtsnd_pcm_tx_notify_cb() - Process all completed TX messages.
- @vqueue: Underlying tx virtqueue.
- Context: Interrupt context.
- */
+void virtsnd_pcm_tx_notify_cb(struct virtqueue *vqueue) +{
- struct virtio_snd *snd = vqueue->vdev->priv;
- virtsnd_pcm_notify_cb(virtsnd_tx_queue(snd));
+}
+/**
- virtsnd_pcm_rx_notify_cb() - Process all completed RX messages.
- @vqueue: Underlying rx virtqueue.
- Context: Interrupt context.
- */
+void virtsnd_pcm_rx_notify_cb(struct virtqueue *vqueue) +{
- struct virtio_snd *snd = vqueue->vdev->priv;
- virtsnd_pcm_notify_cb(virtsnd_rx_queue(snd));
+}
+/**
- virtsnd_pcm_ctl_msg_alloc() - Allocate and initialize the PCM device control
message for the specified substream.
- @substream: VirtIO PCM substream.
- @command: Control request code (VIRTIO_SND_R_PCM_XXX).
- @gfp: Kernel flags for memory allocation.
- Context: Any context. May sleep if @gfp flags permit.
- Return: Allocated message on success, ERR_PTR(-errno) on failure.
- */
+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;
+}
2.30.0
On 20.01.2021 09:29, Michael S. Tsirkin wrote:
CAUTION: This email originated from outside of the organization. Do not click links or open attachments unless you recognize the sender and know the content is safe.
On Wed, Jan 20, 2021 at 01:36:32AM +0100, Anton Yakovlev wrote:
The driver implements a message-based transport for I/O substream operations. Before the start of the substream, the hardware buffer is sliced into I/O messages, the number of which is equal to the current number of periods. The size of each message is equal to the current size of one period.
I/O messages are organized in an ordered queue. The completion of the I/O message indicates an expired period (the only exception is the end of the stream for the capture substream). Upon completion, the message is automatically re-added to the end of the queue.
When an I/O message is completed, the hw_ptr value is incremented unconditionally (to ensure that the hw_ptr always correctly reflects the state of the messages in the virtqueue). Due to its asynchronous nature, a message can be completed when the runtime structure no longer exists. For this reason, the values from this structure are cached in the virtio substream, which are required to calculate the new value of the hw_ptr.
Signed-off-by: Anton Yakovlev anton.yakovlev@opensynergy.com
sound/virtio/Makefile | 3 +- sound/virtio/virtio_card.c | 33 ++++ sound/virtio/virtio_card.h | 9 + sound/virtio/virtio_pcm.c | 3 + sound/virtio/virtio_pcm.h | 31 ++++ sound/virtio/virtio_pcm_msg.c | 317 ++++++++++++++++++++++++++++++++++ 6 files changed, 395 insertions(+), 1 deletion(-) create mode 100644 sound/virtio/virtio_pcm_msg.c
diff --git a/sound/virtio/Makefile b/sound/virtio/Makefile index 69162a545a41..626af3cc3ed7 100644 --- a/sound/virtio/Makefile +++ b/sound/virtio/Makefile @@ -5,5 +5,6 @@ obj-$(CONFIG_SND_VIRTIO) += virtio_snd.o virtio_snd-objs := \ virtio_card.o \ virtio_ctl_msg.o \
virtio_pcm.o
virtio_pcm.o \
virtio_pcm_msg.o
diff --git a/sound/virtio/virtio_card.c b/sound/virtio/virtio_card.c index 293d497f24e7..dc703fc662f5 100644 --- a/sound/virtio/virtio_card.c +++ b/sound/virtio/virtio_card.c @@ -145,6 +145,12 @@ static int virtsnd_find_vqs(struct virtio_snd *snd) 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, &n);
if (n) {
callbacks[VIRTIO_SND_VQ_TX] = virtsnd_pcm_tx_notify_cb;
callbacks[VIRTIO_SND_VQ_RX] = virtsnd_pcm_rx_notify_cb;
}
rc = virtio_find_vqs(vdev, VIRTIO_SND_VQ_MAX, vqs, callbacks, names, NULL); if (rc) {
@@ -186,15 +192,42 @@ static int virtsnd_find_vqs(struct virtio_snd *snd)
- virtsnd_enable_vqs() - Enable the event, tx and rx virtqueues.
- @snd: VirtIO sound device.
- The tx queue is enabled only if the device supports playback stream(s).
- The rx queue is enabled only if the device supports capture stream(s).
- Context: Any context.
*/ static void virtsnd_enable_vqs(struct virtio_snd *snd) {
struct virtio_device *vdev = snd->vdev; struct virtqueue *vqueue;
struct virtio_pcm *pcm;
unsigned int npbs = 0;
unsigned int ncps = 0; vqueue = snd->queues[VIRTIO_SND_VQ_EVENT].vqueue; if (!virtqueue_enable_cb(vqueue)) virtsnd_event_notify_cb(vqueue);
list_for_each_entry(pcm, &snd->pcm_list, list) {
npbs += pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].nsubstreams;
ncps += pcm->streams[SNDRV_PCM_STREAM_CAPTURE].nsubstreams;
}
if (npbs) {
vqueue = snd->queues[VIRTIO_SND_VQ_TX].vqueue;
if (!virtqueue_enable_cb(vqueue))
dev_warn(&vdev->dev,
"suspicious notification in the TX queue\n");
}
if (ncps) {
vqueue = snd->queues[VIRTIO_SND_VQ_RX].vqueue;
if (!virtqueue_enable_cb(vqueue))
dev_warn(&vdev->dev,
"suspicious notification in the RX queue\n");
}
Not sure how all this prevents use of same vq from multiple threads ... And why are we sure there are no buffers yet? If that is because nothing yet happened, then I'd also like to point out that a vq starts out with callbacks enabled, so you don't need to do that first thing ...
Yes, I redone that logic in v2.
}
/** diff --git a/sound/virtio/virtio_card.h b/sound/virtio/virtio_card.h index be6651a6aaf8..b11c09984882 100644 --- a/sound/virtio/virtio_card.h +++ b/sound/virtio/virtio_card.h @@ -89,4 +89,13 @@ 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);
+}
- #endif /* VIRTIO_SND_CARD_H */
diff --git a/sound/virtio/virtio_pcm.c b/sound/virtio/virtio_pcm.c index 036990b7b78a..1ab50dcc88c8 100644 --- a/sound/virtio/virtio_pcm.c +++ b/sound/virtio/virtio_pcm.c @@ -376,6 +376,7 @@ int virtsnd_pcm_parse_cfg(struct virtio_snd *snd)
substream->snd = snd; substream->sid = i;
init_waitqueue_head(&substream->msg_empty); rc = virtsnd_pcm_build_hw(substream, &info[i]); if (rc)
@@ -530,6 +531,8 @@ void virtsnd_pcm_event(struct virtio_snd *snd, struct virtio_snd_event *event) break; } case VIRTIO_SND_EVT_PCM_XRUN: {
if (atomic_read(&substream->xfer_enabled))
atomic_set(&substream->xfer_xrun, 1); break; } }
diff --git a/sound/virtio/virtio_pcm.h b/sound/virtio/virtio_pcm.h index 73fb4d9dc524..d011b7e1d18d 100644 --- a/sound/virtio/virtio_pcm.h +++ b/sound/virtio/virtio_pcm.h @@ -24,6 +24,7 @@ #include <sound/pcm.h>
struct virtio_pcm; +struct virtio_pcm_msg;
/**
- struct virtio_pcm_substream - VirtIO PCM substream.
@@ -34,6 +35,16 @@ struct virtio_pcm;
- @features: Stream VirtIO feature bit map (1 << VIRTIO_SND_PCM_F_XXX).
- @substream: Kernel ALSA substream.
- @hw: Kernel ALSA substream hardware descriptor.
- @frame_bytes: Current frame size in bytes.
- @period_size: Current period size in frames.
- @buffer_size: Current buffer size in frames.
- @hw_ptr: Substream hardware pointer value in frames [0 ... buffer_size).
- @xfer_enabled: Data transfer state (0 - off, 1 - on).
- @xfer_xrun: Data underflow/overflow state (0 - no xrun, 1 - xrun).
- @msgs: I/O messages.
- @msg_last_enqueued: Index of the last I/O message added to the virtqueue.
- @msg_count: Number of pending I/O messages in the virtqueue.
*/ struct virtio_pcm_substream { struct virtio_snd *snd;
- @msg_empty: Notify when msg_count is zero.
@@ -43,6 +54,16 @@ struct virtio_pcm_substream { u32 features; struct snd_pcm_substream *substream; struct snd_pcm_hardware hw;
unsigned int frame_bytes;
snd_pcm_uframes_t period_size;
snd_pcm_uframes_t buffer_size;
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;
};
/**
@@ -86,4 +107,14 @@ 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/sound/virtio/virtio_pcm_msg.c b/sound/virtio/virtio_pcm_msg.c new file mode 100644 index 000000000000..cfbe5935527a --- /dev/null +++ b/sound/virtio/virtio_pcm_msg.c @@ -0,0 +1,317 @@ +// 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 payload 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 - VirtIO I/O message.
- @substream: VirtIO PCM substream.
- @xfer: Request header payload.
- @status: Response header payload.
- @sgs: 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];
+};
+/**
- virtsnd_pcm_msg_alloc() - Allocate I/O messages.
- @substream: VirtIO PCM substream.
- @nmsg: Number of messages (equal to the number of periods).
- @dma_area: Pointer to used audio buffer.
- @period_bytes: Period (message payload) size.
- The function slices the buffer into nmsg parts (each with the size of
- period_bytes), and creates nmsg corresponding I/O messages.
- Context: Any context that permits to sleep.
- Return: 0 on success, -ENOMEM on failure.
- */
+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;
+}
+/**
- virtsnd_pcm_msg_send() - Send asynchronous I/O messages.
- @substream: VirtIO PCM substream.
- All messages are organized in an ordered circular list. Each time the
- function is called, all currently non-enqueued messages are added to the
- virtqueue. For this, the function keeps track of two values:
- msg_last_enqueued = index of the last enqueued message,
- msg_count = # of pending messages in the virtqueue.
- Context: Any context.
- Return: 0 on success, -EIO on failure.
- */
+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;
+}
+/**
- virtsnd_pcm_msg_complete() - Complete an I/O message.
- @msg: I/O message.
- @size: Number of bytes written.
- Completion of the message means the elapsed period:
- update hardware pointer,
- update latency value,
- kick the upper layer.
- Context: Interrupt context.
- */
+static void virtsnd_pcm_msg_complete(struct virtio_pcm_msg *msg, size_t size) +{
struct virtio_pcm_substream *substream = msg->substream;
snd_pcm_uframes_t hw_ptr;
unsigned int msg_count;
hw_ptr = (snd_pcm_uframes_t)atomic_read(&substream->hw_ptr);
/*
* If the capture substream returned an incorrect status, then just
* increase the hw_ptr by the period size.
*/
if (substream->direction == SNDRV_PCM_STREAM_PLAYBACK ||
size <= sizeof(msg->status)) {
hw_ptr += substream->period_size;
} else {
size -= sizeof(msg->status);
hw_ptr += size / substream->frame_bytes;
}
atomic_set(&substream->hw_ptr, (u32)(hw_ptr % substream->buffer_size));
atomic_set(&substream->xfer_xrun, 0);
msg_count = atomic_dec_return(&substream->msg_count);
if (atomic_read(&substream->xfer_enabled)) {
struct snd_pcm_runtime *runtime = substream->substream->runtime;
runtime->delay =
bytes_to_frames(runtime,
le32_to_cpu(msg->status.latency_bytes));
snd_pcm_period_elapsed(substream->substream);
virtsnd_pcm_msg_send(substream);
} else if (!msg_count) {
wake_up_all(&substream->msg_empty);
}
+}
+/**
- virtsnd_pcm_notify_cb() - Process all completed I/O messages.
- @vqueue: Underlying tx/rx virtqueue.
- If transmission is allowed, then each completed message is immediately placed
- back at the end of the queue.
- Context: Interrupt context. Takes and releases the tx/rx queue spinlock.
- */
+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_msg *msg;
u32 length;
msg = virtqueue_get_buf(queue->vqueue, &length);
if (!msg)
break;
virtsnd_pcm_msg_complete(msg, length);
}
if (unlikely(virtqueue_is_broken(queue->vqueue)))
break;
if (virtqueue_enable_cb(queue->vqueue))
break;
}
spin_unlock_irqrestore(&queue->lock, flags);
+}
+/**
- virtsnd_pcm_tx_notify_cb() - Process all completed TX messages.
- @vqueue: Underlying tx virtqueue.
- Context: Interrupt context.
- */
+void virtsnd_pcm_tx_notify_cb(struct virtqueue *vqueue) +{
struct virtio_snd *snd = vqueue->vdev->priv;
virtsnd_pcm_notify_cb(virtsnd_tx_queue(snd));
+}
+/**
- virtsnd_pcm_rx_notify_cb() - Process all completed RX messages.
- @vqueue: Underlying rx virtqueue.
- Context: Interrupt context.
- */
+void virtsnd_pcm_rx_notify_cb(struct virtqueue *vqueue) +{
struct virtio_snd *snd = vqueue->vdev->priv;
virtsnd_pcm_notify_cb(virtsnd_rx_queue(snd));
+}
+/**
- virtsnd_pcm_ctl_msg_alloc() - Allocate and initialize the PCM device control
message for the specified substream.
- @substream: VirtIO PCM substream.
- @command: Control request code (VIRTIO_SND_R_PCM_XXX).
- @gfp: Kernel flags for memory allocation.
- Context: Any context. May sleep if @gfp flags permit.
- Return: Allocated message on success, ERR_PTR(-errno) on failure.
- */
+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;
+}
2.30.0
Introduce the operators required for the operation of substreams.
Signed-off-by: Anton Yakovlev anton.yakovlev@opensynergy.com --- sound/virtio/Makefile | 3 +- sound/virtio/virtio_pcm.c | 5 +- sound/virtio/virtio_pcm.h | 2 + sound/virtio/virtio_pcm_ops.c | 509 ++++++++++++++++++++++++++++++++++ 4 files changed, 517 insertions(+), 2 deletions(-) create mode 100644 sound/virtio/virtio_pcm_ops.c
diff --git a/sound/virtio/Makefile b/sound/virtio/Makefile index 626af3cc3ed7..34493226793f 100644 --- a/sound/virtio/Makefile +++ b/sound/virtio/Makefile @@ -6,5 +6,6 @@ virtio_snd-objs := \ virtio_card.o \ virtio_ctl_msg.o \ virtio_pcm.o \ - virtio_pcm_msg.o + virtio_pcm_msg.o \ + virtio_pcm_ops.o
diff --git a/sound/virtio/virtio_pcm.c b/sound/virtio/virtio_pcm.c index 1ab50dcc88c8..6a1ca6b2c3ca 100644 --- a/sound/virtio/virtio_pcm.c +++ b/sound/virtio/virtio_pcm.c @@ -121,7 +121,8 @@ static int virtsnd_pcm_build_hw(struct virtio_pcm_substream *substream, SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BATCH | SNDRV_PCM_INFO_BLOCK_TRANSFER | - SNDRV_PCM_INFO_INTERLEAVED; + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE;
if (!info->channels_min || info->channels_min > info->channels_max) { dev_err(&vdev->dev, @@ -503,6 +504,8 @@ int virtsnd_pcm_build_devs(struct virtio_snd *snd) if (rc) return rc; } + + snd_pcm_set_ops(pcm->pcm, i, &virtsnd_pcm_ops); }
return 0; diff --git a/sound/virtio/virtio_pcm.h b/sound/virtio/virtio_pcm.h index d011b7e1d18d..fe467bc05d8b 100644 --- a/sound/virtio/virtio_pcm.h +++ b/sound/virtio/virtio_pcm.h @@ -90,6 +90,8 @@ struct virtio_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); diff --git a/sound/virtio/virtio_pcm_ops.c b/sound/virtio/virtio_pcm_ops.c new file mode 100644 index 000000000000..8d26c1144ad6 --- /dev/null +++ b/sound/virtio/virtio_pcm_ops.c @@ -0,0 +1,509 @@ +// 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" + +/* + * Our main concern here is maintaining the correct state of the underlying I/O + * virtqueues. Thus, operators are implemented to support all of the following + * possible control paths (excluding all trivial ones): + * + * +---------+ + * | open() |<------------------+ + * +----+----+ | + * v | + * +------+------+ | + * +------------->| hw_params() |<-------------+ | + * | +-------------+ | | + * | v | | + * | +-----------+ | | + * | | prepare() |<-----------+ | | + * | +-----------+ | | | + * | v | | | + * | +-------------------------+ | | | + * +-----------+ | trigger(START/ | | | | + * | restore() | | PAUSE_RELEASE/ |<-+ | | | + * +-----------+ | RESUME) | | | | | + * ^ +-------------------------+ | | | | + * | v | | | | + * | +-----------+ | | | | + * | | pointer() | | | | | + * | +-----------+ | | | | + * | v | | | | + * | +---------------------+ | | | | + * +-----------+ | trigger(STOP/ |----+ | | | + * | freeze() |<---| PAUSE_PUSH/ |-------+ | | + * +-----------+ | SUSPEND) | | | + * +---------------------+ | | + * v | | + * +-----------+ | | + * | hw_free() |---------------+ | + * +-----------+ | + * v | + * +---------+ | + * | close() |-------------------+ + * +---------+ + */ + +/* Map for converting ALSA format to VirtIO format. */ +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 }, + { SNDRV_PCM_FORMAT_S20_LE, VIRTIO_SND_PCM_FMT_S20 }, + { SNDRV_PCM_FORMAT_U20_LE, VIRTIO_SND_PCM_FMT_U20 }, + { 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 } +}; + +/* Map for converting ALSA frame rate to VirtIO frame rate. */ +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 } +}; + +/** + * virtsnd_pcm_release() - Release the PCM substream on the device side. + * @substream: VirtIO substream. + * + * The function waits for all pending I/O messages to complete. + * + * Context: Any context that permits to sleep. + * Return: 0 on success, -errno on failure. + */ +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; + unsigned int js = msecs_to_jiffies(msg_timeout_ms); + 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) + return rc; + + return wait_event_interruptible_timeout(substream->msg_empty, + virtsnd_pcm_released(substream), + js); +} + +/** + * virtsnd_pcm_open() - Open the PCM substream. + * @substream: Kernel ALSA substream. + * + * Context: Any context. + * Return: 0 on success, -errno on failure. + */ +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; +} + +/** + * virtsnd_pcm_close() - Close the PCM substream. + * @substream: Kernel ALSA substream. + * + * Context: Any context. + * Return: 0. + */ +static int virtsnd_pcm_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +/** + * virtsnd_pcm_hw_params() - Set the parameters of the PCM substream. + * @substream: Kernel ALSA substream. + * @hw_params: Hardware parameters (can be NULL). + * + * The function can be called both from the upper level (in this case, + * @hw_params is not NULL) or from the driver itself (in this case, @hw_params + * is NULL, and the parameter values are taken from the runtime structure). + * + * In all cases, the function: + * 1. checks the state of the virtqueue and, if necessary, tries to fix it, + * 2. sets the parameters on the device side, + * 3. allocates a hardware buffer and I/O messages. + * + * Context: Any context that permits to sleep. + * Return: 0 on success, -errno on failure. + */ +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; + 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; + + /* + * 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) { + dev_err(&vdev->dev, + "SID %u: invalid I/O queue state\n", + ss->sid); + 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; +} + +/** + * virtsnd_pcm_hw_free() - Reset the parameters of the PCM substream. + * @substream: Kernel ALSA substream. + * + * The function does the following: + * 1. tries to release the PCM substream on the device side, + * 2. frees the hardware buffer. + * + * Context: Any context that permits to sleep. + * Return: 0 on success, -errno on failure. + */ +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; +} + +/** + * virtsnd_pcm_hw_params() - Prepare the PCM substream. + * @substream: Kernel ALSA substream. + * + * The function can be called both from the upper level or from the driver + * itself. + * + * In all cases, the function: + * 1. checks the state of the virtqueue and, if necessary, tries to fix it, + * 2. prepares the substream on the device side. + * + * Context: Any context that permits to sleep. May take and release the tx/rx + * queue spinlock. + * Return: 0 on success, -errno on failure. + */ +static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct virtio_pcm_substream *ss = snd_pcm_substream_chip(substream); + struct virtio_snd_queue *queue = virtsnd_pcm_queue(ss); + struct virtio_snd_msg *msg; + unsigned long flags; + int rc; + + /* + * 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); + + /* + * Since I/O messages are asynchronous, they can be completed + * when the runtime structure no longer exists. Since each + * completion implies incrementing the hw_ptr, we cache all the + * current values needed to compute the new hw_ptr value. + */ + ss->frame_bytes = substream->runtime->frame_bits >> 3; + ss->period_size = substream->runtime->period_size; + ss->buffer_size = substream->runtime->buffer_size; + + 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); +} + +/** + * virtsnd_pcm_trigger() - Process command for the PCM substream. + * @substream: Kernel ALSA substream. + * @command: Substream command (SNDRV_PCM_TRIGGER_XXX). + * + * Depending on the command, the function does the following: + * 1. enables/disables data transmission, + * 2. starts/stops the substream on the device side. + * + * Context: Atomic context. May take and release the tx/rx queue spinlock. + * Return: 0 on success, -errno on failure. + */ +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: { + 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: { + 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; + } + } +} + +/** + * virtsnd_pcm_pointer() - Get the current hardware position for the PCM + * substream. + * @substream: Kernel ALSA substream. + * + * Context: Atomic context. + * Return: Hardware position in frames inside [0 ... buffer_size) range. + */ +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); +} + +/* PCM substream operators map. */ +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, +};
On Wed, Jan 20, 2021 at 01:36:33AM +0100, Anton Yakovlev wrote:
Introduce the operators required for the operation of substreams.
Signed-off-by: Anton Yakovlev anton.yakovlev@opensynergy.com
sound/virtio/Makefile | 3 +- sound/virtio/virtio_pcm.c | 5 +- sound/virtio/virtio_pcm.h | 2 + sound/virtio/virtio_pcm_ops.c | 509 ++++++++++++++++++++++++++++++++++ 4 files changed, 517 insertions(+), 2 deletions(-) create mode 100644 sound/virtio/virtio_pcm_ops.c
diff --git a/sound/virtio/Makefile b/sound/virtio/Makefile index 626af3cc3ed7..34493226793f 100644 --- a/sound/virtio/Makefile +++ b/sound/virtio/Makefile @@ -6,5 +6,6 @@ virtio_snd-objs := \ virtio_card.o \ virtio_ctl_msg.o \ virtio_pcm.o \
- virtio_pcm_msg.o
- virtio_pcm_msg.o \
- virtio_pcm_ops.o
diff --git a/sound/virtio/virtio_pcm.c b/sound/virtio/virtio_pcm.c index 1ab50dcc88c8..6a1ca6b2c3ca 100644 --- a/sound/virtio/virtio_pcm.c +++ b/sound/virtio/virtio_pcm.c @@ -121,7 +121,8 @@ static int virtsnd_pcm_build_hw(struct virtio_pcm_substream *substream, SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BATCH | SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_INTERLEAVED;
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE;
if (!info->channels_min || info->channels_min > info->channels_max) { dev_err(&vdev->dev,
@@ -503,6 +504,8 @@ int virtsnd_pcm_build_devs(struct virtio_snd *snd) if (rc) return rc; }
snd_pcm_set_ops(pcm->pcm, i, &virtsnd_pcm_ops);
}
return 0;
diff --git a/sound/virtio/virtio_pcm.h b/sound/virtio/virtio_pcm.h index d011b7e1d18d..fe467bc05d8b 100644 --- a/sound/virtio/virtio_pcm.h +++ b/sound/virtio/virtio_pcm.h @@ -90,6 +90,8 @@ struct virtio_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); diff --git a/sound/virtio/virtio_pcm_ops.c b/sound/virtio/virtio_pcm_ops.c new file mode 100644 index 000000000000..8d26c1144ad6 --- /dev/null +++ b/sound/virtio/virtio_pcm_ops.c @@ -0,0 +1,509 @@ +// 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"
+/*
- Our main concern here is maintaining the correct state of the underlying I/O
- virtqueues. Thus, operators are implemented to support all of the following
- possible control paths (excluding all trivial ones):
+---------+
| open() |<------------------+
+----+----+ |
v |
+------+------+ |
+------------->| hw_params() |<-------------+ |
| +-------------+ | |
| v | |
| +-----------+ | |
| | prepare() |<-----------+ | |
| +-----------+ | | |
| v | | |
| +-------------------------+ | | |
- +-----------+ | trigger(START/ | | | |
- | restore() | | PAUSE_RELEASE/ |<-+ | | |
- +-----------+ | RESUME) | | | | |
^ +-------------------------+ | | | |
| v | | | |
| +-----------+ | | | |
| | pointer() | | | | |
| +-----------+ | | | |
| v | | | |
| +---------------------+ | | | |
- +-----------+ | trigger(STOP/ |----+ | | |
- | freeze() |<---| PAUSE_PUSH/ |-------+ | |
- +-----------+ | SUSPEND) | | |
+---------------------+ | |
v | |
+-----------+ | |
| hw_free() |---------------+ |
+-----------+ |
v |
+---------+ |
| close() |-------------------+
+---------+
- */
+/* Map for converting ALSA format to VirtIO format. */ +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 },
- { SNDRV_PCM_FORMAT_S20_LE, VIRTIO_SND_PCM_FMT_S20 },
- { SNDRV_PCM_FORMAT_U20_LE, VIRTIO_SND_PCM_FMT_U20 },
- { 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 }
+};
+/* Map for converting ALSA frame rate to VirtIO frame rate. */ +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 }
+};
+/**
- virtsnd_pcm_release() - Release the PCM substream on the device side.
- @substream: VirtIO substream.
- The function waits for all pending I/O messages to complete.
- Context: Any context that permits to sleep.
- Return: 0 on success, -errno on failure.
- */
+static inline bool virtsnd_pcm_released(struct virtio_pcm_substream *substream) +{
- return atomic_read(&substream->msg_count) == 0;
All this use of atomic_read/atomic_set all over the place makes me pause. Could you add documentation explaining the use rules for these atomic fields?
+}
+static int virtsnd_pcm_release(struct virtio_pcm_substream *substream) +{
- struct virtio_snd *snd = substream->snd;
- struct virtio_snd_msg *msg;
- unsigned int js = msecs_to_jiffies(msg_timeout_ms);
- 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)
return rc;
- return wait_event_interruptible_timeout(substream->msg_empty,
virtsnd_pcm_released(substream),
js);
+}
+/**
- virtsnd_pcm_open() - Open the PCM substream.
- @substream: Kernel ALSA substream.
- Context: Any context.
- Return: 0 on success, -errno on failure.
- */
+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;
+}
+/**
- virtsnd_pcm_close() - Close the PCM substream.
- @substream: Kernel ALSA substream.
- Context: Any context.
- Return: 0.
- */
+static int virtsnd_pcm_close(struct snd_pcm_substream *substream) +{
- return 0;
+}
+/**
- virtsnd_pcm_hw_params() - Set the parameters of the PCM substream.
- @substream: Kernel ALSA substream.
- @hw_params: Hardware parameters (can be NULL).
- The function can be called both from the upper level (in this case,
- @hw_params is not NULL) or from the driver itself (in this case, @hw_params
- is NULL, and the parameter values are taken from the runtime structure).
- In all cases, the function:
- checks the state of the virtqueue and, if necessary, tries to fix it,
- sets the parameters on the device side,
- allocates a hardware buffer and I/O messages.
- Context: Any context that permits to sleep.
- Return: 0 on success, -errno on failure.
- */
+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;
- 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;
- /*
* 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) {
dev_err(&vdev->dev,
"SID %u: invalid I/O queue state\n",
ss->sid);
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;
+}
+/**
- virtsnd_pcm_hw_free() - Reset the parameters of the PCM substream.
- @substream: Kernel ALSA substream.
- The function does the following:
- tries to release the PCM substream on the device side,
- frees the hardware buffer.
- Context: Any context that permits to sleep.
- Return: 0 on success, -errno on failure.
- */
+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;
+}
+/**
- virtsnd_pcm_hw_params() - Prepare the PCM substream.
- @substream: Kernel ALSA substream.
- The function can be called both from the upper level or from the driver
- itself.
- In all cases, the function:
- checks the state of the virtqueue and, if necessary, tries to fix it,
- prepares the substream on the device side.
- Context: Any context that permits to sleep. May take and release the tx/rx
queue spinlock.
- Return: 0 on success, -errno on failure.
- */
+static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream) +{
- struct virtio_pcm_substream *ss = snd_pcm_substream_chip(substream);
- struct virtio_snd_queue *queue = virtsnd_pcm_queue(ss);
- struct virtio_snd_msg *msg;
- unsigned long flags;
- int rc;
- /*
* 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);
- /*
* Since I/O messages are asynchronous, they can be completed
* when the runtime structure no longer exists. Since each
* completion implies incrementing the hw_ptr, we cache all the
* current values needed to compute the new hw_ptr value.
*/
- ss->frame_bytes = substream->runtime->frame_bits >> 3;
- ss->period_size = substream->runtime->period_size;
- ss->buffer_size = substream->runtime->buffer_size;
- 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);
+}
+/**
- virtsnd_pcm_trigger() - Process command for the PCM substream.
- @substream: Kernel ALSA substream.
- @command: Substream command (SNDRV_PCM_TRIGGER_XXX).
- Depending on the command, the function does the following:
- enables/disables data transmission,
- starts/stops the substream on the device side.
- Context: Atomic context. May take and release the tx/rx queue spinlock.
- Return: 0 on success, -errno on failure.
- */
+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: {
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: {
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;
- }
- }
+}
+/**
- virtsnd_pcm_pointer() - Get the current hardware position for the PCM
substream.
- @substream: Kernel ALSA substream.
- Context: Atomic context.
- Return: Hardware position in frames inside [0 ... buffer_size) range.
- */
+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);
+}
+/* PCM substream operators map. */ +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,
+};
2.30.0
On 20.01.2021 09:36, Michael S. Tsirkin wrote:
CAUTION: This email originated from outside of the organization. Do not click links or open attachments unless you recognize the sender and know the content is safe.
On Wed, Jan 20, 2021 at 01:36:33AM +0100, Anton Yakovlev wrote:
Introduce the operators required for the operation of substreams.
Signed-off-by: Anton Yakovlev anton.yakovlev@opensynergy.com
sound/virtio/Makefile | 3 +- sound/virtio/virtio_pcm.c | 5 +- sound/virtio/virtio_pcm.h | 2 + sound/virtio/virtio_pcm_ops.c | 509 ++++++++++++++++++++++++++++++++++ 4 files changed, 517 insertions(+), 2 deletions(-) create mode 100644 sound/virtio/virtio_pcm_ops.c
diff --git a/sound/virtio/Makefile b/sound/virtio/Makefile index 626af3cc3ed7..34493226793f 100644 --- a/sound/virtio/Makefile +++ b/sound/virtio/Makefile @@ -6,5 +6,6 @@ virtio_snd-objs := \ virtio_card.o \ virtio_ctl_msg.o \ virtio_pcm.o \
virtio_pcm_msg.o
virtio_pcm_msg.o \
virtio_pcm_ops.o
diff --git a/sound/virtio/virtio_pcm.c b/sound/virtio/virtio_pcm.c index 1ab50dcc88c8..6a1ca6b2c3ca 100644 --- a/sound/virtio/virtio_pcm.c +++ b/sound/virtio/virtio_pcm.c @@ -121,7 +121,8 @@ static int virtsnd_pcm_build_hw(struct virtio_pcm_substream *substream, SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BATCH | SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_INTERLEAVED;
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE; if (!info->channels_min || info->channels_min > info->channels_max) { dev_err(&vdev->dev,
@@ -503,6 +504,8 @@ int virtsnd_pcm_build_devs(struct virtio_snd *snd) if (rc) return rc; }
snd_pcm_set_ops(pcm->pcm, i, &virtsnd_pcm_ops); } return 0;
diff --git a/sound/virtio/virtio_pcm.h b/sound/virtio/virtio_pcm.h index d011b7e1d18d..fe467bc05d8b 100644 --- a/sound/virtio/virtio_pcm.h +++ b/sound/virtio/virtio_pcm.h @@ -90,6 +90,8 @@ struct virtio_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);
diff --git a/sound/virtio/virtio_pcm_ops.c b/sound/virtio/virtio_pcm_ops.c new file mode 100644 index 000000000000..8d26c1144ad6 --- /dev/null +++ b/sound/virtio/virtio_pcm_ops.c @@ -0,0 +1,509 @@ +// 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"
+/*
- Our main concern here is maintaining the correct state of the underlying I/O
- virtqueues. Thus, operators are implemented to support all of the following
- possible control paths (excluding all trivial ones):
+---------+
| open() |<------------------+
+----+----+ |
v |
+------+------+ |
+------------->| hw_params() |<-------------+ |
| +-------------+ | |
| v | |
| +-----------+ | |
| | prepare() |<-----------+ | |
| +-----------+ | | |
| v | | |
| +-------------------------+ | | |
- +-----------+ | trigger(START/ | | | |
- | restore() | | PAUSE_RELEASE/ |<-+ | | |
- +-----------+ | RESUME) | | | | |
^ +-------------------------+ | | | |
| v | | | |
| +-----------+ | | | |
| | pointer() | | | | |
| +-----------+ | | | |
| v | | | |
| +---------------------+ | | | |
- +-----------+ | trigger(STOP/ |----+ | | |
- | freeze() |<---| PAUSE_PUSH/ |-------+ | |
- +-----------+ | SUSPEND) | | |
+---------------------+ | |
v | |
+-----------+ | |
| hw_free() |---------------+ |
+-----------+ |
v |
+---------+ |
| close() |-------------------+
+---------+
- */
+/* Map for converting ALSA format to VirtIO format. */ +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 },
{ SNDRV_PCM_FORMAT_S20_LE, VIRTIO_SND_PCM_FMT_S20 },
{ SNDRV_PCM_FORMAT_U20_LE, VIRTIO_SND_PCM_FMT_U20 },
{ 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 }
+};
+/* Map for converting ALSA frame rate to VirtIO frame rate. */ +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 }
+};
+/**
- virtsnd_pcm_release() - Release the PCM substream on the device side.
- @substream: VirtIO substream.
- The function waits for all pending I/O messages to complete.
- Context: Any context that permits to sleep.
- Return: 0 on success, -errno on failure.
- */
+static inline bool virtsnd_pcm_released(struct virtio_pcm_substream *substream) +{
return atomic_read(&substream->msg_count) == 0;
All this use of atomic_read/atomic_set all over the place makes me pause. Could you add documentation explaining the use rules for these atomic fields?
I added some additional comments about that in v2:
[5/9] virtio_pcm_msg.c:virtsnd_pcm_msg_complete() [6/9] virtio_pcm_ops.c:virtsnd_pcm_released()
+}
+static int virtsnd_pcm_release(struct virtio_pcm_substream *substream) +{
struct virtio_snd *snd = substream->snd;
struct virtio_snd_msg *msg;
unsigned int js = msecs_to_jiffies(msg_timeout_ms);
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)
return rc;
return wait_event_interruptible_timeout(substream->msg_empty,
virtsnd_pcm_released(substream),
js);
+}
+/**
- virtsnd_pcm_open() - Open the PCM substream.
- @substream: Kernel ALSA substream.
- Context: Any context.
- Return: 0 on success, -errno on failure.
- */
+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;
+}
+/**
- virtsnd_pcm_close() - Close the PCM substream.
- @substream: Kernel ALSA substream.
- Context: Any context.
- Return: 0.
- */
+static int virtsnd_pcm_close(struct snd_pcm_substream *substream) +{
return 0;
+}
+/**
- virtsnd_pcm_hw_params() - Set the parameters of the PCM substream.
- @substream: Kernel ALSA substream.
- @hw_params: Hardware parameters (can be NULL).
- The function can be called both from the upper level (in this case,
- @hw_params is not NULL) or from the driver itself (in this case, @hw_params
- is NULL, and the parameter values are taken from the runtime structure).
- In all cases, the function:
- checks the state of the virtqueue and, if necessary, tries to fix it,
- sets the parameters on the device side,
- allocates a hardware buffer and I/O messages.
- Context: Any context that permits to sleep.
- Return: 0 on success, -errno on failure.
- */
+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;
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;
/*
* 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) {
dev_err(&vdev->dev,
"SID %u: invalid I/O queue state\n",
ss->sid);
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;
+}
+/**
- virtsnd_pcm_hw_free() - Reset the parameters of the PCM substream.
- @substream: Kernel ALSA substream.
- The function does the following:
- tries to release the PCM substream on the device side,
- frees the hardware buffer.
- Context: Any context that permits to sleep.
- Return: 0 on success, -errno on failure.
- */
+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;
+}
+/**
- virtsnd_pcm_hw_params() - Prepare the PCM substream.
- @substream: Kernel ALSA substream.
- The function can be called both from the upper level or from the driver
- itself.
- In all cases, the function:
- checks the state of the virtqueue and, if necessary, tries to fix it,
- prepares the substream on the device side.
- Context: Any context that permits to sleep. May take and release the tx/rx
queue spinlock.
- Return: 0 on success, -errno on failure.
- */
+static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream) +{
struct virtio_pcm_substream *ss = snd_pcm_substream_chip(substream);
struct virtio_snd_queue *queue = virtsnd_pcm_queue(ss);
struct virtio_snd_msg *msg;
unsigned long flags;
int rc;
/*
* 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);
/*
* Since I/O messages are asynchronous, they can be completed
* when the runtime structure no longer exists. Since each
* completion implies incrementing the hw_ptr, we cache all the
* current values needed to compute the new hw_ptr value.
*/
ss->frame_bytes = substream->runtime->frame_bits >> 3;
ss->period_size = substream->runtime->period_size;
ss->buffer_size = substream->runtime->buffer_size;
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);
+}
+/**
- virtsnd_pcm_trigger() - Process command for the PCM substream.
- @substream: Kernel ALSA substream.
- @command: Substream command (SNDRV_PCM_TRIGGER_XXX).
- Depending on the command, the function does the following:
- enables/disables data transmission,
- starts/stops the substream on the device side.
- Context: Atomic context. May take and release the tx/rx queue spinlock.
- Return: 0 on success, -errno on failure.
- */
+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: {
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: {
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;
}
}
+}
+/**
- virtsnd_pcm_pointer() - Get the current hardware position for the PCM
substream.
- @substream: Kernel ALSA substream.
- Context: Atomic context.
- Return: Hardware position in frames inside [0 ... buffer_size) range.
- */
+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);
+}
+/* PCM substream operators map. */ +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,
+};
2.30.0
Enumerate all available jacks and create ALSA controls.
At the moment jacks have a simple implementation and can only be used to receive notifications about a plugged in/out device.
Signed-off-by: Anton Yakovlev anton.yakovlev@opensynergy.com --- sound/virtio/Makefile | 2 + sound/virtio/virtio_card.c | 33 +++++ sound/virtio/virtio_card.h | 20 +++ sound/virtio/virtio_chmap.c | 237 +++++++++++++++++++++++++++++++++ sound/virtio/virtio_jack.c | 255 ++++++++++++++++++++++++++++++++++++ sound/virtio/virtio_pcm.h | 4 + 6 files changed, 551 insertions(+) create mode 100644 sound/virtio/virtio_chmap.c create mode 100644 sound/virtio/virtio_jack.c
diff --git a/sound/virtio/Makefile b/sound/virtio/Makefile index 34493226793f..2742bddb8874 100644 --- a/sound/virtio/Makefile +++ b/sound/virtio/Makefile @@ -4,7 +4,9 @@ obj-$(CONFIG_SND_VIRTIO) += virtio_snd.o
virtio_snd-objs := \ virtio_card.o \ + virtio_chmap.o \ virtio_ctl_msg.o \ + virtio_jack.o \ virtio_pcm.o \ virtio_pcm_msg.o \ virtio_pcm_ops.o diff --git a/sound/virtio/virtio_card.c b/sound/virtio/virtio_card.c index dc703fc662f5..02814c3932ab 100644 --- a/sound/virtio/virtio_card.c +++ b/sound/virtio/virtio_card.c @@ -94,6 +94,11 @@ static void virtsnd_event_notify_cb(struct virtqueue *vqueue) break;
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); @@ -322,16 +327,36 @@ static int virtsnd_build_devs(struct virtio_snd *snd) strscpy(snd->card->longname, "VirtIO Sound Card", sizeof(snd->card->longname));
+ 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 (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; }
+ if (snd->nchmaps) { + rc = virtsnd_chmap_build_devs(snd); + if (rc) + return rc; + } + return snd_card_register(snd->card); }
@@ -439,14 +464,22 @@ static void virtsnd_remove(struct virtio_device *vdev)
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); + devm_kfree(&vdev->dev, snd);
vdev->priv = NULL; diff --git a/sound/virtio/virtio_card.h b/sound/virtio/virtio_card.h index b11c09984882..09c6e9ab80ca 100644 --- a/sound/virtio/virtio_card.h +++ b/sound/virtio/virtio_card.h @@ -26,6 +26,7 @@ #include "virtio_ctl_msg.h" #include "virtio_pcm.h"
+struct virtio_jack; struct virtio_pcm_substream;
/** @@ -47,8 +48,12 @@ struct virtio_snd_queue { * @ctl_msgs: Pending control request list. * @event_msgs: Device events. * @pcm_list: VirtIO PCM device list. + * @jacks: VirtIO jacks. + * @njacks: Number of jacks. * @substreams: VirtIO PCM substreams. * @nsubstreams: Number of PCM substreams. + * @chmaps: VirtIO channel maps. + * @nchmaps: Number of channel maps. */ struct virtio_snd { struct virtio_device *vdev; @@ -58,8 +63,12 @@ struct virtio_snd { 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; };
/* Message completion timeout in milliseconds (module parameter). */ @@ -98,4 +107,15 @@ virtsnd_pcm_queue(struct virtio_pcm_substream *substream) return virtsnd_rx_queue(substream->snd); }
+int virtsnd_jack_parse_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); + +int virtsnd_chmap_parse_cfg(struct virtio_snd *snd); + +int virtsnd_chmap_build_devs(struct virtio_snd *snd); + #endif /* VIRTIO_SND_CARD_H */ diff --git a/sound/virtio/virtio_chmap.c b/sound/virtio/virtio_chmap.c new file mode 100644 index 000000000000..8a2ddc4dcffb --- /dev/null +++ b/sound/virtio/virtio_chmap.c @@ -0,0 +1,237 @@ +// 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" + +/* VirtIO->ALSA channel position map */ +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 +}; + +/** + * virtsnd_chmap_parse_cfg() - Parse the channel map configuration. + * @snd: VirtIO sound device. + * + * This function is called during initial device initialization. + * + * Context: Any context that permits to sleep. + * Return: 0 on success, -errno on failure. + */ +int virtsnd_chmap_parse_cfg(struct virtio_snd *snd) +{ + struct virtio_device *vdev = snd->vdev; + unsigned int i; + int rc; + + 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 device/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); + struct virtio_pcm *pcm; + struct virtio_pcm_stream *stream; + + 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)\n", i, + info->direction); + return -EINVAL; + } + } + + stream->nchmaps++; + } + + return 0; +} + +/** + * virtsnd_chmap_add_ctls() - Create an ALSA control for channel maps. + * @pcm: ALSA PCM device. + * @direction: PCM stream direction (SNDRV_PCM_STREAM_XXX). + * @stream: VirtIO PCM stream. + * + * Context: Any context. + * Return: 0 on success, -errno on failure. + */ +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); +} + +/** + * virtsnd_chmap_build_devs() - Build ALSA controls for channel maps. + * @snd: VirtIO sound device. + * + * Context: Any context. + * Return: 0 on success, -errno on failure. + */ +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 device/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 device/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]; + } + } + + /* Create an ALSA control per each PCM device/stream. */ + 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/sound/virtio/virtio_jack.c b/sound/virtio/virtio_jack.c new file mode 100644 index 000000000000..83593c59f6bf --- /dev/null +++ b/sound/virtio/virtio_jack.c @@ -0,0 +1,255 @@ +// 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" + +/** + * DOC: Implementation Status + * + * At the moment jacks have a simple implementation and can only be used to + * receive notifications about a plugged in/out device. + * + * VIRTIO_SND_R_JACK_REMAP + * is not supported + */ + +/** + * struct virtio_jack - VirtIO jack. + * @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; +}; + +/** + * virtsnd_jack_get_label() - Get the name string for the jack. + * @jack: VirtIO jack. + * + * Returns the jack name based on the default pin configuration value (see HDA + * specification). + * + * Context: Any context. + * Return: Name string. + */ +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"; + } +} + +/** + * virtsnd_jack_get_type() - Get the type for the jack. + * @jack: VirtIO jack. + * + * Returns the jack type based on the default pin configuration value (see HDA + * specification). + * + * Context: Any context. + * Return: SND_JACK_XXX value. + */ +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; + } +} + +/** + * virtsnd_jack_parse_cfg() - Parse the jack configuration. + * @snd: VirtIO sound device. + * + * This function is called during initial device initialization. + * + * Context: Any context that permits to sleep. + * Return: 0 on success, -errno on failure. + */ +int virtsnd_jack_parse_cfg(struct virtio_snd *snd) +{ + struct virtio_device *vdev = snd->vdev; + struct virtio_snd_jack_info *info; + unsigned int i; + int rc; + + 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; + + rc = virtsnd_ctl_query_info(snd, VIRTIO_SND_R_JACK_INFO, 0, snd->njacks, + sizeof(*info), info); + if (rc) + return rc; + + 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; +} + +/** + * virtsnd_jack_build_devs() - Build ALSA controls for jacks. + * @snd: VirtIO sound device. + * + * Context: Any context that permits to sleep. + * Return: 0 on success, -errno on failure. + */ +int virtsnd_jack_build_devs(struct virtio_snd *snd) +{ + unsigned int i; + int rc; + + for (i = 0; i < snd->njacks; ++i) { + struct virtio_jack *jack = &snd->jacks[i]; + + jack->type = virtsnd_jack_get_type(jack); + + rc = snd_jack_new(snd->card, virtsnd_jack_get_label(jack), + jack->type, &jack->jack, true, true); + if (rc) + return rc; + + if (!jack->jack) + continue; + + jack->jack->private_data = jack; + + snd_jack_report(jack->jack, + jack->connected ? jack->type : 0); + } + + return 0; +} + +/** + * virtsnd_jack_event() - Handle the jack event notification. + * @snd: VirtIO sound device. + * @event: VirtIO sound event. + * + * Context: Interrupt context. + */ +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/sound/virtio/virtio_pcm.h b/sound/virtio/virtio_pcm.h index fe467bc05d8b..a326b921b947 100644 --- a/sound/virtio/virtio_pcm.h +++ b/sound/virtio/virtio_pcm.h @@ -70,10 +70,14 @@ struct virtio_pcm_substream { * struct virtio_pcm_stream - VirtIO PCM stream. * @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; };
/**
All running PCM substreams are stopped on device suspend and restarted on device resume.
Signed-off-by: Anton Yakovlev anton.yakovlev@opensynergy.com --- sound/virtio/virtio_card.c | 54 ++++++++++++++++++++ sound/virtio/virtio_pcm.c | 40 +++++++++++++++ sound/virtio/virtio_pcm.h | 6 +++ sound/virtio/virtio_pcm_ops.c | 93 ++++++++++++++++++++--------------- 4 files changed, 154 insertions(+), 39 deletions(-)
diff --git a/sound/virtio/virtio_card.c b/sound/virtio/virtio_card.c index 02814c3932ab..2bde324f9b95 100644 --- a/sound/virtio/virtio_card.c +++ b/sound/virtio/virtio_card.c @@ -507,6 +507,56 @@ static void virtsnd_config_changed(struct virtio_device *vdev) "sound device configuration was changed\n"); }
+#ifdef CONFIG_PM_SLEEP +/** + * virtsnd_freeze() - Suspend device. + * @vdev: VirtIO parent device. + * + * Context: Any context that permits to sleep. + * Return: 0 on success, -errno on failure. + */ +static int virtsnd_freeze(struct virtio_device *vdev) +{ + struct virtio_snd *snd = vdev->priv; + + virtsnd_disable_vqs(snd); + + vdev->config->reset(vdev); + vdev->config->del_vqs(vdev); + + return 0; +} + +/** + * virtsnd_restore() - Resume device. + * @vdev: VirtIO parent device. + * + * Context: Any context that permits to sleep. + * Return: 0 on success, -errno on failure. + */ +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); + + virtsnd_enable_vqs(snd); + + if (snd->nsubstreams) { + rc = virtsnd_pcm_restore(snd); + if (rc) + return rc; + } + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + static const struct virtio_device_id id_table[] = { { VIRTIO_ID_SOUND, VIRTIO_DEV_ANY_ID }, { 0 }, @@ -520,6 +570,10 @@ static struct virtio_driver virtsnd_driver = { .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) diff --git a/sound/virtio/virtio_pcm.c b/sound/virtio/virtio_pcm.c index 6a1ca6b2c3ca..68d9c6dee13a 100644 --- a/sound/virtio/virtio_pcm.c +++ b/sound/virtio/virtio_pcm.c @@ -122,6 +122,7 @@ static int virtsnd_pcm_build_hw(struct virtio_pcm_substream *substream, 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) { @@ -511,6 +512,45 @@ int virtsnd_pcm_build_devs(struct virtio_snd *snd) return 0; }
+#ifdef CONFIG_PM_SLEEP +/** + * virtsnd_pcm_restore() - Resume PCM substreams. + * @snd: VirtIO sound device. + * + * Context: Any context that permits to sleep. + * Return: 0 on success, -errno on failure. + */ +int virtsnd_pcm_restore(struct virtio_snd *snd) +{ + unsigned int i; + + for (i = 0; i < snd->nsubstreams; ++i) { + struct virtio_pcm_substream *substream = &snd->substreams[i]; + struct snd_pcm_substream *ksubstream = substream->substream; + int rc; + + if (!substream->suspended) + continue; + + /* + * We restart the substream by executing the standard command + * sequence. The START command will be sent from a subsequent + * call to the trigger() callback function after the device has + * been resumed. + */ + rc = ksubstream->ops->hw_params(ksubstream, NULL); + if (rc) + return rc; + + rc = ksubstream->ops->prepare(ksubstream); + if (rc) + return rc; + } + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + /** * virtsnd_pcm_event() - Handle the PCM device event notification. * @snd: VirtIO sound device. diff --git a/sound/virtio/virtio_pcm.h b/sound/virtio/virtio_pcm.h index a326b921b947..23d0fdd57225 100644 --- a/sound/virtio/virtio_pcm.h +++ b/sound/virtio/virtio_pcm.h @@ -41,6 +41,7 @@ struct virtio_pcm_msg; * @hw_ptr: Substream hardware pointer value in frames [0 ... buffer_size). * @xfer_enabled: Data transfer state (0 - off, 1 - on). * @xfer_xrun: Data underflow/overflow state (0 - no xrun, 1 - xrun). + * @suspended: Kernel ALSA substream is suspended. * @msgs: I/O messages. * @msg_last_enqueued: Index of the last I/O message added to the virtqueue. * @msg_count: Number of pending I/O messages in the virtqueue. @@ -60,6 +61,7 @@ struct virtio_pcm_substream { atomic_t hw_ptr; atomic_t xfer_enabled; atomic_t xfer_xrun; + bool suspended; struct virtio_pcm_msg *msgs; int msg_last_enqueued; atomic_t msg_count; @@ -102,6 +104,10 @@ int virtsnd_pcm_parse_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); diff --git a/sound/virtio/virtio_pcm_ops.c b/sound/virtio/virtio_pcm_ops.c index 8d26c1144ad6..1723eda4b052 100644 --- a/sound/virtio/virtio_pcm_ops.c +++ b/sound/virtio/virtio_pcm_ops.c @@ -183,6 +183,8 @@ static int virtsnd_pcm_open(struct snd_pcm_substream *substream) if (!ss) return -EBADFD;
+ ss->suspended = false; + substream->runtime->hw = ss->hw; substream->private_data = ss;
@@ -237,18 +239,20 @@ static int virtsnd_pcm_hw_params(struct snd_pcm_substream *substream, int vrate = -1; int rc;
- /* - * 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) { - dev_err(&vdev->dev, - "SID %u: invalid I/O queue state\n", - ss->sid); - return rc; + if (!ss->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) { + dev_err(&vdev->dev, + "SID %u: invalid I/O queue state\n", + ss->sid); + return rc; + } } }
@@ -379,37 +383,41 @@ static int virtsnd_pcm_hw_free(struct snd_pcm_substream *substream) static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream) { struct virtio_pcm_substream *ss = snd_pcm_substream_chip(substream); - struct virtio_snd_queue *queue = virtsnd_pcm_queue(ss); struct virtio_snd_msg *msg; unsigned long flags; int rc;
- /* - * 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); + if (!ss->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; + }
- /* - * Since I/O messages are asynchronous, they can be completed - * when the runtime structure no longer exists. Since each - * completion implies incrementing the hw_ptr, we cache all the - * current values needed to compute the new hw_ptr value. - */ - ss->frame_bytes = substream->runtime->frame_bits >> 3; - ss->period_size = substream->runtime->period_size; - ss->buffer_size = substream->runtime->buffer_size; + spin_lock_irqsave(&queue->lock, flags); + ss->msg_last_enqueued = -1; + spin_unlock_irqrestore(&queue->lock, flags); + + /* + * Since I/O messages are asynchronous, they can be completed + * when the runtime structure no longer exists. Since each + * completion implies incrementing the hw_ptr, we cache all the + * current values needed to compute the new hw_ptr value. + */ + ss->frame_bytes = substream->runtime->frame_bits >> 3; + ss->period_size = substream->runtime->period_size; + ss->buffer_size = substream->runtime->buffer_size; + + atomic_set(&ss->hw_ptr, 0); + }
- atomic_set(&ss->hw_ptr, 0); atomic_set(&ss->xfer_xrun, 0); atomic_set(&ss->msg_count, 0);
@@ -442,9 +450,12 @@ static int virtsnd_pcm_trigger(struct snd_pcm_substream *substream, int command)
switch (command) { case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: { + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: { int rc;
+ ss->suspended = false; + spin_lock(&queue->lock); rc = virtsnd_pcm_msg_send(ss); spin_unlock(&queue->lock); @@ -461,9 +472,13 @@ static int virtsnd_pcm_trigger(struct snd_pcm_substream *substream, int command) return virtsnd_ctl_msg_send(snd, msg); } case SNDRV_PCM_TRIGGER_STOP: - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: { + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: { atomic_set(&ss->xfer_enabled, 0);
+ if (command == SNDRV_PCM_TRIGGER_SUSPEND) + ss->suspended = true; + msg = virtsnd_pcm_ctl_msg_alloc(ss, VIRTIO_SND_R_PCM_STOP, GFP_ATOMIC); if (IS_ERR(msg))
Hi Anton,
On Wed, 2021-01-20 at 01:36 +0100, Anton Yakovlev wrote:
This series implements a driver part of the virtio sound device specification v8 [1].
The driver supports PCM playback and capture substreams, jack and channel map controls. A message-based transport is used to write/read PCM frames to/from a device.
The series is based (and was actually tested) on Linus's master branch [2], on top of
commit 1e2a199f6ccd ("Merge tag 'spi-fix-v5.11-rc4' of ...")
As a device part was used OpenSynergy proprietary implementation.
Any comments are very welcome.
This just looks like the guest front end here, do you have a follow up series for the host backend ?
Thanks
Liam --------------------------------------------------------------------- Intel Corporation (UK) Limited Registered No. 1134945 (England) Registered Office: Pipers Way, Swindon SN3 1RJ VAT No: 860 2173 47
This e-mail and any attachments may contain confidential material for the sole use of the intended recipient(s). Any review or distribution by others is strictly prohibited. If you are not the intended recipient, please contact the sender and delete all copies.
Hi, Liam!
On 20.01.2021 11:10, Girdwood, Liam R wrote:
CAUTION: This email originated from outside of the organization. Do not click links or open attachments unless you recognize the sender and know the content is safe.
Hi Anton,
On Wed, 2021-01-20 at 01:36 +0100, Anton Yakovlev wrote:
This series implements a driver part of the virtio sound device specification v8 [1].
The driver supports PCM playback and capture substreams, jack and channel map controls. A message-based transport is used to write/read PCM frames to/from a device.
The series is based (and was actually tested) on Linus's master branch [2], on top of
commit 1e2a199f6ccd ("Merge tag 'spi-fix-v5.11-rc4' of ...")
As a device part was used OpenSynergy proprietary implementation.
Any comments are very welcome.
This just looks like the guest front end here, do you have a follow up series for the host backend ?
As I mentioned in the cover message, as a device part was used our own proprietary implementation. And there are no plans to upstream that part.
Thanks
Liam
Intel Corporation (UK) Limited Registered No. 1134945 (England) Registered Office: Pipers Way, Swindon SN3 1RJ VAT No: 860 2173 47
This e-mail and any attachments may contain confidential material for the sole use of the intended recipient(s). Any review or distribution by others is strictly prohibited. If you are not the intended recipient, please contact the sender and delete all copies.
participants (3)
-
Anton Yakovlev
-
Girdwood, Liam R
-
Michael S. Tsirkin