xref: /qemu/hw/audio/virtio-snd.c (revision 18a75281)
12880e676SManos Pitsidianakis /*
22880e676SManos Pitsidianakis  * VIRTIO Sound Device conforming to
32880e676SManos Pitsidianakis  *
42880e676SManos Pitsidianakis  * "Virtual I/O Device (VIRTIO) Version 1.2
52880e676SManos Pitsidianakis  * Committee Specification Draft 01
62880e676SManos Pitsidianakis  * 09 May 2022"
72880e676SManos Pitsidianakis  *
82880e676SManos Pitsidianakis  * <https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html#x1-52900014>
92880e676SManos Pitsidianakis  *
102880e676SManos Pitsidianakis  * Copyright (c) 2023 Emmanouil Pitsidianakis <manos.pitsidianakis@linaro.org>
112880e676SManos Pitsidianakis  * Copyright (C) 2019 OpenSynergy GmbH
122880e676SManos Pitsidianakis  *
132880e676SManos Pitsidianakis  * This work is licensed under the terms of the GNU GPL, version 2 or
142880e676SManos Pitsidianakis  * (at your option) any later version.  See the COPYING file in the
152880e676SManos Pitsidianakis  * top-level directory.
162880e676SManos Pitsidianakis  */
172880e676SManos Pitsidianakis 
182880e676SManos Pitsidianakis #include "qemu/osdep.h"
192880e676SManos Pitsidianakis #include "qemu/iov.h"
202880e676SManos Pitsidianakis #include "qemu/log.h"
212880e676SManos Pitsidianakis #include "qemu/error-report.h"
222880e676SManos Pitsidianakis #include "include/qemu/lockable.h"
232880e676SManos Pitsidianakis #include "sysemu/runstate.h"
242880e676SManos Pitsidianakis #include "trace.h"
252880e676SManos Pitsidianakis #include "qapi/error.h"
262880e676SManos Pitsidianakis #include "hw/audio/virtio-snd.h"
272880e676SManos Pitsidianakis #include "hw/core/cpu.h"
282880e676SManos Pitsidianakis 
292880e676SManos Pitsidianakis #define VIRTIO_SOUND_VM_VERSION 1
302880e676SManos Pitsidianakis #define VIRTIO_SOUND_JACK_DEFAULT 0
312880e676SManos Pitsidianakis #define VIRTIO_SOUND_STREAM_DEFAULT 1
322880e676SManos Pitsidianakis #define VIRTIO_SOUND_CHMAP_DEFAULT 0
332880e676SManos Pitsidianakis #define VIRTIO_SOUND_HDA_FN_NID 0
342880e676SManos Pitsidianakis 
3518a75281SManos Pitsidianakis static void virtio_snd_pcm_out_cb(void *data, int available);
3618a75281SManos Pitsidianakis static void virtio_snd_process_cmdq(VirtIOSound *s);
3718a75281SManos Pitsidianakis static void virtio_snd_pcm_flush(VirtIOSoundPCMStream *stream);
3818a75281SManos Pitsidianakis 
39eb9ad377SManos Pitsidianakis static uint32_t supported_formats = BIT(VIRTIO_SND_PCM_FMT_S8)
40eb9ad377SManos Pitsidianakis                                   | BIT(VIRTIO_SND_PCM_FMT_U8)
41eb9ad377SManos Pitsidianakis                                   | BIT(VIRTIO_SND_PCM_FMT_S16)
42eb9ad377SManos Pitsidianakis                                   | BIT(VIRTIO_SND_PCM_FMT_U16)
43eb9ad377SManos Pitsidianakis                                   | BIT(VIRTIO_SND_PCM_FMT_S32)
44eb9ad377SManos Pitsidianakis                                   | BIT(VIRTIO_SND_PCM_FMT_U32)
45eb9ad377SManos Pitsidianakis                                   | BIT(VIRTIO_SND_PCM_FMT_FLOAT);
46eb9ad377SManos Pitsidianakis 
47eb9ad377SManos Pitsidianakis static uint32_t supported_rates = BIT(VIRTIO_SND_PCM_RATE_5512)
48eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_8000)
49eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_11025)
50eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_16000)
51eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_22050)
52eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_32000)
53eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_44100)
54eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_48000)
55eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_64000)
56eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_88200)
57eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_96000)
58eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_176400)
59eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_192000)
60eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_384000);
61eb9ad377SManos Pitsidianakis 
622880e676SManos Pitsidianakis static const VMStateDescription vmstate_virtio_snd_device = {
632880e676SManos Pitsidianakis     .name = TYPE_VIRTIO_SND,
642880e676SManos Pitsidianakis     .version_id = VIRTIO_SOUND_VM_VERSION,
652880e676SManos Pitsidianakis     .minimum_version_id = VIRTIO_SOUND_VM_VERSION,
662880e676SManos Pitsidianakis };
672880e676SManos Pitsidianakis 
682880e676SManos Pitsidianakis static const VMStateDescription vmstate_virtio_snd = {
692880e676SManos Pitsidianakis     .name = TYPE_VIRTIO_SND,
702880e676SManos Pitsidianakis     .minimum_version_id = VIRTIO_SOUND_VM_VERSION,
712880e676SManos Pitsidianakis     .version_id = VIRTIO_SOUND_VM_VERSION,
722880e676SManos Pitsidianakis     .fields = (VMStateField[]) {
732880e676SManos Pitsidianakis         VMSTATE_VIRTIO_DEVICE,
742880e676SManos Pitsidianakis         VMSTATE_END_OF_LIST()
752880e676SManos Pitsidianakis     },
762880e676SManos Pitsidianakis };
772880e676SManos Pitsidianakis 
782880e676SManos Pitsidianakis static Property virtio_snd_properties[] = {
792880e676SManos Pitsidianakis     DEFINE_AUDIO_PROPERTIES(VirtIOSound, card),
802880e676SManos Pitsidianakis     DEFINE_PROP_UINT32("jacks", VirtIOSound, snd_conf.jacks,
812880e676SManos Pitsidianakis                        VIRTIO_SOUND_JACK_DEFAULT),
822880e676SManos Pitsidianakis     DEFINE_PROP_UINT32("streams", VirtIOSound, snd_conf.streams,
832880e676SManos Pitsidianakis                        VIRTIO_SOUND_STREAM_DEFAULT),
842880e676SManos Pitsidianakis     DEFINE_PROP_UINT32("chmaps", VirtIOSound, snd_conf.chmaps,
852880e676SManos Pitsidianakis                        VIRTIO_SOUND_CHMAP_DEFAULT),
862880e676SManos Pitsidianakis     DEFINE_PROP_END_OF_LIST(),
872880e676SManos Pitsidianakis };
882880e676SManos Pitsidianakis 
892880e676SManos Pitsidianakis static void
902880e676SManos Pitsidianakis virtio_snd_get_config(VirtIODevice *vdev, uint8_t *config)
912880e676SManos Pitsidianakis {
922880e676SManos Pitsidianakis     VirtIOSound *s = VIRTIO_SND(vdev);
932880e676SManos Pitsidianakis     virtio_snd_config *sndconfig =
942880e676SManos Pitsidianakis         (virtio_snd_config *)config;
952880e676SManos Pitsidianakis     trace_virtio_snd_get_config(vdev,
962880e676SManos Pitsidianakis                                 s->snd_conf.jacks,
972880e676SManos Pitsidianakis                                 s->snd_conf.streams,
982880e676SManos Pitsidianakis                                 s->snd_conf.chmaps);
992880e676SManos Pitsidianakis 
1002880e676SManos Pitsidianakis     memcpy(sndconfig, &s->snd_conf, sizeof(s->snd_conf));
1012880e676SManos Pitsidianakis     cpu_to_le32s(&sndconfig->jacks);
1022880e676SManos Pitsidianakis     cpu_to_le32s(&sndconfig->streams);
1032880e676SManos Pitsidianakis     cpu_to_le32s(&sndconfig->chmaps);
1042880e676SManos Pitsidianakis 
1052880e676SManos Pitsidianakis }
1062880e676SManos Pitsidianakis 
1072880e676SManos Pitsidianakis static void
1082880e676SManos Pitsidianakis virtio_snd_set_config(VirtIODevice *vdev, const uint8_t *config)
1092880e676SManos Pitsidianakis {
1102880e676SManos Pitsidianakis     VirtIOSound *s = VIRTIO_SND(vdev);
1112880e676SManos Pitsidianakis     const virtio_snd_config *sndconfig =
1122880e676SManos Pitsidianakis         (const virtio_snd_config *)config;
1132880e676SManos Pitsidianakis 
1142880e676SManos Pitsidianakis 
1152880e676SManos Pitsidianakis    trace_virtio_snd_set_config(vdev,
1162880e676SManos Pitsidianakis                                s->snd_conf.jacks,
1172880e676SManos Pitsidianakis                                sndconfig->jacks,
1182880e676SManos Pitsidianakis                                s->snd_conf.streams,
1192880e676SManos Pitsidianakis                                sndconfig->streams,
1202880e676SManos Pitsidianakis                                s->snd_conf.chmaps,
1212880e676SManos Pitsidianakis                                sndconfig->chmaps);
1222880e676SManos Pitsidianakis 
1232880e676SManos Pitsidianakis     memcpy(&s->snd_conf, sndconfig, sizeof(virtio_snd_config));
1242880e676SManos Pitsidianakis     le32_to_cpus(&s->snd_conf.jacks);
1252880e676SManos Pitsidianakis     le32_to_cpus(&s->snd_conf.streams);
1262880e676SManos Pitsidianakis     le32_to_cpus(&s->snd_conf.chmaps);
1272880e676SManos Pitsidianakis 
1282880e676SManos Pitsidianakis }
1292880e676SManos Pitsidianakis 
130eb9ad377SManos Pitsidianakis static void
13118a75281SManos Pitsidianakis virtio_snd_pcm_buffer_free(VirtIOSoundPCMBuffer *buffer)
13218a75281SManos Pitsidianakis {
13318a75281SManos Pitsidianakis     g_free(buffer->elem);
13418a75281SManos Pitsidianakis     g_free(buffer);
13518a75281SManos Pitsidianakis }
13618a75281SManos Pitsidianakis 
13718a75281SManos Pitsidianakis static void
138eb9ad377SManos Pitsidianakis virtio_snd_ctrl_cmd_free(virtio_snd_ctrl_command *cmd)
139eb9ad377SManos Pitsidianakis {
140eb9ad377SManos Pitsidianakis     g_free(cmd->elem);
141eb9ad377SManos Pitsidianakis     g_free(cmd);
142eb9ad377SManos Pitsidianakis }
143eb9ad377SManos Pitsidianakis 
1442880e676SManos Pitsidianakis /*
145eb9ad377SManos Pitsidianakis  * Get a specific stream from the virtio sound card device.
146eb9ad377SManos Pitsidianakis  * Returns NULL if @stream_id is invalid or not allocated.
147eb9ad377SManos Pitsidianakis  *
148eb9ad377SManos Pitsidianakis  * @s: VirtIOSound device
149eb9ad377SManos Pitsidianakis  * @stream_id: stream id
150eb9ad377SManos Pitsidianakis  */
151eb9ad377SManos Pitsidianakis static VirtIOSoundPCMStream *virtio_snd_pcm_get_stream(VirtIOSound *s,
152eb9ad377SManos Pitsidianakis                                                        uint32_t stream_id)
153eb9ad377SManos Pitsidianakis {
154eb9ad377SManos Pitsidianakis     return stream_id >= s->snd_conf.streams ? NULL :
155eb9ad377SManos Pitsidianakis         s->pcm->streams[stream_id];
156eb9ad377SManos Pitsidianakis }
157eb9ad377SManos Pitsidianakis 
158eb9ad377SManos Pitsidianakis /*
159eb9ad377SManos Pitsidianakis  * Get params for a specific stream.
160eb9ad377SManos Pitsidianakis  *
161eb9ad377SManos Pitsidianakis  * @s: VirtIOSound device
162eb9ad377SManos Pitsidianakis  * @stream_id: stream id
163eb9ad377SManos Pitsidianakis  */
164eb9ad377SManos Pitsidianakis static virtio_snd_pcm_set_params *virtio_snd_pcm_get_params(VirtIOSound *s,
165eb9ad377SManos Pitsidianakis                                                             uint32_t stream_id)
166eb9ad377SManos Pitsidianakis {
167eb9ad377SManos Pitsidianakis     return stream_id >= s->snd_conf.streams ? NULL
168eb9ad377SManos Pitsidianakis         : &s->pcm->pcm_params[stream_id];
169eb9ad377SManos Pitsidianakis }
170eb9ad377SManos Pitsidianakis 
171eb9ad377SManos Pitsidianakis /*
1720ff05dd2SManos Pitsidianakis  * Handle the VIRTIO_SND_R_PCM_INFO request.
1730ff05dd2SManos Pitsidianakis  * The function writes the info structs to the request element.
1740ff05dd2SManos Pitsidianakis  *
1750ff05dd2SManos Pitsidianakis  * @s: VirtIOSound device
1760ff05dd2SManos Pitsidianakis  * @cmd: The request command queue element from VirtIOSound cmdq field
1770ff05dd2SManos Pitsidianakis  */
1780ff05dd2SManos Pitsidianakis static void virtio_snd_handle_pcm_info(VirtIOSound *s,
1790ff05dd2SManos Pitsidianakis                                        virtio_snd_ctrl_command *cmd)
1800ff05dd2SManos Pitsidianakis {
1810ff05dd2SManos Pitsidianakis     uint32_t stream_id, start_id, count, size;
1820ff05dd2SManos Pitsidianakis     virtio_snd_pcm_info val;
1830ff05dd2SManos Pitsidianakis     virtio_snd_query_info req;
1840ff05dd2SManos Pitsidianakis     VirtIOSoundPCMStream *stream = NULL;
1850ff05dd2SManos Pitsidianakis     g_autofree virtio_snd_pcm_info *pcm_info = NULL;
1860ff05dd2SManos Pitsidianakis     size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
1870ff05dd2SManos Pitsidianakis                                cmd->elem->out_num,
1880ff05dd2SManos Pitsidianakis                                0,
1890ff05dd2SManos Pitsidianakis                                &req,
1900ff05dd2SManos Pitsidianakis                                sizeof(virtio_snd_query_info));
1910ff05dd2SManos Pitsidianakis 
1920ff05dd2SManos Pitsidianakis     if (msg_sz != sizeof(virtio_snd_query_info)) {
1930ff05dd2SManos Pitsidianakis         /*
1940ff05dd2SManos Pitsidianakis          * TODO: do we need to set DEVICE_NEEDS_RESET?
1950ff05dd2SManos Pitsidianakis          */
1960ff05dd2SManos Pitsidianakis         qemu_log_mask(LOG_GUEST_ERROR,
1970ff05dd2SManos Pitsidianakis                 "%s: virtio-snd command size incorrect %zu vs \
1980ff05dd2SManos Pitsidianakis                 %zu\n", __func__, msg_sz, sizeof(virtio_snd_query_info));
1990ff05dd2SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
2000ff05dd2SManos Pitsidianakis         return;
2010ff05dd2SManos Pitsidianakis     }
2020ff05dd2SManos Pitsidianakis 
2030ff05dd2SManos Pitsidianakis     start_id = le32_to_cpu(req.start_id);
2040ff05dd2SManos Pitsidianakis     count = le32_to_cpu(req.count);
2050ff05dd2SManos Pitsidianakis     size = le32_to_cpu(req.size);
2060ff05dd2SManos Pitsidianakis 
2070ff05dd2SManos Pitsidianakis     if (iov_size(cmd->elem->in_sg, cmd->elem->in_num) <
2080ff05dd2SManos Pitsidianakis         sizeof(virtio_snd_hdr) + size * count) {
2090ff05dd2SManos Pitsidianakis         /*
2100ff05dd2SManos Pitsidianakis          * TODO: do we need to set DEVICE_NEEDS_RESET?
2110ff05dd2SManos Pitsidianakis          */
2120ff05dd2SManos Pitsidianakis         error_report("pcm info: buffer too small, got: %zu, needed: %zu",
2130ff05dd2SManos Pitsidianakis                 iov_size(cmd->elem->in_sg, cmd->elem->in_num),
2140ff05dd2SManos Pitsidianakis                 sizeof(virtio_snd_pcm_info));
2150ff05dd2SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
2160ff05dd2SManos Pitsidianakis         return;
2170ff05dd2SManos Pitsidianakis     }
2180ff05dd2SManos Pitsidianakis 
2190ff05dd2SManos Pitsidianakis     pcm_info = g_new0(virtio_snd_pcm_info, count);
2200ff05dd2SManos Pitsidianakis     for (uint32_t i = 0; i < count; i++) {
2210ff05dd2SManos Pitsidianakis         stream_id = i + start_id;
2220ff05dd2SManos Pitsidianakis         trace_virtio_snd_handle_pcm_info(stream_id);
2230ff05dd2SManos Pitsidianakis         stream = virtio_snd_pcm_get_stream(s, stream_id);
2240ff05dd2SManos Pitsidianakis         if (!stream) {
2250ff05dd2SManos Pitsidianakis             error_report("Invalid stream id: %"PRIu32, stream_id);
2260ff05dd2SManos Pitsidianakis             cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
2270ff05dd2SManos Pitsidianakis             return;
2280ff05dd2SManos Pitsidianakis         }
2290ff05dd2SManos Pitsidianakis         val = stream->info;
2300ff05dd2SManos Pitsidianakis         val.hdr.hda_fn_nid = cpu_to_le32(val.hdr.hda_fn_nid);
2310ff05dd2SManos Pitsidianakis         val.features = cpu_to_le32(val.features);
2320ff05dd2SManos Pitsidianakis         val.formats = cpu_to_le64(val.formats);
2330ff05dd2SManos Pitsidianakis         val.rates = cpu_to_le64(val.rates);
2340ff05dd2SManos Pitsidianakis         /*
2350ff05dd2SManos Pitsidianakis          * 5.14.6.6.2.1 Device Requirements: Stream Information The device MUST
2360ff05dd2SManos Pitsidianakis          * NOT set undefined feature, format, rate and direction values. The
2370ff05dd2SManos Pitsidianakis          * device MUST initialize the padding bytes to 0.
2380ff05dd2SManos Pitsidianakis          */
2390ff05dd2SManos Pitsidianakis         pcm_info[i] = val;
2400ff05dd2SManos Pitsidianakis         memset(&pcm_info[i].padding, 0, 5);
2410ff05dd2SManos Pitsidianakis     }
2420ff05dd2SManos Pitsidianakis 
2430ff05dd2SManos Pitsidianakis     cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK);
2440ff05dd2SManos Pitsidianakis     iov_from_buf(cmd->elem->in_sg,
2450ff05dd2SManos Pitsidianakis                  cmd->elem->in_num,
2460ff05dd2SManos Pitsidianakis                  sizeof(virtio_snd_hdr),
2470ff05dd2SManos Pitsidianakis                  pcm_info,
2480ff05dd2SManos Pitsidianakis                  sizeof(virtio_snd_pcm_info) * count);
2490ff05dd2SManos Pitsidianakis }
2500ff05dd2SManos Pitsidianakis 
2510ff05dd2SManos Pitsidianakis /*
252eb9ad377SManos Pitsidianakis  * Set the given stream params.
253eb9ad377SManos Pitsidianakis  * Called by both virtio_snd_handle_pcm_set_params and during device
254eb9ad377SManos Pitsidianakis  * initialization.
255eb9ad377SManos Pitsidianakis  * Returns the response status code. (VIRTIO_SND_S_*).
256eb9ad377SManos Pitsidianakis  *
257eb9ad377SManos Pitsidianakis  * @s: VirtIOSound device
258eb9ad377SManos Pitsidianakis  * @params: The PCM params as defined in the virtio specification
259eb9ad377SManos Pitsidianakis  */
260eb9ad377SManos Pitsidianakis static
261eb9ad377SManos Pitsidianakis uint32_t virtio_snd_set_pcm_params(VirtIOSound *s,
262eb9ad377SManos Pitsidianakis                                    uint32_t stream_id,
263eb9ad377SManos Pitsidianakis                                    virtio_snd_pcm_set_params *params)
264eb9ad377SManos Pitsidianakis {
265eb9ad377SManos Pitsidianakis     virtio_snd_pcm_set_params *st_params;
266eb9ad377SManos Pitsidianakis 
267eb9ad377SManos Pitsidianakis     if (stream_id >= s->snd_conf.streams || s->pcm->pcm_params == NULL) {
268eb9ad377SManos Pitsidianakis         /*
269eb9ad377SManos Pitsidianakis          * TODO: do we need to set DEVICE_NEEDS_RESET?
270eb9ad377SManos Pitsidianakis          */
271eb9ad377SManos Pitsidianakis         virtio_error(VIRTIO_DEVICE(s), "Streams have not been initialized.\n");
272eb9ad377SManos Pitsidianakis         return cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
273eb9ad377SManos Pitsidianakis     }
274eb9ad377SManos Pitsidianakis 
275eb9ad377SManos Pitsidianakis     st_params = virtio_snd_pcm_get_params(s, stream_id);
276eb9ad377SManos Pitsidianakis 
277eb9ad377SManos Pitsidianakis     if (params->channels < 1 || params->channels > AUDIO_MAX_CHANNELS) {
278eb9ad377SManos Pitsidianakis         error_report("Number of channels is not supported.");
279eb9ad377SManos Pitsidianakis         return cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
280eb9ad377SManos Pitsidianakis     }
281eb9ad377SManos Pitsidianakis     if (!(supported_formats & BIT(params->format))) {
282eb9ad377SManos Pitsidianakis         error_report("Stream format is not supported.");
283eb9ad377SManos Pitsidianakis         return cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
284eb9ad377SManos Pitsidianakis     }
285eb9ad377SManos Pitsidianakis     if (!(supported_rates & BIT(params->rate))) {
286eb9ad377SManos Pitsidianakis         error_report("Stream rate is not supported.");
287eb9ad377SManos Pitsidianakis         return cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
288eb9ad377SManos Pitsidianakis     }
289eb9ad377SManos Pitsidianakis 
290eb9ad377SManos Pitsidianakis     st_params->buffer_bytes = le32_to_cpu(params->buffer_bytes);
291eb9ad377SManos Pitsidianakis     st_params->period_bytes = le32_to_cpu(params->period_bytes);
292eb9ad377SManos Pitsidianakis     st_params->features = le32_to_cpu(params->features);
293eb9ad377SManos Pitsidianakis     /* the following are uint8_t, so there's no need to bswap the values. */
294eb9ad377SManos Pitsidianakis     st_params->channels = params->channels;
295eb9ad377SManos Pitsidianakis     st_params->format = params->format;
296eb9ad377SManos Pitsidianakis     st_params->rate = params->rate;
297eb9ad377SManos Pitsidianakis 
298eb9ad377SManos Pitsidianakis     return cpu_to_le32(VIRTIO_SND_S_OK);
299eb9ad377SManos Pitsidianakis }
300eb9ad377SManos Pitsidianakis 
301eb9ad377SManos Pitsidianakis /*
30264704ce0SManos Pitsidianakis  * Handles the VIRTIO_SND_R_PCM_SET_PARAMS request.
30364704ce0SManos Pitsidianakis  *
30464704ce0SManos Pitsidianakis  * @s: VirtIOSound device
30564704ce0SManos Pitsidianakis  * @cmd: The request command queue element from VirtIOSound cmdq field
30664704ce0SManos Pitsidianakis  */
30764704ce0SManos Pitsidianakis static void virtio_snd_handle_pcm_set_params(VirtIOSound *s,
30864704ce0SManos Pitsidianakis                                              virtio_snd_ctrl_command *cmd)
30964704ce0SManos Pitsidianakis {
31064704ce0SManos Pitsidianakis     virtio_snd_pcm_set_params req = { 0 };
31164704ce0SManos Pitsidianakis     uint32_t stream_id;
31264704ce0SManos Pitsidianakis     size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
31364704ce0SManos Pitsidianakis                                cmd->elem->out_num,
31464704ce0SManos Pitsidianakis                                0,
31564704ce0SManos Pitsidianakis                                &req,
31664704ce0SManos Pitsidianakis                                sizeof(virtio_snd_pcm_set_params));
31764704ce0SManos Pitsidianakis 
31864704ce0SManos Pitsidianakis     if (msg_sz != sizeof(virtio_snd_pcm_set_params)) {
31964704ce0SManos Pitsidianakis         /*
32064704ce0SManos Pitsidianakis          * TODO: do we need to set DEVICE_NEEDS_RESET?
32164704ce0SManos Pitsidianakis          */
32264704ce0SManos Pitsidianakis         qemu_log_mask(LOG_GUEST_ERROR,
32364704ce0SManos Pitsidianakis                 "%s: virtio-snd command size incorrect %zu vs \
32464704ce0SManos Pitsidianakis                 %zu\n", __func__, msg_sz, sizeof(virtio_snd_pcm_set_params));
32564704ce0SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
32664704ce0SManos Pitsidianakis         return;
32764704ce0SManos Pitsidianakis     }
32864704ce0SManos Pitsidianakis     stream_id = le32_to_cpu(req.hdr.stream_id);
32964704ce0SManos Pitsidianakis     trace_virtio_snd_handle_pcm_set_params(stream_id);
33064704ce0SManos Pitsidianakis     cmd->resp.code = virtio_snd_set_pcm_params(s, stream_id, &req);
33164704ce0SManos Pitsidianakis }
33264704ce0SManos Pitsidianakis 
33364704ce0SManos Pitsidianakis /*
334eb9ad377SManos Pitsidianakis  * Get a QEMU Audiosystem compatible format value from a VIRTIO_SND_PCM_FMT_*
335eb9ad377SManos Pitsidianakis  */
336eb9ad377SManos Pitsidianakis static AudioFormat virtio_snd_get_qemu_format(uint32_t format)
337eb9ad377SManos Pitsidianakis {
338eb9ad377SManos Pitsidianakis     #define CASE(FMT)               \
339eb9ad377SManos Pitsidianakis     case VIRTIO_SND_PCM_FMT_##FMT:  \
340eb9ad377SManos Pitsidianakis         return AUDIO_FORMAT_##FMT;
341eb9ad377SManos Pitsidianakis 
342eb9ad377SManos Pitsidianakis     switch (format) {
343eb9ad377SManos Pitsidianakis     CASE(U8)
344eb9ad377SManos Pitsidianakis     CASE(S8)
345eb9ad377SManos Pitsidianakis     CASE(U16)
346eb9ad377SManos Pitsidianakis     CASE(S16)
347eb9ad377SManos Pitsidianakis     CASE(U32)
348eb9ad377SManos Pitsidianakis     CASE(S32)
349eb9ad377SManos Pitsidianakis     case VIRTIO_SND_PCM_FMT_FLOAT:
350eb9ad377SManos Pitsidianakis         return AUDIO_FORMAT_F32;
351eb9ad377SManos Pitsidianakis     default:
352eb9ad377SManos Pitsidianakis         g_assert_not_reached();
353eb9ad377SManos Pitsidianakis     }
354eb9ad377SManos Pitsidianakis 
355eb9ad377SManos Pitsidianakis     #undef CASE
356eb9ad377SManos Pitsidianakis }
357eb9ad377SManos Pitsidianakis 
358eb9ad377SManos Pitsidianakis /*
359eb9ad377SManos Pitsidianakis  * Get a QEMU Audiosystem compatible frequency value from a
360eb9ad377SManos Pitsidianakis  * VIRTIO_SND_PCM_RATE_*
361eb9ad377SManos Pitsidianakis  */
362eb9ad377SManos Pitsidianakis static uint32_t virtio_snd_get_qemu_freq(uint32_t rate)
363eb9ad377SManos Pitsidianakis {
364eb9ad377SManos Pitsidianakis     #define CASE(RATE)               \
365eb9ad377SManos Pitsidianakis     case VIRTIO_SND_PCM_RATE_##RATE: \
366eb9ad377SManos Pitsidianakis         return RATE;
367eb9ad377SManos Pitsidianakis 
368eb9ad377SManos Pitsidianakis     switch (rate) {
369eb9ad377SManos Pitsidianakis     CASE(5512)
370eb9ad377SManos Pitsidianakis     CASE(8000)
371eb9ad377SManos Pitsidianakis     CASE(11025)
372eb9ad377SManos Pitsidianakis     CASE(16000)
373eb9ad377SManos Pitsidianakis     CASE(22050)
374eb9ad377SManos Pitsidianakis     CASE(32000)
375eb9ad377SManos Pitsidianakis     CASE(44100)
376eb9ad377SManos Pitsidianakis     CASE(48000)
377eb9ad377SManos Pitsidianakis     CASE(64000)
378eb9ad377SManos Pitsidianakis     CASE(88200)
379eb9ad377SManos Pitsidianakis     CASE(96000)
380eb9ad377SManos Pitsidianakis     CASE(176400)
381eb9ad377SManos Pitsidianakis     CASE(192000)
382eb9ad377SManos Pitsidianakis     CASE(384000)
383eb9ad377SManos Pitsidianakis     default:
384eb9ad377SManos Pitsidianakis         g_assert_not_reached();
385eb9ad377SManos Pitsidianakis     }
386eb9ad377SManos Pitsidianakis 
387eb9ad377SManos Pitsidianakis     #undef CASE
388eb9ad377SManos Pitsidianakis }
389eb9ad377SManos Pitsidianakis 
390eb9ad377SManos Pitsidianakis /*
391eb9ad377SManos Pitsidianakis  * Get QEMU Audiosystem compatible audsettings from virtio based pcm stream
392eb9ad377SManos Pitsidianakis  * params.
393eb9ad377SManos Pitsidianakis  */
394eb9ad377SManos Pitsidianakis static void virtio_snd_get_qemu_audsettings(audsettings *as,
395eb9ad377SManos Pitsidianakis                                             virtio_snd_pcm_set_params *params)
396eb9ad377SManos Pitsidianakis {
397eb9ad377SManos Pitsidianakis     as->nchannels = MIN(AUDIO_MAX_CHANNELS, params->channels);
398eb9ad377SManos Pitsidianakis     as->fmt = virtio_snd_get_qemu_format(params->format);
399eb9ad377SManos Pitsidianakis     as->freq = virtio_snd_get_qemu_freq(params->rate);
400eb9ad377SManos Pitsidianakis     as->endianness = target_words_bigendian() ? 1 : 0;
401eb9ad377SManos Pitsidianakis }
402eb9ad377SManos Pitsidianakis 
403eb9ad377SManos Pitsidianakis /*
404eb9ad377SManos Pitsidianakis  * Close a stream and free all its resources.
405eb9ad377SManos Pitsidianakis  *
406eb9ad377SManos Pitsidianakis  * @stream: VirtIOSoundPCMStream *stream
407eb9ad377SManos Pitsidianakis  */
408eb9ad377SManos Pitsidianakis static void virtio_snd_pcm_close(VirtIOSoundPCMStream *stream)
409eb9ad377SManos Pitsidianakis {
41018a75281SManos Pitsidianakis     if (stream) {
41118a75281SManos Pitsidianakis         if (stream->info.direction == VIRTIO_SND_D_OUTPUT) {
41218a75281SManos Pitsidianakis             virtio_snd_pcm_flush(stream);
41318a75281SManos Pitsidianakis             AUD_close_out(&stream->pcm->snd->card, stream->voice.out);
41418a75281SManos Pitsidianakis             stream->voice.out = NULL;
41518a75281SManos Pitsidianakis         }
41618a75281SManos Pitsidianakis     }
417eb9ad377SManos Pitsidianakis }
418eb9ad377SManos Pitsidianakis 
419eb9ad377SManos Pitsidianakis /*
420eb9ad377SManos Pitsidianakis  * Prepares a VirtIOSound card stream.
421eb9ad377SManos Pitsidianakis  * Returns the response status code. (VIRTIO_SND_S_*).
422eb9ad377SManos Pitsidianakis  *
423eb9ad377SManos Pitsidianakis  * @s: VirtIOSound device
424eb9ad377SManos Pitsidianakis  * @stream_id: stream id
425eb9ad377SManos Pitsidianakis  */
426eb9ad377SManos Pitsidianakis static uint32_t virtio_snd_pcm_prepare(VirtIOSound *s, uint32_t stream_id)
427eb9ad377SManos Pitsidianakis {
428eb9ad377SManos Pitsidianakis     audsettings as;
429eb9ad377SManos Pitsidianakis     virtio_snd_pcm_set_params *params;
430eb9ad377SManos Pitsidianakis     VirtIOSoundPCMStream *stream;
431eb9ad377SManos Pitsidianakis 
432eb9ad377SManos Pitsidianakis     if (s->pcm->streams == NULL ||
433eb9ad377SManos Pitsidianakis         s->pcm->pcm_params == NULL ||
434eb9ad377SManos Pitsidianakis         stream_id >= s->snd_conf.streams) {
435eb9ad377SManos Pitsidianakis         return cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
436eb9ad377SManos Pitsidianakis     }
437eb9ad377SManos Pitsidianakis 
438eb9ad377SManos Pitsidianakis     params = virtio_snd_pcm_get_params(s, stream_id);
439eb9ad377SManos Pitsidianakis     if (params == NULL) {
440eb9ad377SManos Pitsidianakis         return cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
441eb9ad377SManos Pitsidianakis     }
442eb9ad377SManos Pitsidianakis 
443eb9ad377SManos Pitsidianakis     stream = virtio_snd_pcm_get_stream(s, stream_id);
444eb9ad377SManos Pitsidianakis     if (stream == NULL) {
445eb9ad377SManos Pitsidianakis         stream = g_new0(VirtIOSoundPCMStream, 1);
446eb9ad377SManos Pitsidianakis         stream->active = false;
447eb9ad377SManos Pitsidianakis         stream->id = stream_id;
448eb9ad377SManos Pitsidianakis         stream->pcm = s->pcm;
449eb9ad377SManos Pitsidianakis         stream->s = s;
45018a75281SManos Pitsidianakis         qemu_mutex_init(&stream->queue_mutex);
45118a75281SManos Pitsidianakis         QSIMPLEQ_INIT(&stream->queue);
45218a75281SManos Pitsidianakis         QSIMPLEQ_INIT(&stream->invalid);
453eb9ad377SManos Pitsidianakis 
454eb9ad377SManos Pitsidianakis         /*
455eb9ad377SManos Pitsidianakis          * stream_id >= s->snd_conf.streams was checked before so this is
456eb9ad377SManos Pitsidianakis          * in-bounds
457eb9ad377SManos Pitsidianakis          */
458eb9ad377SManos Pitsidianakis         s->pcm->streams[stream_id] = stream;
459eb9ad377SManos Pitsidianakis     }
460eb9ad377SManos Pitsidianakis 
461eb9ad377SManos Pitsidianakis     virtio_snd_get_qemu_audsettings(&as, params);
462eb9ad377SManos Pitsidianakis     stream->info.direction = stream_id < s->snd_conf.streams / 2 +
463eb9ad377SManos Pitsidianakis         (s->snd_conf.streams & 1) ? VIRTIO_SND_D_OUTPUT : VIRTIO_SND_D_INPUT;
464eb9ad377SManos Pitsidianakis     stream->info.hdr.hda_fn_nid = VIRTIO_SOUND_HDA_FN_NID;
465eb9ad377SManos Pitsidianakis     stream->info.features = 0;
466eb9ad377SManos Pitsidianakis     stream->info.channels_min = 1;
467eb9ad377SManos Pitsidianakis     stream->info.channels_max = as.nchannels;
468eb9ad377SManos Pitsidianakis     stream->info.formats = supported_formats;
469eb9ad377SManos Pitsidianakis     stream->info.rates = supported_rates;
470eb9ad377SManos Pitsidianakis     stream->params = *params;
471eb9ad377SManos Pitsidianakis 
472eb9ad377SManos Pitsidianakis     stream->positions[0] = VIRTIO_SND_CHMAP_FL;
473eb9ad377SManos Pitsidianakis     stream->positions[1] = VIRTIO_SND_CHMAP_FR;
474eb9ad377SManos Pitsidianakis     stream->as = as;
475eb9ad377SManos Pitsidianakis 
47618a75281SManos Pitsidianakis     if (stream->info.direction == VIRTIO_SND_D_OUTPUT) {
47718a75281SManos Pitsidianakis         stream->voice.out = AUD_open_out(&s->card,
47818a75281SManos Pitsidianakis                                          stream->voice.out,
47918a75281SManos Pitsidianakis                                          "virtio-sound.out",
48018a75281SManos Pitsidianakis                                          stream,
48118a75281SManos Pitsidianakis                                          virtio_snd_pcm_out_cb,
48218a75281SManos Pitsidianakis                                          &as);
48318a75281SManos Pitsidianakis         AUD_set_volume_out(stream->voice.out, 0, 255, 255);
48418a75281SManos Pitsidianakis     } else {
48518a75281SManos Pitsidianakis         qemu_log_mask(LOG_UNIMP, "virtio_snd: input/capture is unimplemented.");
48618a75281SManos Pitsidianakis     }
48718a75281SManos Pitsidianakis 
488eb9ad377SManos Pitsidianakis     return cpu_to_le32(VIRTIO_SND_S_OK);
489eb9ad377SManos Pitsidianakis }
490eb9ad377SManos Pitsidianakis 
491eb9ad377SManos Pitsidianakis static const char *print_code(uint32_t code)
492eb9ad377SManos Pitsidianakis {
493eb9ad377SManos Pitsidianakis     #define CASE(CODE)            \
494eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_##CODE:     \
495eb9ad377SManos Pitsidianakis         return "VIRTIO_SND_R_"#CODE
496eb9ad377SManos Pitsidianakis 
497eb9ad377SManos Pitsidianakis     switch (code) {
498eb9ad377SManos Pitsidianakis     CASE(JACK_INFO);
499eb9ad377SManos Pitsidianakis     CASE(JACK_REMAP);
500eb9ad377SManos Pitsidianakis     CASE(PCM_INFO);
501eb9ad377SManos Pitsidianakis     CASE(PCM_SET_PARAMS);
502eb9ad377SManos Pitsidianakis     CASE(PCM_PREPARE);
503eb9ad377SManos Pitsidianakis     CASE(PCM_RELEASE);
504eb9ad377SManos Pitsidianakis     CASE(PCM_START);
505eb9ad377SManos Pitsidianakis     CASE(PCM_STOP);
506eb9ad377SManos Pitsidianakis     CASE(CHMAP_INFO);
507eb9ad377SManos Pitsidianakis     default:
508eb9ad377SManos Pitsidianakis         return "invalid code";
509eb9ad377SManos Pitsidianakis     }
510eb9ad377SManos Pitsidianakis 
511eb9ad377SManos Pitsidianakis     #undef CASE
512eb9ad377SManos Pitsidianakis };
513eb9ad377SManos Pitsidianakis 
514eb9ad377SManos Pitsidianakis /*
515e5788b8fSManos Pitsidianakis  * Handles VIRTIO_SND_R_PCM_PREPARE.
516e5788b8fSManos Pitsidianakis  *
517e5788b8fSManos Pitsidianakis  * @s: VirtIOSound device
518e5788b8fSManos Pitsidianakis  * @cmd: The request command queue element from VirtIOSound cmdq field
519e5788b8fSManos Pitsidianakis  */
520e5788b8fSManos Pitsidianakis static void virtio_snd_handle_pcm_prepare(VirtIOSound *s,
521e5788b8fSManos Pitsidianakis                                           virtio_snd_ctrl_command *cmd)
522e5788b8fSManos Pitsidianakis {
523e5788b8fSManos Pitsidianakis     uint32_t stream_id;
524e5788b8fSManos Pitsidianakis     size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
525e5788b8fSManos Pitsidianakis                                cmd->elem->out_num,
526e5788b8fSManos Pitsidianakis                                sizeof(virtio_snd_hdr),
527e5788b8fSManos Pitsidianakis                                &stream_id,
528e5788b8fSManos Pitsidianakis                                sizeof(stream_id));
529e5788b8fSManos Pitsidianakis 
530e5788b8fSManos Pitsidianakis     stream_id = le32_to_cpu(stream_id);
531e5788b8fSManos Pitsidianakis     cmd->resp.code = msg_sz == sizeof(stream_id)
532e5788b8fSManos Pitsidianakis                    ? virtio_snd_pcm_prepare(s, stream_id)
533e5788b8fSManos Pitsidianakis                    : cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
534e5788b8fSManos Pitsidianakis }
535e5788b8fSManos Pitsidianakis 
536e5788b8fSManos Pitsidianakis /*
537fa131d4aSManos Pitsidianakis  * Handles VIRTIO_SND_R_PCM_START.
538fa131d4aSManos Pitsidianakis  *
539fa131d4aSManos Pitsidianakis  * @s: VirtIOSound device
540fa131d4aSManos Pitsidianakis  * @cmd: The request command queue element from VirtIOSound cmdq field
541fa131d4aSManos Pitsidianakis  * @start: whether to start or stop the device
542fa131d4aSManos Pitsidianakis  */
543fa131d4aSManos Pitsidianakis static void virtio_snd_handle_pcm_start_stop(VirtIOSound *s,
544fa131d4aSManos Pitsidianakis                                              virtio_snd_ctrl_command *cmd,
545fa131d4aSManos Pitsidianakis                                              bool start)
546fa131d4aSManos Pitsidianakis {
547fa131d4aSManos Pitsidianakis     VirtIOSoundPCMStream *stream;
548fa131d4aSManos Pitsidianakis     virtio_snd_pcm_hdr req;
549fa131d4aSManos Pitsidianakis     uint32_t stream_id;
550fa131d4aSManos Pitsidianakis     size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
551fa131d4aSManos Pitsidianakis                                cmd->elem->out_num,
552fa131d4aSManos Pitsidianakis                                0,
553fa131d4aSManos Pitsidianakis                                &req,
554fa131d4aSManos Pitsidianakis                                sizeof(virtio_snd_pcm_hdr));
555fa131d4aSManos Pitsidianakis 
556fa131d4aSManos Pitsidianakis     if (msg_sz != sizeof(virtio_snd_pcm_hdr)) {
557fa131d4aSManos Pitsidianakis         qemu_log_mask(LOG_GUEST_ERROR,
558fa131d4aSManos Pitsidianakis                 "%s: virtio-snd command size incorrect %zu vs \
559fa131d4aSManos Pitsidianakis                 %zu\n", __func__, msg_sz, sizeof(virtio_snd_pcm_hdr));
560fa131d4aSManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
561fa131d4aSManos Pitsidianakis         return;
562fa131d4aSManos Pitsidianakis     }
563fa131d4aSManos Pitsidianakis 
564fa131d4aSManos Pitsidianakis     stream_id = le32_to_cpu(req.stream_id);
565fa131d4aSManos Pitsidianakis     cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK);
566fa131d4aSManos Pitsidianakis     trace_virtio_snd_handle_pcm_start_stop(start ? "VIRTIO_SND_R_PCM_START" :
567fa131d4aSManos Pitsidianakis             "VIRTIO_SND_R_PCM_STOP", stream_id);
56818a75281SManos Pitsidianakis 
569fa131d4aSManos Pitsidianakis     stream = virtio_snd_pcm_get_stream(s, stream_id);
57018a75281SManos Pitsidianakis     if (stream) {
57118a75281SManos Pitsidianakis         WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
57218a75281SManos Pitsidianakis             stream->active = start;
57318a75281SManos Pitsidianakis         }
57418a75281SManos Pitsidianakis         if (stream->info.direction == VIRTIO_SND_D_OUTPUT) {
57518a75281SManos Pitsidianakis             AUD_set_active_out(stream->voice.out, start);
57618a75281SManos Pitsidianakis         }
57718a75281SManos Pitsidianakis     } else {
57818a75281SManos Pitsidianakis         error_report("Invalid stream id: %"PRIu32, stream_id);
579fa131d4aSManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
580fa131d4aSManos Pitsidianakis         return;
581fa131d4aSManos Pitsidianakis     }
582fa131d4aSManos Pitsidianakis     stream->active = start;
583fa131d4aSManos Pitsidianakis }
584fa131d4aSManos Pitsidianakis 
585fa131d4aSManos Pitsidianakis /*
58618a75281SManos Pitsidianakis  * Returns the number of I/O messages that are being processed.
58718a75281SManos Pitsidianakis  *
58818a75281SManos Pitsidianakis  * @stream: VirtIOSoundPCMStream
58918a75281SManos Pitsidianakis  */
59018a75281SManos Pitsidianakis static size_t virtio_snd_pcm_get_io_msgs_count(VirtIOSoundPCMStream *stream)
59118a75281SManos Pitsidianakis {
59218a75281SManos Pitsidianakis     VirtIOSoundPCMBuffer *buffer, *next;
59318a75281SManos Pitsidianakis     size_t count = 0;
59418a75281SManos Pitsidianakis 
59518a75281SManos Pitsidianakis     WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
59618a75281SManos Pitsidianakis         QSIMPLEQ_FOREACH_SAFE(buffer, &stream->queue, entry, next) {
59718a75281SManos Pitsidianakis             count += 1;
59818a75281SManos Pitsidianakis         }
59918a75281SManos Pitsidianakis         QSIMPLEQ_FOREACH_SAFE(buffer, &stream->invalid, entry, next) {
60018a75281SManos Pitsidianakis             count += 1;
60118a75281SManos Pitsidianakis         }
60218a75281SManos Pitsidianakis     }
60318a75281SManos Pitsidianakis     return count;
60418a75281SManos Pitsidianakis }
60518a75281SManos Pitsidianakis 
60618a75281SManos Pitsidianakis /*
60718a75281SManos Pitsidianakis  * Handles VIRTIO_SND_R_PCM_RELEASE.
608d48800d7SManos Pitsidianakis  *
609d48800d7SManos Pitsidianakis  * @s: VirtIOSound device
610d48800d7SManos Pitsidianakis  * @cmd: The request command queue element from VirtIOSound cmdq field
611d48800d7SManos Pitsidianakis  */
612d48800d7SManos Pitsidianakis static void virtio_snd_handle_pcm_release(VirtIOSound *s,
613d48800d7SManos Pitsidianakis                                           virtio_snd_ctrl_command *cmd)
614d48800d7SManos Pitsidianakis {
615d48800d7SManos Pitsidianakis     uint32_t stream_id;
616d48800d7SManos Pitsidianakis     VirtIOSoundPCMStream *stream;
617d48800d7SManos Pitsidianakis     size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
618d48800d7SManos Pitsidianakis                                cmd->elem->out_num,
619d48800d7SManos Pitsidianakis                                sizeof(virtio_snd_hdr),
620d48800d7SManos Pitsidianakis                                &stream_id,
621d48800d7SManos Pitsidianakis                                sizeof(stream_id));
622d48800d7SManos Pitsidianakis 
623d48800d7SManos Pitsidianakis     if (msg_sz != sizeof(stream_id)) {
624d48800d7SManos Pitsidianakis         /*
625d48800d7SManos Pitsidianakis          * TODO: do we need to set DEVICE_NEEDS_RESET?
626d48800d7SManos Pitsidianakis          */
627d48800d7SManos Pitsidianakis         qemu_log_mask(LOG_GUEST_ERROR,
628d48800d7SManos Pitsidianakis                 "%s: virtio-snd command size incorrect %zu vs \
629d48800d7SManos Pitsidianakis                 %zu\n", __func__, msg_sz, sizeof(stream_id));
630d48800d7SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
631d48800d7SManos Pitsidianakis         return;
632d48800d7SManos Pitsidianakis     }
633d48800d7SManos Pitsidianakis 
634d48800d7SManos Pitsidianakis     stream_id = le32_to_cpu(stream_id);
635d48800d7SManos Pitsidianakis     trace_virtio_snd_handle_pcm_release(stream_id);
636d48800d7SManos Pitsidianakis     stream = virtio_snd_pcm_get_stream(s, stream_id);
637d48800d7SManos Pitsidianakis     if (stream == NULL) {
638d48800d7SManos Pitsidianakis         /*
639d48800d7SManos Pitsidianakis          * TODO: do we need to set DEVICE_NEEDS_RESET?
640d48800d7SManos Pitsidianakis          */
641d48800d7SManos Pitsidianakis         error_report("already released stream %"PRIu32, stream_id);
642d48800d7SManos Pitsidianakis         virtio_error(VIRTIO_DEVICE(s),
643d48800d7SManos Pitsidianakis                      "already released stream %"PRIu32,
644d48800d7SManos Pitsidianakis                      stream_id);
645d48800d7SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
646d48800d7SManos Pitsidianakis         return;
647d48800d7SManos Pitsidianakis     }
64818a75281SManos Pitsidianakis 
64918a75281SManos Pitsidianakis     if (virtio_snd_pcm_get_io_msgs_count(stream)) {
65018a75281SManos Pitsidianakis         /*
65118a75281SManos Pitsidianakis          * virtio-v1.2-csd01, 5.14.6.6.5.1,
65218a75281SManos Pitsidianakis          * Device Requirements: Stream Release
65318a75281SManos Pitsidianakis          *
65418a75281SManos Pitsidianakis          * - The device MUST complete all pending I/O messages for the
65518a75281SManos Pitsidianakis          *   specified stream ID.
65618a75281SManos Pitsidianakis          * - The device MUST NOT complete the control request while there
65718a75281SManos Pitsidianakis          *   are pending I/O messages for the specified stream ID.
65818a75281SManos Pitsidianakis          */
65918a75281SManos Pitsidianakis         trace_virtio_snd_pcm_stream_flush(stream_id);
66018a75281SManos Pitsidianakis         virtio_snd_pcm_flush(stream);
66118a75281SManos Pitsidianakis     }
66218a75281SManos Pitsidianakis 
663d48800d7SManos Pitsidianakis     cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK);
664d48800d7SManos Pitsidianakis }
665d48800d7SManos Pitsidianakis 
666d48800d7SManos Pitsidianakis /*
667eb9ad377SManos Pitsidianakis  * The actual processing done in virtio_snd_process_cmdq().
668eb9ad377SManos Pitsidianakis  *
669eb9ad377SManos Pitsidianakis  * @s: VirtIOSound device
670eb9ad377SManos Pitsidianakis  * @cmd: control command request
671eb9ad377SManos Pitsidianakis  */
672eb9ad377SManos Pitsidianakis static inline void
673eb9ad377SManos Pitsidianakis process_cmd(VirtIOSound *s, virtio_snd_ctrl_command *cmd)
674eb9ad377SManos Pitsidianakis {
675eb9ad377SManos Pitsidianakis     uint32_t code;
676eb9ad377SManos Pitsidianakis     size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
677eb9ad377SManos Pitsidianakis                                cmd->elem->out_num,
678eb9ad377SManos Pitsidianakis                                0,
679eb9ad377SManos Pitsidianakis                                &cmd->ctrl,
680eb9ad377SManos Pitsidianakis                                sizeof(virtio_snd_hdr));
681eb9ad377SManos Pitsidianakis 
682eb9ad377SManos Pitsidianakis     if (msg_sz != sizeof(virtio_snd_hdr)) {
683eb9ad377SManos Pitsidianakis         /*
684eb9ad377SManos Pitsidianakis          * TODO: do we need to set DEVICE_NEEDS_RESET?
685eb9ad377SManos Pitsidianakis          */
686eb9ad377SManos Pitsidianakis         qemu_log_mask(LOG_GUEST_ERROR,
687eb9ad377SManos Pitsidianakis                 "%s: virtio-snd command size incorrect %zu vs \
688eb9ad377SManos Pitsidianakis                 %zu\n", __func__, msg_sz, sizeof(virtio_snd_hdr));
689eb9ad377SManos Pitsidianakis         return;
690eb9ad377SManos Pitsidianakis     }
691eb9ad377SManos Pitsidianakis 
692eb9ad377SManos Pitsidianakis     code = le32_to_cpu(cmd->ctrl.code);
693eb9ad377SManos Pitsidianakis 
694eb9ad377SManos Pitsidianakis     trace_virtio_snd_handle_code(code, print_code(code));
695eb9ad377SManos Pitsidianakis 
696eb9ad377SManos Pitsidianakis     switch (code) {
697eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_JACK_INFO:
698eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_JACK_REMAP:
699eb9ad377SManos Pitsidianakis         qemu_log_mask(LOG_UNIMP,
700eb9ad377SManos Pitsidianakis                      "virtio_snd: jack functionality is unimplemented.\n");
701eb9ad377SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
702eb9ad377SManos Pitsidianakis         break;
703eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_PCM_INFO:
7040ff05dd2SManos Pitsidianakis         virtio_snd_handle_pcm_info(s, cmd);
7050ff05dd2SManos Pitsidianakis         break;
706fa131d4aSManos Pitsidianakis     case VIRTIO_SND_R_PCM_START:
707fa131d4aSManos Pitsidianakis         virtio_snd_handle_pcm_start_stop(s, cmd, true);
708fa131d4aSManos Pitsidianakis         break;
709fa131d4aSManos Pitsidianakis     case VIRTIO_SND_R_PCM_STOP:
710fa131d4aSManos Pitsidianakis         virtio_snd_handle_pcm_start_stop(s, cmd, false);
711fa131d4aSManos Pitsidianakis         break;
712eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_PCM_SET_PARAMS:
71364704ce0SManos Pitsidianakis         virtio_snd_handle_pcm_set_params(s, cmd);
71464704ce0SManos Pitsidianakis         break;
715eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_PCM_PREPARE:
716e5788b8fSManos Pitsidianakis         virtio_snd_handle_pcm_prepare(s, cmd);
717e5788b8fSManos Pitsidianakis         break;
718eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_PCM_RELEASE:
719d48800d7SManos Pitsidianakis         virtio_snd_handle_pcm_release(s, cmd);
720eb9ad377SManos Pitsidianakis         break;
721eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_CHMAP_INFO:
722eb9ad377SManos Pitsidianakis         qemu_log_mask(LOG_UNIMP,
723eb9ad377SManos Pitsidianakis                      "virtio_snd: chmap info functionality is unimplemented.\n");
724eb9ad377SManos Pitsidianakis         trace_virtio_snd_handle_chmap_info();
725eb9ad377SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
726eb9ad377SManos Pitsidianakis         break;
727eb9ad377SManos Pitsidianakis     default:
728eb9ad377SManos Pitsidianakis         /* error */
729eb9ad377SManos Pitsidianakis         error_report("virtio snd header not recognized: %"PRIu32, code);
730eb9ad377SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
731eb9ad377SManos Pitsidianakis     }
732eb9ad377SManos Pitsidianakis 
733eb9ad377SManos Pitsidianakis     iov_from_buf(cmd->elem->in_sg,
734eb9ad377SManos Pitsidianakis                  cmd->elem->in_num,
735eb9ad377SManos Pitsidianakis                  0,
736eb9ad377SManos Pitsidianakis                  &cmd->resp,
737eb9ad377SManos Pitsidianakis                  sizeof(virtio_snd_hdr));
738eb9ad377SManos Pitsidianakis     virtqueue_push(cmd->vq, cmd->elem, sizeof(virtio_snd_hdr));
739eb9ad377SManos Pitsidianakis     virtio_notify(VIRTIO_DEVICE(s), cmd->vq);
740eb9ad377SManos Pitsidianakis }
741eb9ad377SManos Pitsidianakis 
742eb9ad377SManos Pitsidianakis /*
743eb9ad377SManos Pitsidianakis  * Consume all elements in command queue.
744eb9ad377SManos Pitsidianakis  *
745eb9ad377SManos Pitsidianakis  * @s: VirtIOSound device
746eb9ad377SManos Pitsidianakis  */
747eb9ad377SManos Pitsidianakis static void virtio_snd_process_cmdq(VirtIOSound *s)
748eb9ad377SManos Pitsidianakis {
749eb9ad377SManos Pitsidianakis     virtio_snd_ctrl_command *cmd;
750eb9ad377SManos Pitsidianakis 
751eb9ad377SManos Pitsidianakis     if (unlikely(qatomic_read(&s->processing_cmdq))) {
752eb9ad377SManos Pitsidianakis         return;
753eb9ad377SManos Pitsidianakis     }
754eb9ad377SManos Pitsidianakis 
755eb9ad377SManos Pitsidianakis     WITH_QEMU_LOCK_GUARD(&s->cmdq_mutex) {
756eb9ad377SManos Pitsidianakis         qatomic_set(&s->processing_cmdq, true);
757eb9ad377SManos Pitsidianakis         while (!QTAILQ_EMPTY(&s->cmdq)) {
758eb9ad377SManos Pitsidianakis             cmd = QTAILQ_FIRST(&s->cmdq);
759eb9ad377SManos Pitsidianakis 
760eb9ad377SManos Pitsidianakis             /* process command */
761eb9ad377SManos Pitsidianakis             process_cmd(s, cmd);
762eb9ad377SManos Pitsidianakis 
763eb9ad377SManos Pitsidianakis             QTAILQ_REMOVE(&s->cmdq, cmd, next);
764eb9ad377SManos Pitsidianakis 
765eb9ad377SManos Pitsidianakis             virtio_snd_ctrl_cmd_free(cmd);
766eb9ad377SManos Pitsidianakis         }
767eb9ad377SManos Pitsidianakis         qatomic_set(&s->processing_cmdq, false);
768eb9ad377SManos Pitsidianakis     }
769eb9ad377SManos Pitsidianakis }
770eb9ad377SManos Pitsidianakis 
771eb9ad377SManos Pitsidianakis /*
772eb9ad377SManos Pitsidianakis  * The control message handler. Pops an element from the control virtqueue,
773eb9ad377SManos Pitsidianakis  * and stores them to VirtIOSound's cmdq queue and finally calls
774eb9ad377SManos Pitsidianakis  * virtio_snd_process_cmdq() for processing.
775eb9ad377SManos Pitsidianakis  *
776eb9ad377SManos Pitsidianakis  * @vdev: VirtIOSound device
777eb9ad377SManos Pitsidianakis  * @vq: Control virtqueue
778eb9ad377SManos Pitsidianakis  */
779eb9ad377SManos Pitsidianakis static void virtio_snd_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq)
780eb9ad377SManos Pitsidianakis {
781eb9ad377SManos Pitsidianakis     VirtIOSound *s = VIRTIO_SND(vdev);
782eb9ad377SManos Pitsidianakis     VirtQueueElement *elem;
783eb9ad377SManos Pitsidianakis     virtio_snd_ctrl_command *cmd;
784eb9ad377SManos Pitsidianakis 
785eb9ad377SManos Pitsidianakis     trace_virtio_snd_handle_ctrl(vdev, vq);
786eb9ad377SManos Pitsidianakis 
787eb9ad377SManos Pitsidianakis     if (!virtio_queue_ready(vq)) {
788eb9ad377SManos Pitsidianakis         return;
789eb9ad377SManos Pitsidianakis     }
790eb9ad377SManos Pitsidianakis 
791eb9ad377SManos Pitsidianakis     elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
792eb9ad377SManos Pitsidianakis     while (elem) {
793eb9ad377SManos Pitsidianakis         cmd = g_new0(virtio_snd_ctrl_command, 1);
794eb9ad377SManos Pitsidianakis         cmd->elem = elem;
795eb9ad377SManos Pitsidianakis         cmd->vq = vq;
796eb9ad377SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK);
797eb9ad377SManos Pitsidianakis         QTAILQ_INSERT_TAIL(&s->cmdq, cmd, next);
798eb9ad377SManos Pitsidianakis         elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
799eb9ad377SManos Pitsidianakis     }
800eb9ad377SManos Pitsidianakis 
801eb9ad377SManos Pitsidianakis     virtio_snd_process_cmdq(s);
802eb9ad377SManos Pitsidianakis }
803eb9ad377SManos Pitsidianakis 
804eb9ad377SManos Pitsidianakis /*
805eb9ad377SManos Pitsidianakis  * The event virtqueue handler.
806eb9ad377SManos Pitsidianakis  * Not implemented yet.
807eb9ad377SManos Pitsidianakis  *
808eb9ad377SManos Pitsidianakis  * @vdev: VirtIOSound device
809eb9ad377SManos Pitsidianakis  * @vq: event vq
810eb9ad377SManos Pitsidianakis  */
811eb9ad377SManos Pitsidianakis static void virtio_snd_handle_event(VirtIODevice *vdev, VirtQueue *vq)
812eb9ad377SManos Pitsidianakis {
813eb9ad377SManos Pitsidianakis     qemu_log_mask(LOG_UNIMP, "virtio_snd: event queue is unimplemented.\n");
814eb9ad377SManos Pitsidianakis     trace_virtio_snd_handle_event();
815eb9ad377SManos Pitsidianakis }
816eb9ad377SManos Pitsidianakis 
817eb9ad377SManos Pitsidianakis /*
81818a75281SManos Pitsidianakis  * The tx virtqueue handler. Makes the buffers available to their respective
81918a75281SManos Pitsidianakis  * streams for consumption.
82018a75281SManos Pitsidianakis  *
82118a75281SManos Pitsidianakis  * @vdev: VirtIOSound device
82218a75281SManos Pitsidianakis  * @vq: tx virtqueue
82318a75281SManos Pitsidianakis  */
82418a75281SManos Pitsidianakis static void virtio_snd_handle_tx(VirtIODevice *vdev, VirtQueue *vq)
82518a75281SManos Pitsidianakis {
82618a75281SManos Pitsidianakis     VirtIOSound *s = VIRTIO_SND(vdev);
82718a75281SManos Pitsidianakis     VirtIOSoundPCMStream *stream = NULL;
82818a75281SManos Pitsidianakis     VirtIOSoundPCMBuffer *buffer;
82918a75281SManos Pitsidianakis     VirtQueueElement *elem;
83018a75281SManos Pitsidianakis     size_t msg_sz, size;
83118a75281SManos Pitsidianakis     virtio_snd_pcm_xfer hdr;
83218a75281SManos Pitsidianakis     virtio_snd_pcm_status resp = { 0 };
83318a75281SManos Pitsidianakis     uint32_t stream_id;
83418a75281SManos Pitsidianakis     /*
83518a75281SManos Pitsidianakis      * If any of the I/O messages are invalid, put them in stream->invalid and
83618a75281SManos Pitsidianakis      * return them after the for loop.
83718a75281SManos Pitsidianakis      */
83818a75281SManos Pitsidianakis     bool must_empty_invalid_queue = false;
83918a75281SManos Pitsidianakis 
84018a75281SManos Pitsidianakis     if (!virtio_queue_ready(vq)) {
84118a75281SManos Pitsidianakis         return;
84218a75281SManos Pitsidianakis     }
84318a75281SManos Pitsidianakis     trace_virtio_snd_handle_xfer();
84418a75281SManos Pitsidianakis 
84518a75281SManos Pitsidianakis     for (;;) {
84618a75281SManos Pitsidianakis         elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
84718a75281SManos Pitsidianakis         if (!elem) {
84818a75281SManos Pitsidianakis             break;
84918a75281SManos Pitsidianakis         }
85018a75281SManos Pitsidianakis         /* get the message hdr object */
85118a75281SManos Pitsidianakis         msg_sz = iov_to_buf(elem->out_sg,
85218a75281SManos Pitsidianakis                             elem->out_num,
85318a75281SManos Pitsidianakis                             0,
85418a75281SManos Pitsidianakis                             &hdr,
85518a75281SManos Pitsidianakis                             sizeof(virtio_snd_pcm_xfer));
85618a75281SManos Pitsidianakis         if (msg_sz != sizeof(virtio_snd_pcm_xfer)) {
85718a75281SManos Pitsidianakis             goto tx_err;
85818a75281SManos Pitsidianakis         }
85918a75281SManos Pitsidianakis         stream_id = le32_to_cpu(hdr.stream_id);
86018a75281SManos Pitsidianakis 
86118a75281SManos Pitsidianakis         if (stream_id >= s->snd_conf.streams
86218a75281SManos Pitsidianakis             || s->pcm->streams[stream_id] == NULL) {
86318a75281SManos Pitsidianakis             goto tx_err;
86418a75281SManos Pitsidianakis         }
86518a75281SManos Pitsidianakis 
86618a75281SManos Pitsidianakis         stream = s->pcm->streams[stream_id];
86718a75281SManos Pitsidianakis         if (stream->info.direction != VIRTIO_SND_D_OUTPUT) {
86818a75281SManos Pitsidianakis             goto tx_err;
86918a75281SManos Pitsidianakis         }
87018a75281SManos Pitsidianakis 
87118a75281SManos Pitsidianakis         WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
87218a75281SManos Pitsidianakis             size = iov_size(elem->out_sg, elem->out_num) - msg_sz;
87318a75281SManos Pitsidianakis 
87418a75281SManos Pitsidianakis             buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer) + size);
87518a75281SManos Pitsidianakis             buffer->elem = elem;
87618a75281SManos Pitsidianakis             buffer->populated = false;
87718a75281SManos Pitsidianakis             buffer->vq = vq;
87818a75281SManos Pitsidianakis             buffer->size = size;
87918a75281SManos Pitsidianakis             buffer->offset = 0;
88018a75281SManos Pitsidianakis 
88118a75281SManos Pitsidianakis             QSIMPLEQ_INSERT_TAIL(&stream->queue, buffer, entry);
88218a75281SManos Pitsidianakis         }
88318a75281SManos Pitsidianakis         continue;
88418a75281SManos Pitsidianakis 
88518a75281SManos Pitsidianakis tx_err:
88618a75281SManos Pitsidianakis         WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
88718a75281SManos Pitsidianakis             must_empty_invalid_queue = true;
88818a75281SManos Pitsidianakis             buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer));
88918a75281SManos Pitsidianakis             buffer->elem = elem;
89018a75281SManos Pitsidianakis             buffer->vq = vq;
89118a75281SManos Pitsidianakis             QSIMPLEQ_INSERT_TAIL(&stream->invalid, buffer, entry);
89218a75281SManos Pitsidianakis         }
89318a75281SManos Pitsidianakis     }
89418a75281SManos Pitsidianakis 
89518a75281SManos Pitsidianakis     if (must_empty_invalid_queue) {
89618a75281SManos Pitsidianakis         WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
89718a75281SManos Pitsidianakis             while (!QSIMPLEQ_EMPTY(&stream->invalid)) {
89818a75281SManos Pitsidianakis                 buffer = QSIMPLEQ_FIRST(&stream->invalid);
89918a75281SManos Pitsidianakis 
90018a75281SManos Pitsidianakis                 resp.status = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
90118a75281SManos Pitsidianakis                 iov_from_buf(buffer->elem->in_sg,
90218a75281SManos Pitsidianakis                              buffer->elem->in_num,
90318a75281SManos Pitsidianakis                              0,
90418a75281SManos Pitsidianakis                              &resp,
90518a75281SManos Pitsidianakis                              sizeof(virtio_snd_pcm_status));
90618a75281SManos Pitsidianakis                 virtqueue_push(vq, buffer->elem, sizeof(virtio_snd_pcm_status));
90718a75281SManos Pitsidianakis                 QSIMPLEQ_REMOVE_HEAD(&stream->invalid, entry);
90818a75281SManos Pitsidianakis                 virtio_snd_pcm_buffer_free(buffer);
90918a75281SManos Pitsidianakis             }
91018a75281SManos Pitsidianakis             /*
91118a75281SManos Pitsidianakis              * Notify vq about virtio_snd_pcm_status responses.
91218a75281SManos Pitsidianakis              * Buffer responses must be notified separately later.
91318a75281SManos Pitsidianakis              */
91418a75281SManos Pitsidianakis             virtio_notify(vdev, vq);
91518a75281SManos Pitsidianakis         }
91618a75281SManos Pitsidianakis     }
91718a75281SManos Pitsidianakis }
91818a75281SManos Pitsidianakis 
91918a75281SManos Pitsidianakis /*
920eb9ad377SManos Pitsidianakis  * Stub buffer virtqueue handler.
9212880e676SManos Pitsidianakis  *
9222880e676SManos Pitsidianakis  * @vdev: VirtIOSound device
9232880e676SManos Pitsidianakis  * @vq: virtqueue
9242880e676SManos Pitsidianakis  */
925eb9ad377SManos Pitsidianakis static void virtio_snd_handle_xfer(VirtIODevice *vdev, VirtQueue *vq) {}
9262880e676SManos Pitsidianakis 
9272880e676SManos Pitsidianakis static uint64_t get_features(VirtIODevice *vdev, uint64_t features,
9282880e676SManos Pitsidianakis                              Error **errp)
9292880e676SManos Pitsidianakis {
9302880e676SManos Pitsidianakis     /*
9312880e676SManos Pitsidianakis      * virtio-v1.2-csd01, 5.14.3,
9322880e676SManos Pitsidianakis      * Feature Bits
9332880e676SManos Pitsidianakis      * None currently defined.
9342880e676SManos Pitsidianakis      */
9352880e676SManos Pitsidianakis     VirtIOSound *s = VIRTIO_SND(vdev);
9362880e676SManos Pitsidianakis     features |= s->features;
9372880e676SManos Pitsidianakis 
9382880e676SManos Pitsidianakis     trace_virtio_snd_get_features(vdev, features);
9392880e676SManos Pitsidianakis 
9402880e676SManos Pitsidianakis     return features;
9412880e676SManos Pitsidianakis }
9422880e676SManos Pitsidianakis 
9432880e676SManos Pitsidianakis static void
9442880e676SManos Pitsidianakis virtio_snd_vm_state_change(void *opaque, bool running,
9452880e676SManos Pitsidianakis                                        RunState state)
9462880e676SManos Pitsidianakis {
9472880e676SManos Pitsidianakis     if (running) {
9482880e676SManos Pitsidianakis         trace_virtio_snd_vm_state_running();
9492880e676SManos Pitsidianakis     } else {
9502880e676SManos Pitsidianakis         trace_virtio_snd_vm_state_stopped();
9512880e676SManos Pitsidianakis     }
9522880e676SManos Pitsidianakis }
9532880e676SManos Pitsidianakis 
9542880e676SManos Pitsidianakis static void virtio_snd_realize(DeviceState *dev, Error **errp)
9552880e676SManos Pitsidianakis {
9562880e676SManos Pitsidianakis     ERRP_GUARD();
9572880e676SManos Pitsidianakis     VirtIOSound *vsnd = VIRTIO_SND(dev);
9582880e676SManos Pitsidianakis     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
959eb9ad377SManos Pitsidianakis     virtio_snd_pcm_set_params default_params = { 0 };
960eb9ad377SManos Pitsidianakis     uint32_t status;
9612880e676SManos Pitsidianakis 
962eb9ad377SManos Pitsidianakis     vsnd->pcm = NULL;
9632880e676SManos Pitsidianakis     vsnd->vmstate =
9642880e676SManos Pitsidianakis         qemu_add_vm_change_state_handler(virtio_snd_vm_state_change, vsnd);
9652880e676SManos Pitsidianakis 
9662880e676SManos Pitsidianakis     trace_virtio_snd_realize(vsnd);
9672880e676SManos Pitsidianakis 
968eb9ad377SManos Pitsidianakis     vsnd->pcm = g_new0(VirtIOSoundPCM, 1);
969eb9ad377SManos Pitsidianakis     vsnd->pcm->snd = vsnd;
970eb9ad377SManos Pitsidianakis     vsnd->pcm->streams =
971eb9ad377SManos Pitsidianakis         g_new0(VirtIOSoundPCMStream *, vsnd->snd_conf.streams);
972eb9ad377SManos Pitsidianakis     vsnd->pcm->pcm_params =
973eb9ad377SManos Pitsidianakis         g_new0(virtio_snd_pcm_set_params, vsnd->snd_conf.streams);
974eb9ad377SManos Pitsidianakis 
9752880e676SManos Pitsidianakis     virtio_init(vdev, VIRTIO_ID_SOUND, sizeof(virtio_snd_config));
9762880e676SManos Pitsidianakis     virtio_add_feature(&vsnd->features, VIRTIO_F_VERSION_1);
9772880e676SManos Pitsidianakis 
9782880e676SManos Pitsidianakis     /* set number of jacks and streams */
9792880e676SManos Pitsidianakis     if (vsnd->snd_conf.jacks > 8) {
9802880e676SManos Pitsidianakis         error_setg(errp,
9812880e676SManos Pitsidianakis                    "Invalid number of jacks: %"PRIu32,
9822880e676SManos Pitsidianakis                    vsnd->snd_conf.jacks);
9832880e676SManos Pitsidianakis         return;
9842880e676SManos Pitsidianakis     }
9852880e676SManos Pitsidianakis     if (vsnd->snd_conf.streams < 1 || vsnd->snd_conf.streams > 10) {
9862880e676SManos Pitsidianakis         error_setg(errp,
9872880e676SManos Pitsidianakis                    "Invalid number of streams: %"PRIu32,
9882880e676SManos Pitsidianakis                     vsnd->snd_conf.streams);
9892880e676SManos Pitsidianakis         return;
9902880e676SManos Pitsidianakis     }
9912880e676SManos Pitsidianakis 
9922880e676SManos Pitsidianakis     if (vsnd->snd_conf.chmaps > VIRTIO_SND_CHMAP_MAX_SIZE) {
9932880e676SManos Pitsidianakis         error_setg(errp,
9942880e676SManos Pitsidianakis                    "Invalid number of channel maps: %"PRIu32,
9952880e676SManos Pitsidianakis                    vsnd->snd_conf.chmaps);
9962880e676SManos Pitsidianakis         return;
9972880e676SManos Pitsidianakis     }
9982880e676SManos Pitsidianakis 
9992880e676SManos Pitsidianakis     AUD_register_card("virtio-sound", &vsnd->card, errp);
10002880e676SManos Pitsidianakis 
1001eb9ad377SManos Pitsidianakis     /* set default params for all streams */
1002eb9ad377SManos Pitsidianakis     default_params.features = 0;
1003eb9ad377SManos Pitsidianakis     default_params.buffer_bytes = cpu_to_le32(8192);
1004eb9ad377SManos Pitsidianakis     default_params.period_bytes = cpu_to_le32(2048);
1005eb9ad377SManos Pitsidianakis     default_params.channels = 2;
1006eb9ad377SManos Pitsidianakis     default_params.format = VIRTIO_SND_PCM_FMT_S16;
1007eb9ad377SManos Pitsidianakis     default_params.rate = VIRTIO_SND_PCM_RATE_48000;
10082880e676SManos Pitsidianakis     vsnd->queues[VIRTIO_SND_VQ_CONTROL] =
1009eb9ad377SManos Pitsidianakis         virtio_add_queue(vdev, 64, virtio_snd_handle_ctrl);
10102880e676SManos Pitsidianakis     vsnd->queues[VIRTIO_SND_VQ_EVENT] =
1011eb9ad377SManos Pitsidianakis         virtio_add_queue(vdev, 64, virtio_snd_handle_event);
10122880e676SManos Pitsidianakis     vsnd->queues[VIRTIO_SND_VQ_TX] =
101318a75281SManos Pitsidianakis         virtio_add_queue(vdev, 64, virtio_snd_handle_tx);
10142880e676SManos Pitsidianakis     vsnd->queues[VIRTIO_SND_VQ_RX] =
1015eb9ad377SManos Pitsidianakis         virtio_add_queue(vdev, 64, virtio_snd_handle_xfer);
1016eb9ad377SManos Pitsidianakis     qemu_mutex_init(&vsnd->cmdq_mutex);
1017eb9ad377SManos Pitsidianakis     QTAILQ_INIT(&vsnd->cmdq);
1018eb9ad377SManos Pitsidianakis 
1019eb9ad377SManos Pitsidianakis     for (uint32_t i = 0; i < vsnd->snd_conf.streams; i++) {
1020eb9ad377SManos Pitsidianakis         status = virtio_snd_set_pcm_params(vsnd, i, &default_params);
1021eb9ad377SManos Pitsidianakis         if (status != cpu_to_le32(VIRTIO_SND_S_OK)) {
1022eb9ad377SManos Pitsidianakis             error_setg(errp,
1023eb9ad377SManos Pitsidianakis                        "Can't initalize stream params, device responded with %s.",
1024eb9ad377SManos Pitsidianakis                        print_code(status));
1025eb9ad377SManos Pitsidianakis             return;
1026eb9ad377SManos Pitsidianakis         }
1027eb9ad377SManos Pitsidianakis         status = virtio_snd_pcm_prepare(vsnd, i);
1028eb9ad377SManos Pitsidianakis         if (status != cpu_to_le32(VIRTIO_SND_S_OK)) {
1029eb9ad377SManos Pitsidianakis             error_setg(errp,
1030eb9ad377SManos Pitsidianakis                        "Can't prepare streams, device responded with %s.",
1031eb9ad377SManos Pitsidianakis                        print_code(status));
1032eb9ad377SManos Pitsidianakis             return;
1033eb9ad377SManos Pitsidianakis         }
1034eb9ad377SManos Pitsidianakis     }
10352880e676SManos Pitsidianakis }
10362880e676SManos Pitsidianakis 
103718a75281SManos Pitsidianakis static inline void return_tx_buffer(VirtIOSoundPCMStream *stream,
103818a75281SManos Pitsidianakis                                     VirtIOSoundPCMBuffer *buffer)
103918a75281SManos Pitsidianakis {
104018a75281SManos Pitsidianakis     virtio_snd_pcm_status resp = { 0 };
104118a75281SManos Pitsidianakis     resp.status = cpu_to_le32(VIRTIO_SND_S_OK);
104218a75281SManos Pitsidianakis     resp.latency_bytes = cpu_to_le32((uint32_t)buffer->size);
104318a75281SManos Pitsidianakis     iov_from_buf(buffer->elem->in_sg,
104418a75281SManos Pitsidianakis                  buffer->elem->in_num,
104518a75281SManos Pitsidianakis                  0,
104618a75281SManos Pitsidianakis                  &resp,
104718a75281SManos Pitsidianakis                  sizeof(virtio_snd_pcm_status));
104818a75281SManos Pitsidianakis     virtqueue_push(buffer->vq,
104918a75281SManos Pitsidianakis                    buffer->elem,
105018a75281SManos Pitsidianakis                    sizeof(virtio_snd_pcm_status));
105118a75281SManos Pitsidianakis     virtio_notify(VIRTIO_DEVICE(stream->s), buffer->vq);
105218a75281SManos Pitsidianakis     QSIMPLEQ_REMOVE(&stream->queue,
105318a75281SManos Pitsidianakis                     buffer,
105418a75281SManos Pitsidianakis                     VirtIOSoundPCMBuffer,
105518a75281SManos Pitsidianakis                     entry);
105618a75281SManos Pitsidianakis     virtio_snd_pcm_buffer_free(buffer);
105718a75281SManos Pitsidianakis }
105818a75281SManos Pitsidianakis 
105918a75281SManos Pitsidianakis /*
106018a75281SManos Pitsidianakis  * AUD_* output callback.
106118a75281SManos Pitsidianakis  *
106218a75281SManos Pitsidianakis  * @data: VirtIOSoundPCMStream stream
106318a75281SManos Pitsidianakis  * @available: number of bytes that can be written with AUD_write()
106418a75281SManos Pitsidianakis  */
106518a75281SManos Pitsidianakis static void virtio_snd_pcm_out_cb(void *data, int available)
106618a75281SManos Pitsidianakis {
106718a75281SManos Pitsidianakis     VirtIOSoundPCMStream *stream = data;
106818a75281SManos Pitsidianakis     VirtIOSoundPCMBuffer *buffer;
106918a75281SManos Pitsidianakis     size_t size;
107018a75281SManos Pitsidianakis 
107118a75281SManos Pitsidianakis     WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
107218a75281SManos Pitsidianakis         while (!QSIMPLEQ_EMPTY(&stream->queue)) {
107318a75281SManos Pitsidianakis             buffer = QSIMPLEQ_FIRST(&stream->queue);
107418a75281SManos Pitsidianakis             if (!virtio_queue_ready(buffer->vq)) {
107518a75281SManos Pitsidianakis                 return;
107618a75281SManos Pitsidianakis             }
107718a75281SManos Pitsidianakis             if (!stream->active) {
107818a75281SManos Pitsidianakis                 /* Stream has stopped, so do not perform AUD_write. */
107918a75281SManos Pitsidianakis                 return_tx_buffer(stream, buffer);
108018a75281SManos Pitsidianakis                 continue;
108118a75281SManos Pitsidianakis             }
108218a75281SManos Pitsidianakis             if (!buffer->populated) {
108318a75281SManos Pitsidianakis                 iov_to_buf(buffer->elem->out_sg,
108418a75281SManos Pitsidianakis                            buffer->elem->out_num,
108518a75281SManos Pitsidianakis                            sizeof(virtio_snd_pcm_xfer),
108618a75281SManos Pitsidianakis                            buffer->data,
108718a75281SManos Pitsidianakis                            buffer->size);
108818a75281SManos Pitsidianakis                 buffer->populated = true;
108918a75281SManos Pitsidianakis             }
109018a75281SManos Pitsidianakis             for (;;) {
109118a75281SManos Pitsidianakis                 size = AUD_write(stream->voice.out,
109218a75281SManos Pitsidianakis                                  buffer->data + buffer->offset,
109318a75281SManos Pitsidianakis                                  MIN(buffer->size, available));
109418a75281SManos Pitsidianakis                 assert(size <= MIN(buffer->size, available));
109518a75281SManos Pitsidianakis                 if (size == 0) {
109618a75281SManos Pitsidianakis                     /* break out of both loops */
109718a75281SManos Pitsidianakis                     available = 0;
109818a75281SManos Pitsidianakis                     break;
109918a75281SManos Pitsidianakis                 }
110018a75281SManos Pitsidianakis                 buffer->size -= size;
110118a75281SManos Pitsidianakis                 buffer->offset += size;
110218a75281SManos Pitsidianakis                 available -= size;
110318a75281SManos Pitsidianakis                 if (buffer->size < 1) {
110418a75281SManos Pitsidianakis                     return_tx_buffer(stream, buffer);
110518a75281SManos Pitsidianakis                     break;
110618a75281SManos Pitsidianakis                 }
110718a75281SManos Pitsidianakis                 if (!available) {
110818a75281SManos Pitsidianakis                     break;
110918a75281SManos Pitsidianakis                 }
111018a75281SManos Pitsidianakis             }
111118a75281SManos Pitsidianakis             if (!available) {
111218a75281SManos Pitsidianakis                 break;
111318a75281SManos Pitsidianakis             }
111418a75281SManos Pitsidianakis         }
111518a75281SManos Pitsidianakis     }
111618a75281SManos Pitsidianakis }
111718a75281SManos Pitsidianakis 
111818a75281SManos Pitsidianakis /*
111918a75281SManos Pitsidianakis  * Flush all buffer data from this stream's queue into the driver's virtual
112018a75281SManos Pitsidianakis  * queue.
112118a75281SManos Pitsidianakis  *
112218a75281SManos Pitsidianakis  * @stream: VirtIOSoundPCMStream *stream
112318a75281SManos Pitsidianakis  */
112418a75281SManos Pitsidianakis static inline void virtio_snd_pcm_flush(VirtIOSoundPCMStream *stream)
112518a75281SManos Pitsidianakis {
112618a75281SManos Pitsidianakis     VirtIOSoundPCMBuffer *buffer;
112718a75281SManos Pitsidianakis 
112818a75281SManos Pitsidianakis     WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
112918a75281SManos Pitsidianakis         while (!QSIMPLEQ_EMPTY(&stream->queue)) {
113018a75281SManos Pitsidianakis             buffer = QSIMPLEQ_FIRST(&stream->queue);
113118a75281SManos Pitsidianakis             return_tx_buffer(stream, buffer);
113218a75281SManos Pitsidianakis         }
113318a75281SManos Pitsidianakis     }
113418a75281SManos Pitsidianakis }
113518a75281SManos Pitsidianakis 
11362880e676SManos Pitsidianakis static void virtio_snd_unrealize(DeviceState *dev)
11372880e676SManos Pitsidianakis {
11382880e676SManos Pitsidianakis     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
11392880e676SManos Pitsidianakis     VirtIOSound *vsnd = VIRTIO_SND(dev);
1140eb9ad377SManos Pitsidianakis     VirtIOSoundPCMStream *stream;
11412880e676SManos Pitsidianakis 
11422880e676SManos Pitsidianakis     qemu_del_vm_change_state_handler(vsnd->vmstate);
11432880e676SManos Pitsidianakis     trace_virtio_snd_unrealize(vsnd);
11442880e676SManos Pitsidianakis 
1145eb9ad377SManos Pitsidianakis     if (vsnd->pcm) {
1146eb9ad377SManos Pitsidianakis         if (vsnd->pcm->streams) {
1147eb9ad377SManos Pitsidianakis             for (uint32_t i = 0; i < vsnd->snd_conf.streams; i++) {
1148eb9ad377SManos Pitsidianakis                 stream = vsnd->pcm->streams[i];
1149eb9ad377SManos Pitsidianakis                 if (stream) {
1150eb9ad377SManos Pitsidianakis                     virtio_snd_process_cmdq(stream->s);
1151eb9ad377SManos Pitsidianakis                     virtio_snd_pcm_close(stream);
115218a75281SManos Pitsidianakis                     qemu_mutex_destroy(&stream->queue_mutex);
1153eb9ad377SManos Pitsidianakis                     g_free(stream);
1154eb9ad377SManos Pitsidianakis                 }
1155eb9ad377SManos Pitsidianakis             }
1156eb9ad377SManos Pitsidianakis             g_free(vsnd->pcm->streams);
1157eb9ad377SManos Pitsidianakis         }
1158eb9ad377SManos Pitsidianakis         g_free(vsnd->pcm->pcm_params);
1159eb9ad377SManos Pitsidianakis         g_free(vsnd->pcm);
1160eb9ad377SManos Pitsidianakis         vsnd->pcm = NULL;
1161eb9ad377SManos Pitsidianakis     }
11622880e676SManos Pitsidianakis     AUD_remove_card(&vsnd->card);
1163eb9ad377SManos Pitsidianakis     qemu_mutex_destroy(&vsnd->cmdq_mutex);
11642880e676SManos Pitsidianakis     virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_CONTROL]);
11652880e676SManos Pitsidianakis     virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_EVENT]);
11662880e676SManos Pitsidianakis     virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_TX]);
11672880e676SManos Pitsidianakis     virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_RX]);
11682880e676SManos Pitsidianakis     virtio_cleanup(vdev);
11692880e676SManos Pitsidianakis }
11702880e676SManos Pitsidianakis 
11712880e676SManos Pitsidianakis 
1172eb9ad377SManos Pitsidianakis static void virtio_snd_reset(VirtIODevice *vdev)
1173eb9ad377SManos Pitsidianakis {
1174eb9ad377SManos Pitsidianakis     VirtIOSound *s = VIRTIO_SND(vdev);
1175eb9ad377SManos Pitsidianakis     virtio_snd_ctrl_command *cmd;
1176eb9ad377SManos Pitsidianakis 
1177eb9ad377SManos Pitsidianakis     WITH_QEMU_LOCK_GUARD(&s->cmdq_mutex) {
1178eb9ad377SManos Pitsidianakis         while (!QTAILQ_EMPTY(&s->cmdq)) {
1179eb9ad377SManos Pitsidianakis             cmd = QTAILQ_FIRST(&s->cmdq);
1180eb9ad377SManos Pitsidianakis             QTAILQ_REMOVE(&s->cmdq, cmd, next);
1181eb9ad377SManos Pitsidianakis             virtio_snd_ctrl_cmd_free(cmd);
1182eb9ad377SManos Pitsidianakis         }
1183eb9ad377SManos Pitsidianakis     }
1184eb9ad377SManos Pitsidianakis }
11852880e676SManos Pitsidianakis 
11862880e676SManos Pitsidianakis static void virtio_snd_class_init(ObjectClass *klass, void *data)
11872880e676SManos Pitsidianakis {
11882880e676SManos Pitsidianakis     DeviceClass *dc = DEVICE_CLASS(klass);
11892880e676SManos Pitsidianakis     VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
11902880e676SManos Pitsidianakis 
11912880e676SManos Pitsidianakis 
11922880e676SManos Pitsidianakis     set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
11932880e676SManos Pitsidianakis     device_class_set_props(dc, virtio_snd_properties);
11942880e676SManos Pitsidianakis 
11952880e676SManos Pitsidianakis     dc->vmsd = &vmstate_virtio_snd;
11962880e676SManos Pitsidianakis     vdc->vmsd = &vmstate_virtio_snd_device;
11972880e676SManos Pitsidianakis     vdc->realize = virtio_snd_realize;
11982880e676SManos Pitsidianakis     vdc->unrealize = virtio_snd_unrealize;
11992880e676SManos Pitsidianakis     vdc->get_config = virtio_snd_get_config;
12002880e676SManos Pitsidianakis     vdc->set_config = virtio_snd_set_config;
12012880e676SManos Pitsidianakis     vdc->get_features = get_features;
12022880e676SManos Pitsidianakis     vdc->reset = virtio_snd_reset;
12032880e676SManos Pitsidianakis     vdc->legacy_features = 0;
12042880e676SManos Pitsidianakis }
12052880e676SManos Pitsidianakis 
12062880e676SManos Pitsidianakis static const TypeInfo virtio_snd_types[] = {
12072880e676SManos Pitsidianakis     {
12082880e676SManos Pitsidianakis       .name          = TYPE_VIRTIO_SND,
12092880e676SManos Pitsidianakis       .parent        = TYPE_VIRTIO_DEVICE,
12102880e676SManos Pitsidianakis       .instance_size = sizeof(VirtIOSound),
12112880e676SManos Pitsidianakis       .class_init    = virtio_snd_class_init,
12122880e676SManos Pitsidianakis     }
12132880e676SManos Pitsidianakis };
12142880e676SManos Pitsidianakis 
12152880e676SManos Pitsidianakis DEFINE_TYPES(virtio_snd_types)
1216