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. As a device part was used OpenSynergy proprietary implementation. Any comments are very welcome. v3 changes: - Fixed license headers for all files. - Many coding style and kernel doc fixes. - Replaced devm_kmalloc/devm_kfree with kmalloc/kfree wherever appropriate. - Made the names of the card and PCM devices more informative. - To process the DEVICE_NEEDS_RESET status, simply call device_reprobe(). - For control messages replaced atomic_t by refcount_t for the reference counter and simplified the general logic. - Use vmalloc'ed managed buffer for PCM substreams. - Replaced all atomic fields in the virtio substream structure with non-atomic + spinlock. - Use the non-atomic PCM ops. - Use ops->sync_stop() to release the substream on the device side. - Rebased and tested on top of v5.11-rc6. v2 changes: - For some reason, in the previous patch series, several patches were squashed. Fixed this issue to make the review easier. - Added mst at redhat.com to the MAINTAINERS. - When creating virtqueues, now only the event virtqueue is disabled. It's enabled only after successful initialization of the device. - Added additional comments to the reset worker function: [2/9] virtio_card.c:virtsnd_reset_fn() - Added check that VIRTIO_F_VERSION_1 feature bit is set. - Added additional comments to the device removing function: [2/9] virtio_card.c:virtsnd_remove() - Added additional comments to the tx/rx interrupt handler: [5/9] virtio_pcm_msg.c:virtsnd_pcm_msg_complete() - Added additional comments to substream release wait function. [6/9] virtio_pcm_ops.c:virtsnd_pcm_released() [1] https://lists.oasis-open.org/archives/virtio-dev/202003/msg00185.html Anton Yakovlev (9): uapi: virtio_ids: add a sound device type ID from OASIS spec ALSA: virtio: add virtio sound driver ALSA: virtio: handling control messages ALSA: virtio: build PCM devices and substream hardware descriptors 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 PCM channel map support ALSA: virtio: introduce device suspend/resume support MAINTAINERS | 9 + include/uapi/linux/virtio_ids.h | 1 + include/uapi/linux/virtio_snd.h | 334 +++++++++++++++++++++ sound/Kconfig | 2 + sound/Makefile | 3 +- sound/virtio/Kconfig | 10 + sound/virtio/Makefile | 13 + sound/virtio/virtio_card.c | 462 +++++++++++++++++++++++++++++ sound/virtio/virtio_card.h | 113 ++++++++ sound/virtio/virtio_chmap.c | 219 ++++++++++++++ sound/virtio/virtio_ctl_msg.c | 311 ++++++++++++++++++++ sound/virtio/virtio_ctl_msg.h | 78 +++++ sound/virtio/virtio_jack.c | 233 +++++++++++++++ sound/virtio/virtio_pcm.c | 499 ++++++++++++++++++++++++++++++++ sound/virtio/virtio_pcm.h | 121 ++++++++ sound/virtio/virtio_pcm_msg.c | 393 +++++++++++++++++++++++++ sound/virtio/virtio_pcm_ops.c | 493 +++++++++++++++++++++++++++++++ 17 files changed, 3293 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 -- 2.30.0
Anton Yakovlev
2021-Feb-09 12:40 UTC
[PATCH v3 1/9] uapi: virtio_ids: add a sound device type ID from OASIS spec
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 at 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 */ -- 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 at opensynergy.com> --- MAINTAINERS | 9 + include/uapi/linux/virtio_snd.h | 334 +++++++++++++++++++++++++++++++ sound/Kconfig | 2 + sound/Makefile | 3 +- sound/virtio/Kconfig | 10 + sound/virtio/Makefile | 7 + sound/virtio/virtio_card.c | 340 ++++++++++++++++++++++++++++++++ sound/virtio/virtio_card.h | 67 +++++++ 8 files changed, 771 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 diff --git a/MAINTAINERS b/MAINTAINERS index 2a0737dfca89..51f20656a5ad 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18945,6 +18945,15 @@ 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 at opensynergy.com> +M: "Michael S. Tsirkin" <mst at redhat.com> +L: virtualization at lists.linux-foundation.org +L: alsa-devel at 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 at redhat.com> M: Arnd Bergmann <arnd at arndb.de> diff --git a/include/uapi/linux/virtio_snd.h b/include/uapi/linux/virtio_snd.h new file mode 100644 index 000000000000..dfe49547a7b0 --- /dev/null +++ b/include/uapi/linux/virtio_snd.h @@ -0,0 +1,334 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (C) 2021 OpenSynergy GmbH + */ +#ifndef VIRTIO_SND_IF_H +#define VIRTIO_SND_IF_H + +#include <linux/virtio_types.h> + +/******************************************************************************* + * CONFIGURATION SPACE + */ +struct virtio_snd_config { + /* # of available physical jacks */ + __le32 jacks; + /* # of available PCM streams */ + __le32 streams; + /* # of available channel maps */ + __le32 chmaps; +}; + +enum { + /* device virtqueue indexes */ + VIRTIO_SND_VQ_CONTROL = 0, + VIRTIO_SND_VQ_EVENT, + VIRTIO_SND_VQ_TX, + VIRTIO_SND_VQ_RX, + /* # of device virtqueues */ + VIRTIO_SND_VQ_MAX +}; + +/******************************************************************************* + * COMMON DEFINITIONS + */ + +/* supported dataflow directions */ +enum { + VIRTIO_SND_D_OUTPUT = 0, + VIRTIO_SND_D_INPUT +}; + +enum { + /* jack control request types */ + VIRTIO_SND_R_JACK_INFO = 1, + VIRTIO_SND_R_JACK_REMAP, + + /* PCM control request types */ + VIRTIO_SND_R_PCM_INFO = 0x0100, + VIRTIO_SND_R_PCM_SET_PARAMS, + VIRTIO_SND_R_PCM_PREPARE, + VIRTIO_SND_R_PCM_RELEASE, + VIRTIO_SND_R_PCM_START, + VIRTIO_SND_R_PCM_STOP, + + /* channel map control request types */ + VIRTIO_SND_R_CHMAP_INFO = 0x0200, + + /* jack event types */ + VIRTIO_SND_EVT_JACK_CONNECTED = 0x1000, + VIRTIO_SND_EVT_JACK_DISCONNECTED, + + /* PCM event types */ + VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED = 0x1100, + VIRTIO_SND_EVT_PCM_XRUN, + + /* common status codes */ + VIRTIO_SND_S_OK = 0x8000, + VIRTIO_SND_S_BAD_MSG, + VIRTIO_SND_S_NOT_SUPP, + VIRTIO_SND_S_IO_ERR +}; + +/* common header */ +struct virtio_snd_hdr { + __le32 code; +}; + +/* event notification */ +struct virtio_snd_event { + /* VIRTIO_SND_EVT_XXX */ + struct virtio_snd_hdr hdr; + /* optional event data */ + __le32 data; +}; + +/* common control request to query an item information */ +struct virtio_snd_query_info { + /* VIRTIO_SND_R_XXX_INFO */ + struct virtio_snd_hdr hdr; + /* item start identifier */ + __le32 start_id; + /* item count to query */ + __le32 count; + /* item information size in bytes */ + __le32 size; +}; + +/* common item information header */ +struct virtio_snd_info { + /* function group node id (High Definition Audio Specification 7.1.2) */ + __le32 hda_fn_nid; +}; + +/******************************************************************************* + * JACK CONTROL MESSAGES + */ +struct virtio_snd_jack_hdr { + /* VIRTIO_SND_R_JACK_XXX */ + struct virtio_snd_hdr hdr; + /* 0 ... virtio_snd_config::jacks - 1 */ + __le32 jack_id; +}; + +/* supported jack features */ +enum { + VIRTIO_SND_JACK_F_REMAP = 0 +}; + +struct virtio_snd_jack_info { + /* common header */ + struct virtio_snd_info hdr; + /* supported feature bit map (1 << VIRTIO_SND_JACK_F_XXX) */ + __le32 features; + /* pin configuration (High Definition Audio Specification 7.3.3.31) */ + __le32 hda_reg_defconf; + /* pin capabilities (High Definition Audio Specification 7.3.4.9) */ + __le32 hda_reg_caps; + /* current jack connection status (0: disconnected, 1: connected) */ + __u8 connected; + + __u8 padding[7]; +}; + +/* jack remapping control request */ +struct virtio_snd_jack_remap { + /* .code = VIRTIO_SND_R_JACK_REMAP */ + struct virtio_snd_jack_hdr hdr; + /* selected association number */ + __le32 association; + /* selected sequence number */ + __le32 sequence; +}; + +/******************************************************************************* + * PCM CONTROL MESSAGES + */ +struct virtio_snd_pcm_hdr { + /* VIRTIO_SND_R_PCM_XXX */ + struct virtio_snd_hdr hdr; + /* 0 ... virtio_snd_config::streams - 1 */ + __le32 stream_id; +}; + +/* supported PCM stream features */ +enum { + VIRTIO_SND_PCM_F_SHMEM_HOST = 0, + VIRTIO_SND_PCM_F_SHMEM_GUEST, + VIRTIO_SND_PCM_F_MSG_POLLING, + VIRTIO_SND_PCM_F_EVT_SHMEM_PERIODS, + VIRTIO_SND_PCM_F_EVT_XRUNS +}; + +/* supported PCM sample formats */ +enum { + /* analog formats (width / physical width) */ + VIRTIO_SND_PCM_FMT_IMA_ADPCM = 0, /* 4 / 4 bits */ + VIRTIO_SND_PCM_FMT_MU_LAW, /* 8 / 8 bits */ + VIRTIO_SND_PCM_FMT_A_LAW, /* 8 / 8 bits */ + VIRTIO_SND_PCM_FMT_S8, /* 8 / 8 bits */ + VIRTIO_SND_PCM_FMT_U8, /* 8 / 8 bits */ + VIRTIO_SND_PCM_FMT_S16, /* 16 / 16 bits */ + VIRTIO_SND_PCM_FMT_U16, /* 16 / 16 bits */ + VIRTIO_SND_PCM_FMT_S18_3, /* 18 / 24 bits */ + VIRTIO_SND_PCM_FMT_U18_3, /* 18 / 24 bits */ + VIRTIO_SND_PCM_FMT_S20_3, /* 20 / 24 bits */ + VIRTIO_SND_PCM_FMT_U20_3, /* 20 / 24 bits */ + VIRTIO_SND_PCM_FMT_S24_3, /* 24 / 24 bits */ + VIRTIO_SND_PCM_FMT_U24_3, /* 24 / 24 bits */ + VIRTIO_SND_PCM_FMT_S20, /* 20 / 32 bits */ + VIRTIO_SND_PCM_FMT_U20, /* 20 / 32 bits */ + VIRTIO_SND_PCM_FMT_S24, /* 24 / 32 bits */ + VIRTIO_SND_PCM_FMT_U24, /* 24 / 32 bits */ + VIRTIO_SND_PCM_FMT_S32, /* 32 / 32 bits */ + VIRTIO_SND_PCM_FMT_U32, /* 32 / 32 bits */ + VIRTIO_SND_PCM_FMT_FLOAT, /* 32 / 32 bits */ + VIRTIO_SND_PCM_FMT_FLOAT64, /* 64 / 64 bits */ + /* digital formats (width / physical width) */ + VIRTIO_SND_PCM_FMT_DSD_U8, /* 8 / 8 bits */ + VIRTIO_SND_PCM_FMT_DSD_U16, /* 16 / 16 bits */ + VIRTIO_SND_PCM_FMT_DSD_U32, /* 32 / 32 bits */ + VIRTIO_SND_PCM_FMT_IEC958_SUBFRAME /* 32 / 32 bits */ +}; + +/* supported PCM frame rates */ +enum { + VIRTIO_SND_PCM_RATE_5512 = 0, + VIRTIO_SND_PCM_RATE_8000, + VIRTIO_SND_PCM_RATE_11025, + VIRTIO_SND_PCM_RATE_16000, + VIRTIO_SND_PCM_RATE_22050, + VIRTIO_SND_PCM_RATE_32000, + VIRTIO_SND_PCM_RATE_44100, + VIRTIO_SND_PCM_RATE_48000, + VIRTIO_SND_PCM_RATE_64000, + VIRTIO_SND_PCM_RATE_88200, + VIRTIO_SND_PCM_RATE_96000, + VIRTIO_SND_PCM_RATE_176400, + VIRTIO_SND_PCM_RATE_192000, + VIRTIO_SND_PCM_RATE_384000 +}; + +struct virtio_snd_pcm_info { + /* common header */ + struct virtio_snd_info hdr; + /* supported feature bit map (1 << VIRTIO_SND_PCM_F_XXX) */ + __le32 features; + /* supported sample format bit map (1 << VIRTIO_SND_PCM_FMT_XXX) */ + __le64 formats; + /* supported frame rate bit map (1 << VIRTIO_SND_PCM_RATE_XXX) */ + __le64 rates; + /* dataflow direction (VIRTIO_SND_D_XXX) */ + __u8 direction; + /* minimum # of supported channels */ + __u8 channels_min; + /* maximum # of supported channels */ + __u8 channels_max; + + __u8 padding[5]; +}; + +/* set PCM stream format */ +struct virtio_snd_pcm_set_params { + /* .code = VIRTIO_SND_R_PCM_SET_PARAMS */ + struct virtio_snd_pcm_hdr hdr; + /* size of the hardware buffer */ + __le32 buffer_bytes; + /* size of the hardware period */ + __le32 period_bytes; + /* selected feature bit map (1 << VIRTIO_SND_PCM_F_XXX) */ + __le32 features; + /* selected # of channels */ + __u8 channels; + /* selected sample format (VIRTIO_SND_PCM_FMT_XXX) */ + __u8 format; + /* selected frame rate (VIRTIO_SND_PCM_RATE_XXX) */ + __u8 rate; + + __u8 padding; +}; + +/******************************************************************************* + * PCM I/O MESSAGES + */ + +/* I/O request header */ +struct virtio_snd_pcm_xfer { + /* 0 ... virtio_snd_config::streams - 1 */ + __le32 stream_id; +}; + +/* I/O request status */ +struct virtio_snd_pcm_status { + /* VIRTIO_SND_S_XXX */ + __le32 status; + /* current device latency */ + __le32 latency_bytes; +}; + +/******************************************************************************* + * CHANNEL MAP CONTROL MESSAGES + */ +struct virtio_snd_chmap_hdr { + /* VIRTIO_SND_R_CHMAP_XXX */ + struct virtio_snd_hdr hdr; + /* 0 ... virtio_snd_config::chmaps - 1 */ + __le32 chmap_id; +}; + +/* standard channel position definition */ +enum { + VIRTIO_SND_CHMAP_NONE = 0, /* undefined */ + VIRTIO_SND_CHMAP_NA, /* silent */ + VIRTIO_SND_CHMAP_MONO, /* mono stream */ + VIRTIO_SND_CHMAP_FL, /* front left */ + VIRTIO_SND_CHMAP_FR, /* front right */ + VIRTIO_SND_CHMAP_RL, /* rear left */ + VIRTIO_SND_CHMAP_RR, /* rear right */ + VIRTIO_SND_CHMAP_FC, /* front center */ + VIRTIO_SND_CHMAP_LFE, /* low frequency (LFE) */ + VIRTIO_SND_CHMAP_SL, /* side left */ + VIRTIO_SND_CHMAP_SR, /* side right */ + VIRTIO_SND_CHMAP_RC, /* rear center */ + VIRTIO_SND_CHMAP_FLC, /* front left center */ + VIRTIO_SND_CHMAP_FRC, /* front right center */ + VIRTIO_SND_CHMAP_RLC, /* rear left center */ + VIRTIO_SND_CHMAP_RRC, /* rear right center */ + VIRTIO_SND_CHMAP_FLW, /* front left wide */ + VIRTIO_SND_CHMAP_FRW, /* front right wide */ + VIRTIO_SND_CHMAP_FLH, /* front left high */ + VIRTIO_SND_CHMAP_FCH, /* front center high */ + VIRTIO_SND_CHMAP_FRH, /* front right high */ + VIRTIO_SND_CHMAP_TC, /* top center */ + VIRTIO_SND_CHMAP_TFL, /* top front left */ + VIRTIO_SND_CHMAP_TFR, /* top front right */ + VIRTIO_SND_CHMAP_TFC, /* top front center */ + VIRTIO_SND_CHMAP_TRL, /* top rear left */ + VIRTIO_SND_CHMAP_TRR, /* top rear right */ + VIRTIO_SND_CHMAP_TRC, /* top rear center */ + VIRTIO_SND_CHMAP_TFLC, /* top front left center */ + VIRTIO_SND_CHMAP_TFRC, /* top front right center */ + VIRTIO_SND_CHMAP_TSL, /* top side left */ + VIRTIO_SND_CHMAP_TSR, /* top side right */ + VIRTIO_SND_CHMAP_LLFE, /* left LFE */ + VIRTIO_SND_CHMAP_RLFE, /* right LFE */ + VIRTIO_SND_CHMAP_BC, /* bottom center */ + VIRTIO_SND_CHMAP_BLC, /* bottom left center */ + VIRTIO_SND_CHMAP_BRC /* bottom right center */ +}; + +/* maximum possible number of channels */ +#define VIRTIO_SND_CHMAP_MAX_SIZE 18 + +struct virtio_snd_chmap_info { + /* common header */ + struct virtio_snd_info hdr; + /* dataflow direction (VIRTIO_SND_D_XXX) */ + __u8 direction; + /* # of valid channel position values */ + __u8 channels; + /* channel position values (VIRTIO_SND_CHMAP_XXX) */ + __u8 positions[VIRTIO_SND_CHMAP_MAX_SIZE]; +}; + +#endif /* VIRTIO_SND_IF_H */ diff --git a/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..8c87ebb9982b --- /dev/null +++ b/sound/virtio/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0+ + +obj-$(CONFIG_SND_VIRTIO) += virtio_snd.o + +virtio_snd-objs := \ + virtio_card.o + diff --git a/sound/virtio/virtio_card.c b/sound/virtio/virtio_card.c new file mode 100644 index 000000000000..b5f63b5b7db4 --- /dev/null +++ b/sound/virtio/virtio_card.c @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * virtio-snd: Virtio sound device + * Copyright (C) 2021 OpenSynergy GmbH + */ +#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" + +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. + */ +static void virtsnd_event_send(struct virtqueue *vqueue, + struct virtio_snd_event *event, bool notify, + gfp_t gfp) +{ + struct scatterlist sg; + struct scatterlist *psgs[1] = { &sg }; + + /* reset event content */ + memset(event, 0, sizeof(*event)); + + sg_init_one(&sg, event, sizeof(*event)); + + if (virtqueue_add_sgs(vqueue, psgs, 0, 1, event, gfp) || !notify) + return; + + if (virtqueue_kick_prepare(vqueue)) + virtqueue_notify(vqueue); +} + +/** + * 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); + struct virtio_snd_event *event; + u32 length; + unsigned long flags; + + spin_lock_irqsave(&queue->lock, flags); + do { + virtqueue_disable_cb(vqueue); + while ((event = virtqueue_get_buf(vqueue, &length))) + virtsnd_event_send(vqueue, event, true, GFP_ATOMIC); + if (unlikely(virtqueue_is_broken(vqueue))) + break; + } while (!virtqueue_enable_cb(vqueue)); + spin_unlock_irqrestore(&queue->lock, flags); +} + +/** + * virtsnd_find_vqs() - Enumerate and initialize all virtqueues. + * @snd: VirtIO sound device. + * + * After calling this function, the event queue is 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] = { + [VIRTIO_SND_VQ_EVENT] = virtsnd_event_notify_cb + }; + 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; + int rc; + + 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) + snd->queues[i].vqueue = vqs[i]; + + /* Allocate events and populate the event queue */ + virtqueue_disable_cb(vqs[VIRTIO_SND_VQ_EVENT]); + + n = virtqueue_get_vring_size(vqs[VIRTIO_SND_VQ_EVENT]); + + snd->event_msgs = kmalloc_array(n, sizeof(*snd->event_msgs), + GFP_KERNEL); + if (!snd->event_msgs) + return -ENOMEM; + + for (i = 0; i < n; ++i) + virtsnd_event_send(vqs[VIRTIO_SND_VQ_EVENT], + &snd->event_msgs[i], false, GFP_KERNEL); + + return 0; +} + +/** + * virtsnd_enable_event_vq() - Enable the event virtqueue. + * @snd: VirtIO sound device. + * + * Context: Any context. + */ +static void virtsnd_enable_event_vq(struct virtio_snd *snd) +{ + struct virtio_snd_queue *queue = virtsnd_event_queue(snd); + + if (!virtqueue_enable_cb(queue->vqueue)) + virtsnd_event_notify_cb(queue->vqueue); +} + +/** + * 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. + */ + rc = device_reprobe(dev); + if (rc) + dev_err(dev, "failed to reprobe sound device: %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; + struct device *dev = &vdev->dev; + int rc; + + rc = snd_card_new(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->driver, VIRTIO_SND_CARD_DRIVER, + sizeof(snd->card->driver)); + strscpy(snd->card->shortname, VIRTIO_SND_CARD_NAME, + sizeof(snd->card->shortname)); + if (dev->parent->bus) + snprintf(snd->card->longname, sizeof(snd->card->longname), + VIRTIO_SND_CARD_NAME " at %s/%s/%s", + dev->parent->bus->name, dev_name(dev->parent), + dev_name(dev)); + else + snprintf(snd->card->longname, sizeof(snd->card->longname), + VIRTIO_SND_CARD_NAME " at %s/%s", + dev_name(dev->parent), dev_name(dev)); + + 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 (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1)) { + dev_err(&vdev->dev, + "device does not comply with spec version 1.x\n"); + 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); + + 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_exit; + + virtio_device_ready(vdev); + + rc = virtsnd_build_devs(snd); + if (rc) + goto on_exit; + + virtsnd_enable_event_vq(snd); + +on_exit: + 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; + + /* Stop all the virtqueues. */ + vdev->config->reset(vdev); + + if (snd->card) + snd_card_free(snd->card); + + vdev->config->del_vqs(vdev); + + kfree(snd->event_msgs); +} + +/** + * 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..244381cc7a9b --- /dev/null +++ b/sound/virtio/virtio_card.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * virtio-snd: Virtio sound device + * Copyright (C) 2021 OpenSynergy GmbH + */ +#ifndef VIRTIO_SND_CARD_H +#define VIRTIO_SND_CARD_H + +#include <linux/slab.h> +#include <linux/virtio.h> +#include <sound/core.h> +#include <uapi/linux/virtio_snd.h> + +#define VIRTIO_SND_CARD_DRIVER "virtio-snd" +#define VIRTIO_SND_CARD_NAME "VirtIO SoundCard" + +/** + * 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. + * @event_msgs: Device events. + */ +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 virtio_snd_event *event_msgs; +}; + +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 */ -- 2.30.0
Anton Yakovlev
2021-Feb-09 12:40 UTC
[PATCH v3 4/9] ALSA: virtio: build PCM devices and substream hardware descriptors
Like the HDA specification, the virtio sound device specification links PCM substreams, jacks and PCM channel maps into functional groups. For each discovered group, a PCM device is created, the number of which coincides with the group number. Introduce the module parameters for setting the hardware buffer parameters: pcm_buffer_ms [=160] pcm_periods_min [=2] pcm_periods_max [=16] pcm_period_ms_min [=10] pcm_period_ms_max [=80] Signed-off-by: Anton Yakovlev <anton.yakovlev at opensynergy.com> --- sound/virtio/Makefile | 3 +- sound/virtio/virtio_card.c | 14 ++ sound/virtio/virtio_card.h | 10 + sound/virtio/virtio_pcm.c | 464 +++++++++++++++++++++++++++++++++++++ sound/virtio/virtio_pcm.h | 71 ++++++ 5 files changed, 561 insertions(+), 1 deletion(-) create mode 100644 sound/virtio/virtio_pcm.c create mode 100644 sound/virtio/virtio_pcm.h diff --git a/sound/virtio/Makefile b/sound/virtio/Makefile index dc551e637441..69162a545a41 100644 --- a/sound/virtio/Makefile +++ b/sound/virtio/Makefile @@ -4,5 +4,6 @@ obj-$(CONFIG_SND_VIRTIO) += virtio_snd.o virtio_snd-objs := \ virtio_card.o \ - virtio_ctl_msg.o + virtio_ctl_msg.o \ + virtio_pcm.o diff --git a/sound/virtio/virtio_card.c b/sound/virtio/virtio_card.c index c62b3a2da148..235afc25fce7 100644 --- a/sound/virtio/virtio_card.c +++ b/sound/virtio/virtio_card.c @@ -200,6 +200,16 @@ static int virtsnd_build_devs(struct virtio_snd *snd) VIRTIO_SND_CARD_NAME " at %s/%s", dev_name(dev->parent), dev_name(dev)); + 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); } @@ -228,6 +238,9 @@ static int virtsnd_validate(struct virtio_device *vdev) return -EINVAL; } + if (virtsnd_pcm_validate(vdev)) + return -EINVAL; + return 0; } @@ -251,6 +264,7 @@ static int virtsnd_probe(struct virtio_device *vdev) 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; diff --git a/sound/virtio/virtio_card.h b/sound/virtio/virtio_card.h index c51a71a79388..687d3ed6d1c3 100644 --- a/sound/virtio/virtio_card.h +++ b/sound/virtio/virtio_card.h @@ -12,9 +12,13 @@ #include <uapi/linux/virtio_snd.h> #include "virtio_ctl_msg.h" +#include "virtio_pcm.h" #define VIRTIO_SND_CARD_DRIVER "virtio-snd" #define VIRTIO_SND_CARD_NAME "VirtIO SoundCard" +#define VIRTIO_SND_PCM_NAME "VirtIO PCM" + +struct virtio_pcm_substream; /** * struct virtio_snd_queue - Virtqueue wrapper structure. @@ -34,6 +38,9 @@ struct virtio_snd_queue { * @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; @@ -42,6 +49,9 @@ struct virtio_snd { 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). */ diff --git a/sound/virtio/virtio_pcm.c b/sound/virtio/virtio_pcm.c new file mode 100644 index 000000000000..92f2f4cb338f --- /dev/null +++ b/sound/virtio/virtio_pcm.c @@ -0,0 +1,464 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * virtio-snd: Virtio sound device + * Copyright (C) 2021 OpenSynergy GmbH + */ +#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. + * @vss: 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 *vss, + struct virtio_snd_pcm_info *info) +{ + struct virtio_device *vdev = vss->snd->vdev; + unsigned int i; + u64 values; + size_t sample_max = 0; + size_t sample_min = 0; + + vss->features = le32_to_cpu(info->features); + + /* + * TODO: set SNDRV_PCM_INFO_{BATCH,BLOCK_TRANSFER} if device supports + * only message-based transport. + */ + vss->hw.info + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE; + + if (!info->channels_min || info->channels_min > info->channels_max) { + dev_err(&vdev->dev, + "SID %u: invalid channel range [%u %u]\n", + vss->sid, info->channels_min, info->channels_max); + return -EINVAL; + } + + vss->hw.channels_min = info->channels_min; + vss->hw.channels_max = info->channels_max; + + values = le64_to_cpu(info->formats); + + vss->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; + + vss->hw.formats |= (1ULL << alsa_fmt); + } + + if (!vss->hw.formats) { + dev_err(&vdev->dev, + "SID %u: no supported PCM sample formats found\n", + vss->sid); + return -EINVAL; + } + + values = le64_to_cpu(info->rates); + + vss->hw.rates = 0; + + for (i = 0; i < ARRAY_SIZE(g_v2a_rate_map); ++i) + if (values & (1ULL << i)) { + if (!vss->hw.rate_min || + vss->hw.rate_min > g_v2a_rate_map[i].rate) + vss->hw.rate_min = g_v2a_rate_map[i].rate; + + if (vss->hw.rate_max < g_v2a_rate_map[i].rate) + vss->hw.rate_max = g_v2a_rate_map[i].rate; + + vss->hw.rates |= g_v2a_rate_map[i].alsa_bit; + } + + if (!vss->hw.rates) { + dev_err(&vdev->dev, + "SID %u: no supported PCM frame rates found\n", + vss->sid); + return -EINVAL; + } + + vss->hw.periods_min = pcm_periods_min; + vss->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. + */ + vss->hw.buffer_bytes_max + sample_max * vss->hw.channels_max * pcm_buffer_ms * + (vss->hw.rate_max / MSEC_PER_SEC); + + /* Align the buffer size to the page size */ + vss->hw.buffer_bytes_max + (vss->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. + */ + vss->hw.period_bytes_min + sample_min * vss->hw.channels_min * pcm_period_ms_min * + (vss->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). + */ + vss->hw.period_bytes_max + sample_max * vss->hw.channels_max * pcm_period_ms_max * + (vss->hw.rate_max / MSEC_PER_SEC); + + 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 *vpcm; + + list_for_each_entry(vpcm, &snd->pcm_list, list) + if (vpcm->nid == nid) + return vpcm; + + 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 *vpcm; + + vpcm = virtsnd_pcm_find(snd, nid); + if (!IS_ERR(vpcm)) + return vpcm; + + vpcm = devm_kzalloc(&vdev->dev, sizeof(*vpcm), GFP_KERNEL); + if (!vpcm) + return ERR_PTR(-ENOMEM); + + vpcm->nid = nid; + list_add_tail(&vpcm->list, &snd->pcm_list); + + return vpcm; +} + +/** + * 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 = kcalloc(snd->nsubstreams, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + rc = virtsnd_ctl_query_info(snd, VIRTIO_SND_R_PCM_INFO, 0, + snd->nsubstreams, sizeof(*info), info); + if (rc) + goto on_exit; + + for (i = 0; i < snd->nsubstreams; ++i) { + struct virtio_pcm_substream *vss = &snd->substreams[i]; + struct virtio_pcm *vpcm; + + vss->snd = snd; + vss->sid = i; + + rc = virtsnd_pcm_build_hw(vss, &info[i]); + if (rc) + goto on_exit; + + vss->nid = le32_to_cpu(info[i].hdr.hda_fn_nid); + + vpcm = virtsnd_pcm_find_or_create(snd, vss->nid); + if (IS_ERR(vpcm)) { + rc = PTR_ERR(vpcm); + goto on_exit; + } + + switch (info[i].direction) { + case VIRTIO_SND_D_OUTPUT: + vss->direction = SNDRV_PCM_STREAM_PLAYBACK; + break; + case VIRTIO_SND_D_INPUT: + vss->direction = SNDRV_PCM_STREAM_CAPTURE; + break; + default: + dev_err(&vdev->dev, "SID %u: unknown direction (%u)\n", + vss->sid, info[i].direction); + rc = -EINVAL; + goto on_exit; + } + + vpcm->streams[vss->direction].nsubstreams++; + } + +on_exit: + kfree(info); + + return rc; +} + +/** + * 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 *vpcm; + unsigned int i; + int rc; + + list_for_each_entry(vpcm, &snd->pcm_list, list) { + unsigned int npbs + vpcm->streams[SNDRV_PCM_STREAM_PLAYBACK].nsubstreams; + unsigned int ncps + vpcm->streams[SNDRV_PCM_STREAM_CAPTURE].nsubstreams; + + if (!npbs && !ncps) + continue; + + rc = snd_pcm_new(snd->card, VIRTIO_SND_CARD_DRIVER, vpcm->nid, + npbs, ncps, &vpcm->pcm); + if (rc) { + dev_err(&vdev->dev, "snd_pcm_new[%u] failed: %d\n", + vpcm->nid, rc); + return rc; + } + + vpcm->pcm->info_flags = 0; + vpcm->pcm->dev_class = SNDRV_PCM_CLASS_GENERIC; + vpcm->pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX; + snprintf(vpcm->pcm->name, sizeof(vpcm->pcm->name), + VIRTIO_SND_PCM_NAME " %u", vpcm->pcm->device); + vpcm->pcm->private_data = vpcm; + vpcm->pcm->nonatomic = true; + + for (i = 0; i < ARRAY_SIZE(vpcm->streams); ++i) { + struct virtio_pcm_stream *stream = &vpcm->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_stream *vs; + struct virtio_pcm_substream *vss = &snd->substreams[i]; + + vpcm = virtsnd_pcm_find(snd, vss->nid); + if (IS_ERR(vpcm)) + return PTR_ERR(vpcm); + + vs = &vpcm->streams[vss->direction]; + vs->substreams[vs->nsubstreams++] = vss; + } + + list_for_each_entry(vpcm, &snd->pcm_list, list) { + for (i = 0; i < ARRAY_SIZE(vpcm->streams); ++i) { + struct virtio_pcm_stream *vs = &vpcm->streams[i]; + struct snd_pcm_str *ks = &vpcm->pcm->streams[i]; + struct snd_pcm_substream *kss; + + if (!vs->nsubstreams) + continue; + + for (kss = ks->substream; kss; kss = kss->next) + vs->substreams[kss->number]->substream = kss; + } + + snd_pcm_set_managed_buffer_all(vpcm->pcm, + SNDRV_DMA_TYPE_VMALLOC, NULL, + 0, 0); + } + + return 0; +} diff --git a/sound/virtio/virtio_pcm.h b/sound/virtio/virtio_pcm.h new file mode 100644 index 000000000000..42d592da41e5 --- /dev/null +++ b/sound/virtio/virtio_pcm.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * virtio-snd: Virtio sound device + * Copyright (C) 2021 OpenSynergy GmbH + */ +#ifndef VIRTIO_SND_PCM_H +#define VIRTIO_SND_PCM_H + +#include <linux/atomic.h> +#include <linux/virtio_config.h> +#include <sound/pcm.h> + +struct virtio_pcm; +struct virtio_pcm_msg; + +/** + * struct virtio_pcm_substream - VirtIO PCM substream. + * @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: VirtIO 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); + +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
Anton Yakovlev
2021-Feb-09 12:40 UTC
[PATCH v3 5/9] ALSA: virtio: handling control and I/O messages for the PCM device
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 elapsed 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. Signed-off-by: Anton Yakovlev <anton.yakovlev at opensynergy.com> --- sound/virtio/Makefile | 3 +- sound/virtio/virtio_card.c | 18 +- sound/virtio/virtio_card.h | 9 + sound/virtio/virtio_pcm.c | 32 +++ sound/virtio/virtio_pcm.h | 42 ++++ sound/virtio/virtio_pcm_msg.c | 393 ++++++++++++++++++++++++++++++++++ 6 files changed, 494 insertions(+), 3 deletions(-) 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 235afc25fce7..a845978111d6 100644 --- a/sound/virtio/virtio_card.c +++ b/sound/virtio/virtio_card.c @@ -65,8 +65,16 @@ static void virtsnd_event_notify_cb(struct virtqueue *vqueue) spin_lock_irqsave(&queue->lock, flags); do { virtqueue_disable_cb(vqueue); - while ((event = virtqueue_get_buf(vqueue, &length))) + while ((event = virtqueue_get_buf(vqueue, &length))) { + 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; + } + virtsnd_event_send(vqueue, event, true, GFP_ATOMIC); + } if (unlikely(virtqueue_is_broken(vqueue))) break; } while (!virtqueue_enable_cb(vqueue)); @@ -87,7 +95,9 @@ static int virtsnd_find_vqs(struct virtio_snd *snd) struct virtio_device *vdev = snd->vdev; vq_callback_t *callbacks[VIRTIO_SND_VQ_MAX] = { [VIRTIO_SND_VQ_CONTROL] = virtsnd_ctl_notify_cb, - [VIRTIO_SND_VQ_EVENT] = virtsnd_event_notify_cb + [VIRTIO_SND_VQ_EVENT] = virtsnd_event_notify_cb, + [VIRTIO_SND_VQ_TX] = virtsnd_pcm_tx_notify_cb, + [VIRTIO_SND_VQ_RX] = virtsnd_pcm_rx_notify_cb }; const char *names[VIRTIO_SND_VQ_MAX] = { [VIRTIO_SND_VQ_CONTROL] = "virtsnd-ctl", @@ -299,6 +309,7 @@ static int virtsnd_probe(struct virtio_device *vdev) static void virtsnd_remove(struct virtio_device *vdev) { struct virtio_snd *snd = vdev->priv; + unsigned int i; /* Stop all the virtqueues. */ vdev->config->reset(vdev); @@ -310,6 +321,9 @@ static void virtsnd_remove(struct virtio_device *vdev) vdev->config->del_vqs(vdev); + for (i = 0; snd->substreams && i < snd->nsubstreams; ++i) + virtsnd_pcm_msg_free(&snd->substreams[i]); + kfree(snd->event_msgs); } diff --git a/sound/virtio/virtio_card.h b/sound/virtio/virtio_card.h index 687d3ed6d1c3..aca87059564e 100644 --- a/sound/virtio/virtio_card.h +++ b/sound/virtio/virtio_card.h @@ -81,4 +81,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 *vss) +{ + if (vss->direction == SNDRV_PCM_STREAM_PLAYBACK) + return virtsnd_tx_queue(vss->snd); + else + return virtsnd_rx_queue(vss->snd); +} + #endif /* VIRTIO_SND_CARD_H */ diff --git a/sound/virtio/virtio_pcm.c b/sound/virtio/virtio_pcm.c index 92f2f4cb338f..a74fbfb9f35c 100644 --- a/sound/virtio/virtio_pcm.c +++ b/sound/virtio/virtio_pcm.c @@ -338,6 +338,8 @@ int virtsnd_pcm_parse_cfg(struct virtio_snd *snd) vss->snd = snd; vss->sid = i; + init_waitqueue_head(&vss->msg_empty); + spin_lock_init(&vss->lock); rc = virtsnd_pcm_build_hw(vss, &info[i]); if (rc) @@ -462,3 +464,33 @@ int virtsnd_pcm_build_devs(struct virtio_snd *snd) 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 *vss; + unsigned int sid = le32_to_cpu(event->data); + + if (sid >= snd->nsubstreams) + return; + + vss = &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: + spin_lock(&vss->lock); + if (vss->xfer_enabled) + vss->xfer_xrun = true; + spin_unlock(&vss->lock); + break; + } +} diff --git a/sound/virtio/virtio_pcm.h b/sound/virtio/virtio_pcm.h index 42d592da41e5..d6be62b52421 100644 --- a/sound/virtio/virtio_pcm.h +++ b/sound/virtio/virtio_pcm.h @@ -22,6 +22,19 @@ struct virtio_pcm_msg; * @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. + * @lock: Spinlock that protects fields shared by interrupt handlers and + * substream operators. + * @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: Allocated I/O messages. + * @nmsgs: Number of allocated 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; @@ -31,6 +44,18 @@ 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; + spinlock_t lock; + snd_pcm_uframes_t hw_ptr; + bool xfer_enabled; + bool xfer_xrun; + struct virtio_pcm_msg **msgs; + unsigned int nmsgs; + int msg_last_enqueued; + unsigned int msg_count; + wait_queue_head_t msg_empty; }; /** @@ -63,9 +88,26 @@ 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); +struct virtio_snd_msg * +virtsnd_pcm_ctl_msg_alloc(struct virtio_pcm_substream *vss, + unsigned int command, gfp_t gfp); + +int virtsnd_pcm_msg_alloc(struct virtio_pcm_substream *vss, + unsigned int periods, unsigned int period_bytes); + +void virtsnd_pcm_msg_free(struct virtio_pcm_substream *vss); + +int virtsnd_pcm_msg_send(struct virtio_pcm_substream *vss); + #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..38748e93b5b4 --- /dev/null +++ b/sound/virtio/virtio_pcm_msg.c @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * virtio-snd: Virtio sound device + * Copyright (C) 2021 OpenSynergy GmbH + */ +#include <sound/pcm_params.h> + +#include "virtio_card.h" + +/** + * 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[0]; +}; + +/** + * enum pcm_msg_sg_index - Index values for the virtio_pcm_msg->sgs field in + * an I/O message. + * @PCM_MSG_SG_XFER: Element containing a virtio_snd_pcm_xfer structure. + * @PCM_MSG_SG_STATUS: Element containing a virtio_snd_pcm_status structure. + * @PCM_MSG_SG_DATA: The first element containing a data buffer. + */ +enum pcm_msg_sg_index { + PCM_MSG_SG_XFER = 0, + PCM_MSG_SG_STATUS, + PCM_MSG_SG_DATA +}; + +/** + * virtsnd_pcm_sg_num() - Count the number of sg-elements required to represent + * vmalloc'ed buffer. + * @data: Pointer to vmalloc'ed buffer. + * @length: Buffer size. + * + * Context: Any context. + * Return: Number of physically contiguous parts in the @data. + */ +static int virtsnd_pcm_sg_num(u8 *data, unsigned int length) +{ + phys_addr_t sg_address; + unsigned int sg_length; + int num = 0; + + while (length) { + struct page *pg = vmalloc_to_page(data); + phys_addr_t pg_address = page_to_phys(pg); + size_t pg_length; + + pg_length = PAGE_SIZE - offset_in_page(data); + if (pg_length > length) + pg_length = length; + + if (!num || sg_address + sg_length != pg_address) { + sg_address = pg_address; + sg_length = pg_length; + num++; + } else { + sg_length += pg_length; + } + + data += pg_length; + length -= pg_length; + } + + return num; +} + +/** + * virtsnd_pcm_sg_from() - Build sg-list from vmalloc'ed buffer. + * @sgs: Preallocated sg-list to populate. + * @nsgs: The maximum number of elements in the @sgs. + * @data: Pointer to vmalloc'ed buffer. + * @length: Buffer size. + * + * Splits the buffer into physically contiguous parts and makes an sg-list of + * such parts. + * + * Context: Any context. + */ +static void virtsnd_pcm_sg_from(struct scatterlist *sgs, int nsgs, u8 *data, + unsigned int length) +{ + int idx = -1; + + while (length) { + struct page *pg = vmalloc_to_page(data); + size_t pg_length; + + pg_length = PAGE_SIZE - offset_in_page(data); + if (pg_length > length) + pg_length = length; + + if (idx == -1 || + sg_phys(&sgs[idx]) + sgs[idx].length != page_to_phys(pg)) { + if (idx + 1 == nsgs) + break; + sg_set_page(&sgs[++idx], pg, pg_length, + offset_in_page(data)); + } else { + sgs[idx].length += pg_length; + } + + data += pg_length; + length -= pg_length; + } + + sg_mark_end(&sgs[idx]); +} + +/** + * virtsnd_pcm_msg_alloc() - Allocate I/O messages. + * @vss: VirtIO PCM substream. + * @periods: Current number of periods. + * @period_bytes: Current period size in bytes. + * + * The function slices the buffer into @periods parts (each with the size of + * @period_bytes), and creates @periods 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 *vss, + unsigned int periods, unsigned int period_bytes) +{ + struct snd_pcm_runtime *runtime = vss->substream->runtime; + unsigned int i; + + /* Free previously allocated messages (if any). */ + virtsnd_pcm_msg_free(vss); + + vss->msgs = kcalloc(periods, sizeof(*vss->msgs), GFP_KERNEL); + if (!vss->msgs) + return -ENOMEM; + + vss->nmsgs = periods; + + for (i = 0; i < periods; ++i) { + u8 *data = runtime->dma_area + period_bytes * i; + int sg_num = virtsnd_pcm_sg_num(data, period_bytes); + struct virtio_pcm_msg *msg; + + msg = kzalloc(sizeof(*msg) + sizeof(*msg->sgs) * (sg_num + 2), + GFP_KERNEL); + if (!msg) + return -ENOMEM; + + msg->substream = vss; + sg_init_one(&msg->sgs[PCM_MSG_SG_XFER], &msg->xfer, + sizeof(msg->xfer)); + sg_init_one(&msg->sgs[PCM_MSG_SG_STATUS], &msg->status, + sizeof(msg->status)); + virtsnd_pcm_sg_from(&msg->sgs[PCM_MSG_SG_DATA], sg_num, data, + period_bytes); + + vss->msgs[i] = msg; + } + + return 0; +} + +/** + * virtsnd_pcm_msg_free() - Free all allocated I/O messages. + * @vss: VirtIO PCM substream. + * + * Context: Any context. + */ +void virtsnd_pcm_msg_free(struct virtio_pcm_substream *vss) +{ + unsigned int i; + + for (i = 0; i < vss->nmsgs; ++i) + kfree(vss->msgs[i]); + kfree(vss->msgs); + + vss->msgs = NULL; + vss->nmsgs = 0; +} + +/** + * virtsnd_pcm_msg_send() - Send asynchronous I/O messages. + * @vss: 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. Expects the tx/rx queue and the VirtIO substream + * spinlocks to be held by caller. + * Return: 0 on success, -errno on failure. + */ +int virtsnd_pcm_msg_send(struct virtio_pcm_substream *vss) +{ + struct snd_pcm_runtime *runtime = vss->substream->runtime; + struct virtio_snd *snd = vss->snd; + struct virtio_device *vdev = snd->vdev; + struct virtqueue *vqueue = virtsnd_pcm_queue(vss)->vqueue; + int i; + int n; + bool notify = false; + + i = (vss->msg_last_enqueued + 1) % runtime->periods; + n = runtime->periods - vss->msg_count; + + for (; n; --n, i = (i + 1) % runtime->periods) { + struct virtio_pcm_msg *msg = vss->msgs[i]; + struct scatterlist *psgs[] = { + &msg->sgs[PCM_MSG_SG_XFER], + &msg->sgs[PCM_MSG_SG_DATA], + &msg->sgs[PCM_MSG_SG_STATUS] + }; + int rc; + + msg->xfer.stream_id = cpu_to_virtio32(vdev, vss->sid); + memset(&msg->status, 0, sizeof(msg->status)); + + if (vss->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) { + dev_err(&vdev->dev, + "SID %u: failed to send I/O message\n", + vss->sid); + return rc; + } + + vss->msg_last_enqueued = i; + vss->msg_count++; + } + + if (!(vss->features & (1U << VIRTIO_SND_PCM_F_MSG_POLLING))) + notify = virtqueue_kick_prepare(vqueue); + + if (notify) + virtqueue_notify(vqueue); + + 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. + * + * Context: Interrupt context. Takes and releases the VirtIO substream spinlock. + */ +static void virtsnd_pcm_msg_complete(struct virtio_pcm_msg *msg, size_t size) +{ + struct virtio_pcm_substream *vss = msg->substream; + + /* + * hw_ptr always indicates the buffer position of the first I/O message + * in the virtqueue. Therefore, on each completion of an I/O message, + * the hw_ptr value is unconditionally advanced. + */ + spin_lock(&vss->lock); + /* + * If the capture substream returned an incorrect status, then just + * increase the hw_ptr by the period size. + */ + if (vss->direction == SNDRV_PCM_STREAM_PLAYBACK || + size <= sizeof(msg->status)) { + vss->hw_ptr += vss->period_size; + } else { + size -= sizeof(msg->status); + vss->hw_ptr += size / vss->frame_bytes; + } + + vss->hw_ptr %= vss->buffer_size; + vss->xfer_xrun = false; + vss->msg_count--; + + if (vss->xfer_enabled) { + struct snd_pcm_runtime *runtime = vss->substream->runtime; + + runtime->delay + bytes_to_frames(runtime, + le32_to_cpu(msg->status.latency_bytes)); + + spin_unlock(&vss->lock); + snd_pcm_period_elapsed(vss->substream); + spin_lock(&vss->lock); + + virtsnd_pcm_msg_send(vss); + } else if (!vss->msg_count) { + wake_up_all(&vss->msg_empty); + } + spin_unlock(&vss->lock); +} + +/** + * virtsnd_pcm_notify_cb() - Process all completed I/O messages. + * @queue: 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) +{ + struct virtio_pcm_msg *msg; + u32 length; + unsigned long flags; + + spin_lock_irqsave(&queue->lock, flags); + do { + virtqueue_disable_cb(queue->vqueue); + while ((msg = virtqueue_get_buf(queue->vqueue, &length))) + virtsnd_pcm_msg_complete(msg, length); + if (unlikely(virtqueue_is_broken(queue->vqueue))) + break; + } while (!virtqueue_enable_cb(queue->vqueue)); + 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. + * @vss: 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, NULL on failure. + */ +struct virtio_snd_msg * +virtsnd_pcm_ctl_msg_alloc(struct virtio_pcm_substream *vss, + unsigned int command, gfp_t gfp) +{ + struct virtio_device *vdev = vss->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(request_size, response_size, gfp); + if (msg) { + struct virtio_snd_pcm_hdr *hdr = virtsnd_ctl_msg_request(msg); + + hdr->hdr.code = cpu_to_virtio32(vdev, command); + hdr->stream_id = cpu_to_virtio32(vdev, vss->sid); + } + + return msg; +} -- 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 at opensynergy.com> --- sound/virtio/Makefile | 1 + sound/virtio/virtio_card.c | 14 +++ sound/virtio/virtio_card.h | 12 ++ sound/virtio/virtio_jack.c | 233 +++++++++++++++++++++++++++++++++++++ 4 files changed, 260 insertions(+) create mode 100644 sound/virtio/virtio_jack.c diff --git a/sound/virtio/Makefile b/sound/virtio/Makefile index 34493226793f..09f485291285 100644 --- a/sound/virtio/Makefile +++ b/sound/virtio/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_SND_VIRTIO) += virtio_snd.o virtio_snd-objs := \ virtio_card.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 a845978111d6..4578d0ce0726 100644 --- a/sound/virtio/virtio_card.c +++ b/sound/virtio/virtio_card.c @@ -67,6 +67,10 @@ static void virtsnd_event_notify_cb(struct virtqueue *vqueue) virtqueue_disable_cb(vqueue); while ((event = virtqueue_get_buf(vqueue, &length))) { 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); @@ -210,10 +214,20 @@ static int virtsnd_build_devs(struct virtio_snd *snd) VIRTIO_SND_CARD_NAME " at %s/%s", dev_name(dev->parent), dev_name(dev)); + rc = virtsnd_jack_parse_cfg(snd); + if (rc) + return rc; + rc = virtsnd_pcm_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) diff --git a/sound/virtio/virtio_card.h b/sound/virtio/virtio_card.h index aca87059564e..9e6cd79eda25 100644 --- a/sound/virtio/virtio_card.h +++ b/sound/virtio/virtio_card.h @@ -18,6 +18,7 @@ #define VIRTIO_SND_CARD_NAME "VirtIO SoundCard" #define VIRTIO_SND_PCM_NAME "VirtIO PCM" +struct virtio_jack; struct virtio_pcm_substream; /** @@ -39,6 +40,8 @@ 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. */ @@ -50,6 +53,8 @@ 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; }; @@ -90,4 +95,11 @@ virtsnd_pcm_queue(struct virtio_pcm_substream *vss) return virtsnd_rx_queue(vss->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); + #endif /* VIRTIO_SND_CARD_H */ diff --git a/sound/virtio/virtio_jack.c b/sound/virtio/virtio_jack.c new file mode 100644 index 000000000000..a78cf465171d --- /dev/null +++ b/sound/virtio/virtio_jack.c @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * virtio-snd: Virtio sound device + * Copyright (C) 2021 OpenSynergy GmbH + */ +#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. + * @vjack: 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 *vjack) +{ + unsigned int defconf = vjack->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. + * @vjack: 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 *vjack) +{ + unsigned int defconf = vjack->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(snd->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 = kcalloc(snd->njacks, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + rc = virtsnd_ctl_query_info(snd, VIRTIO_SND_R_JACK_INFO, 0, snd->njacks, + sizeof(*info), info); + if (rc) + goto on_exit; + + for (i = 0; i < snd->njacks; ++i) { + struct virtio_jack *vjack = &snd->jacks[i]; + + vjack->nid = le32_to_cpu(info[i].hdr.hda_fn_nid); + vjack->features = le32_to_cpu(info[i].features); + vjack->defconf = le32_to_cpu(info[i].hda_reg_defconf); + vjack->caps = le32_to_cpu(info[i].hda_reg_caps); + vjack->connected = info[i].connected; + } + +on_exit: + kfree(info); + + return rc; +} + +/** + * 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 *vjack = &snd->jacks[i]; + + vjack->type = virtsnd_jack_get_type(vjack); + + rc = snd_jack_new(snd->card, virtsnd_jack_get_label(vjack), + vjack->type, &vjack->jack, true, true); + if (rc) + return rc; + + if (vjack->jack) + vjack->jack->private_data = vjack; + + snd_jack_report(vjack->jack, + vjack->connected ? vjack->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 *vjack; + + if (jack_id >= snd->njacks) + return; + + vjack = &snd->jacks[jack_id]; + + switch (le32_to_cpu(event->hdr.code)) { + case VIRTIO_SND_EVT_JACK_CONNECTED: + vjack->connected = true; + break; + case VIRTIO_SND_EVT_JACK_DISCONNECTED: + vjack->connected = false; + break; + default: + return; + } + + snd_jack_report(vjack->jack, vjack->connected ? vjack->type : 0); +} -- 2.30.0
Anton Yakovlev
2021-Feb-09 12:40 UTC
[PATCH v3 8/9] ALSA: virtio: introduce PCM channel map support
Enumerate all available PCM channel maps and create ALSA controls. Signed-off-by: Anton Yakovlev <anton.yakovlev at opensynergy.com> --- sound/virtio/Makefile | 1 + sound/virtio/virtio_card.c | 10 ++ sound/virtio/virtio_card.h | 8 ++ sound/virtio/virtio_chmap.c | 219 ++++++++++++++++++++++++++++++++++++ sound/virtio/virtio_pcm.h | 4 + 5 files changed, 242 insertions(+) create mode 100644 sound/virtio/virtio_chmap.c diff --git a/sound/virtio/Makefile b/sound/virtio/Makefile index 09f485291285..2742bddb8874 100644 --- a/sound/virtio/Makefile +++ b/sound/virtio/Makefile @@ -4,6 +4,7 @@ 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 \ diff --git a/sound/virtio/virtio_card.c b/sound/virtio/virtio_card.c index 4578d0ce0726..787a4dec1da8 100644 --- a/sound/virtio/virtio_card.c +++ b/sound/virtio/virtio_card.c @@ -222,6 +222,10 @@ static int virtsnd_build_devs(struct virtio_snd *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) @@ -234,6 +238,12 @@ static int virtsnd_build_devs(struct virtio_snd *snd) return rc; } + if (snd->nchmaps) { + rc = virtsnd_chmap_build_devs(snd); + if (rc) + return rc; + } + return snd_card_register(snd->card); } diff --git a/sound/virtio/virtio_card.h b/sound/virtio/virtio_card.h index 9e6cd79eda25..8ec8bc3ea75e 100644 --- a/sound/virtio/virtio_card.h +++ b/sound/virtio/virtio_card.h @@ -44,6 +44,8 @@ struct virtio_snd_queue { * @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; @@ -57,6 +59,8 @@ struct virtio_snd { 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). */ @@ -102,4 +106,8 @@ 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..c54d7daa13e3 --- /dev/null +++ b/sound/virtio/virtio_chmap.c @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * virtio-snd: Virtio sound device + * Copyright (C) 2021 OpenSynergy GmbH + */ +#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 *vpcm; + struct virtio_pcm_stream *vs; + + vpcm = virtsnd_pcm_find_or_create(snd, nid); + if (IS_ERR(vpcm)) + return PTR_ERR(vpcm); + + switch (info->direction) { + case VIRTIO_SND_D_OUTPUT: + vs = &vpcm->streams[SNDRV_PCM_STREAM_PLAYBACK]; + break; + case VIRTIO_SND_D_INPUT: + vs = &vpcm->streams[SNDRV_PCM_STREAM_CAPTURE]; + break; + default: + dev_err(&vdev->dev, + "chmap #%u: unknown direction (%u)\n", i, + info->direction); + return -EINVAL; + } + + vs->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). + * @vs: 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 *vs) +{ + unsigned int i; + int max_channels = 0; + + for (i = 0; i < vs->nchmaps; i++) + if (max_channels < vs->chmaps[i].channels) + max_channels = vs->chmaps[i].channels; + + return snd_pcm_add_chmap_ctls(pcm, direction, vs->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 *vpcm; + struct virtio_pcm_stream *vs; + unsigned int i; + int rc; + + /* Allocate channel map elements per each PCM device/stream. */ + list_for_each_entry(vpcm, &snd->pcm_list, list) { + for (i = 0; i < ARRAY_SIZE(vpcm->streams); ++i) { + vs = &vpcm->streams[i]; + + if (!vs->nchmaps) + continue; + + vs->chmaps = devm_kcalloc(&vdev->dev, vs->nchmaps + 1, + sizeof(*vs->chmaps), + GFP_KERNEL); + if (!vs->chmaps) + return -ENOMEM; + + vs->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 channels = info->channels; + unsigned int ch; + struct snd_pcm_chmap_elem *chmap; + + vpcm = virtsnd_pcm_find(snd, le32_to_cpu(info->hdr.hda_fn_nid)); + if (IS_ERR(vpcm)) + return PTR_ERR(vpcm); + + if (info->direction == VIRTIO_SND_D_OUTPUT) + vs = &vpcm->streams[SNDRV_PCM_STREAM_PLAYBACK]; + else + vs = &vpcm->streams[SNDRV_PCM_STREAM_CAPTURE]; + + chmap = &vs->chmaps[vs->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(vpcm, &snd->pcm_list, list) { + if (!vpcm->pcm) + continue; + + for (i = 0; i < ARRAY_SIZE(vpcm->streams); ++i) { + vs = &vpcm->streams[i]; + + if (!vs->nchmaps) + continue; + + rc = virtsnd_chmap_add_ctls(vpcm->pcm, i, vs); + if (rc) + return rc; + } + } + + return 0; +} diff --git a/sound/virtio/virtio_pcm.h b/sound/virtio/virtio_pcm.h index 4378918b441a..ed864b57a55b 100644 --- a/sound/virtio/virtio_pcm.h +++ b/sound/virtio/virtio_pcm.h @@ -64,10 +64,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; }; /** -- 2.30.0
Anton Yakovlev
2021-Feb-09 12:40 UTC
[PATCH v3 9/9] ALSA: virtio: introduce device suspend/resume support
All running PCM substreams are stopped on device suspend and restarted on device resume. Signed-off-by: Anton Yakovlev <anton.yakovlev at opensynergy.com> --- sound/virtio/virtio_card.c | 57 +++++++++++++++++++++++++++++++++++ sound/virtio/virtio_pcm.c | 1 + sound/virtio/virtio_pcm_ops.c | 44 ++++++++++++++++++++------- 3 files changed, 91 insertions(+), 11 deletions(-) diff --git a/sound/virtio/virtio_card.c b/sound/virtio/virtio_card.c index 787a4dec1da8..1f0a0fa7bbc0 100644 --- a/sound/virtio/virtio_card.c +++ b/sound/virtio/virtio_card.c @@ -373,6 +373,59 @@ 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. + * Return: 0 on success, -errno on failure. + */ +static int virtsnd_freeze(struct virtio_device *vdev) +{ + struct virtio_snd *snd = vdev->priv; + + /* Stop all the virtqueues. */ + vdev->config->reset(vdev); + vdev->config->del_vqs(vdev); + + virtsnd_ctl_msg_cancel_all(snd); + + kfree(snd->event_msgs); + + /* + * If the virtsnd_restore() fails before re-allocating events, then we + * get a dangling pointer here. + */ + snd->event_msgs = NULL; + + return 0; +} + +/** + * virtsnd_restore() - Resume device. + * @vdev: VirtIO parent device. + * + * Context: Any context. + * 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_event_vq(snd); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + static const struct virtio_device_id id_table[] = { { VIRTIO_ID_SOUND, VIRTIO_DEV_ANY_ID }, { 0 }, @@ -386,6 +439,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 1d98de878385..401d4c975d2b 100644 --- a/sound/virtio/virtio_pcm.c +++ b/sound/virtio/virtio_pcm.c @@ -109,6 +109,7 @@ static int virtsnd_pcm_build_hw(struct virtio_pcm_substream *vss, 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) { diff --git a/sound/virtio/virtio_pcm_ops.c b/sound/virtio/virtio_pcm_ops.c index c2224f5461c4..207f4877a5ec 100644 --- a/sound/virtio/virtio_pcm_ops.c +++ b/sound/virtio/virtio_pcm_ops.c @@ -220,6 +220,10 @@ static int virtsnd_pcm_hw_params(struct snd_pcm_substream *substream, if (rc) return rc; + /* If messages have already been allocated before, do nothing. */ + if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) + return 0; + return virtsnd_pcm_msg_alloc(vss, periods, period_bytes); } @@ -260,19 +264,21 @@ static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream) } spin_lock_irqsave(&vss->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. - */ - vss->frame_bytes = runtime->frame_bits >> 3; - vss->period_size = runtime->period_size; - vss->buffer_size = runtime->buffer_size; + if (runtime->status->state != SNDRV_PCM_STATE_SUSPENDED) { + /* + * 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. + */ + vss->frame_bytes = runtime->frame_bits >> 3; + vss->period_size = runtime->period_size; + vss->buffer_size = runtime->buffer_size; - vss->hw_ptr = 0; + vss->hw_ptr = 0; + vss->msg_last_enqueued = -1; + } vss->xfer_xrun = false; - vss->msg_last_enqueued = -1; vss->msg_count = 0; spin_unlock_irqrestore(&vss->lock, flags); @@ -302,6 +308,21 @@ static int virtsnd_pcm_trigger(struct snd_pcm_substream *substream, int command) int rc; switch (command) { + case SNDRV_PCM_TRIGGER_RESUME: { + /* + * We restart the substream by executing the standard command + * sequence. + */ + rc = virtsnd_pcm_hw_params(substream, NULL); + if (rc) + return rc; + + rc = virtsnd_pcm_prepare(substream); + if (rc) + return rc; + + fallthrough; + } case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: { struct virtio_snd_queue *queue = virtsnd_pcm_queue(vss); @@ -328,6 +349,7 @@ static int virtsnd_pcm_trigger(struct snd_pcm_substream *substream, int command) return virtsnd_ctl_msg_send_sync(snd, msg); } + case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: { spin_lock_irqsave(&vss->lock, flags); -- 2.30.0