On Fri, Sep 29, 2023 at 05:08:20PM +0300, Emmanouil Pitsidianakis wrote: > This patch series adds an audio device implementing the recent virtio > sound spec (1.2) and a corresponding PCI wrapper device. > > v10 can be found online at: > > https://gitlab.com/epilys/qemu/-/tree/virtio-snd-v10 > > Ref b720e00121878cb91c9690fd1f9ca609b9484346 > > Main differences with v9 patch series [^v9] > <cover.1694588927.git.manos.pitsidiana...@linaro.org>: > > - Addressed [^v9] review comments. > - Copy buffer data just before playing it on the host instead of when > the IO message arrives. This in most cases takes care of the buffer > not being populated with data from the guest application when it > firsts arrives.
I hope you are also fixing the linux driver to fix this in a robust way, yes? > Previously: > > [^v9]: > https://lore.kernel.org/qemu-devel/cover.1694588927.git.manos.pitsidiana...@linaro.org/ > [^v8]: > https://lore.kernel.org/qemu-devel/cover.1693252037.git.manos.pitsidiana...@linaro.org/ > [^v7]: > https://lore.kernel.org/qemu-devel/cover.1692731646.git.manos.pitsidiana...@linaro.org/ > [^v6]: > https://lore.kernel.org/qemu-devel/cover.1692089917.git.manos.pitsidiana...@linaro.org/ > [^v5]: > https://lore.kernel.org/qemu-devel/cover.1690626150.git.manos.pitsidiana...@linaro.org/ > [^v4]: > https://lore.kernel.org/qemu-devel/cover.1689857559.git.manos.pitsidiana...@linaro.org/ > [^v3]: > https://lore.kernel.org/qemu-devel/cover.1689692765.git.manos.pitsidiana...@linaro.org/ > > > Emmanouil Pitsidianakis (11): > Add virtio-sound device stub > Add virtio-sound-pci device > virtio-sound: handle control messages and streams > virtio-sound: handle VIRTIO_SND_R_PCM_INFO request > virtio-sound: handle VIRTIO_SND_R_PCM_{START,STOP} > virtio-sound: handle VIRTIO_SND_R_PCM_SET_PARAMS > virtio-sound: handle VIRTIO_SND_R_PCM_PREPARE > virtio-sound: handle VIRTIO_SND_R_PCM_RELEASE > virtio-sound: implement audio output (TX) > virtio-sound: implement audio capture (RX) > docs/system: add basic virtio-snd documentation > > MAINTAINERS | 7 + > docs/system/device-emulation.rst | 1 + > docs/system/devices/virtio-snd.rst | 49 + > hw/virtio/Kconfig | 5 + > hw/virtio/meson.build | 2 + > hw/virtio/trace-events | 20 + > hw/virtio/virtio-snd-pci.c | 93 ++ > hw/virtio/virtio-snd.c | 1455 ++++++++++++++++++++++++++++ > include/hw/virtio/virtio-snd.h | 235 +++++ > softmmu/qdev-monitor.c | 1 + > 10 files changed, 1868 insertions(+) > create mode 100644 docs/system/devices/virtio-snd.rst > create mode 100644 hw/virtio/virtio-snd-pci.c > create mode 100644 hw/virtio/virtio-snd.c > create mode 100644 include/hw/virtio/virtio-snd.h > > Range-diff against v9: > 1: 5173e2c243 = 1: 6e7bdf6dda Add virtio-sound device stub > 2: dd3acea293 ! 2: 82138b9c7d Add virtio-sound-pci device > @@ hw/virtio/virtio-snd-pci.c (new) > + > +type_init(virtio_snd_pci_register); > > + ## hw/virtio/virtio-snd.c ## > +@@ hw/virtio/virtio-snd.c: static void > + virtio_snd_get_config(VirtIODevice *vdev, uint8_t *config) > + { > + VirtIOSound *s = VIRTIO_SND(vdev); > ++ virtio_snd_config *sndconfig = > ++ (virtio_snd_config *)config; > + trace_virtio_snd_get_config(vdev, > + s->snd_conf.jacks, > + s->snd_conf.streams, > + s->snd_conf.chmaps); > + > +- memcpy(config, &s->snd_conf, sizeof(s->snd_conf)); > ++ memcpy(sndconfig, &s->snd_conf, sizeof(s->snd_conf)); > ++ cpu_to_le32s(&sndconfig->jacks); > ++ cpu_to_le32s(&sndconfig->streams); > ++ cpu_to_le32s(&sndconfig->chmaps); > ++ > + } > + > + static void > +@@ hw/virtio/virtio-snd.c: virtio_snd_set_config(VirtIODevice *vdev, > const uint8_t *config) > + s->snd_conf.chmaps, > + sndconfig->chmaps); > + > +- memcpy(&s->snd_conf, sndconfig, sizeof(s->snd_conf)); > ++ memcpy(&s->snd_conf, sndconfig, sizeof(virtio_snd_config)); > ++ le32_to_cpus(&s->snd_conf.jacks); > ++ le32_to_cpus(&s->snd_conf.streams); > ++ le32_to_cpus(&s->snd_conf.chmaps); > ++ > + } > + > + /* > + > ## softmmu/qdev-monitor.c ## > @@ softmmu/qdev-monitor.c: static const QDevAlias qdev_alias_table[] = { > { "virtio-serial-device", "virtio-serial", QEMU_ARCH_VIRTIO_MMIO }, > 3: a75dc75f24 ! 3: c1a2cb0304 virtio-sound: handle control messages and > streams > @@ hw/virtio/trace-events: virtio_snd_vm_state_running(void) "vm state > running" > +virtio_snd_handle_event(void) "event queue callback called" > > ## hw/virtio/virtio-snd.c ## > +@@ > + #define VIRTIO_SOUND_CHMAP_DEFAULT 0 > + #define VIRTIO_SOUND_HDA_FN_NID 0 > + > ++static uint32_t supported_formats = BIT(VIRTIO_SND_PCM_FMT_S8) > ++ | BIT(VIRTIO_SND_PCM_FMT_U8) > ++ | BIT(VIRTIO_SND_PCM_FMT_S16) > ++ | BIT(VIRTIO_SND_PCM_FMT_U16) > ++ | BIT(VIRTIO_SND_PCM_FMT_S32) > ++ | BIT(VIRTIO_SND_PCM_FMT_U32) > ++ | BIT(VIRTIO_SND_PCM_FMT_FLOAT); > ++ > ++static uint32_t supported_rates = BIT(VIRTIO_SND_PCM_RATE_5512) > ++ | BIT(VIRTIO_SND_PCM_RATE_8000) > ++ | BIT(VIRTIO_SND_PCM_RATE_11025) > ++ | BIT(VIRTIO_SND_PCM_RATE_16000) > ++ | BIT(VIRTIO_SND_PCM_RATE_22050) > ++ | BIT(VIRTIO_SND_PCM_RATE_32000) > ++ | BIT(VIRTIO_SND_PCM_RATE_44100) > ++ | BIT(VIRTIO_SND_PCM_RATE_48000) > ++ | BIT(VIRTIO_SND_PCM_RATE_64000) > ++ | BIT(VIRTIO_SND_PCM_RATE_88200) > ++ | BIT(VIRTIO_SND_PCM_RATE_96000) > ++ | BIT(VIRTIO_SND_PCM_RATE_176400) > ++ | BIT(VIRTIO_SND_PCM_RATE_192000) > ++ | BIT(VIRTIO_SND_PCM_RATE_384000); > ++ > + static const VMStateDescription vmstate_virtio_snd_device = { > + .name = TYPE_VIRTIO_SND, > + .version_id = VIRTIO_SOUND_VM_VERSION, > @@ hw/virtio/virtio-snd.c: virtio_snd_set_config(VirtIODevice *vdev, > const uint8_t *config) > - memcpy(&s->snd_conf, sndconfig, sizeof(s->snd_conf)); > + > } > > +static void > @@ hw/virtio/virtio-snd.c: virtio_snd_set_config(VirtIODevice *vdev, > const uint8_t > + g_free(cmd); > +} > + > ++/* > ++ * Get a specific stream from the virtio sound card device. > ++ * Returns NULL if @stream_id is invalid or not allocated. > ++ * > ++ * @s: VirtIOSound device > ++ * @stream_id: stream id > ++ */ > ++static VirtIOSoundPCMStream *virtio_snd_pcm_get_stream(VirtIOSound *s, > ++ uint32_t > stream_id) > ++{ > ++ return stream_id >= s->snd_conf.streams ? NULL : > ++ s->pcm->streams[stream_id]; > ++} > ++ > ++/* > ++ * Get params for a specific stream. > ++ * > ++ * @s: VirtIOSound device > ++ * @stream_id: stream id > ++ */ > ++static virtio_snd_pcm_set_params *virtio_snd_pcm_get_params(VirtIOSound > *s, > ++ uint32_t > stream_id) > ++{ > ++ return stream_id >= s->snd_conf.streams ? NULL > ++ : &s->pcm->pcm_params[stream_id]; > ++} > ++ > ++/* > ++ * Set the given stream params. > ++ * Called by both virtio_snd_handle_pcm_set_params and during device > ++ * initialization. > ++ * Returns the response status code. (VIRTIO_SND_S_*). > ++ * > ++ * @s: VirtIOSound device > ++ * @params: The PCM params as defined in the virtio specification > ++ */ > ++static > ++uint32_t virtio_snd_set_pcm_params(VirtIOSound *s, > ++ uint32_t stream_id, > ++ virtio_snd_pcm_set_params *params) > ++{ > ++ virtio_snd_pcm_set_params *st_params; > ++ > ++ if (stream_id >= s->snd_conf.streams || s->pcm->pcm_params == NULL) > { > ++ /* > ++ * TODO: do we need to set DEVICE_NEEDS_RESET? > ++ */ > ++ virtio_error(VIRTIO_DEVICE(s), "Streams have not been > initialized.\n"); > ++ return cpu_to_le32(VIRTIO_SND_S_BAD_MSG); > ++ } > ++ > ++ st_params = virtio_snd_pcm_get_params(s, stream_id); > ++ > ++ if (params->channels < 1 || params->channels > AUDIO_MAX_CHANNELS) { > ++ error_report("Number of channels is not supported."); > ++ return cpu_to_le32(VIRTIO_SND_S_NOT_SUPP); > ++ } > ++ if (!(supported_formats & BIT(params->format))) { > ++ error_report("Stream format is not supported."); > ++ return cpu_to_le32(VIRTIO_SND_S_NOT_SUPP); > ++ } > ++ if (!(supported_rates & BIT(params->rate))) { > ++ error_report("Stream rate is not supported."); > ++ return cpu_to_le32(VIRTIO_SND_S_NOT_SUPP); > ++ } > ++ > ++ st_params->buffer_bytes = le32_to_cpu(params->buffer_bytes); > ++ st_params->period_bytes = le32_to_cpu(params->period_bytes); > ++ st_params->features = le32_to_cpu(params->features); > ++ /* the following are uint8_t, so there's no need to bswap the > values. */ > ++ st_params->channels = params->channels; > ++ st_params->format = params->format; > ++ st_params->rate = params->rate; > ++ > ++ return cpu_to_le32(VIRTIO_SND_S_OK); > ++} > ++ > ++/* > ++ * Get a QEMU Audiosystem compatible format value from a > VIRTIO_SND_PCM_FMT_* > ++ */ > ++static AudioFormat virtio_snd_get_qemu_format(uint32_t format) > ++{ > ++ #define CASE(FMT) \ > ++ case VIRTIO_SND_PCM_FMT_##FMT: \ > ++ return AUDIO_FORMAT_##FMT; > ++ > ++ switch (format) { > ++ CASE(U8) > ++ CASE(S8) > ++ CASE(U16) > ++ CASE(S16) > ++ CASE(U32) > ++ CASE(S32) > ++ case VIRTIO_SND_PCM_FMT_FLOAT: > ++ return AUDIO_FORMAT_F32; > ++ default: > ++ g_assert_not_reached(); > ++ } > ++ > ++ #undef CASE > ++} > ++ > ++/* > ++ * Get a QEMU Audiosystem compatible frequency value from a > ++ * VIRTIO_SND_PCM_RATE_* > ++ */ > ++static uint32_t virtio_snd_get_qemu_freq(uint32_t rate) > ++{ > ++ #define CASE(RATE) \ > ++ case VIRTIO_SND_PCM_RATE_##RATE: \ > ++ return RATE; > ++ > ++ switch (rate) { > ++ CASE(5512) > ++ CASE(8000) > ++ CASE(11025) > ++ CASE(16000) > ++ CASE(22050) > ++ CASE(32000) > ++ CASE(44100) > ++ CASE(48000) > ++ CASE(64000) > ++ CASE(88200) > ++ CASE(96000) > ++ CASE(176400) > ++ CASE(192000) > ++ CASE(384000) > ++ default: > ++ g_assert_not_reached(); > ++ } > ++ > ++ #undef CASE > ++} > ++ > ++/* > ++ * Get QEMU Audiosystem compatible audsettings from virtio based pcm > stream > ++ * params. > ++ */ > ++static void virtio_snd_get_qemu_audsettings(audsettings *as, > ++ virtio_snd_pcm_set_params > *params) > ++{ > ++ as->nchannels = MIN(AUDIO_MAX_CHANNELS, params->channels); > ++ as->fmt = virtio_snd_get_qemu_format(params->format); > ++ as->freq = virtio_snd_get_qemu_freq(params->rate); > ++ as->endianness = target_words_bigendian() ? 1 : 0; > ++} > ++ > ++/* > ++ * Close a stream and free all its resources. > ++ * > ++ * @stream: VirtIOSoundPCMStream *stream > ++ */ > ++static void virtio_snd_pcm_close(VirtIOSoundPCMStream *stream) > ++{ > ++} > ++ > + /* > +- * Queue handler stub. > ++ * Prepares a VirtIOSound card stream. > ++ * Returns the response status code. (VIRTIO_SND_S_*). > ++ * > ++ * @s: VirtIOSound device > ++ * @stream_id: stream id > ++ */ > ++static uint32_t virtio_snd_pcm_prepare(VirtIOSound *s, uint32_t > stream_id) > ++{ > ++ audsettings as; > ++ virtio_snd_pcm_set_params *params; > ++ VirtIOSoundPCMStream *stream; > ++ > ++ if (s->pcm->streams == NULL || > ++ s->pcm->pcm_params == NULL || > ++ stream_id >= s->snd_conf.streams) { > ++ return cpu_to_le32(VIRTIO_SND_S_BAD_MSG); > ++ } > ++ > ++ params = virtio_snd_pcm_get_params(s, stream_id); > ++ if (params == NULL) { > ++ return cpu_to_le32(VIRTIO_SND_S_BAD_MSG); > ++ } > ++ > ++ stream = virtio_snd_pcm_get_stream(s, stream_id); > ++ if (stream == NULL) { > ++ stream = g_new0(VirtIOSoundPCMStream, 1); > ++ stream->active = false; > ++ stream->id = stream_id; > ++ stream->pcm = s->pcm; > ++ stream->s = s; > ++ > ++ /* > ++ * stream_id >= s->snd_conf.streams was checked before so this > is > ++ * in-bounds > ++ */ > ++ s->pcm->streams[stream_id] = stream; > ++ } > ++ > ++ virtio_snd_get_qemu_audsettings(&as, params); > ++ stream->info.direction = stream_id < s->snd_conf.streams / 2 + > ++ (s->snd_conf.streams & 1) ? VIRTIO_SND_D_OUTPUT : > VIRTIO_SND_D_INPUT; > ++ stream->info.hdr.hda_fn_nid = VIRTIO_SOUND_HDA_FN_NID; > ++ stream->info.features = 0; > ++ stream->info.channels_min = 1; > ++ stream->info.channels_max = as.nchannels; > ++ stream->info.formats = supported_formats; > ++ stream->info.rates = supported_rates; > ++ stream->params = *params; > ++ > ++ stream->positions[0] = VIRTIO_SND_CHMAP_FL; > ++ stream->positions[1] = VIRTIO_SND_CHMAP_FR; > ++ stream->as = as; > ++ > ++ return cpu_to_le32(VIRTIO_SND_S_OK); > ++} > ++ > +static const char *print_code(uint32_t code) > +{ > + #define CASE(CODE) \ > @@ hw/virtio/virtio-snd.c: virtio_snd_set_config(VirtIODevice *vdev, > const uint8_t > + cmd->elem->out_num, > + 0, > + &cmd->ctrl, > -+ sizeof(cmd->ctrl)); > ++ sizeof(virtio_snd_hdr)); > + > -+ if (msg_sz != sizeof(cmd->ctrl)) { > ++ if (msg_sz != sizeof(virtio_snd_hdr)) { > ++ /* > ++ * TODO: do we need to set DEVICE_NEEDS_RESET? > ++ */ > + qemu_log_mask(LOG_GUEST_ERROR, > + "%s: virtio-snd command size incorrect %zu vs \ > -+ %zu\n", __func__, msg_sz, sizeof(cmd->ctrl)); > ++ %zu\n", __func__, msg_sz, sizeof(virtio_snd_hdr)); > + return; > + } > + > @@ hw/virtio/virtio-snd.c: virtio_snd_set_config(VirtIODevice *vdev, > const uint8_t > + cmd->elem->in_num, > + 0, > + &cmd->resp, > -+ sizeof(cmd->resp)); > -+ virtqueue_push(cmd->vq, cmd->elem, sizeof(cmd->elem)); > ++ sizeof(virtio_snd_hdr)); > ++ virtqueue_push(cmd->vq, cmd->elem, sizeof(virtio_snd_hdr)); > + virtio_notify(VIRTIO_DEVICE(s), cmd->vq); > +} > + > @@ hw/virtio/virtio-snd.c: virtio_snd_set_config(VirtIODevice *vdev, > const uint8_t > + } > +} > + > - /* > -- * Queue handler stub. > ++/* > + * The control message handler. Pops an element from the control > virtqueue, > + * and stores them to VirtIOSound's cmdq queue and finally calls > + * virtio_snd_process_cmdq() for processing. > @@ hw/virtio/virtio-snd.c: virtio_snd_set_config(VirtIODevice *vdev, > const uint8_t > static uint64_t get_features(VirtIODevice *vdev, uint64_t features, > Error **errp) > @@ hw/virtio/virtio-snd.c: static void virtio_snd_realize(DeviceState > *dev, Error **errp) > + ERRP_GUARD(); > VirtIOSound *vsnd = VIRTIO_SND(dev); > VirtIODevice *vdev = VIRTIO_DEVICE(dev); > ++ virtio_snd_pcm_set_params default_params = { 0 }; > ++ uint32_t status; > > + vsnd->pcm = NULL; > vsnd->vmstate = > @@ hw/virtio/virtio-snd.c: static void virtio_snd_realize(DeviceState > *dev, Error * > > + vsnd->pcm = g_new0(VirtIOSoundPCM, 1); > + vsnd->pcm->snd = vsnd; > -+ vsnd->pcm->streams = g_new0(VirtIOSoundPCMStream *, > vsnd->snd_conf.streams); > -+ vsnd->pcm->pcm_params = g_new0(virtio_snd_pcm_set_params, > vsnd->snd_conf.streams); > ++ vsnd->pcm->streams = > ++ g_new0(VirtIOSoundPCMStream *, vsnd->snd_conf.streams); > ++ vsnd->pcm->pcm_params = > ++ g_new0(virtio_snd_pcm_set_params, vsnd->snd_conf.streams); > + > virtio_init(vdev, VIRTIO_ID_SOUND, sizeof(virtio_snd_config)); > virtio_add_feature(&vsnd->features, VIRTIO_F_VERSION_1); > > @@ hw/virtio/virtio-snd.c: static void virtio_snd_realize(DeviceState > *dev, Error **errp) > + > AUD_register_card("virtio-sound", &vsnd->card); > > ++ /* set default params for all streams */ > ++ default_params.features = 0; > ++ default_params.buffer_bytes = cpu_to_le32(8192); > ++ default_params.period_bytes = cpu_to_le32(2048); > ++ default_params.channels = 2; > ++ default_params.format = VIRTIO_SND_PCM_FMT_S16; > ++ default_params.rate = VIRTIO_SND_PCM_RATE_48000; > vsnd->queues[VIRTIO_SND_VQ_CONTROL] = > - virtio_add_queue(vdev, 64, virtio_snd_handle_queue); > + virtio_add_queue(vdev, 64, virtio_snd_handle_ctrl); > @@ hw/virtio/virtio-snd.c: static void virtio_snd_realize(DeviceState > *dev, Error * > + virtio_add_queue(vdev, 64, virtio_snd_handle_xfer); > + qemu_mutex_init(&vsnd->cmdq_mutex); > + QTAILQ_INIT(&vsnd->cmdq); > -+} > + > -+/* > -+ * Close the stream and free its resources. > -+ * > -+ * @stream: VirtIOSoundPCMStream *stream > -+ */ > -+static void virtio_snd_pcm_close(VirtIOSoundPCMStream *stream) > -+{ > ++ for (uint32_t i = 0; i < vsnd->snd_conf.streams; i++) { > ++ status = virtio_snd_set_pcm_params(vsnd, i, &default_params); > ++ if (status != cpu_to_le32(VIRTIO_SND_S_OK)) { > ++ error_setg(errp, > ++ "Can't initalize stream params, device responded > with %s.", > ++ print_code(status)); > ++ return; > ++ } > ++ status = virtio_snd_pcm_prepare(vsnd, i); > ++ if (status != cpu_to_le32(VIRTIO_SND_S_OK)) { > ++ error_setg(errp, > ++ "Can't prepare streams, device responded with > %s.", > ++ print_code(status)); > ++ return; > ++ } > ++ } > } > > static void virtio_snd_unrealize(DeviceState *dev) > @@ include/hw/virtio/virtio-snd.h: typedef struct virtio_snd_pcm_xfer > virtio_snd_pc > + > +struct VirtIOSoundPCM { > + VirtIOSound *snd; > ++ /* > ++ * PCM parameters are a separate field instead of a > VirtIOSoundPCMStream > ++ * field, because the operation of PCM control requests is first > ++ * VIRTIO_SND_R_PCM_SET_PARAMS and then VIRTIO_SND_R_PCM_PREPARE; > this > ++ * means that some times we get parameters without having an > allocated > ++ * stream yet. > ++ */ > + virtio_snd_pcm_set_params *pcm_params; > + VirtIOSoundPCMStream **streams; > +}; > + > ++struct VirtIOSoundPCMStream { > ++ VirtIOSoundPCM *pcm; > ++ virtio_snd_pcm_info info; > ++ virtio_snd_pcm_set_params params; > ++ uint32_t id; > ++ /* channel position values (VIRTIO_SND_CHMAP_XXX) */ > ++ uint8_t positions[VIRTIO_SND_CHMAP_MAX_SIZE]; > ++ VirtIOSound *s; > ++ bool flushing; > ++ audsettings as; > ++ union { > ++ SWVoiceIn *in; > ++ SWVoiceOut *out; > ++ } voice; > ++ bool active; > ++}; > ++ > +/* > + * PCM stream state machine. > + * ------------------------- > @@ include/hw/virtio/virtio-snd.h: typedef struct virtio_snd_pcm_xfer > virtio_snd_pc > + * > + * The CTRL message is finally handled in `process_cmd()`. > + */ > -+struct VirtIOSoundPCMStream { > -+ VirtIOSoundPCM *pcm; > -+ virtio_snd_pcm_info info; > -+ virtio_snd_pcm_set_params params; > -+ uint32_t id; > -+ /* channel position values (VIRTIO_SND_CHMAP_XXX) */ > -+ uint8_t positions[VIRTIO_SND_CHMAP_MAX_SIZE]; > -+ VirtIOSound *s; > -+ bool flushing; > -+ audsettings as; > -+ union { > -+ SWVoiceIn *in; > -+ SWVoiceOut *out; > -+ } voice; > -+ QemuMutex queue_mutex; > -+ QSIMPLEQ_HEAD(, VirtIOSoundPCMBlock) queue; > -+}; > -+ > +struct VirtIOSound { > VirtIODevice parent_obj; > > 4: 49eb46f184 < -: ---------- virtio-sound: set PCM stream parameters > 5: 1f1dc7a200 ! 4: 28b2ecfa1f virtio-sound: handle VIRTIO_SND_R_PCM_INFO > request > @@ hw/virtio/trace-events: virtio_snd_vm_state_stopped(void) "vm state > stopped" > virtio_snd_handle_event(void) "event queue callback called" > > ## hw/virtio/virtio-snd.c ## > -@@ > - #define VIRTIO_SOUND_CHMAP_DEFAULT 0 > - #define VIRTIO_SOUND_HDA_FN_NID 0 > - > -+static struct virtio_snd_info info_to_le32(struct virtio_snd_info val) { > -+ val.hda_fn_nid = cpu_to_le32(val.hda_fn_nid); > -+ > -+ return val; > -+} > -+ > -+static virtio_snd_pcm_info pcm_info_to_le32(virtio_snd_pcm_info val) { > -+ val.hdr = info_to_le32(val.hdr); > -+ val.features = cpu_to_le32(val.features); > -+ val.formats = cpu_to_le64(val.formats); > -+ val.rates = cpu_to_le64(val.rates); > -+ > -+ return val; > -+} > -+ > - static uint32_t supported_formats = BIT(VIRTIO_SND_PCM_FMT_S8) > - | BIT(VIRTIO_SND_PCM_FMT_U8) > - | BIT(VIRTIO_SND_PCM_FMT_S16) > @@ hw/virtio/virtio-snd.c: static virtio_snd_pcm_set_params > *virtio_snd_pcm_get_params(VirtIOSound *s, > : &s->pcm->pcm_params[stream_id]; > } > @@ hw/virtio/virtio-snd.c: static virtio_snd_pcm_set_params > *virtio_snd_pcm_get_par > + virtio_snd_ctrl_command *cmd) > +{ > + uint32_t stream_id, start_id, count, size; > ++ virtio_snd_pcm_info val; > + virtio_snd_query_info req; > + VirtIOSoundPCMStream *stream = NULL; > + g_autofree virtio_snd_pcm_info *pcm_info = NULL; > @@ hw/virtio/virtio-snd.c: static virtio_snd_pcm_set_params > *virtio_snd_pcm_get_par > + cmd->elem->out_num, > + 0, > + &req, > -+ sizeof(req)); > ++ sizeof(virtio_snd_query_info)); > + > + if (msg_sz != sizeof(virtio_snd_query_info)) { > ++ /* > ++ * TODO: do we need to set DEVICE_NEEDS_RESET? > ++ */ > ++ qemu_log_mask(LOG_GUEST_ERROR, > ++ "%s: virtio-snd command size incorrect %zu vs \ > ++ %zu\n", __func__, msg_sz, > sizeof(virtio_snd_query_info)); > + cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG); > + return; > + } > @@ hw/virtio/virtio-snd.c: static virtio_snd_pcm_set_params > *virtio_snd_pcm_get_par > + > + if (iov_size(cmd->elem->in_sg, cmd->elem->in_num) < > + sizeof(virtio_snd_hdr) + size * count) { > ++ /* > ++ * TODO: do we need to set DEVICE_NEEDS_RESET? > ++ */ > + error_report("pcm info: buffer too small, got: %zu, needed: > %zu", > + iov_size(cmd->elem->in_sg, cmd->elem->in_num), > + sizeof(virtio_snd_pcm_info)); > @@ hw/virtio/virtio-snd.c: static virtio_snd_pcm_set_params > *virtio_snd_pcm_get_par > + cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG); > + return; > + } > -+ pcm_info[i] = pcm_info_to_le32(stream->info); > ++ val = stream->info; > ++ val.hdr.hda_fn_nid = cpu_to_le32(val.hdr.hda_fn_nid); > ++ val.features = cpu_to_le32(val.features); > ++ val.formats = cpu_to_le64(val.formats); > ++ val.rates = cpu_to_le64(val.rates); > ++ /* > ++ * 5.14.6.6.2.1 Device Requirements: Stream Information The > device MUST > ++ * NOT set undefined feature, format, rate and direction > values. The > ++ * device MUST initialize the padding bytes to 0. > ++ */ > ++ pcm_info[i] = val; > ++ memset(&pcm_info[i].padding, 0, 5); > + } > + > + cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK); > 6: 7fbbe07fda ! 5: a52d20b2c3 virtio-sound: handle > VIRTIO_SND_R_PCM_{START,STOP} > @@ hw/virtio/virtio-snd.c: static const char *print_code(uint32_t code) > + cmd->elem->out_num, > + 0, > + &req, > -+ sizeof(req)); > ++ sizeof(virtio_snd_pcm_hdr)); > + > + if (msg_sz != sizeof(virtio_snd_pcm_hdr)) { > ++ qemu_log_mask(LOG_GUEST_ERROR, > ++ "%s: virtio-snd command size incorrect %zu vs \ > ++ %zu\n", __func__, msg_sz, sizeof(virtio_snd_pcm_hdr)); > + cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG); > + return; > + } > @@ hw/virtio/virtio-snd.c: static const char *print_code(uint32_t code) > + trace_virtio_snd_handle_pcm_start_stop(start ? > "VIRTIO_SND_R_PCM_START" : > + "VIRTIO_SND_R_PCM_STOP", stream_id); > + stream = virtio_snd_pcm_get_stream(s, stream_id); > -+ if (!stream) { > ++ if (stream == NULL) { > + error_report("Invalid stream id: %"PRIu32, req.stream_id); > + cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG); > ++ return; > + } > ++ stream->active = start; > +} > + > /* > 7: 92e65a6115 ! 6: 25fbb2eb25 virtio-sound: handle > VIRTIO_SND_R_PCM_SET_PARAMS > @@ hw/virtio/virtio-snd.c: uint32_t virtio_snd_set_pcm_params(VirtIOSound > *s, > + cmd->elem->out_num, > + 0, > + &req, > -+ sizeof(req)); > ++ sizeof(virtio_snd_pcm_set_params)); > + > + if (msg_sz != sizeof(virtio_snd_pcm_set_params)) { > ++ /* > ++ * TODO: do we need to set DEVICE_NEEDS_RESET? > ++ */ > ++ qemu_log_mask(LOG_GUEST_ERROR, > ++ "%s: virtio-snd command size incorrect %zu vs \ > ++ %zu\n", __func__, msg_sz, > sizeof(virtio_snd_pcm_set_params)); > + cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG); > + return; > + } > 8: 173bd9dafe ! 7: 9e8d9923ba virtio-sound: handle > VIRTIO_SND_R_PCM_PREPARE > @@ hw/virtio/virtio-snd.c: static const char *print_code(uint32_t code) > + sizeof(stream_id)); > + > + stream_id = le32_to_cpu(stream_id); > -+ cmd->resp.code = msg_sz == sizeof(uint32_t) > ++ cmd->resp.code = msg_sz == sizeof(stream_id) > + ? virtio_snd_pcm_prepare(s, stream_id) > + : cpu_to_le32(VIRTIO_SND_S_BAD_MSG); > +} > 9: 6e05194e02 ! 8: b50c94decc virtio-sound: handle > VIRTIO_SND_R_PCM_RELEASE > @@ hw/virtio/trace-events: virtio_snd_handle_pcm_set_params(uint32_t > stream) "VIRTI > > ## hw/virtio/virtio-snd.c ## > @@ hw/virtio/virtio-snd.c: static void > virtio_snd_handle_pcm_start_stop(VirtIOSound *s, > - } > + stream->active = start; > } > > +/* > @@ hw/virtio/virtio-snd.c: static void > virtio_snd_handle_pcm_start_stop(VirtIOSound > + &stream_id, > + sizeof(stream_id)); > + > -+ if (msg_sz != sizeof(uint32_t)) { > ++ if (msg_sz != sizeof(stream_id)) { > ++ /* > ++ * TODO: do we need to set DEVICE_NEEDS_RESET? > ++ */ > ++ qemu_log_mask(LOG_GUEST_ERROR, > ++ "%s: virtio-snd command size incorrect %zu vs \ > ++ %zu\n", __func__, msg_sz, sizeof(stream_id)); > + cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG); > + return; > + } > @@ hw/virtio/virtio-snd.c: static void > virtio_snd_handle_pcm_start_stop(VirtIOSound > + stream_id = le32_to_cpu(stream_id); > + trace_virtio_snd_handle_pcm_release(stream_id); > + stream = virtio_snd_pcm_get_stream(s, stream_id); > -+ if (!stream) { > ++ if (stream == NULL) { > ++ /* > ++ * TODO: do we need to set DEVICE_NEEDS_RESET? > ++ */ > + error_report("already released stream %"PRIu32, stream_id); > + virtio_error(VIRTIO_DEVICE(s), > + "already released stream %"PRIu32, > 10: a241f8cf92 ! 9: 4cbb908742 virtio-sound: implement audio output (TX) > @@ Commit message > > Handle output IO messages in the transmit (TX) virtqueue. > > - It allocates a VirtIOSoundPCMBlock for each IO message and copies the > + It allocates a VirtIOSoundPCMBuffer for each IO message and copies > the > data buffer to it. When the IO buffer is written to the host's sound > card, the guest will be notified that it has been consumed. > > @@ hw/virtio/trace-events: virtio_snd_handle_pcm_release(uint32_t stream) > "VIRTIO_S > +virtio_snd_handle_xfer(void) "tx/rx queue callback called" > > ## hw/virtio/virtio-snd.c ## > -@@ hw/virtio/virtio-snd.c: static virtio_snd_pcm_info > pcm_info_to_le32(virtio_snd_pcm_info val) { > - return val; > - } > +@@ > + #define VIRTIO_SOUND_CHMAP_DEFAULT 0 > + #define VIRTIO_SOUND_HDA_FN_NID 0 > > +static void virtio_snd_pcm_out_cb(void *data, int available); > +static void virtio_snd_process_cmdq(VirtIOSound *s); > @@ hw/virtio/virtio-snd.c: static virtio_snd_pcm_info > pcm_info_to_le32(virtio_snd_p > | BIT(VIRTIO_SND_PCM_FMT_U8) > | BIT(VIRTIO_SND_PCM_FMT_S16) > @@ hw/virtio/virtio-snd.c: virtio_snd_set_config(VirtIODevice *vdev, > const uint8_t *config) > - memcpy(&s->snd_conf, sndconfig, sizeof(s->snd_conf)); > + > } > > +static void > -+virtio_snd_pcm_block_free(VirtIOSoundPCMBlock *block) > ++virtio_snd_pcm_buffer_free(VirtIOSoundPCMBuffer *buffer) > +{ > -+ g_free(block->elem); > -+ g_free(block); > ++ g_free(buffer->elem); > ++ g_free(buffer); > +} > + > static void > @@ hw/virtio/virtio-snd.c: static void > virtio_snd_get_qemu_audsettings(audsettings > */ > static void virtio_snd_pcm_close(VirtIOSoundPCMStream *stream) > { > -+ VirtIOSoundPCMBlock *block, *next; > -+ > + if (stream) { > -+ WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) { > -+ QSIMPLEQ_FOREACH_SAFE(block, &stream->queue, entry, next) { > -+ virtqueue_push(block->vq, > -+ block->elem, > -+ sizeof(block->elem)); > -+ virtio_notify(VIRTIO_DEVICE(stream->s), > -+ block->vq); > -+ QSIMPLEQ_REMOVE_HEAD(&stream->queue, entry); > -+ virtio_snd_pcm_block_free(block); > -+ } > -+ } > + if (stream->info.direction == VIRTIO_SND_D_OUTPUT) { > ++ virtio_snd_pcm_flush(stream); > + AUD_close_out(&stream->pcm->snd->card, stream->voice.out); > + stream->voice.out = NULL; > + } > @@ hw/virtio/virtio-snd.c: static void > virtio_snd_get_qemu_audsettings(audsettings > } > > /* > +@@ hw/virtio/virtio-snd.c: static uint32_t > virtio_snd_pcm_prepare(VirtIOSound *s, uint32_t stream_id) > + stream->id = stream_id; > + stream->pcm = s->pcm; > + stream->s = s; > ++ qemu_mutex_init(&stream->queue_mutex); > ++ QSIMPLEQ_INIT(&stream->queue); > ++ QSIMPLEQ_INIT(&stream->invalid); > + > + /* > + * stream_id >= s->snd_conf.streams was checked before so this > is > @@ hw/virtio/virtio-snd.c: static uint32_t > virtio_snd_pcm_prepare(VirtIOSound *s, uint32_t stream_id) > stream->positions[1] = VIRTIO_SND_CHMAP_FR; > stream->as = as; > @@ hw/virtio/virtio-snd.c: static uint32_t > virtio_snd_pcm_prepare(VirtIOSound *s, u > return cpu_to_le32(VIRTIO_SND_S_OK); > } > > -@@ hw/virtio/virtio-snd.c: static void > virtio_snd_handle_pcm_start_stop(VirtIOSound *s, > - bool start) > - { > - VirtIOSoundPCMStream *stream; > -+ VirtIOSoundPCMBlock *block, *next; > - virtio_snd_pcm_hdr req; > - uint32_t stream_id; > - size_t msg_sz = iov_to_buf(cmd->elem->out_sg, > @@ hw/virtio/virtio-snd.c: static void > virtio_snd_handle_pcm_start_stop(VirtIOSound *s, > cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK); > trace_virtio_snd_handle_pcm_start_stop(start ? > "VIRTIO_SND_R_PCM_START" : > "VIRTIO_SND_R_PCM_STOP", stream_id); > + > stream = virtio_snd_pcm_get_stream(s, stream_id); > -- if (!stream) { > +- if (stream == NULL) { > - error_report("Invalid stream id: %"PRIu32, req.stream_id); > + if (stream) { > ++ WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) { > ++ stream->active = start; > ++ } > + if (stream->info.direction == VIRTIO_SND_D_OUTPUT) { > + AUD_set_active_out(stream->voice.out, start); > + } > -+ /* remove previous buffers. */ > -+ WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) { > -+ QSIMPLEQ_FOREACH_SAFE(block, &stream->queue, entry, next) { > -+ virtqueue_push(block->vq, > -+ block->elem, > -+ sizeof(block->elem)); > -+ virtio_notify(VIRTIO_DEVICE(stream->s), block->vq); > -+ QSIMPLEQ_REMOVE_HEAD(&stream->queue, entry); > -+ virtio_snd_pcm_block_free(block); > -+ } > -+ } > + } else { > + error_report("Invalid stream id: %"PRIu32, stream_id); > cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG); > + return; > } > +@@ hw/virtio/virtio-snd.c: static void > virtio_snd_handle_pcm_start_stop(VirtIOSound *s, > } > > /* > @@ hw/virtio/virtio-snd.c: static void > virtio_snd_handle_pcm_start_stop(VirtIOSound > + */ > +static size_t virtio_snd_pcm_get_io_msgs_count(VirtIOSoundPCMStream > *stream) > +{ > -+ VirtIOSoundPCMBlock *block; > -+ VirtIOSoundPCMBlock *next; > ++ VirtIOSoundPCMBuffer *buffer, *next; > + size_t count = 0; > + > + WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) { > -+ QSIMPLEQ_FOREACH_SAFE(block, &stream->queue, entry, next) { > ++ QSIMPLEQ_FOREACH_SAFE(buffer, &stream->queue, entry, next) { > ++ count += 1; > ++ } > ++ QSIMPLEQ_FOREACH_SAFE(buffer, &stream->invalid, entry, next) { > + count += 1; > + } > + } > @@ hw/virtio/virtio-snd.c: static void > virtio_snd_handle_pcm_release(VirtIOSound *s > + * - The device MUST NOT complete the control request while > there > + * are pending I/O messages for the specified stream ID. > + */ > -+ virtio_snd_process_cmdq(stream->s); > + trace_virtio_snd_pcm_stream_flush(stream_id); > + virtio_snd_pcm_flush(stream); > + } > @@ hw/virtio/virtio-snd.c: static void > virtio_snd_handle_event(VirtIODevice *vdev, > +{ > + VirtIOSound *s = VIRTIO_SND(vdev); > + VirtIOSoundPCMStream *stream = NULL; > -+ VirtIOSoundPCMBlock *block; > ++ VirtIOSoundPCMBuffer *buffer; > + VirtQueueElement *elem; > + size_t msg_sz, size; > + virtio_snd_pcm_xfer hdr; > + virtio_snd_pcm_status resp = { 0 }; > + uint32_t stream_id; > ++ /* > ++ * If any of the I/O messages are invalid, put them in > stream->invalid and > ++ * return them after the for loop. > ++ */ > ++ bool must_empty_invalid_queue = false; > + > -+ trace_virtio_snd_handle_xfer(); > ++ if (!virtio_queue_ready(vq)) { > ++ return; > ++ } > ++ trace_virtio_snd_handle_tx_xfer(); > + > + for (;;) { > + elem = virtqueue_pop(vq, sizeof(VirtQueueElement)); > @@ hw/virtio/virtio-snd.c: static void > virtio_snd_handle_event(VirtIODevice *vdev, > + } > + /* get the message hdr object */ > + msg_sz = iov_to_buf(elem->out_sg, > -+ elem->out_num, > -+ 0, > -+ &hdr, > -+ sizeof(hdr)); > -+ if (msg_sz != sizeof(hdr)) { > ++ elem->out_num, > ++ 0, > ++ &hdr, > ++ sizeof(virtio_snd_pcm_xfer)); > ++ if (msg_sz != sizeof(virtio_snd_pcm_xfer)) { > + goto tx_err; > + } > + stream_id = le32_to_cpu(hdr.stream_id); > + > + if (stream_id >= s->snd_conf.streams > -+ || !s->pcm->streams[stream_id]) { > ++ || s->pcm->streams[stream_id] == NULL) { > + goto tx_err; > + } > + > + stream = s->pcm->streams[stream_id]; > -+ if (!stream || stream->info.direction != VIRTIO_SND_D_OUTPUT) { > ++ if (stream->info.direction != VIRTIO_SND_D_OUTPUT) { > + goto tx_err; > + } > + > + WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) { > -+ size = iov_size(elem->out_sg, elem->out_num) - > -+ sizeof(virtio_snd_pcm_xfer); > -+ block = g_malloc0(sizeof(VirtIOSoundPCMBlock) + size); > -+ block->elem = elem; > -+ block->vq = vq; > -+ block->size = size; > -+ block->offset = 0; > ++ size = iov_size(elem->out_sg, elem->out_num); > + > -+ iov_to_buf(elem->out_sg, > -+ elem->out_num, > -+ sizeof(virtio_snd_pcm_xfer), > -+ block->data, > -+ size); > ++ buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer) + size); > ++ buffer->elem = elem; > ++ buffer->stale = true; > ++ buffer->vq = vq; > ++ buffer->size = size; > ++ buffer->offset = sizeof(virtio_snd_pcm_xfer); > + > -+ QSIMPLEQ_INSERT_TAIL(&stream->queue, block, entry); > ++ QSIMPLEQ_INSERT_TAIL(&stream->queue, buffer, entry); > + } > + continue; > + > +tx_err: > + WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) { > -+ resp.status = cpu_to_le32(VIRTIO_SND_S_BAD_MSG); > -+ iov_from_buf(elem->in_sg, > -+ elem->in_num, > -+ 0, > -+ &resp, > -+ sizeof(resp)); > -+ virtqueue_push(vq, elem, sizeof(elem)); > -+ break; > ++ must_empty_invalid_queue = true; > ++ buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer)); > ++ buffer->elem = elem; > ++ buffer->vq = vq; > ++ QSIMPLEQ_INSERT_TAIL(&stream->invalid, buffer, entry); > + } > + } > + > -+ /* > -+ * Notify vq about virtio_snd_pcm_status responses. > -+ * Buffer responses must be notified separately later. > -+ */ > -+ virtio_notify(VIRTIO_DEVICE(s), vq); > ++ if (must_empty_invalid_queue) { > ++ WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) { > ++ while (!QSIMPLEQ_EMPTY(&stream->invalid)) { > ++ buffer = QSIMPLEQ_FIRST(&stream->invalid); > ++ > ++ resp.status = cpu_to_le32(VIRTIO_SND_S_BAD_MSG); > ++ iov_from_buf(buffer->elem->in_sg, > ++ buffer->elem->in_num, > ++ 0, > ++ &resp, > ++ sizeof(virtio_snd_pcm_status)); > ++ virtqueue_push(vq, buffer->elem, > sizeof(virtio_snd_pcm_status)); > ++ QSIMPLEQ_REMOVE_HEAD(&stream->invalid, entry); > ++ virtio_snd_pcm_buffer_free(buffer); > ++ } > ++ /* > ++ * Notify vq about virtio_snd_pcm_status responses. > ++ * Buffer responses must be notified separately later. > ++ */ > ++ virtio_notify(vdev, vq); > ++ } > ++ } > +} > + > /* > @@ hw/virtio/virtio-snd.c: static void virtio_snd_realize(DeviceState > *dev, Error * > +static void virtio_snd_pcm_out_cb(void *data, int available) > +{ > + VirtIOSoundPCMStream *stream = data; > -+ VirtIOSoundPCMBlock *block; > ++ VirtIOSoundPCMBuffer *buffer; > + size_t size; > + > + WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) { > + while (!QSIMPLEQ_EMPTY(&stream->queue)) { > -+ block = QSIMPLEQ_FIRST(&stream->queue); > -+ > ++ buffer = QSIMPLEQ_FIRST(&stream->queue); > ++ if (!virtio_queue_ready(buffer->vq)) { > ++ return; > ++ } > ++ if (!stream->active) { > ++ /* Stream has stopped, so do not perform AUD_write. */ > ++ goto return_tx_buffer; > ++ } > ++ if (buffer->stale) { > ++ iov_to_buf(buffer->elem->out_sg, > ++ buffer->elem->out_num, > ++ buffer->offset, > ++ buffer->data, > ++ buffer->size); > ++ buffer->stale = false; > ++ } > + for (;;) { > + size = AUD_write(stream->voice.out, > -+ block->data + block->offset, > -+ MIN(block->size, available)); > -+ assert(size <= MIN(block->size, available)); > ++ buffer->data + buffer->offset, > ++ MIN(buffer->size, available)); > ++ assert(size <= MIN(buffer->size, available)); > + if (size == 0) { > + /* break out of both loops */ > + available = 0; > + break; > + } > -+ block->size -= size; > -+ block->offset += size; > ++ buffer->size -= size; > ++ buffer->offset += size; > + available -= size; > -+ if (!block->size) { > ++ if (buffer->size < 1) { > ++return_tx_buffer: > + virtio_snd_pcm_status resp = { 0 }; > + resp.status = cpu_to_le32(VIRTIO_SND_S_OK); > + resp.latency_bytes = 0; > -+ iov_from_buf(block->elem->in_sg, > -+ block->elem->in_num, > ++ iov_from_buf(buffer->elem->in_sg, > ++ buffer->elem->in_num, > + 0, > + &resp, > -+ sizeof(resp)); > -+ virtqueue_push(block->vq, > -+ block->elem, > -+ sizeof(block->elem)); > -+ virtio_notify(VIRTIO_DEVICE(stream->s), block->vq); > ++ sizeof(virtio_snd_pcm_status)); > ++ virtqueue_push(buffer->vq, > ++ buffer->elem, > ++ sizeof(virtio_snd_pcm_status)); > ++ virtio_notify(VIRTIO_DEVICE(stream->s), buffer->vq); > + QSIMPLEQ_REMOVE_HEAD(&stream->queue, entry); > -+ virtio_snd_pcm_block_free(block); > ++ virtio_snd_pcm_buffer_free(buffer); > + break; > + } > -+ > + if (!available) { > + break; > + } > @@ hw/virtio/virtio-snd.c: static void virtio_snd_realize(DeviceState > *dev, Error * > + */ > +static void virtio_snd_pcm_flush(VirtIOSoundPCMStream *stream) > +{ > -+ VirtIOSoundPCMBlock *block; > -+ VirtIOSoundPCMBlock *next; > ++ VirtIOSoundPCMBuffer *buffer, *next; > + > + WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) { > -+ QSIMPLEQ_FOREACH_SAFE(block, &stream->queue, entry, next) { > -+ AUD_write(stream->voice.out, block->data + block->offset, > block->size); > -+ virtqueue_push(block->vq, block->elem, sizeof(block->elem)); > -+ virtio_notify(VIRTIO_DEVICE(stream->s), block->vq); > -+ QSIMPLEQ_REMOVE(&stream->queue, block, VirtIOSoundPCMBlock, > entry); > ++ QSIMPLEQ_FOREACH_SAFE(buffer, &stream->queue, entry, next) { > ++ AUD_write(stream->voice.out, > ++ buffer->data + buffer->offset, > ++ buffer->size); > ++ virtqueue_push(buffer->vq, > ++ buffer->elem, > ++ sizeof(VirtQueueElement)); > ++ virtio_notify(VIRTIO_DEVICE(stream->s), buffer->vq); > ++ QSIMPLEQ_REMOVE(&stream->queue, > ++ buffer, > ++ VirtIOSoundPCMBuffer, > ++ entry); > ++ virtio_snd_pcm_buffer_free(buffer); > + } > + } > +} > @@ hw/virtio/virtio-snd.c: static void virtio_snd_realize(DeviceState > *dev, Error * > static void virtio_snd_unrealize(DeviceState *dev) > { > VirtIODevice *vdev = VIRTIO_DEVICE(dev); > +@@ hw/virtio/virtio-snd.c: static void virtio_snd_unrealize(DeviceState > *dev) > + if (stream) { > + virtio_snd_process_cmdq(stream->s); > + virtio_snd_pcm_close(stream); > ++ qemu_mutex_destroy(&stream->queue_mutex); > + g_free(stream); > + } > + } > > ## include/hw/virtio/virtio-snd.h ## > @@ include/hw/virtio/virtio-snd.h: typedef struct > virtio_snd_ctrl_command virtio_snd_ctrl_command; > > typedef struct VirtIOSoundPCM VirtIOSoundPCM; > > -+typedef struct VirtIOSoundPCMBlock VirtIOSoundPCMBlock; > ++typedef struct VirtIOSoundPCMBuffer VirtIOSoundPCMBuffer; > + > -+struct VirtIOSoundPCMBlock { > -+ QSIMPLEQ_ENTRY(VirtIOSoundPCMBlock) entry; > ++/* > ++ * The VirtIO sound spec reuses layouts and values from the High > Definition > ++ * Audio spec (virtio/v1.2: 5.14 Sound Device). This struct handles > each I/O > ++ * message's buffer (virtio/v1.2: 5.14.6.8 PCM I/O Messages). > ++ * > ++ * In the case of TX (i.e. playback) buffers, we defer reading the raw > PCM data > ++ * from the virtqueue until QEMU's sound backsystem calls the output > callback. > ++ * This is tracked by the `bool stale;` field, which is set to false > when data > ++ * has been read into our own buffer for consumption. > ++ * > ++ * VirtIOSoundPCMBuffer has a dynamic size since it includes the raw > PCM data > ++ * in its allocation. It must be initialized and destroyed as follows: > ++ * > ++ * size_t size = [[derived from owned VQ element descriptor sizes]]; > ++ * buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer) + size); > ++ * buffer->elem = [[owned VQ element]]; > ++ * > ++ * [..] > ++ * > ++ * g_free(buffer->elem); > ++ * g_free(buffer); > ++ */ > ++struct VirtIOSoundPCMBuffer { > ++ QSIMPLEQ_ENTRY(VirtIOSoundPCMBuffer) entry; > + VirtQueueElement *elem; > + VirtQueue *vq; > + size_t size; > ++ /* > ++ * In TX / Plaback, `offset` represents the first unused position > inside > ++ * `data`. If `offset == size` then there are no unused data left. > ++ */ > + uint64_t offset; > ++ /* Used for the TX queue for lazy I/O copy from `elem` */ > ++ bool stale; > ++ /* > ++ * VirtIOSoundPCMBuffer is an unsized type because it ends with an > array of > ++ * bytes. The size of `data` is determined from the I/O message's > read-only > ++ * or write-only size when allocating VirtIOSoundPCMBuffer. > ++ */ > + uint8_t data[]; > +}; > + > struct VirtIOSoundPCM { > VirtIOSound *snd; > - virtio_snd_pcm_set_params *pcm_params; > + /* > +@@ include/hw/virtio/virtio-snd.h: struct VirtIOSoundPCMStream { > + SWVoiceIn *in; > + SWVoiceOut *out; > + } voice; > ++ QemuMutex queue_mutex; > + bool active; > ++ QSIMPLEQ_HEAD(, VirtIOSoundPCMBuffer) queue; > ++ QSIMPLEQ_HEAD(, VirtIOSoundPCMBuffer) invalid; > + }; > + > + /* > 11: 6f3526e067 ! 10: 992b0d5ff4 virtio-sound: implement audio capture (RX) > @@ hw/virtio/virtio-snd.c > #define VIRTIO_SOUND_CHMAP_DEFAULT 0 > #define VIRTIO_SOUND_HDA_FN_NID 0 > > -@@ hw/virtio/virtio-snd.c: static virtio_snd_pcm_info > pcm_info_to_le32(virtio_snd_pcm_info val) { > - > static void virtio_snd_pcm_out_cb(void *data, int available); > static void virtio_snd_process_cmdq(VirtIOSound *s); > -static void virtio_snd_pcm_flush(VirtIOSoundPCMStream *stream); > @@ hw/virtio/virtio-snd.c: static virtio_snd_pcm_info > pcm_info_to_le32(virtio_snd_p > static uint32_t supported_formats = BIT(VIRTIO_SND_PCM_FMT_S8) > | BIT(VIRTIO_SND_PCM_FMT_U8) > @@ hw/virtio/virtio-snd.c: static void > virtio_snd_pcm_close(VirtIOSoundPCMStream *stream) > + { > + if (stream) { > if (stream->info.direction == VIRTIO_SND_D_OUTPUT) { > +- virtio_snd_pcm_flush(stream); > ++ virtio_snd_pcm_out_flush(stream); > AUD_close_out(&stream->pcm->snd->card, stream->voice.out); > stream->voice.out = NULL; > + } else if (stream->info.direction == VIRTIO_SND_D_INPUT) { > ++ virtio_snd_pcm_in_flush(stream); > + AUD_close_in(&stream->pcm->snd->card, stream->voice.in); > + stream->voice.in = NULL; > } > @@ hw/virtio/virtio-snd.c: static uint32_t > virtio_snd_pcm_prepare(VirtIOSound *s, u > + stream, > + virtio_snd_pcm_in_cb, > + &as); > ++ AUD_set_volume_in(stream->voice.in, 0, 255, 255); > } > > return cpu_to_le32(VIRTIO_SND_S_OK); > @@ hw/virtio/virtio-snd.c: static void > virtio_snd_handle_pcm_start_stop(VirtIOSound *s, > - if (stream) { > + } > if (stream->info.direction == VIRTIO_SND_D_OUTPUT) { > AUD_set_active_out(stream->voice.out, start); > + } else { > + AUD_set_active_in(stream->voice.in, start); > } > - /* remove previous buffers. */ > - WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) { > + } else { > + error_report("Invalid stream id: %"PRIu32, stream_id); > @@ hw/virtio/virtio-snd.c: static void > virtio_snd_handle_pcm_release(VirtIOSound *s, > + * are pending I/O messages for the specified stream ID. > */ > - virtio_snd_process_cmdq(stream->s); > trace_virtio_snd_pcm_stream_flush(stream_id); > - virtio_snd_pcm_flush(stream); > + if (stream->info.direction == VIRTIO_SND_D_OUTPUT) { > @@ hw/virtio/virtio-snd.c: static void > virtio_snd_handle_event(VirtIODevice *vdev, > { > VirtIOSound *s = VIRTIO_SND(vdev); > VirtIOSoundPCMStream *stream = NULL; > -@@ hw/virtio/virtio-snd.c: static void virtio_snd_handle_tx(VirtIODevice > *vdev, VirtQueue *vq) > - virtio_snd_pcm_status resp = { 0 }; > - uint32_t stream_id; > - > -- trace_virtio_snd_handle_xfer(); > -+ trace_virtio_snd_handle_tx_xfer(); > - > - for (;;) { > - elem = virtqueue_pop(vq, sizeof(VirtQueueElement)); > @@ hw/virtio/virtio-snd.c: tx_err: > } > > @@ hw/virtio/virtio-snd.c: tx_err: > +{ > + VirtIOSound *s = VIRTIO_SND(vdev); > + VirtIOSoundPCMStream *stream = NULL; > -+ VirtIOSoundPCMBlock *block; > ++ VirtIOSoundPCMBuffer *buffer; > + VirtQueueElement *elem; > + size_t msg_sz, size; > + virtio_snd_pcm_xfer hdr; > + virtio_snd_pcm_status resp = { 0 }; > + uint32_t stream_id; > ++ /* > ++ * if any of the I/O messages are invalid, put them in > stream->invalid and > ++ * return them after the for loop. > ++ */ > ++ bool must_empty_invalid_queue = false; > + > ++ if (!virtio_queue_ready(vq)) { > ++ return; > ++ } > + trace_virtio_snd_handle_rx_xfer(); > + > + for (;;) { > @@ hw/virtio/virtio-snd.c: tx_err: > + elem->out_num, > + 0, > + &hdr, > -+ sizeof(hdr)); > -+ if (msg_sz != sizeof(hdr)) { > ++ sizeof(virtio_snd_pcm_xfer)); > ++ if (msg_sz != sizeof(virtio_snd_pcm_xfer)) { > + goto rx_err; > + } > + stream_id = le32_to_cpu(hdr.stream_id); > @@ hw/virtio/virtio-snd.c: tx_err: > + } > + > + stream = s->pcm->streams[stream_id]; > -+ if (!stream || stream->info.direction != VIRTIO_SND_D_INPUT) { > ++ if (stream == NULL || stream->info.direction != > VIRTIO_SND_D_INPUT) { > + goto rx_err; > + } > + WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) { > + size = iov_size(elem->in_sg, elem->in_num) - > + sizeof(virtio_snd_pcm_status); > -+ block = g_malloc0(sizeof(VirtIOSoundPCMBlock) + size); > -+ block->elem = elem; > -+ block->vq = vq; > -+ block->size = 0; > -+ block->offset = 0; > -+ QSIMPLEQ_INSERT_TAIL(&stream->queue, block, entry); > ++ buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer) + size); > ++ buffer->elem = elem; > ++ buffer->vq = vq; > ++ buffer->size = 0; > ++ buffer->offset = 0; > ++ QSIMPLEQ_INSERT_TAIL(&stream->queue, buffer, entry); > + } > + continue; > + > +rx_err: > + WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) { > -+ resp.status = cpu_to_le32(VIRTIO_SND_S_BAD_MSG); > -+ iov_from_buf(elem->in_sg, > -+ elem->in_num, > -+ 0, > -+ &resp, > -+ sizeof(resp)); > ++ must_empty_invalid_queue = true; > ++ buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer)); > ++ buffer->elem = elem; > ++ buffer->vq = vq; > ++ QSIMPLEQ_INSERT_TAIL(&stream->invalid, buffer, entry); > + } > + } > + > -+ /* > -+ * Notify vq about virtio_snd_pcm_status responses. > -+ * Buffer responses must be notified separately later. > -+ */ > -+ virtio_notify(VIRTIO_DEVICE(s), vq); > ++ if (must_empty_invalid_queue) { > ++ WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) { > ++ while (!QSIMPLEQ_EMPTY(&stream->invalid)) { > ++ buffer = QSIMPLEQ_FIRST(&stream->invalid); > ++ > ++ resp.status = cpu_to_le32(VIRTIO_SND_S_BAD_MSG); > ++ iov_from_buf(buffer->elem->in_sg, > ++ buffer->elem->in_num, > ++ 0, > ++ &resp, > ++ sizeof(virtio_snd_pcm_status)); > ++ virtqueue_push(vq, buffer->elem, > sizeof(virtio_snd_pcm_status)); > ++ QSIMPLEQ_REMOVE_HEAD(&stream->invalid, entry); > ++ virtio_snd_pcm_buffer_free(buffer); > ++ } > ++ /* > ++ * Notify vq about virtio_snd_pcm_status responses. > ++ * Buffer responses must be notified separately later. > ++ */ > ++ virtio_notify(vdev, vq); > ++ } > ++ } > +} > > static uint64_t get_features(VirtIODevice *vdev, uint64_t features, > @@ hw/virtio/virtio-snd.c: static void virtio_snd_realize(DeviceState > *dev, Error * > qemu_mutex_init(&vsnd->cmdq_mutex); > QTAILQ_INIT(&vsnd->cmdq); > > -@@ hw/virtio/virtio-snd.c: static void virtio_snd_pcm_out_cb(void *data, > int available) > +@@ hw/virtio/virtio-snd.c: return_tx_buffer: > } > > /* > @@ hw/virtio/virtio-snd.c: static void virtio_snd_pcm_out_cb(void *data, > int availa > -static void virtio_snd_pcm_flush(VirtIOSoundPCMStream *stream) > +static void virtio_snd_pcm_in_cb(void *data, int available) > { > +- VirtIOSoundPCMBuffer *buffer, *next; > + VirtIOSoundPCMStream *stream = data; > - VirtIOSoundPCMBlock *block; > -- VirtIOSoundPCMBlock *next; > ++ VirtIOSoundPCMBuffer *buffer; > + virtio_snd_pcm_status resp = { 0 }; > + size_t size; > > WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) { > -- QSIMPLEQ_FOREACH_SAFE(block, &stream->queue, entry, next) { > -- AUD_write(stream->voice.out, block->data + block->offset, > block->size); > -- virtqueue_push(block->vq, block->elem, sizeof(block->elem)); > -- virtio_notify(VIRTIO_DEVICE(stream->s), block->vq); > -- QSIMPLEQ_REMOVE(&stream->queue, block, VirtIOSoundPCMBlock, > entry); > +- QSIMPLEQ_FOREACH_SAFE(buffer, &stream->queue, entry, next) { > +- AUD_write(stream->voice.out, > +- buffer->data + buffer->offset, > +- buffer->size); > +- virtqueue_push(buffer->vq, > +- buffer->elem, > +- sizeof(VirtQueueElement)); > +- virtio_notify(VIRTIO_DEVICE(stream->s), buffer->vq); > +- QSIMPLEQ_REMOVE(&stream->queue, > +- buffer, > +- VirtIOSoundPCMBuffer, > +- entry); > +- virtio_snd_pcm_buffer_free(buffer); > + while (!QSIMPLEQ_EMPTY(&stream->queue)) { > -+ block = QSIMPLEQ_FIRST(&stream->queue); > ++ buffer = QSIMPLEQ_FIRST(&stream->queue); > ++ if (!virtio_queue_ready(buffer->vq)) { > ++ return; > ++ } > ++ if (!stream->active) { > ++ /* Stream has stopped, so do not perform AUD_read. */ > ++ goto return_rx_buffer; > ++ } > + > + for (;;) { > + size = AUD_read(stream->voice.in, > -+ block->data + block->size, > -+ MIN(available, (stream->params.period_bytes - > block->size))); > ++ buffer->data + buffer->size, > ++ MIN(available, (stream->params.period_bytes - > ++ buffer->size))); > + if (!size) { > + available = 0; > + break; > + } > -+ block->size += size; > ++ buffer->size += size; > + available -= size; > -+ if (block->size >= stream->params.period_bytes) { > ++ if (buffer->size >= stream->params.period_bytes) { > ++return_rx_buffer: > + resp.status = cpu_to_le32(VIRTIO_SND_S_OK); > + resp.latency_bytes = 0; > + /* Copy data -if any- to guest */ > -+ iov_from_buf(block->elem->in_sg, > -+ block->elem->in_num, > ++ iov_from_buf(buffer->elem->in_sg, > ++ buffer->elem->in_num, > + 0, > -+ block->data, > -+ stream->params.period_bytes); > -+ iov_from_buf(block->elem->in_sg, > -+ block->elem->in_num, > -+ block->size, > ++ buffer->data, > ++ buffer->size); > ++ iov_from_buf(buffer->elem->in_sg, > ++ buffer->elem->in_num, > ++ buffer->size, > + &resp, > + sizeof(resp)); > -+ virtqueue_push(block->vq, > -+ block->elem, > -+ sizeof(block->elem)); > -+ virtio_notify(VIRTIO_DEVICE(stream->s), > -+ block->vq); > ++ virtqueue_push(buffer->vq, > ++ buffer->elem, > ++ sizeof(virtio_snd_pcm_status) + > ++ buffer->size); > ++ virtio_notify(VIRTIO_DEVICE(stream->s), buffer->vq); > + QSIMPLEQ_REMOVE_HEAD(&stream->queue, entry); > -+ virtio_snd_pcm_block_free(block); > ++ virtio_snd_pcm_buffer_free(buffer); > + break; > + } > + if (!available) { > @@ hw/virtio/virtio-snd.c: static void virtio_snd_pcm_out_cb(void *data, > int availa > } > } > > -+#define virtio_snd_pcm_flush(AUD_CB) > \ > -+ VirtIOSoundPCMBlock *block; > \ > -+ VirtIOSoundPCMBlock *next; > \ > -+ virtio_snd_pcm_status resp = { 0 }; > \ > -+ resp.status = cpu_to_le32(VIRTIO_SND_S_OK); > \ > -+ WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) { > \ > -+ QSIMPLEQ_FOREACH_SAFE(block, &stream->queue, entry, next) { > \ > -+ do { > \ > -+ AUD_CB; > \ > -+ } while (0) > \ > -+ ; > \ > -+ virtqueue_push(block->vq, block->elem, > sizeof(block->elem));\ > -+ virtio_notify(VIRTIO_DEVICE(stream->s), block->vq); > \ > -+ QSIMPLEQ_REMOVE(&stream->queue, > \ > -+ block, > \ > -+ VirtIOSoundPCMBlock, > \ > -+ entry); > \ > -+ virtio_snd_pcm_block_free(block); > \ > -+ } > \ > -+ } > \ > ++#define virtio_snd_pcm_flush(AUD_CB) > \ > ++ VirtIOSoundPCMBuffer *buffer; > \ > ++ virtio_snd_pcm_status resp = { 0 }; > \ > ++ unsigned int len = 0; > \ > ++ resp.status = cpu_to_le32(VIRTIO_SND_S_OK); > \ > ++ WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) { > \ > ++ while (!QSIMPLEQ_EMPTY(&stream->queue)) { > \ > ++ buffer = QSIMPLEQ_FIRST(&stream->queue); > \ > ++ do { > \ > ++ AUD_CB; > \ > ++ } while (0) > \ > ++ ; > \ > ++ virtqueue_push(buffer->vq, buffer->elem, len); > \ > ++ virtio_notify(VIRTIO_DEVICE(stream->s), buffer->vq); > \ > ++ QSIMPLEQ_REMOVE_HEAD(&stream->queue, entry); > \ > ++ virtio_snd_pcm_buffer_free(buffer); > \ > ++ } > \ > ++ } > + > + > +/* > @@ hw/virtio/virtio-snd.c: static void virtio_snd_pcm_out_cb(void *data, > int availa > + */ > +static void virtio_snd_pcm_out_flush(VirtIOSoundPCMStream *stream) > +{ > -+ /* We should flush the buffers as soon as possible, because it is a > ++ /* > ++ * We should flush the buffers as soon as possible, because it is a > + * time-sensitive operation. > + * > + * TODO: find out if copying leftover flushed data to an > intermediate > + * buffer is a good approach. > + */ > ++ size_t written; > + virtio_snd_pcm_flush( > -+ iov_from_buf(block->elem->in_sg, > -+ block->elem->in_num, > ++ if (stream->active && buffer->stale) { > ++ iov_to_buf(buffer->elem->out_sg, > ++ buffer->elem->out_num, > ++ buffer->offset, > ++ buffer->data, > ++ buffer->size); > ++ buffer->stale = false; > ++ } > ++ if (stream->active) > ++ while (buffer->size > 0) { > ++ written = AUD_write(stream->voice.out, > ++ buffer->data + buffer->offset, > ++ buffer->size); > ++ if (written < 1) { > ++ break; > ++ } > ++ buffer->size -= written; > ++ buffer->offset += written; > ++ } > ++ len = sizeof(virtio_snd_pcm_status); > ++ iov_from_buf(buffer->elem->in_sg, > ++ buffer->elem->in_num, > + 0, > + &resp, > -+ sizeof(resp)); > ++ sizeof(virtio_snd_pcm_status)); > + ); > +} > + > @@ hw/virtio/virtio-snd.c: static void virtio_snd_pcm_out_cb(void *data, > int availa > +static void virtio_snd_pcm_in_flush(VirtIOSoundPCMStream *stream) > +{ > + virtio_snd_pcm_flush( > -+ iov_from_buf(block->elem->in_sg, > -+ block->elem->in_num, > ++ len = sizeof(virtio_snd_pcm_status) + buffer->size; > ++ iov_from_buf(buffer->elem->in_sg, > ++ buffer->elem->in_num, > + 0, > -+ block->data, > -+ block->size); > -+ iov_from_buf(block->elem->in_sg, > -+ block->elem->in_num, > -+ block->size, > ++ buffer->data, > ++ buffer->size); > ++ iov_from_buf(buffer->elem->in_sg, > ++ buffer->elem->in_num, > ++ buffer->size, > + &resp, > -+ sizeof(resp)); > ++ sizeof(virtio_snd_pcm_status)); > + ); > +} > + > static void virtio_snd_unrealize(DeviceState *dev) > { > VirtIODevice *vdev = VIRTIO_DEVICE(dev); > -@@ hw/virtio/virtio-snd.c: static void virtio_snd_unrealize(DeviceState > *dev) > - virtio_cleanup(vdev); > - } > - > -- > - static void virtio_snd_reset(VirtIODevice *vdev) > - { > - VirtIOSound *s = VIRTIO_SND(vdev); > 12: 06e6b17186 ! 11: b720e00121 docs/system: add basic virtio-snd > documentation > @@ Commit message > Signed-off-by: Emmanouil Pitsidianakis > <manos.pitsidiana...@linaro.org> > Reviewed-by: Alex Bennée <alex.ben...@linaro.org> > > + ## MAINTAINERS ## > +@@ MAINTAINERS: M: Manos Pitsidianakis <manos.pitsidiana...@linaro.org> > + S: Supported > + F: hw/virtio/virtio-snd*.c > + F: include/hw/virtio/virtio-snd.h > ++F: docs/system/devices/virtio-snd.rst > + > + nvme > + M: Keith Busch <kbu...@kernel.org> > + > ## docs/system/device-emulation.rst ## > @@ docs/system/device-emulation.rst: Emulated Devices > devices/usb.rst > > base-commit: 36e9aab3c569d4c9ad780473596e18479838d1aa > -- > 2.39.2