 a276ec8e26
			
		
	
	
		a276ec8e26
		
	
	
	
	
		
			
			The VIRTIO Sound Device conforms with the Virtio spec v1.2, thus only use little endianness. Remove the suspicious target_words_bigendian() noticed during code review. Cc: qemu-stable@nongnu.org Fixes: eb9ad377bb ("virtio-sound: handle control messages and streams") Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Michael S. Tsirkin <mst@redhat.com> Message-Id: <20240422211830.25606-1-philmd@linaro.org>
		
			
				
	
	
		
			1412 lines
		
	
	
		
			44 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1412 lines
		
	
	
		
			44 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * VIRTIO Sound Device conforming to
 | |
|  *
 | |
|  * "Virtual I/O Device (VIRTIO) Version 1.2
 | |
|  * Committee Specification Draft 01
 | |
|  * 09 May 2022"
 | |
|  *
 | |
|  * <https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html#x1-52900014>
 | |
|  *
 | |
|  * Copyright (c) 2023 Emmanouil Pitsidianakis <manos.pitsidianakis@linaro.org>
 | |
|  * Copyright (C) 2019 OpenSynergy GmbH
 | |
|  *
 | |
|  * This work is licensed under the terms of the GNU GPL, version 2 or
 | |
|  * (at your option) any later version.  See the COPYING file in the
 | |
|  * top-level directory.
 | |
|  */
 | |
| 
 | |
| #include "qemu/osdep.h"
 | |
| #include "qemu/iov.h"
 | |
| #include "qemu/log.h"
 | |
| #include "qemu/error-report.h"
 | |
| #include "qemu/lockable.h"
 | |
| #include "exec/tswap.h"
 | |
| #include "sysemu/runstate.h"
 | |
| #include "trace.h"
 | |
| #include "qapi/error.h"
 | |
| #include "hw/audio/virtio-snd.h"
 | |
| 
 | |
| #define VIRTIO_SOUND_VM_VERSION 1
 | |
| #define VIRTIO_SOUND_JACK_DEFAULT 0
 | |
| #define VIRTIO_SOUND_STREAM_DEFAULT 2
 | |
| #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);
 | |
| static void virtio_snd_pcm_flush(VirtIOSoundPCMStream *stream);
 | |
| static void virtio_snd_pcm_in_cb(void *data, int available);
 | |
| static void virtio_snd_unrealize(DeviceState *dev);
 | |
| 
 | |
| 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,
 | |
|     .minimum_version_id = VIRTIO_SOUND_VM_VERSION,
 | |
| };
 | |
| 
 | |
| static const VMStateDescription vmstate_virtio_snd = {
 | |
|     .name = TYPE_VIRTIO_SND,
 | |
|     .unmigratable = 1,
 | |
|     .minimum_version_id = VIRTIO_SOUND_VM_VERSION,
 | |
|     .version_id = VIRTIO_SOUND_VM_VERSION,
 | |
|     .fields = (const VMStateField[]) {
 | |
|         VMSTATE_VIRTIO_DEVICE,
 | |
|         VMSTATE_END_OF_LIST()
 | |
|     },
 | |
| };
 | |
| 
 | |
| static Property virtio_snd_properties[] = {
 | |
|     DEFINE_AUDIO_PROPERTIES(VirtIOSound, card),
 | |
|     DEFINE_PROP_UINT32("jacks", VirtIOSound, snd_conf.jacks,
 | |
|                        VIRTIO_SOUND_JACK_DEFAULT),
 | |
|     DEFINE_PROP_UINT32("streams", VirtIOSound, snd_conf.streams,
 | |
|                        VIRTIO_SOUND_STREAM_DEFAULT),
 | |
|     DEFINE_PROP_UINT32("chmaps", VirtIOSound, snd_conf.chmaps,
 | |
|                        VIRTIO_SOUND_CHMAP_DEFAULT),
 | |
|     DEFINE_PROP_END_OF_LIST(),
 | |
| };
 | |
| 
 | |
| 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(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
 | |
| virtio_snd_set_config(VirtIODevice *vdev, const uint8_t *config)
 | |
| {
 | |
|     VirtIOSound *s = VIRTIO_SND(vdev);
 | |
|     const virtio_snd_config *sndconfig =
 | |
|         (const virtio_snd_config *)config;
 | |
| 
 | |
| 
 | |
|    trace_virtio_snd_set_config(vdev,
 | |
|                                s->snd_conf.jacks,
 | |
|                                sndconfig->jacks,
 | |
|                                s->snd_conf.streams,
 | |
|                                sndconfig->streams,
 | |
|                                s->snd_conf.chmaps,
 | |
|                                sndconfig->chmaps);
 | |
| 
 | |
|     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);
 | |
| 
 | |
| }
 | |
| 
 | |
| static void
 | |
| virtio_snd_pcm_buffer_free(VirtIOSoundPCMBuffer *buffer)
 | |
| {
 | |
|     g_free(buffer->elem);
 | |
|     g_free(buffer);
 | |
| }
 | |
| 
 | |
| static void
 | |
| virtio_snd_ctrl_cmd_free(virtio_snd_ctrl_command *cmd)
 | |
| {
 | |
|     g_free(cmd->elem);
 | |
|     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];
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Handle the VIRTIO_SND_R_PCM_INFO request.
 | |
|  * The function writes the info structs to the request element.
 | |
|  *
 | |
|  * @s: VirtIOSound device
 | |
|  * @cmd: The request command queue element from VirtIOSound cmdq field
 | |
|  */
 | |
| static void virtio_snd_handle_pcm_info(VirtIOSound *s,
 | |
|                                        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;
 | |
|     size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
 | |
|                                cmd->elem->out_num,
 | |
|                                0,
 | |
|                                &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;
 | |
|     }
 | |
| 
 | |
|     start_id = le32_to_cpu(req.start_id);
 | |
|     count = le32_to_cpu(req.count);
 | |
|     size = le32_to_cpu(req.size);
 | |
| 
 | |
|     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));
 | |
|         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     pcm_info = g_new0(virtio_snd_pcm_info, count);
 | |
|     for (uint32_t i = 0; i < count; i++) {
 | |
|         stream_id = i + start_id;
 | |
|         trace_virtio_snd_handle_pcm_info(stream_id);
 | |
|         stream = virtio_snd_pcm_get_stream(s, stream_id);
 | |
|         if (!stream) {
 | |
|             error_report("Invalid stream id: %"PRIu32, stream_id);
 | |
|             cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
 | |
|             return;
 | |
|         }
 | |
|         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->payload_size = sizeof(virtio_snd_pcm_info) * count;
 | |
|     cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK);
 | |
|     iov_from_buf(cmd->elem->in_sg,
 | |
|                  cmd->elem->in_num,
 | |
|                  sizeof(virtio_snd_hdr),
 | |
|                  pcm_info,
 | |
|                  cmd->payload_size);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * 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);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Handles the VIRTIO_SND_R_PCM_SET_PARAMS request.
 | |
|  *
 | |
|  * @s: VirtIOSound device
 | |
|  * @cmd: The request command queue element from VirtIOSound cmdq field
 | |
|  */
 | |
| static void virtio_snd_handle_pcm_set_params(VirtIOSound *s,
 | |
|                                              virtio_snd_ctrl_command *cmd)
 | |
| {
 | |
|     virtio_snd_pcm_set_params req = { 0 };
 | |
|     uint32_t stream_id;
 | |
|     size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
 | |
|                                cmd->elem->out_num,
 | |
|                                0,
 | |
|                                &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;
 | |
|     }
 | |
|     stream_id = le32_to_cpu(req.hdr.stream_id);
 | |
|     trace_virtio_snd_handle_pcm_set_params(stream_id);
 | |
|     cmd->resp.code = virtio_snd_set_pcm_params(s, stream_id, &req);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * 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 = 0; /* Conforming to VIRTIO 1.0: always little endian. */
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Close a stream and free all its resources.
 | |
|  *
 | |
|  * @stream: VirtIOSoundPCMStream *stream
 | |
|  */
 | |
| static void virtio_snd_pcm_close(VirtIOSoundPCMStream *stream)
 | |
| {
 | |
|     if (stream) {
 | |
|         virtio_snd_pcm_flush(stream);
 | |
|         if (stream->info.direction == VIRTIO_SND_D_OUTPUT) {
 | |
|             AUD_close_out(&stream->pcm->snd->card, stream->voice.out);
 | |
|             stream->voice.out = NULL;
 | |
|         } else if (stream->info.direction == VIRTIO_SND_D_INPUT) {
 | |
|             AUD_close_in(&stream->pcm->snd->card, stream->voice.in);
 | |
|             stream->voice.in = NULL;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * 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;
 | |
|         qemu_mutex_init(&stream->queue_mutex);
 | |
|         QSIMPLEQ_INIT(&stream->queue);
 | |
| 
 | |
|         /*
 | |
|          * 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;
 | |
| 
 | |
|     if (stream->info.direction == VIRTIO_SND_D_OUTPUT) {
 | |
|         stream->voice.out = AUD_open_out(&s->card,
 | |
|                                          stream->voice.out,
 | |
|                                          "virtio-sound.out",
 | |
|                                          stream,
 | |
|                                          virtio_snd_pcm_out_cb,
 | |
|                                          &as);
 | |
|         AUD_set_volume_out(stream->voice.out, 0, 255, 255);
 | |
|     } else {
 | |
|         stream->voice.in = AUD_open_in(&s->card,
 | |
|                                         stream->voice.in,
 | |
|                                         "virtio-sound.in",
 | |
|                                         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);
 | |
| }
 | |
| 
 | |
| static const char *print_code(uint32_t code)
 | |
| {
 | |
|     #define CASE(CODE)            \
 | |
|     case VIRTIO_SND_R_##CODE:     \
 | |
|         return "VIRTIO_SND_R_"#CODE
 | |
| 
 | |
|     switch (code) {
 | |
|     CASE(JACK_INFO);
 | |
|     CASE(JACK_REMAP);
 | |
|     CASE(PCM_INFO);
 | |
|     CASE(PCM_SET_PARAMS);
 | |
|     CASE(PCM_PREPARE);
 | |
|     CASE(PCM_RELEASE);
 | |
|     CASE(PCM_START);
 | |
|     CASE(PCM_STOP);
 | |
|     CASE(CHMAP_INFO);
 | |
|     default:
 | |
|         return "invalid code";
 | |
|     }
 | |
| 
 | |
|     #undef CASE
 | |
| };
 | |
| 
 | |
| /*
 | |
|  * Handles VIRTIO_SND_R_PCM_PREPARE.
 | |
|  *
 | |
|  * @s: VirtIOSound device
 | |
|  * @cmd: The request command queue element from VirtIOSound cmdq field
 | |
|  */
 | |
| static void virtio_snd_handle_pcm_prepare(VirtIOSound *s,
 | |
|                                           virtio_snd_ctrl_command *cmd)
 | |
| {
 | |
|     uint32_t stream_id;
 | |
|     size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
 | |
|                                cmd->elem->out_num,
 | |
|                                sizeof(virtio_snd_hdr),
 | |
|                                &stream_id,
 | |
|                                sizeof(stream_id));
 | |
| 
 | |
|     stream_id = le32_to_cpu(stream_id);
 | |
|     cmd->resp.code = msg_sz == sizeof(stream_id)
 | |
|                    ? virtio_snd_pcm_prepare(s, stream_id)
 | |
|                    : cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Handles VIRTIO_SND_R_PCM_START.
 | |
|  *
 | |
|  * @s: VirtIOSound device
 | |
|  * @cmd: The request command queue element from VirtIOSound cmdq field
 | |
|  * @start: whether to start or stop the device
 | |
|  */
 | |
| static void virtio_snd_handle_pcm_start_stop(VirtIOSound *s,
 | |
|                                              virtio_snd_ctrl_command *cmd,
 | |
|                                              bool start)
 | |
| {
 | |
|     VirtIOSoundPCMStream *stream;
 | |
|     virtio_snd_pcm_hdr req;
 | |
|     uint32_t stream_id;
 | |
|     size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
 | |
|                                cmd->elem->out_num,
 | |
|                                0,
 | |
|                                &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;
 | |
|     }
 | |
| 
 | |
|     stream_id = le32_to_cpu(req.stream_id);
 | |
|     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) {
 | |
|         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);
 | |
|         } else {
 | |
|             AUD_set_active_in(stream->voice.in, start);
 | |
|         }
 | |
|     } else {
 | |
|         error_report("Invalid stream id: %"PRIu32, stream_id);
 | |
|         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
 | |
|         return;
 | |
|     }
 | |
|     stream->active = start;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Returns the number of I/O messages that are being processed.
 | |
|  *
 | |
|  * @stream: VirtIOSoundPCMStream
 | |
|  */
 | |
| static size_t virtio_snd_pcm_get_io_msgs_count(VirtIOSoundPCMStream *stream)
 | |
| {
 | |
|     VirtIOSoundPCMBuffer *buffer, *next;
 | |
|     size_t count = 0;
 | |
| 
 | |
|     WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
 | |
|         QSIMPLEQ_FOREACH_SAFE(buffer, &stream->queue, entry, next) {
 | |
|             count += 1;
 | |
|         }
 | |
|     }
 | |
|     return count;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Handles VIRTIO_SND_R_PCM_RELEASE.
 | |
|  *
 | |
|  * @s: VirtIOSound device
 | |
|  * @cmd: The request command queue element from VirtIOSound cmdq field
 | |
|  */
 | |
| static void virtio_snd_handle_pcm_release(VirtIOSound *s,
 | |
|                                           virtio_snd_ctrl_command *cmd)
 | |
| {
 | |
|     uint32_t stream_id;
 | |
|     VirtIOSoundPCMStream *stream;
 | |
|     size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
 | |
|                                cmd->elem->out_num,
 | |
|                                sizeof(virtio_snd_hdr),
 | |
|                                &stream_id,
 | |
|                                sizeof(stream_id));
 | |
| 
 | |
|     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;
 | |
|     }
 | |
| 
 | |
|     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 == 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,
 | |
|                      stream_id);
 | |
|         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     if (virtio_snd_pcm_get_io_msgs_count(stream)) {
 | |
|         /*
 | |
|          * virtio-v1.2-csd01, 5.14.6.6.5.1,
 | |
|          * Device Requirements: Stream Release
 | |
|          *
 | |
|          * - The device MUST complete all pending I/O messages for the
 | |
|          *   specified stream ID.
 | |
|          * - The device MUST NOT complete the control request while there
 | |
|          *   are pending I/O messages for the specified stream ID.
 | |
|          */
 | |
|         trace_virtio_snd_pcm_stream_flush(stream_id);
 | |
|         virtio_snd_pcm_flush(stream);
 | |
|     }
 | |
| 
 | |
|     cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * The actual processing done in virtio_snd_process_cmdq().
 | |
|  *
 | |
|  * @s: VirtIOSound device
 | |
|  * @cmd: control command request
 | |
|  */
 | |
| static inline void
 | |
| process_cmd(VirtIOSound *s, virtio_snd_ctrl_command *cmd)
 | |
| {
 | |
|     uint32_t code;
 | |
|     size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
 | |
|                                cmd->elem->out_num,
 | |
|                                0,
 | |
|                                &cmd->ctrl,
 | |
|                                sizeof(virtio_snd_hdr));
 | |
| 
 | |
|     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(virtio_snd_hdr));
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     code = le32_to_cpu(cmd->ctrl.code);
 | |
| 
 | |
|     trace_virtio_snd_handle_code(code, print_code(code));
 | |
| 
 | |
|     switch (code) {
 | |
|     case VIRTIO_SND_R_JACK_INFO:
 | |
|     case VIRTIO_SND_R_JACK_REMAP:
 | |
|         qemu_log_mask(LOG_UNIMP,
 | |
|                      "virtio_snd: jack functionality is unimplemented.\n");
 | |
|         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
 | |
|         break;
 | |
|     case VIRTIO_SND_R_PCM_INFO:
 | |
|         virtio_snd_handle_pcm_info(s, cmd);
 | |
|         break;
 | |
|     case VIRTIO_SND_R_PCM_START:
 | |
|         virtio_snd_handle_pcm_start_stop(s, cmd, true);
 | |
|         break;
 | |
|     case VIRTIO_SND_R_PCM_STOP:
 | |
|         virtio_snd_handle_pcm_start_stop(s, cmd, false);
 | |
|         break;
 | |
|     case VIRTIO_SND_R_PCM_SET_PARAMS:
 | |
|         virtio_snd_handle_pcm_set_params(s, cmd);
 | |
|         break;
 | |
|     case VIRTIO_SND_R_PCM_PREPARE:
 | |
|         virtio_snd_handle_pcm_prepare(s, cmd);
 | |
|         break;
 | |
|     case VIRTIO_SND_R_PCM_RELEASE:
 | |
|         virtio_snd_handle_pcm_release(s, cmd);
 | |
|         break;
 | |
|     case VIRTIO_SND_R_CHMAP_INFO:
 | |
|         qemu_log_mask(LOG_UNIMP,
 | |
|                      "virtio_snd: chmap info functionality is unimplemented.\n");
 | |
|         trace_virtio_snd_handle_chmap_info();
 | |
|         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
 | |
|         break;
 | |
|     default:
 | |
|         /* error */
 | |
|         error_report("virtio snd header not recognized: %"PRIu32, code);
 | |
|         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
 | |
|     }
 | |
| 
 | |
|     iov_from_buf(cmd->elem->in_sg,
 | |
|                  cmd->elem->in_num,
 | |
|                  0,
 | |
|                  &cmd->resp,
 | |
|                  sizeof(virtio_snd_hdr));
 | |
|     virtqueue_push(cmd->vq, cmd->elem,
 | |
|                    sizeof(virtio_snd_hdr) + cmd->payload_size);
 | |
|     virtio_notify(VIRTIO_DEVICE(s), cmd->vq);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Consume all elements in command queue.
 | |
|  *
 | |
|  * @s: VirtIOSound device
 | |
|  */
 | |
| static void virtio_snd_process_cmdq(VirtIOSound *s)
 | |
| {
 | |
|     virtio_snd_ctrl_command *cmd;
 | |
| 
 | |
|     if (unlikely(qatomic_read(&s->processing_cmdq))) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     WITH_QEMU_LOCK_GUARD(&s->cmdq_mutex) {
 | |
|         qatomic_set(&s->processing_cmdq, true);
 | |
|         while (!QTAILQ_EMPTY(&s->cmdq)) {
 | |
|             cmd = QTAILQ_FIRST(&s->cmdq);
 | |
| 
 | |
|             /* process command */
 | |
|             process_cmd(s, cmd);
 | |
| 
 | |
|             QTAILQ_REMOVE(&s->cmdq, cmd, next);
 | |
| 
 | |
|             virtio_snd_ctrl_cmd_free(cmd);
 | |
|         }
 | |
|         qatomic_set(&s->processing_cmdq, false);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * 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.
 | |
|  *
 | |
|  * @vdev: VirtIOSound device
 | |
|  * @vq: Control virtqueue
 | |
|  */
 | |
| static void virtio_snd_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq)
 | |
| {
 | |
|     VirtIOSound *s = VIRTIO_SND(vdev);
 | |
|     VirtQueueElement *elem;
 | |
|     virtio_snd_ctrl_command *cmd;
 | |
| 
 | |
|     trace_virtio_snd_handle_ctrl(vdev, vq);
 | |
| 
 | |
|     if (!virtio_queue_ready(vq)) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
 | |
|     while (elem) {
 | |
|         cmd = g_new0(virtio_snd_ctrl_command, 1);
 | |
|         cmd->elem = elem;
 | |
|         cmd->vq = vq;
 | |
|         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK);
 | |
|         /* implicit cmd->payload_size = 0; */
 | |
|         QTAILQ_INSERT_TAIL(&s->cmdq, cmd, next);
 | |
|         elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
 | |
|     }
 | |
| 
 | |
|     virtio_snd_process_cmdq(s);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * The event virtqueue handler.
 | |
|  * Not implemented yet.
 | |
|  *
 | |
|  * @vdev: VirtIOSound device
 | |
|  * @vq: event vq
 | |
|  */
 | |
| static void virtio_snd_handle_event(VirtIODevice *vdev, VirtQueue *vq)
 | |
| {
 | |
|     qemu_log_mask(LOG_UNIMP, "virtio_snd: event queue is unimplemented.\n");
 | |
|     trace_virtio_snd_handle_event();
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Must only be called if vsnd->invalid is not empty.
 | |
|  */
 | |
| static inline void empty_invalid_queue(VirtIODevice *vdev, VirtQueue *vq)
 | |
| {
 | |
|     VirtIOSoundPCMBuffer *buffer = NULL;
 | |
|     virtio_snd_pcm_status resp = { 0 };
 | |
|     VirtIOSound *vsnd = VIRTIO_SND(vdev);
 | |
| 
 | |
|     g_assert(!QSIMPLEQ_EMPTY(&vsnd->invalid));
 | |
| 
 | |
|     while (!QSIMPLEQ_EMPTY(&vsnd->invalid)) {
 | |
|         buffer = QSIMPLEQ_FIRST(&vsnd->invalid);
 | |
|         /* If buffer->vq != vq, our logic is fundamentally wrong, so bail out */
 | |
|         g_assert(buffer->vq == vq);
 | |
| 
 | |
|         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(&vsnd->invalid, entry);
 | |
|         virtio_snd_pcm_buffer_free(buffer);
 | |
|     }
 | |
|     /* Notify vq about virtio_snd_pcm_status responses. */
 | |
|     virtio_notify(vdev, vq);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * The tx virtqueue handler. Makes the buffers available to their respective
 | |
|  * streams for consumption.
 | |
|  *
 | |
|  * @vdev: VirtIOSound device
 | |
|  * @vq: tx virtqueue
 | |
|  */
 | |
| static void virtio_snd_handle_tx_xfer(VirtIODevice *vdev, VirtQueue *vq)
 | |
| {
 | |
|     VirtIOSound *vsnd = VIRTIO_SND(vdev);
 | |
|     VirtIOSoundPCMBuffer *buffer;
 | |
|     VirtQueueElement *elem;
 | |
|     size_t msg_sz, size;
 | |
|     virtio_snd_pcm_xfer hdr;
 | |
|     uint32_t stream_id;
 | |
|     /*
 | |
|      * If any of the I/O messages are invalid, put them in vsnd->invalid and
 | |
|      * return them after the for loop.
 | |
|      */
 | |
|     bool must_empty_invalid_queue = false;
 | |
| 
 | |
|     if (!virtio_queue_ready(vq)) {
 | |
|         return;
 | |
|     }
 | |
|     trace_virtio_snd_handle_tx_xfer();
 | |
| 
 | |
|     for (;;) {
 | |
|         VirtIOSoundPCMStream *stream;
 | |
| 
 | |
|         elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
 | |
|         if (!elem) {
 | |
|             break;
 | |
|         }
 | |
|         /* get the message hdr object */
 | |
|         msg_sz = iov_to_buf(elem->out_sg,
 | |
|                             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 >= vsnd->snd_conf.streams
 | |
|             || vsnd->pcm->streams[stream_id] == NULL) {
 | |
|             goto tx_err;
 | |
|         }
 | |
| 
 | |
|         stream = vsnd->pcm->streams[stream_id];
 | |
|         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) - msg_sz;
 | |
| 
 | |
|             buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer) + size);
 | |
|             buffer->elem = elem;
 | |
|             buffer->populated = false;
 | |
|             buffer->vq = vq;
 | |
|             buffer->size = size;
 | |
|             buffer->offset = 0;
 | |
| 
 | |
|             QSIMPLEQ_INSERT_TAIL(&stream->queue, buffer, entry);
 | |
|         }
 | |
|         continue;
 | |
| 
 | |
| tx_err:
 | |
|         must_empty_invalid_queue = true;
 | |
|         buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer));
 | |
|         buffer->elem = elem;
 | |
|         buffer->vq = vq;
 | |
|         QSIMPLEQ_INSERT_TAIL(&vsnd->invalid, buffer, entry);
 | |
|     }
 | |
| 
 | |
|     if (must_empty_invalid_queue) {
 | |
|         empty_invalid_queue(vdev, vq);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * The rx virtqueue handler. Makes the buffers available to their respective
 | |
|  * streams for consumption.
 | |
|  *
 | |
|  * @vdev: VirtIOSound device
 | |
|  * @vq: rx virtqueue
 | |
|  */
 | |
| static void virtio_snd_handle_rx_xfer(VirtIODevice *vdev, VirtQueue *vq)
 | |
| {
 | |
|     VirtIOSound *vsnd = VIRTIO_SND(vdev);
 | |
|     VirtIOSoundPCMBuffer *buffer;
 | |
|     VirtQueueElement *elem;
 | |
|     size_t msg_sz, size;
 | |
|     virtio_snd_pcm_xfer hdr;
 | |
|     uint32_t stream_id;
 | |
|     /*
 | |
|      * if any of the I/O messages are invalid, put them in vsnd->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 (;;) {
 | |
|         VirtIOSoundPCMStream *stream;
 | |
| 
 | |
|         elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
 | |
|         if (!elem) {
 | |
|             break;
 | |
|         }
 | |
|         /* get the message hdr object */
 | |
|         msg_sz = iov_to_buf(elem->out_sg,
 | |
|                             elem->out_num,
 | |
|                             0,
 | |
|                             &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);
 | |
| 
 | |
|         if (stream_id >= vsnd->snd_conf.streams
 | |
|             || !vsnd->pcm->streams[stream_id]) {
 | |
|             goto rx_err;
 | |
|         }
 | |
| 
 | |
|         stream = vsnd->pcm->streams[stream_id];
 | |
|         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);
 | |
|             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:
 | |
|         must_empty_invalid_queue = true;
 | |
|         buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer));
 | |
|         buffer->elem = elem;
 | |
|         buffer->vq = vq;
 | |
|         QSIMPLEQ_INSERT_TAIL(&vsnd->invalid, buffer, entry);
 | |
|     }
 | |
| 
 | |
|     if (must_empty_invalid_queue) {
 | |
|         empty_invalid_queue(vdev, vq);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static uint64_t get_features(VirtIODevice *vdev, uint64_t features,
 | |
|                              Error **errp)
 | |
| {
 | |
|     /*
 | |
|      * virtio-v1.2-csd01, 5.14.3,
 | |
|      * Feature Bits
 | |
|      * None currently defined.
 | |
|      */
 | |
|     VirtIOSound *s = VIRTIO_SND(vdev);
 | |
|     features |= s->features;
 | |
| 
 | |
|     trace_virtio_snd_get_features(vdev, features);
 | |
| 
 | |
|     return features;
 | |
| }
 | |
| 
 | |
| static void
 | |
| virtio_snd_vm_state_change(void *opaque, bool running,
 | |
|                                        RunState state)
 | |
| {
 | |
|     if (running) {
 | |
|         trace_virtio_snd_vm_state_running();
 | |
|     } else {
 | |
|         trace_virtio_snd_vm_state_stopped();
 | |
|     }
 | |
| }
 | |
| 
 | |
| 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;
 | |
| 
 | |
|     trace_virtio_snd_realize(vsnd);
 | |
| 
 | |
|     /* check number of jacks and streams */
 | |
|     if (vsnd->snd_conf.jacks > 8) {
 | |
|         error_setg(errp,
 | |
|                    "Invalid number of jacks: %"PRIu32,
 | |
|                    vsnd->snd_conf.jacks);
 | |
|         return;
 | |
|     }
 | |
|     if (vsnd->snd_conf.streams < 1 || vsnd->snd_conf.streams > 10) {
 | |
|         error_setg(errp,
 | |
|                    "Invalid number of streams: %"PRIu32,
 | |
|                     vsnd->snd_conf.streams);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     if (vsnd->snd_conf.chmaps > VIRTIO_SND_CHMAP_MAX_SIZE) {
 | |
|         error_setg(errp,
 | |
|                    "Invalid number of channel maps: %"PRIu32,
 | |
|                    vsnd->snd_conf.chmaps);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     if (!AUD_register_card("virtio-sound", &vsnd->card, errp)) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     vsnd->vmstate =
 | |
|         qemu_add_vm_change_state_handler(virtio_snd_vm_state_change, vsnd);
 | |
| 
 | |
|     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);
 | |
| 
 | |
|     virtio_init(vdev, VIRTIO_ID_SOUND, sizeof(virtio_snd_config));
 | |
|     virtio_add_feature(&vsnd->features, VIRTIO_F_VERSION_1);
 | |
| 
 | |
|     /* 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_ctrl);
 | |
|     vsnd->queues[VIRTIO_SND_VQ_EVENT] =
 | |
|         virtio_add_queue(vdev, 64, virtio_snd_handle_event);
 | |
|     vsnd->queues[VIRTIO_SND_VQ_TX] =
 | |
|         virtio_add_queue(vdev, 64, virtio_snd_handle_tx_xfer);
 | |
|     vsnd->queues[VIRTIO_SND_VQ_RX] =
 | |
|         virtio_add_queue(vdev, 64, virtio_snd_handle_rx_xfer);
 | |
|     qemu_mutex_init(&vsnd->cmdq_mutex);
 | |
|     QTAILQ_INIT(&vsnd->cmdq);
 | |
|     QSIMPLEQ_INIT(&vsnd->invalid);
 | |
| 
 | |
|     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 initialize stream params, device responded with %s.",
 | |
|                        print_code(status));
 | |
|             goto error_cleanup;
 | |
|         }
 | |
|         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));
 | |
|             goto error_cleanup;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return;
 | |
| 
 | |
| error_cleanup:
 | |
|     virtio_snd_unrealize(dev);
 | |
| }
 | |
| 
 | |
| static inline void return_tx_buffer(VirtIOSoundPCMStream *stream,
 | |
|                                     VirtIOSoundPCMBuffer *buffer)
 | |
| {
 | |
|     virtio_snd_pcm_status resp = { 0 };
 | |
|     resp.status = cpu_to_le32(VIRTIO_SND_S_OK);
 | |
|     resp.latency_bytes = cpu_to_le32((uint32_t)buffer->size);
 | |
|     iov_from_buf(buffer->elem->in_sg,
 | |
|                  buffer->elem->in_num,
 | |
|                  0,
 | |
|                  &resp,
 | |
|                  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(&stream->queue,
 | |
|                     buffer,
 | |
|                     VirtIOSoundPCMBuffer,
 | |
|                     entry);
 | |
|     virtio_snd_pcm_buffer_free(buffer);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * AUD_* output callback.
 | |
|  *
 | |
|  * @data: VirtIOSoundPCMStream stream
 | |
|  * @available: number of bytes that can be written with AUD_write()
 | |
|  */
 | |
| static void virtio_snd_pcm_out_cb(void *data, int available)
 | |
| {
 | |
|     VirtIOSoundPCMStream *stream = data;
 | |
|     VirtIOSoundPCMBuffer *buffer;
 | |
|     size_t size;
 | |
| 
 | |
|     WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
 | |
|         while (!QSIMPLEQ_EMPTY(&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. */
 | |
|                 return_tx_buffer(stream, buffer);
 | |
|                 continue;
 | |
|             }
 | |
|             if (!buffer->populated) {
 | |
|                 iov_to_buf(buffer->elem->out_sg,
 | |
|                            buffer->elem->out_num,
 | |
|                            sizeof(virtio_snd_pcm_xfer),
 | |
|                            buffer->data,
 | |
|                            buffer->size);
 | |
|                 buffer->populated = true;
 | |
|             }
 | |
|             for (;;) {
 | |
|                 size = AUD_write(stream->voice.out,
 | |
|                                  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;
 | |
|                 }
 | |
|                 buffer->size -= size;
 | |
|                 buffer->offset += size;
 | |
|                 available -= size;
 | |
|                 if (buffer->size < 1) {
 | |
|                     return_tx_buffer(stream, buffer);
 | |
|                     break;
 | |
|                 }
 | |
|                 if (!available) {
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|             if (!available) {
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Flush all buffer data from this input stream's queue into the driver's
 | |
|  * virtual queue.
 | |
|  *
 | |
|  * @stream: VirtIOSoundPCMStream *stream
 | |
|  */
 | |
| static inline void return_rx_buffer(VirtIOSoundPCMStream *stream,
 | |
|                                     VirtIOSoundPCMBuffer *buffer)
 | |
| {
 | |
|     virtio_snd_pcm_status resp = { 0 };
 | |
|     resp.status = cpu_to_le32(VIRTIO_SND_S_OK);
 | |
|     resp.latency_bytes = 0;
 | |
|     /* Copy data -if any- to guest */
 | |
|     iov_from_buf(buffer->elem->in_sg,
 | |
|                  buffer->elem->in_num,
 | |
|                  0,
 | |
|                  buffer->data,
 | |
|                  buffer->size);
 | |
|     iov_from_buf(buffer->elem->in_sg,
 | |
|                  buffer->elem->in_num,
 | |
|                  buffer->size,
 | |
|                  &resp,
 | |
|                  sizeof(virtio_snd_pcm_status));
 | |
|     virtqueue_push(buffer->vq,
 | |
|                    buffer->elem,
 | |
|                    sizeof(virtio_snd_pcm_status) + buffer->size);
 | |
|     virtio_notify(VIRTIO_DEVICE(stream->s), buffer->vq);
 | |
|     QSIMPLEQ_REMOVE(&stream->queue,
 | |
|                     buffer,
 | |
|                     VirtIOSoundPCMBuffer,
 | |
|                     entry);
 | |
|     virtio_snd_pcm_buffer_free(buffer);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * AUD_* input callback.
 | |
|  *
 | |
|  * @data: VirtIOSoundPCMStream stream
 | |
|  * @available: number of bytes that can be read with AUD_read()
 | |
|  */
 | |
| static void virtio_snd_pcm_in_cb(void *data, int available)
 | |
| {
 | |
|     VirtIOSoundPCMStream *stream = data;
 | |
|     VirtIOSoundPCMBuffer *buffer;
 | |
|     size_t size;
 | |
| 
 | |
|     WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
 | |
|         while (!QSIMPLEQ_EMPTY(&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. */
 | |
|                 return_rx_buffer(stream, buffer);
 | |
|                 continue;
 | |
|             }
 | |
| 
 | |
|             for (;;) {
 | |
|                 size = AUD_read(stream->voice.in,
 | |
|                         buffer->data + buffer->size,
 | |
|                         MIN(available, (stream->params.period_bytes -
 | |
|                                         buffer->size)));
 | |
|                 if (!size) {
 | |
|                     available = 0;
 | |
|                     break;
 | |
|                 }
 | |
|                 buffer->size += size;
 | |
|                 available -= size;
 | |
|                 if (buffer->size >= stream->params.period_bytes) {
 | |
|                     return_rx_buffer(stream, buffer);
 | |
|                     break;
 | |
|                 }
 | |
|                 if (!available) {
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|             if (!available) {
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Flush all buffer data from this output stream's queue into the driver's
 | |
|  * virtual queue.
 | |
|  *
 | |
|  * @stream: VirtIOSoundPCMStream *stream
 | |
|  */
 | |
| static inline void virtio_snd_pcm_flush(VirtIOSoundPCMStream *stream)
 | |
| {
 | |
|     VirtIOSoundPCMBuffer *buffer;
 | |
|     void (*cb)(VirtIOSoundPCMStream *, VirtIOSoundPCMBuffer *) =
 | |
|         (stream->info.direction == VIRTIO_SND_D_OUTPUT) ? return_tx_buffer :
 | |
|         return_rx_buffer;
 | |
| 
 | |
|     WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
 | |
|         while (!QSIMPLEQ_EMPTY(&stream->queue)) {
 | |
|             buffer = QSIMPLEQ_FIRST(&stream->queue);
 | |
|             cb(stream, buffer);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void virtio_snd_unrealize(DeviceState *dev)
 | |
| {
 | |
|     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
 | |
|     VirtIOSound *vsnd = VIRTIO_SND(dev);
 | |
|     VirtIOSoundPCMStream *stream;
 | |
| 
 | |
|     qemu_del_vm_change_state_handler(vsnd->vmstate);
 | |
|     trace_virtio_snd_unrealize(vsnd);
 | |
| 
 | |
|     if (vsnd->pcm) {
 | |
|         if (vsnd->pcm->streams) {
 | |
|             for (uint32_t i = 0; i < vsnd->snd_conf.streams; i++) {
 | |
|                 stream = vsnd->pcm->streams[i];
 | |
|                 if (stream) {
 | |
|                     virtio_snd_process_cmdq(stream->s);
 | |
|                     virtio_snd_pcm_close(stream);
 | |
|                     qemu_mutex_destroy(&stream->queue_mutex);
 | |
|                     g_free(stream);
 | |
|                 }
 | |
|             }
 | |
|             g_free(vsnd->pcm->streams);
 | |
|         }
 | |
|         g_free(vsnd->pcm->pcm_params);
 | |
|         g_free(vsnd->pcm);
 | |
|         vsnd->pcm = NULL;
 | |
|     }
 | |
|     AUD_remove_card(&vsnd->card);
 | |
|     qemu_mutex_destroy(&vsnd->cmdq_mutex);
 | |
|     virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_CONTROL]);
 | |
|     virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_EVENT]);
 | |
|     virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_TX]);
 | |
|     virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_RX]);
 | |
|     virtio_cleanup(vdev);
 | |
| }
 | |
| 
 | |
| 
 | |
| static void virtio_snd_reset(VirtIODevice *vdev)
 | |
| {
 | |
|     VirtIOSound *vsnd = VIRTIO_SND(vdev);
 | |
|     virtio_snd_ctrl_command *cmd;
 | |
| 
 | |
|     /*
 | |
|      * Sanity check that the invalid buffer message queue is emptied at the end
 | |
|      * of every virtio_snd_handle_tx_xfer/virtio_snd_handle_rx_xfer call, and
 | |
|      * must be empty otherwise.
 | |
|      */
 | |
|     g_assert(QSIMPLEQ_EMPTY(&vsnd->invalid));
 | |
| 
 | |
|     WITH_QEMU_LOCK_GUARD(&vsnd->cmdq_mutex) {
 | |
|         while (!QTAILQ_EMPTY(&vsnd->cmdq)) {
 | |
|             cmd = QTAILQ_FIRST(&vsnd->cmdq);
 | |
|             QTAILQ_REMOVE(&vsnd->cmdq, cmd, next);
 | |
|             virtio_snd_ctrl_cmd_free(cmd);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void virtio_snd_class_init(ObjectClass *klass, void *data)
 | |
| {
 | |
|     DeviceClass *dc = DEVICE_CLASS(klass);
 | |
|     VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
 | |
| 
 | |
| 
 | |
|     set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
 | |
|     device_class_set_props(dc, virtio_snd_properties);
 | |
| 
 | |
|     dc->vmsd = &vmstate_virtio_snd;
 | |
|     vdc->vmsd = &vmstate_virtio_snd_device;
 | |
|     vdc->realize = virtio_snd_realize;
 | |
|     vdc->unrealize = virtio_snd_unrealize;
 | |
|     vdc->get_config = virtio_snd_get_config;
 | |
|     vdc->set_config = virtio_snd_set_config;
 | |
|     vdc->get_features = get_features;
 | |
|     vdc->reset = virtio_snd_reset;
 | |
|     vdc->legacy_features = 0;
 | |
| }
 | |
| 
 | |
| static const TypeInfo virtio_snd_types[] = {
 | |
|     {
 | |
|       .name          = TYPE_VIRTIO_SND,
 | |
|       .parent        = TYPE_VIRTIO_DEVICE,
 | |
|       .instance_size = sizeof(VirtIOSound),
 | |
|       .class_init    = virtio_snd_class_init,
 | |
|     }
 | |
| };
 | |
| 
 | |
| DEFINE_TYPES(virtio_snd_types)
 |