xref: /openbmc/qemu/hw/audio/virtio-snd.c (revision aaf851a2)
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
31d8d64acbSManos Pitsidianakis #define VIRTIO_SOUND_STREAM_DEFAULT 2
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);
38d8d64acbSManos Pitsidianakis static void virtio_snd_pcm_in_cb(void *data, int available);
3918a75281SManos Pitsidianakis 
40eb9ad377SManos Pitsidianakis static uint32_t supported_formats = BIT(VIRTIO_SND_PCM_FMT_S8)
41eb9ad377SManos Pitsidianakis                                   | BIT(VIRTIO_SND_PCM_FMT_U8)
42eb9ad377SManos Pitsidianakis                                   | BIT(VIRTIO_SND_PCM_FMT_S16)
43eb9ad377SManos Pitsidianakis                                   | BIT(VIRTIO_SND_PCM_FMT_U16)
44eb9ad377SManos Pitsidianakis                                   | BIT(VIRTIO_SND_PCM_FMT_S32)
45eb9ad377SManos Pitsidianakis                                   | BIT(VIRTIO_SND_PCM_FMT_U32)
46eb9ad377SManos Pitsidianakis                                   | BIT(VIRTIO_SND_PCM_FMT_FLOAT);
47eb9ad377SManos Pitsidianakis 
48eb9ad377SManos Pitsidianakis static uint32_t supported_rates = BIT(VIRTIO_SND_PCM_RATE_5512)
49eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_8000)
50eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_11025)
51eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_16000)
52eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_22050)
53eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_32000)
54eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_44100)
55eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_48000)
56eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_64000)
57eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_88200)
58eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_96000)
59eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_176400)
60eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_192000)
61eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_384000);
62eb9ad377SManos Pitsidianakis 
632880e676SManos Pitsidianakis static const VMStateDescription vmstate_virtio_snd_device = {
642880e676SManos Pitsidianakis     .name = TYPE_VIRTIO_SND,
652880e676SManos Pitsidianakis     .version_id = VIRTIO_SOUND_VM_VERSION,
662880e676SManos Pitsidianakis     .minimum_version_id = VIRTIO_SOUND_VM_VERSION,
672880e676SManos Pitsidianakis };
682880e676SManos Pitsidianakis 
692880e676SManos Pitsidianakis static const VMStateDescription vmstate_virtio_snd = {
702880e676SManos Pitsidianakis     .name = TYPE_VIRTIO_SND,
712880e676SManos Pitsidianakis     .minimum_version_id = VIRTIO_SOUND_VM_VERSION,
722880e676SManos Pitsidianakis     .version_id = VIRTIO_SOUND_VM_VERSION,
732880e676SManos Pitsidianakis     .fields = (VMStateField[]) {
742880e676SManos Pitsidianakis         VMSTATE_VIRTIO_DEVICE,
752880e676SManos Pitsidianakis         VMSTATE_END_OF_LIST()
762880e676SManos Pitsidianakis     },
772880e676SManos Pitsidianakis };
782880e676SManos Pitsidianakis 
792880e676SManos Pitsidianakis static Property virtio_snd_properties[] = {
802880e676SManos Pitsidianakis     DEFINE_AUDIO_PROPERTIES(VirtIOSound, card),
812880e676SManos Pitsidianakis     DEFINE_PROP_UINT32("jacks", VirtIOSound, snd_conf.jacks,
822880e676SManos Pitsidianakis                        VIRTIO_SOUND_JACK_DEFAULT),
832880e676SManos Pitsidianakis     DEFINE_PROP_UINT32("streams", VirtIOSound, snd_conf.streams,
842880e676SManos Pitsidianakis                        VIRTIO_SOUND_STREAM_DEFAULT),
852880e676SManos Pitsidianakis     DEFINE_PROP_UINT32("chmaps", VirtIOSound, snd_conf.chmaps,
862880e676SManos Pitsidianakis                        VIRTIO_SOUND_CHMAP_DEFAULT),
872880e676SManos Pitsidianakis     DEFINE_PROP_END_OF_LIST(),
882880e676SManos Pitsidianakis };
892880e676SManos Pitsidianakis 
902880e676SManos Pitsidianakis static void
912880e676SManos Pitsidianakis virtio_snd_get_config(VirtIODevice *vdev, uint8_t *config)
922880e676SManos Pitsidianakis {
932880e676SManos Pitsidianakis     VirtIOSound *s = VIRTIO_SND(vdev);
942880e676SManos Pitsidianakis     virtio_snd_config *sndconfig =
952880e676SManos Pitsidianakis         (virtio_snd_config *)config;
962880e676SManos Pitsidianakis     trace_virtio_snd_get_config(vdev,
972880e676SManos Pitsidianakis                                 s->snd_conf.jacks,
982880e676SManos Pitsidianakis                                 s->snd_conf.streams,
992880e676SManos Pitsidianakis                                 s->snd_conf.chmaps);
1002880e676SManos Pitsidianakis 
1012880e676SManos Pitsidianakis     memcpy(sndconfig, &s->snd_conf, sizeof(s->snd_conf));
1022880e676SManos Pitsidianakis     cpu_to_le32s(&sndconfig->jacks);
1032880e676SManos Pitsidianakis     cpu_to_le32s(&sndconfig->streams);
1042880e676SManos Pitsidianakis     cpu_to_le32s(&sndconfig->chmaps);
1052880e676SManos Pitsidianakis 
1062880e676SManos Pitsidianakis }
1072880e676SManos Pitsidianakis 
1082880e676SManos Pitsidianakis static void
1092880e676SManos Pitsidianakis virtio_snd_set_config(VirtIODevice *vdev, const uint8_t *config)
1102880e676SManos Pitsidianakis {
1112880e676SManos Pitsidianakis     VirtIOSound *s = VIRTIO_SND(vdev);
1122880e676SManos Pitsidianakis     const virtio_snd_config *sndconfig =
1132880e676SManos Pitsidianakis         (const virtio_snd_config *)config;
1142880e676SManos Pitsidianakis 
1152880e676SManos Pitsidianakis 
1162880e676SManos Pitsidianakis    trace_virtio_snd_set_config(vdev,
1172880e676SManos Pitsidianakis                                s->snd_conf.jacks,
1182880e676SManos Pitsidianakis                                sndconfig->jacks,
1192880e676SManos Pitsidianakis                                s->snd_conf.streams,
1202880e676SManos Pitsidianakis                                sndconfig->streams,
1212880e676SManos Pitsidianakis                                s->snd_conf.chmaps,
1222880e676SManos Pitsidianakis                                sndconfig->chmaps);
1232880e676SManos Pitsidianakis 
1242880e676SManos Pitsidianakis     memcpy(&s->snd_conf, sndconfig, sizeof(virtio_snd_config));
1252880e676SManos Pitsidianakis     le32_to_cpus(&s->snd_conf.jacks);
1262880e676SManos Pitsidianakis     le32_to_cpus(&s->snd_conf.streams);
1272880e676SManos Pitsidianakis     le32_to_cpus(&s->snd_conf.chmaps);
1282880e676SManos Pitsidianakis 
1292880e676SManos Pitsidianakis }
1302880e676SManos Pitsidianakis 
131eb9ad377SManos Pitsidianakis static void
13218a75281SManos Pitsidianakis virtio_snd_pcm_buffer_free(VirtIOSoundPCMBuffer *buffer)
13318a75281SManos Pitsidianakis {
13418a75281SManos Pitsidianakis     g_free(buffer->elem);
13518a75281SManos Pitsidianakis     g_free(buffer);
13618a75281SManos Pitsidianakis }
13718a75281SManos Pitsidianakis 
13818a75281SManos Pitsidianakis static void
139eb9ad377SManos Pitsidianakis virtio_snd_ctrl_cmd_free(virtio_snd_ctrl_command *cmd)
140eb9ad377SManos Pitsidianakis {
141eb9ad377SManos Pitsidianakis     g_free(cmd->elem);
142eb9ad377SManos Pitsidianakis     g_free(cmd);
143eb9ad377SManos Pitsidianakis }
144eb9ad377SManos Pitsidianakis 
1452880e676SManos Pitsidianakis /*
146eb9ad377SManos Pitsidianakis  * Get a specific stream from the virtio sound card device.
147eb9ad377SManos Pitsidianakis  * Returns NULL if @stream_id is invalid or not allocated.
148eb9ad377SManos Pitsidianakis  *
149eb9ad377SManos Pitsidianakis  * @s: VirtIOSound device
150eb9ad377SManos Pitsidianakis  * @stream_id: stream id
151eb9ad377SManos Pitsidianakis  */
152eb9ad377SManos Pitsidianakis static VirtIOSoundPCMStream *virtio_snd_pcm_get_stream(VirtIOSound *s,
153eb9ad377SManos Pitsidianakis                                                        uint32_t stream_id)
154eb9ad377SManos Pitsidianakis {
155eb9ad377SManos Pitsidianakis     return stream_id >= s->snd_conf.streams ? NULL :
156eb9ad377SManos Pitsidianakis         s->pcm->streams[stream_id];
157eb9ad377SManos Pitsidianakis }
158eb9ad377SManos Pitsidianakis 
159eb9ad377SManos Pitsidianakis /*
160eb9ad377SManos Pitsidianakis  * Get params for a specific stream.
161eb9ad377SManos Pitsidianakis  *
162eb9ad377SManos Pitsidianakis  * @s: VirtIOSound device
163eb9ad377SManos Pitsidianakis  * @stream_id: stream id
164eb9ad377SManos Pitsidianakis  */
165eb9ad377SManos Pitsidianakis static virtio_snd_pcm_set_params *virtio_snd_pcm_get_params(VirtIOSound *s,
166eb9ad377SManos Pitsidianakis                                                             uint32_t stream_id)
167eb9ad377SManos Pitsidianakis {
168eb9ad377SManos Pitsidianakis     return stream_id >= s->snd_conf.streams ? NULL
169eb9ad377SManos Pitsidianakis         : &s->pcm->pcm_params[stream_id];
170eb9ad377SManos Pitsidianakis }
171eb9ad377SManos Pitsidianakis 
172eb9ad377SManos Pitsidianakis /*
1730ff05dd2SManos Pitsidianakis  * Handle the VIRTIO_SND_R_PCM_INFO request.
1740ff05dd2SManos Pitsidianakis  * The function writes the info structs to the request element.
1750ff05dd2SManos Pitsidianakis  *
1760ff05dd2SManos Pitsidianakis  * @s: VirtIOSound device
1770ff05dd2SManos Pitsidianakis  * @cmd: The request command queue element from VirtIOSound cmdq field
1780ff05dd2SManos Pitsidianakis  */
1790ff05dd2SManos Pitsidianakis static void virtio_snd_handle_pcm_info(VirtIOSound *s,
1800ff05dd2SManos Pitsidianakis                                        virtio_snd_ctrl_command *cmd)
1810ff05dd2SManos Pitsidianakis {
1820ff05dd2SManos Pitsidianakis     uint32_t stream_id, start_id, count, size;
1830ff05dd2SManos Pitsidianakis     virtio_snd_pcm_info val;
1840ff05dd2SManos Pitsidianakis     virtio_snd_query_info req;
1850ff05dd2SManos Pitsidianakis     VirtIOSoundPCMStream *stream = NULL;
1860ff05dd2SManos Pitsidianakis     g_autofree virtio_snd_pcm_info *pcm_info = NULL;
1870ff05dd2SManos Pitsidianakis     size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
1880ff05dd2SManos Pitsidianakis                                cmd->elem->out_num,
1890ff05dd2SManos Pitsidianakis                                0,
1900ff05dd2SManos Pitsidianakis                                &req,
1910ff05dd2SManos Pitsidianakis                                sizeof(virtio_snd_query_info));
1920ff05dd2SManos Pitsidianakis 
1930ff05dd2SManos Pitsidianakis     if (msg_sz != sizeof(virtio_snd_query_info)) {
1940ff05dd2SManos Pitsidianakis         /*
1950ff05dd2SManos Pitsidianakis          * TODO: do we need to set DEVICE_NEEDS_RESET?
1960ff05dd2SManos Pitsidianakis          */
1970ff05dd2SManos Pitsidianakis         qemu_log_mask(LOG_GUEST_ERROR,
1980ff05dd2SManos Pitsidianakis                 "%s: virtio-snd command size incorrect %zu vs \
1990ff05dd2SManos Pitsidianakis                 %zu\n", __func__, msg_sz, sizeof(virtio_snd_query_info));
2000ff05dd2SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
2010ff05dd2SManos Pitsidianakis         return;
2020ff05dd2SManos Pitsidianakis     }
2030ff05dd2SManos Pitsidianakis 
2040ff05dd2SManos Pitsidianakis     start_id = le32_to_cpu(req.start_id);
2050ff05dd2SManos Pitsidianakis     count = le32_to_cpu(req.count);
2060ff05dd2SManos Pitsidianakis     size = le32_to_cpu(req.size);
2070ff05dd2SManos Pitsidianakis 
2080ff05dd2SManos Pitsidianakis     if (iov_size(cmd->elem->in_sg, cmd->elem->in_num) <
2090ff05dd2SManos Pitsidianakis         sizeof(virtio_snd_hdr) + size * count) {
2100ff05dd2SManos Pitsidianakis         /*
2110ff05dd2SManos Pitsidianakis          * TODO: do we need to set DEVICE_NEEDS_RESET?
2120ff05dd2SManos Pitsidianakis          */
2130ff05dd2SManos Pitsidianakis         error_report("pcm info: buffer too small, got: %zu, needed: %zu",
2140ff05dd2SManos Pitsidianakis                 iov_size(cmd->elem->in_sg, cmd->elem->in_num),
2150ff05dd2SManos Pitsidianakis                 sizeof(virtio_snd_pcm_info));
2160ff05dd2SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
2170ff05dd2SManos Pitsidianakis         return;
2180ff05dd2SManos Pitsidianakis     }
2190ff05dd2SManos Pitsidianakis 
2200ff05dd2SManos Pitsidianakis     pcm_info = g_new0(virtio_snd_pcm_info, count);
2210ff05dd2SManos Pitsidianakis     for (uint32_t i = 0; i < count; i++) {
2220ff05dd2SManos Pitsidianakis         stream_id = i + start_id;
2230ff05dd2SManos Pitsidianakis         trace_virtio_snd_handle_pcm_info(stream_id);
2240ff05dd2SManos Pitsidianakis         stream = virtio_snd_pcm_get_stream(s, stream_id);
2250ff05dd2SManos Pitsidianakis         if (!stream) {
2260ff05dd2SManos Pitsidianakis             error_report("Invalid stream id: %"PRIu32, stream_id);
2270ff05dd2SManos Pitsidianakis             cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
2280ff05dd2SManos Pitsidianakis             return;
2290ff05dd2SManos Pitsidianakis         }
2300ff05dd2SManos Pitsidianakis         val = stream->info;
2310ff05dd2SManos Pitsidianakis         val.hdr.hda_fn_nid = cpu_to_le32(val.hdr.hda_fn_nid);
2320ff05dd2SManos Pitsidianakis         val.features = cpu_to_le32(val.features);
2330ff05dd2SManos Pitsidianakis         val.formats = cpu_to_le64(val.formats);
2340ff05dd2SManos Pitsidianakis         val.rates = cpu_to_le64(val.rates);
2350ff05dd2SManos Pitsidianakis         /*
2360ff05dd2SManos Pitsidianakis          * 5.14.6.6.2.1 Device Requirements: Stream Information The device MUST
2370ff05dd2SManos Pitsidianakis          * NOT set undefined feature, format, rate and direction values. The
2380ff05dd2SManos Pitsidianakis          * device MUST initialize the padding bytes to 0.
2390ff05dd2SManos Pitsidianakis          */
2400ff05dd2SManos Pitsidianakis         pcm_info[i] = val;
2410ff05dd2SManos Pitsidianakis         memset(&pcm_info[i].padding, 0, 5);
2420ff05dd2SManos Pitsidianakis     }
2430ff05dd2SManos Pitsidianakis 
2440ff05dd2SManos Pitsidianakis     cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK);
2450ff05dd2SManos Pitsidianakis     iov_from_buf(cmd->elem->in_sg,
2460ff05dd2SManos Pitsidianakis                  cmd->elem->in_num,
2470ff05dd2SManos Pitsidianakis                  sizeof(virtio_snd_hdr),
2480ff05dd2SManos Pitsidianakis                  pcm_info,
2490ff05dd2SManos Pitsidianakis                  sizeof(virtio_snd_pcm_info) * count);
2500ff05dd2SManos Pitsidianakis }
2510ff05dd2SManos Pitsidianakis 
2520ff05dd2SManos Pitsidianakis /*
253eb9ad377SManos Pitsidianakis  * Set the given stream params.
254eb9ad377SManos Pitsidianakis  * Called by both virtio_snd_handle_pcm_set_params and during device
255eb9ad377SManos Pitsidianakis  * initialization.
256eb9ad377SManos Pitsidianakis  * Returns the response status code. (VIRTIO_SND_S_*).
257eb9ad377SManos Pitsidianakis  *
258eb9ad377SManos Pitsidianakis  * @s: VirtIOSound device
259eb9ad377SManos Pitsidianakis  * @params: The PCM params as defined in the virtio specification
260eb9ad377SManos Pitsidianakis  */
261eb9ad377SManos Pitsidianakis static
262eb9ad377SManos Pitsidianakis uint32_t virtio_snd_set_pcm_params(VirtIOSound *s,
263eb9ad377SManos Pitsidianakis                                    uint32_t stream_id,
264eb9ad377SManos Pitsidianakis                                    virtio_snd_pcm_set_params *params)
265eb9ad377SManos Pitsidianakis {
266eb9ad377SManos Pitsidianakis     virtio_snd_pcm_set_params *st_params;
267eb9ad377SManos Pitsidianakis 
268eb9ad377SManos Pitsidianakis     if (stream_id >= s->snd_conf.streams || s->pcm->pcm_params == NULL) {
269eb9ad377SManos Pitsidianakis         /*
270eb9ad377SManos Pitsidianakis          * TODO: do we need to set DEVICE_NEEDS_RESET?
271eb9ad377SManos Pitsidianakis          */
272eb9ad377SManos Pitsidianakis         virtio_error(VIRTIO_DEVICE(s), "Streams have not been initialized.\n");
273eb9ad377SManos Pitsidianakis         return cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
274eb9ad377SManos Pitsidianakis     }
275eb9ad377SManos Pitsidianakis 
276eb9ad377SManos Pitsidianakis     st_params = virtio_snd_pcm_get_params(s, stream_id);
277eb9ad377SManos Pitsidianakis 
278eb9ad377SManos Pitsidianakis     if (params->channels < 1 || params->channels > AUDIO_MAX_CHANNELS) {
279eb9ad377SManos Pitsidianakis         error_report("Number of channels is not supported.");
280eb9ad377SManos Pitsidianakis         return cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
281eb9ad377SManos Pitsidianakis     }
282eb9ad377SManos Pitsidianakis     if (!(supported_formats & BIT(params->format))) {
283eb9ad377SManos Pitsidianakis         error_report("Stream format is not supported.");
284eb9ad377SManos Pitsidianakis         return cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
285eb9ad377SManos Pitsidianakis     }
286eb9ad377SManos Pitsidianakis     if (!(supported_rates & BIT(params->rate))) {
287eb9ad377SManos Pitsidianakis         error_report("Stream rate is not supported.");
288eb9ad377SManos Pitsidianakis         return cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
289eb9ad377SManos Pitsidianakis     }
290eb9ad377SManos Pitsidianakis 
291eb9ad377SManos Pitsidianakis     st_params->buffer_bytes = le32_to_cpu(params->buffer_bytes);
292eb9ad377SManos Pitsidianakis     st_params->period_bytes = le32_to_cpu(params->period_bytes);
293eb9ad377SManos Pitsidianakis     st_params->features = le32_to_cpu(params->features);
294eb9ad377SManos Pitsidianakis     /* the following are uint8_t, so there's no need to bswap the values. */
295eb9ad377SManos Pitsidianakis     st_params->channels = params->channels;
296eb9ad377SManos Pitsidianakis     st_params->format = params->format;
297eb9ad377SManos Pitsidianakis     st_params->rate = params->rate;
298eb9ad377SManos Pitsidianakis 
299eb9ad377SManos Pitsidianakis     return cpu_to_le32(VIRTIO_SND_S_OK);
300eb9ad377SManos Pitsidianakis }
301eb9ad377SManos Pitsidianakis 
302eb9ad377SManos Pitsidianakis /*
30364704ce0SManos Pitsidianakis  * Handles the VIRTIO_SND_R_PCM_SET_PARAMS request.
30464704ce0SManos Pitsidianakis  *
30564704ce0SManos Pitsidianakis  * @s: VirtIOSound device
30664704ce0SManos Pitsidianakis  * @cmd: The request command queue element from VirtIOSound cmdq field
30764704ce0SManos Pitsidianakis  */
30864704ce0SManos Pitsidianakis static void virtio_snd_handle_pcm_set_params(VirtIOSound *s,
30964704ce0SManos Pitsidianakis                                              virtio_snd_ctrl_command *cmd)
31064704ce0SManos Pitsidianakis {
31164704ce0SManos Pitsidianakis     virtio_snd_pcm_set_params req = { 0 };
31264704ce0SManos Pitsidianakis     uint32_t stream_id;
31364704ce0SManos Pitsidianakis     size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
31464704ce0SManos Pitsidianakis                                cmd->elem->out_num,
31564704ce0SManos Pitsidianakis                                0,
31664704ce0SManos Pitsidianakis                                &req,
31764704ce0SManos Pitsidianakis                                sizeof(virtio_snd_pcm_set_params));
31864704ce0SManos Pitsidianakis 
31964704ce0SManos Pitsidianakis     if (msg_sz != sizeof(virtio_snd_pcm_set_params)) {
32064704ce0SManos Pitsidianakis         /*
32164704ce0SManos Pitsidianakis          * TODO: do we need to set DEVICE_NEEDS_RESET?
32264704ce0SManos Pitsidianakis          */
32364704ce0SManos Pitsidianakis         qemu_log_mask(LOG_GUEST_ERROR,
32464704ce0SManos Pitsidianakis                 "%s: virtio-snd command size incorrect %zu vs \
32564704ce0SManos Pitsidianakis                 %zu\n", __func__, msg_sz, sizeof(virtio_snd_pcm_set_params));
32664704ce0SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
32764704ce0SManos Pitsidianakis         return;
32864704ce0SManos Pitsidianakis     }
32964704ce0SManos Pitsidianakis     stream_id = le32_to_cpu(req.hdr.stream_id);
33064704ce0SManos Pitsidianakis     trace_virtio_snd_handle_pcm_set_params(stream_id);
33164704ce0SManos Pitsidianakis     cmd->resp.code = virtio_snd_set_pcm_params(s, stream_id, &req);
33264704ce0SManos Pitsidianakis }
33364704ce0SManos Pitsidianakis 
33464704ce0SManos Pitsidianakis /*
335eb9ad377SManos Pitsidianakis  * Get a QEMU Audiosystem compatible format value from a VIRTIO_SND_PCM_FMT_*
336eb9ad377SManos Pitsidianakis  */
337eb9ad377SManos Pitsidianakis static AudioFormat virtio_snd_get_qemu_format(uint32_t format)
338eb9ad377SManos Pitsidianakis {
339eb9ad377SManos Pitsidianakis     #define CASE(FMT)               \
340eb9ad377SManos Pitsidianakis     case VIRTIO_SND_PCM_FMT_##FMT:  \
341eb9ad377SManos Pitsidianakis         return AUDIO_FORMAT_##FMT;
342eb9ad377SManos Pitsidianakis 
343eb9ad377SManos Pitsidianakis     switch (format) {
344eb9ad377SManos Pitsidianakis     CASE(U8)
345eb9ad377SManos Pitsidianakis     CASE(S8)
346eb9ad377SManos Pitsidianakis     CASE(U16)
347eb9ad377SManos Pitsidianakis     CASE(S16)
348eb9ad377SManos Pitsidianakis     CASE(U32)
349eb9ad377SManos Pitsidianakis     CASE(S32)
350eb9ad377SManos Pitsidianakis     case VIRTIO_SND_PCM_FMT_FLOAT:
351eb9ad377SManos Pitsidianakis         return AUDIO_FORMAT_F32;
352eb9ad377SManos Pitsidianakis     default:
353eb9ad377SManos Pitsidianakis         g_assert_not_reached();
354eb9ad377SManos Pitsidianakis     }
355eb9ad377SManos Pitsidianakis 
356eb9ad377SManos Pitsidianakis     #undef CASE
357eb9ad377SManos Pitsidianakis }
358eb9ad377SManos Pitsidianakis 
359eb9ad377SManos Pitsidianakis /*
360eb9ad377SManos Pitsidianakis  * Get a QEMU Audiosystem compatible frequency value from a
361eb9ad377SManos Pitsidianakis  * VIRTIO_SND_PCM_RATE_*
362eb9ad377SManos Pitsidianakis  */
363eb9ad377SManos Pitsidianakis static uint32_t virtio_snd_get_qemu_freq(uint32_t rate)
364eb9ad377SManos Pitsidianakis {
365eb9ad377SManos Pitsidianakis     #define CASE(RATE)               \
366eb9ad377SManos Pitsidianakis     case VIRTIO_SND_PCM_RATE_##RATE: \
367eb9ad377SManos Pitsidianakis         return RATE;
368eb9ad377SManos Pitsidianakis 
369eb9ad377SManos Pitsidianakis     switch (rate) {
370eb9ad377SManos Pitsidianakis     CASE(5512)
371eb9ad377SManos Pitsidianakis     CASE(8000)
372eb9ad377SManos Pitsidianakis     CASE(11025)
373eb9ad377SManos Pitsidianakis     CASE(16000)
374eb9ad377SManos Pitsidianakis     CASE(22050)
375eb9ad377SManos Pitsidianakis     CASE(32000)
376eb9ad377SManos Pitsidianakis     CASE(44100)
377eb9ad377SManos Pitsidianakis     CASE(48000)
378eb9ad377SManos Pitsidianakis     CASE(64000)
379eb9ad377SManos Pitsidianakis     CASE(88200)
380eb9ad377SManos Pitsidianakis     CASE(96000)
381eb9ad377SManos Pitsidianakis     CASE(176400)
382eb9ad377SManos Pitsidianakis     CASE(192000)
383eb9ad377SManos Pitsidianakis     CASE(384000)
384eb9ad377SManos Pitsidianakis     default:
385eb9ad377SManos Pitsidianakis         g_assert_not_reached();
386eb9ad377SManos Pitsidianakis     }
387eb9ad377SManos Pitsidianakis 
388eb9ad377SManos Pitsidianakis     #undef CASE
389eb9ad377SManos Pitsidianakis }
390eb9ad377SManos Pitsidianakis 
391eb9ad377SManos Pitsidianakis /*
392eb9ad377SManos Pitsidianakis  * Get QEMU Audiosystem compatible audsettings from virtio based pcm stream
393eb9ad377SManos Pitsidianakis  * params.
394eb9ad377SManos Pitsidianakis  */
395eb9ad377SManos Pitsidianakis static void virtio_snd_get_qemu_audsettings(audsettings *as,
396eb9ad377SManos Pitsidianakis                                             virtio_snd_pcm_set_params *params)
397eb9ad377SManos Pitsidianakis {
398eb9ad377SManos Pitsidianakis     as->nchannels = MIN(AUDIO_MAX_CHANNELS, params->channels);
399eb9ad377SManos Pitsidianakis     as->fmt = virtio_snd_get_qemu_format(params->format);
400eb9ad377SManos Pitsidianakis     as->freq = virtio_snd_get_qemu_freq(params->rate);
401eb9ad377SManos Pitsidianakis     as->endianness = target_words_bigendian() ? 1 : 0;
402eb9ad377SManos Pitsidianakis }
403eb9ad377SManos Pitsidianakis 
404eb9ad377SManos Pitsidianakis /*
405eb9ad377SManos Pitsidianakis  * Close a stream and free all its resources.
406eb9ad377SManos Pitsidianakis  *
407eb9ad377SManos Pitsidianakis  * @stream: VirtIOSoundPCMStream *stream
408eb9ad377SManos Pitsidianakis  */
409eb9ad377SManos Pitsidianakis static void virtio_snd_pcm_close(VirtIOSoundPCMStream *stream)
410eb9ad377SManos Pitsidianakis {
41118a75281SManos Pitsidianakis     if (stream) {
41218a75281SManos Pitsidianakis         virtio_snd_pcm_flush(stream);
413d8d64acbSManos Pitsidianakis         if (stream->info.direction == VIRTIO_SND_D_OUTPUT) {
41418a75281SManos Pitsidianakis             AUD_close_out(&stream->pcm->snd->card, stream->voice.out);
41518a75281SManos Pitsidianakis             stream->voice.out = NULL;
416d8d64acbSManos Pitsidianakis         } else if (stream->info.direction == VIRTIO_SND_D_INPUT) {
417d8d64acbSManos Pitsidianakis             AUD_close_in(&stream->pcm->snd->card, stream->voice.in);
418d8d64acbSManos Pitsidianakis             stream->voice.in = NULL;
41918a75281SManos Pitsidianakis         }
42018a75281SManos Pitsidianakis     }
421eb9ad377SManos Pitsidianakis }
422eb9ad377SManos Pitsidianakis 
423eb9ad377SManos Pitsidianakis /*
424eb9ad377SManos Pitsidianakis  * Prepares a VirtIOSound card stream.
425eb9ad377SManos Pitsidianakis  * Returns the response status code. (VIRTIO_SND_S_*).
426eb9ad377SManos Pitsidianakis  *
427eb9ad377SManos Pitsidianakis  * @s: VirtIOSound device
428eb9ad377SManos Pitsidianakis  * @stream_id: stream id
429eb9ad377SManos Pitsidianakis  */
430eb9ad377SManos Pitsidianakis static uint32_t virtio_snd_pcm_prepare(VirtIOSound *s, uint32_t stream_id)
431eb9ad377SManos Pitsidianakis {
432eb9ad377SManos Pitsidianakis     audsettings as;
433eb9ad377SManos Pitsidianakis     virtio_snd_pcm_set_params *params;
434eb9ad377SManos Pitsidianakis     VirtIOSoundPCMStream *stream;
435eb9ad377SManos Pitsidianakis 
436eb9ad377SManos Pitsidianakis     if (s->pcm->streams == NULL ||
437eb9ad377SManos Pitsidianakis         s->pcm->pcm_params == NULL ||
438eb9ad377SManos Pitsidianakis         stream_id >= s->snd_conf.streams) {
439eb9ad377SManos Pitsidianakis         return cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
440eb9ad377SManos Pitsidianakis     }
441eb9ad377SManos Pitsidianakis 
442eb9ad377SManos Pitsidianakis     params = virtio_snd_pcm_get_params(s, stream_id);
443eb9ad377SManos Pitsidianakis     if (params == NULL) {
444eb9ad377SManos Pitsidianakis         return cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
445eb9ad377SManos Pitsidianakis     }
446eb9ad377SManos Pitsidianakis 
447eb9ad377SManos Pitsidianakis     stream = virtio_snd_pcm_get_stream(s, stream_id);
448eb9ad377SManos Pitsidianakis     if (stream == NULL) {
449eb9ad377SManos Pitsidianakis         stream = g_new0(VirtIOSoundPCMStream, 1);
450eb9ad377SManos Pitsidianakis         stream->active = false;
451eb9ad377SManos Pitsidianakis         stream->id = stream_id;
452eb9ad377SManos Pitsidianakis         stream->pcm = s->pcm;
453eb9ad377SManos Pitsidianakis         stream->s = s;
45418a75281SManos Pitsidianakis         qemu_mutex_init(&stream->queue_mutex);
45518a75281SManos Pitsidianakis         QSIMPLEQ_INIT(&stream->queue);
45618a75281SManos Pitsidianakis         QSIMPLEQ_INIT(&stream->invalid);
457eb9ad377SManos Pitsidianakis 
458eb9ad377SManos Pitsidianakis         /*
459eb9ad377SManos Pitsidianakis          * stream_id >= s->snd_conf.streams was checked before so this is
460eb9ad377SManos Pitsidianakis          * in-bounds
461eb9ad377SManos Pitsidianakis          */
462eb9ad377SManos Pitsidianakis         s->pcm->streams[stream_id] = stream;
463eb9ad377SManos Pitsidianakis     }
464eb9ad377SManos Pitsidianakis 
465eb9ad377SManos Pitsidianakis     virtio_snd_get_qemu_audsettings(&as, params);
466eb9ad377SManos Pitsidianakis     stream->info.direction = stream_id < s->snd_conf.streams / 2 +
467eb9ad377SManos Pitsidianakis         (s->snd_conf.streams & 1) ? VIRTIO_SND_D_OUTPUT : VIRTIO_SND_D_INPUT;
468eb9ad377SManos Pitsidianakis     stream->info.hdr.hda_fn_nid = VIRTIO_SOUND_HDA_FN_NID;
469eb9ad377SManos Pitsidianakis     stream->info.features = 0;
470eb9ad377SManos Pitsidianakis     stream->info.channels_min = 1;
471eb9ad377SManos Pitsidianakis     stream->info.channels_max = as.nchannels;
472eb9ad377SManos Pitsidianakis     stream->info.formats = supported_formats;
473eb9ad377SManos Pitsidianakis     stream->info.rates = supported_rates;
474eb9ad377SManos Pitsidianakis     stream->params = *params;
475eb9ad377SManos Pitsidianakis 
476eb9ad377SManos Pitsidianakis     stream->positions[0] = VIRTIO_SND_CHMAP_FL;
477eb9ad377SManos Pitsidianakis     stream->positions[1] = VIRTIO_SND_CHMAP_FR;
478eb9ad377SManos Pitsidianakis     stream->as = as;
479eb9ad377SManos Pitsidianakis 
48018a75281SManos Pitsidianakis     if (stream->info.direction == VIRTIO_SND_D_OUTPUT) {
48118a75281SManos Pitsidianakis         stream->voice.out = AUD_open_out(&s->card,
48218a75281SManos Pitsidianakis                                          stream->voice.out,
48318a75281SManos Pitsidianakis                                          "virtio-sound.out",
48418a75281SManos Pitsidianakis                                          stream,
48518a75281SManos Pitsidianakis                                          virtio_snd_pcm_out_cb,
48618a75281SManos Pitsidianakis                                          &as);
48718a75281SManos Pitsidianakis         AUD_set_volume_out(stream->voice.out, 0, 255, 255);
48818a75281SManos Pitsidianakis     } else {
489d8d64acbSManos Pitsidianakis         stream->voice.in = AUD_open_in(&s->card,
490d8d64acbSManos Pitsidianakis                                         stream->voice.in,
491d8d64acbSManos Pitsidianakis                                         "virtio-sound.in",
492d8d64acbSManos Pitsidianakis                                         stream,
493d8d64acbSManos Pitsidianakis                                         virtio_snd_pcm_in_cb,
494d8d64acbSManos Pitsidianakis                                         &as);
495d8d64acbSManos Pitsidianakis         AUD_set_volume_in(stream->voice.in, 0, 255, 255);
49618a75281SManos Pitsidianakis     }
49718a75281SManos Pitsidianakis 
498eb9ad377SManos Pitsidianakis     return cpu_to_le32(VIRTIO_SND_S_OK);
499eb9ad377SManos Pitsidianakis }
500eb9ad377SManos Pitsidianakis 
501eb9ad377SManos Pitsidianakis static const char *print_code(uint32_t code)
502eb9ad377SManos Pitsidianakis {
503eb9ad377SManos Pitsidianakis     #define CASE(CODE)            \
504eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_##CODE:     \
505eb9ad377SManos Pitsidianakis         return "VIRTIO_SND_R_"#CODE
506eb9ad377SManos Pitsidianakis 
507eb9ad377SManos Pitsidianakis     switch (code) {
508eb9ad377SManos Pitsidianakis     CASE(JACK_INFO);
509eb9ad377SManos Pitsidianakis     CASE(JACK_REMAP);
510eb9ad377SManos Pitsidianakis     CASE(PCM_INFO);
511eb9ad377SManos Pitsidianakis     CASE(PCM_SET_PARAMS);
512eb9ad377SManos Pitsidianakis     CASE(PCM_PREPARE);
513eb9ad377SManos Pitsidianakis     CASE(PCM_RELEASE);
514eb9ad377SManos Pitsidianakis     CASE(PCM_START);
515eb9ad377SManos Pitsidianakis     CASE(PCM_STOP);
516eb9ad377SManos Pitsidianakis     CASE(CHMAP_INFO);
517eb9ad377SManos Pitsidianakis     default:
518eb9ad377SManos Pitsidianakis         return "invalid code";
519eb9ad377SManos Pitsidianakis     }
520eb9ad377SManos Pitsidianakis 
521eb9ad377SManos Pitsidianakis     #undef CASE
522eb9ad377SManos Pitsidianakis };
523eb9ad377SManos Pitsidianakis 
524eb9ad377SManos Pitsidianakis /*
525e5788b8fSManos Pitsidianakis  * Handles VIRTIO_SND_R_PCM_PREPARE.
526e5788b8fSManos Pitsidianakis  *
527e5788b8fSManos Pitsidianakis  * @s: VirtIOSound device
528e5788b8fSManos Pitsidianakis  * @cmd: The request command queue element from VirtIOSound cmdq field
529e5788b8fSManos Pitsidianakis  */
530e5788b8fSManos Pitsidianakis static void virtio_snd_handle_pcm_prepare(VirtIOSound *s,
531e5788b8fSManos Pitsidianakis                                           virtio_snd_ctrl_command *cmd)
532e5788b8fSManos Pitsidianakis {
533e5788b8fSManos Pitsidianakis     uint32_t stream_id;
534e5788b8fSManos Pitsidianakis     size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
535e5788b8fSManos Pitsidianakis                                cmd->elem->out_num,
536e5788b8fSManos Pitsidianakis                                sizeof(virtio_snd_hdr),
537e5788b8fSManos Pitsidianakis                                &stream_id,
538e5788b8fSManos Pitsidianakis                                sizeof(stream_id));
539e5788b8fSManos Pitsidianakis 
540e5788b8fSManos Pitsidianakis     stream_id = le32_to_cpu(stream_id);
541e5788b8fSManos Pitsidianakis     cmd->resp.code = msg_sz == sizeof(stream_id)
542e5788b8fSManos Pitsidianakis                    ? virtio_snd_pcm_prepare(s, stream_id)
543e5788b8fSManos Pitsidianakis                    : cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
544e5788b8fSManos Pitsidianakis }
545e5788b8fSManos Pitsidianakis 
546e5788b8fSManos Pitsidianakis /*
547fa131d4aSManos Pitsidianakis  * Handles VIRTIO_SND_R_PCM_START.
548fa131d4aSManos Pitsidianakis  *
549fa131d4aSManos Pitsidianakis  * @s: VirtIOSound device
550fa131d4aSManos Pitsidianakis  * @cmd: The request command queue element from VirtIOSound cmdq field
551fa131d4aSManos Pitsidianakis  * @start: whether to start or stop the device
552fa131d4aSManos Pitsidianakis  */
553fa131d4aSManos Pitsidianakis static void virtio_snd_handle_pcm_start_stop(VirtIOSound *s,
554fa131d4aSManos Pitsidianakis                                              virtio_snd_ctrl_command *cmd,
555fa131d4aSManos Pitsidianakis                                              bool start)
556fa131d4aSManos Pitsidianakis {
557fa131d4aSManos Pitsidianakis     VirtIOSoundPCMStream *stream;
558fa131d4aSManos Pitsidianakis     virtio_snd_pcm_hdr req;
559fa131d4aSManos Pitsidianakis     uint32_t stream_id;
560fa131d4aSManos Pitsidianakis     size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
561fa131d4aSManos Pitsidianakis                                cmd->elem->out_num,
562fa131d4aSManos Pitsidianakis                                0,
563fa131d4aSManos Pitsidianakis                                &req,
564fa131d4aSManos Pitsidianakis                                sizeof(virtio_snd_pcm_hdr));
565fa131d4aSManos Pitsidianakis 
566fa131d4aSManos Pitsidianakis     if (msg_sz != sizeof(virtio_snd_pcm_hdr)) {
567fa131d4aSManos Pitsidianakis         qemu_log_mask(LOG_GUEST_ERROR,
568fa131d4aSManos Pitsidianakis                 "%s: virtio-snd command size incorrect %zu vs \
569fa131d4aSManos Pitsidianakis                 %zu\n", __func__, msg_sz, sizeof(virtio_snd_pcm_hdr));
570fa131d4aSManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
571fa131d4aSManos Pitsidianakis         return;
572fa131d4aSManos Pitsidianakis     }
573fa131d4aSManos Pitsidianakis 
574fa131d4aSManos Pitsidianakis     stream_id = le32_to_cpu(req.stream_id);
575fa131d4aSManos Pitsidianakis     cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK);
576fa131d4aSManos Pitsidianakis     trace_virtio_snd_handle_pcm_start_stop(start ? "VIRTIO_SND_R_PCM_START" :
577fa131d4aSManos Pitsidianakis             "VIRTIO_SND_R_PCM_STOP", stream_id);
57818a75281SManos Pitsidianakis 
579fa131d4aSManos Pitsidianakis     stream = virtio_snd_pcm_get_stream(s, stream_id);
58018a75281SManos Pitsidianakis     if (stream) {
58118a75281SManos Pitsidianakis         WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
58218a75281SManos Pitsidianakis             stream->active = start;
58318a75281SManos Pitsidianakis         }
58418a75281SManos Pitsidianakis         if (stream->info.direction == VIRTIO_SND_D_OUTPUT) {
58518a75281SManos Pitsidianakis             AUD_set_active_out(stream->voice.out, start);
586d8d64acbSManos Pitsidianakis         } else {
587d8d64acbSManos Pitsidianakis             AUD_set_active_in(stream->voice.in, start);
58818a75281SManos Pitsidianakis         }
58918a75281SManos Pitsidianakis     } else {
59018a75281SManos Pitsidianakis         error_report("Invalid stream id: %"PRIu32, stream_id);
591fa131d4aSManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
592fa131d4aSManos Pitsidianakis         return;
593fa131d4aSManos Pitsidianakis     }
594fa131d4aSManos Pitsidianakis     stream->active = start;
595fa131d4aSManos Pitsidianakis }
596fa131d4aSManos Pitsidianakis 
597fa131d4aSManos Pitsidianakis /*
59818a75281SManos Pitsidianakis  * Returns the number of I/O messages that are being processed.
59918a75281SManos Pitsidianakis  *
60018a75281SManos Pitsidianakis  * @stream: VirtIOSoundPCMStream
60118a75281SManos Pitsidianakis  */
60218a75281SManos Pitsidianakis static size_t virtio_snd_pcm_get_io_msgs_count(VirtIOSoundPCMStream *stream)
60318a75281SManos Pitsidianakis {
60418a75281SManos Pitsidianakis     VirtIOSoundPCMBuffer *buffer, *next;
60518a75281SManos Pitsidianakis     size_t count = 0;
60618a75281SManos Pitsidianakis 
60718a75281SManos Pitsidianakis     WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
60818a75281SManos Pitsidianakis         QSIMPLEQ_FOREACH_SAFE(buffer, &stream->queue, entry, next) {
60918a75281SManos Pitsidianakis             count += 1;
61018a75281SManos Pitsidianakis         }
61118a75281SManos Pitsidianakis         QSIMPLEQ_FOREACH_SAFE(buffer, &stream->invalid, entry, next) {
61218a75281SManos Pitsidianakis             count += 1;
61318a75281SManos Pitsidianakis         }
61418a75281SManos Pitsidianakis     }
61518a75281SManos Pitsidianakis     return count;
61618a75281SManos Pitsidianakis }
61718a75281SManos Pitsidianakis 
61818a75281SManos Pitsidianakis /*
61918a75281SManos Pitsidianakis  * Handles VIRTIO_SND_R_PCM_RELEASE.
620d48800d7SManos Pitsidianakis  *
621d48800d7SManos Pitsidianakis  * @s: VirtIOSound device
622d48800d7SManos Pitsidianakis  * @cmd: The request command queue element from VirtIOSound cmdq field
623d48800d7SManos Pitsidianakis  */
624d48800d7SManos Pitsidianakis static void virtio_snd_handle_pcm_release(VirtIOSound *s,
625d48800d7SManos Pitsidianakis                                           virtio_snd_ctrl_command *cmd)
626d48800d7SManos Pitsidianakis {
627d48800d7SManos Pitsidianakis     uint32_t stream_id;
628d48800d7SManos Pitsidianakis     VirtIOSoundPCMStream *stream;
629d48800d7SManos Pitsidianakis     size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
630d48800d7SManos Pitsidianakis                                cmd->elem->out_num,
631d48800d7SManos Pitsidianakis                                sizeof(virtio_snd_hdr),
632d48800d7SManos Pitsidianakis                                &stream_id,
633d48800d7SManos Pitsidianakis                                sizeof(stream_id));
634d48800d7SManos Pitsidianakis 
635d48800d7SManos Pitsidianakis     if (msg_sz != sizeof(stream_id)) {
636d48800d7SManos Pitsidianakis         /*
637d48800d7SManos Pitsidianakis          * TODO: do we need to set DEVICE_NEEDS_RESET?
638d48800d7SManos Pitsidianakis          */
639d48800d7SManos Pitsidianakis         qemu_log_mask(LOG_GUEST_ERROR,
640d48800d7SManos Pitsidianakis                 "%s: virtio-snd command size incorrect %zu vs \
641d48800d7SManos Pitsidianakis                 %zu\n", __func__, msg_sz, sizeof(stream_id));
642d48800d7SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
643d48800d7SManos Pitsidianakis         return;
644d48800d7SManos Pitsidianakis     }
645d48800d7SManos Pitsidianakis 
646d48800d7SManos Pitsidianakis     stream_id = le32_to_cpu(stream_id);
647d48800d7SManos Pitsidianakis     trace_virtio_snd_handle_pcm_release(stream_id);
648d48800d7SManos Pitsidianakis     stream = virtio_snd_pcm_get_stream(s, stream_id);
649d48800d7SManos Pitsidianakis     if (stream == NULL) {
650d48800d7SManos Pitsidianakis         /*
651d48800d7SManos Pitsidianakis          * TODO: do we need to set DEVICE_NEEDS_RESET?
652d48800d7SManos Pitsidianakis          */
653d48800d7SManos Pitsidianakis         error_report("already released stream %"PRIu32, stream_id);
654d48800d7SManos Pitsidianakis         virtio_error(VIRTIO_DEVICE(s),
655d48800d7SManos Pitsidianakis                      "already released stream %"PRIu32,
656d48800d7SManos Pitsidianakis                      stream_id);
657d48800d7SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
658d48800d7SManos Pitsidianakis         return;
659d48800d7SManos Pitsidianakis     }
66018a75281SManos Pitsidianakis 
66118a75281SManos Pitsidianakis     if (virtio_snd_pcm_get_io_msgs_count(stream)) {
66218a75281SManos Pitsidianakis         /*
66318a75281SManos Pitsidianakis          * virtio-v1.2-csd01, 5.14.6.6.5.1,
66418a75281SManos Pitsidianakis          * Device Requirements: Stream Release
66518a75281SManos Pitsidianakis          *
66618a75281SManos Pitsidianakis          * - The device MUST complete all pending I/O messages for the
66718a75281SManos Pitsidianakis          *   specified stream ID.
66818a75281SManos Pitsidianakis          * - The device MUST NOT complete the control request while there
66918a75281SManos Pitsidianakis          *   are pending I/O messages for the specified stream ID.
67018a75281SManos Pitsidianakis          */
67118a75281SManos Pitsidianakis         trace_virtio_snd_pcm_stream_flush(stream_id);
67218a75281SManos Pitsidianakis         virtio_snd_pcm_flush(stream);
67318a75281SManos Pitsidianakis     }
67418a75281SManos Pitsidianakis 
675d48800d7SManos Pitsidianakis     cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK);
676d48800d7SManos Pitsidianakis }
677d48800d7SManos Pitsidianakis 
678d48800d7SManos Pitsidianakis /*
679eb9ad377SManos Pitsidianakis  * The actual processing done in virtio_snd_process_cmdq().
680eb9ad377SManos Pitsidianakis  *
681eb9ad377SManos Pitsidianakis  * @s: VirtIOSound device
682eb9ad377SManos Pitsidianakis  * @cmd: control command request
683eb9ad377SManos Pitsidianakis  */
684eb9ad377SManos Pitsidianakis static inline void
685eb9ad377SManos Pitsidianakis process_cmd(VirtIOSound *s, virtio_snd_ctrl_command *cmd)
686eb9ad377SManos Pitsidianakis {
687eb9ad377SManos Pitsidianakis     uint32_t code;
688eb9ad377SManos Pitsidianakis     size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
689eb9ad377SManos Pitsidianakis                                cmd->elem->out_num,
690eb9ad377SManos Pitsidianakis                                0,
691eb9ad377SManos Pitsidianakis                                &cmd->ctrl,
692eb9ad377SManos Pitsidianakis                                sizeof(virtio_snd_hdr));
693eb9ad377SManos Pitsidianakis 
694eb9ad377SManos Pitsidianakis     if (msg_sz != sizeof(virtio_snd_hdr)) {
695eb9ad377SManos Pitsidianakis         /*
696eb9ad377SManos Pitsidianakis          * TODO: do we need to set DEVICE_NEEDS_RESET?
697eb9ad377SManos Pitsidianakis          */
698eb9ad377SManos Pitsidianakis         qemu_log_mask(LOG_GUEST_ERROR,
699eb9ad377SManos Pitsidianakis                 "%s: virtio-snd command size incorrect %zu vs \
700eb9ad377SManos Pitsidianakis                 %zu\n", __func__, msg_sz, sizeof(virtio_snd_hdr));
701eb9ad377SManos Pitsidianakis         return;
702eb9ad377SManos Pitsidianakis     }
703eb9ad377SManos Pitsidianakis 
704eb9ad377SManos Pitsidianakis     code = le32_to_cpu(cmd->ctrl.code);
705eb9ad377SManos Pitsidianakis 
706eb9ad377SManos Pitsidianakis     trace_virtio_snd_handle_code(code, print_code(code));
707eb9ad377SManos Pitsidianakis 
708eb9ad377SManos Pitsidianakis     switch (code) {
709eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_JACK_INFO:
710eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_JACK_REMAP:
711eb9ad377SManos Pitsidianakis         qemu_log_mask(LOG_UNIMP,
712eb9ad377SManos Pitsidianakis                      "virtio_snd: jack functionality is unimplemented.\n");
713eb9ad377SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
714eb9ad377SManos Pitsidianakis         break;
715eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_PCM_INFO:
7160ff05dd2SManos Pitsidianakis         virtio_snd_handle_pcm_info(s, cmd);
7170ff05dd2SManos Pitsidianakis         break;
718fa131d4aSManos Pitsidianakis     case VIRTIO_SND_R_PCM_START:
719fa131d4aSManos Pitsidianakis         virtio_snd_handle_pcm_start_stop(s, cmd, true);
720fa131d4aSManos Pitsidianakis         break;
721fa131d4aSManos Pitsidianakis     case VIRTIO_SND_R_PCM_STOP:
722fa131d4aSManos Pitsidianakis         virtio_snd_handle_pcm_start_stop(s, cmd, false);
723fa131d4aSManos Pitsidianakis         break;
724eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_PCM_SET_PARAMS:
72564704ce0SManos Pitsidianakis         virtio_snd_handle_pcm_set_params(s, cmd);
72664704ce0SManos Pitsidianakis         break;
727eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_PCM_PREPARE:
728e5788b8fSManos Pitsidianakis         virtio_snd_handle_pcm_prepare(s, cmd);
729e5788b8fSManos Pitsidianakis         break;
730eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_PCM_RELEASE:
731d48800d7SManos Pitsidianakis         virtio_snd_handle_pcm_release(s, cmd);
732eb9ad377SManos Pitsidianakis         break;
733eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_CHMAP_INFO:
734eb9ad377SManos Pitsidianakis         qemu_log_mask(LOG_UNIMP,
735eb9ad377SManos Pitsidianakis                      "virtio_snd: chmap info functionality is unimplemented.\n");
736eb9ad377SManos Pitsidianakis         trace_virtio_snd_handle_chmap_info();
737eb9ad377SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
738eb9ad377SManos Pitsidianakis         break;
739eb9ad377SManos Pitsidianakis     default:
740eb9ad377SManos Pitsidianakis         /* error */
741eb9ad377SManos Pitsidianakis         error_report("virtio snd header not recognized: %"PRIu32, code);
742eb9ad377SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
743eb9ad377SManos Pitsidianakis     }
744eb9ad377SManos Pitsidianakis 
745eb9ad377SManos Pitsidianakis     iov_from_buf(cmd->elem->in_sg,
746eb9ad377SManos Pitsidianakis                  cmd->elem->in_num,
747eb9ad377SManos Pitsidianakis                  0,
748eb9ad377SManos Pitsidianakis                  &cmd->resp,
749eb9ad377SManos Pitsidianakis                  sizeof(virtio_snd_hdr));
750eb9ad377SManos Pitsidianakis     virtqueue_push(cmd->vq, cmd->elem, sizeof(virtio_snd_hdr));
751eb9ad377SManos Pitsidianakis     virtio_notify(VIRTIO_DEVICE(s), cmd->vq);
752eb9ad377SManos Pitsidianakis }
753eb9ad377SManos Pitsidianakis 
754eb9ad377SManos Pitsidianakis /*
755eb9ad377SManos Pitsidianakis  * Consume all elements in command queue.
756eb9ad377SManos Pitsidianakis  *
757eb9ad377SManos Pitsidianakis  * @s: VirtIOSound device
758eb9ad377SManos Pitsidianakis  */
759eb9ad377SManos Pitsidianakis static void virtio_snd_process_cmdq(VirtIOSound *s)
760eb9ad377SManos Pitsidianakis {
761eb9ad377SManos Pitsidianakis     virtio_snd_ctrl_command *cmd;
762eb9ad377SManos Pitsidianakis 
763eb9ad377SManos Pitsidianakis     if (unlikely(qatomic_read(&s->processing_cmdq))) {
764eb9ad377SManos Pitsidianakis         return;
765eb9ad377SManos Pitsidianakis     }
766eb9ad377SManos Pitsidianakis 
767eb9ad377SManos Pitsidianakis     WITH_QEMU_LOCK_GUARD(&s->cmdq_mutex) {
768eb9ad377SManos Pitsidianakis         qatomic_set(&s->processing_cmdq, true);
769eb9ad377SManos Pitsidianakis         while (!QTAILQ_EMPTY(&s->cmdq)) {
770eb9ad377SManos Pitsidianakis             cmd = QTAILQ_FIRST(&s->cmdq);
771eb9ad377SManos Pitsidianakis 
772eb9ad377SManos Pitsidianakis             /* process command */
773eb9ad377SManos Pitsidianakis             process_cmd(s, cmd);
774eb9ad377SManos Pitsidianakis 
775eb9ad377SManos Pitsidianakis             QTAILQ_REMOVE(&s->cmdq, cmd, next);
776eb9ad377SManos Pitsidianakis 
777eb9ad377SManos Pitsidianakis             virtio_snd_ctrl_cmd_free(cmd);
778eb9ad377SManos Pitsidianakis         }
779eb9ad377SManos Pitsidianakis         qatomic_set(&s->processing_cmdq, false);
780eb9ad377SManos Pitsidianakis     }
781eb9ad377SManos Pitsidianakis }
782eb9ad377SManos Pitsidianakis 
783eb9ad377SManos Pitsidianakis /*
784eb9ad377SManos Pitsidianakis  * The control message handler. Pops an element from the control virtqueue,
785eb9ad377SManos Pitsidianakis  * and stores them to VirtIOSound's cmdq queue and finally calls
786eb9ad377SManos Pitsidianakis  * virtio_snd_process_cmdq() for processing.
787eb9ad377SManos Pitsidianakis  *
788eb9ad377SManos Pitsidianakis  * @vdev: VirtIOSound device
789eb9ad377SManos Pitsidianakis  * @vq: Control virtqueue
790eb9ad377SManos Pitsidianakis  */
791eb9ad377SManos Pitsidianakis static void virtio_snd_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq)
792eb9ad377SManos Pitsidianakis {
793eb9ad377SManos Pitsidianakis     VirtIOSound *s = VIRTIO_SND(vdev);
794eb9ad377SManos Pitsidianakis     VirtQueueElement *elem;
795eb9ad377SManos Pitsidianakis     virtio_snd_ctrl_command *cmd;
796eb9ad377SManos Pitsidianakis 
797eb9ad377SManos Pitsidianakis     trace_virtio_snd_handle_ctrl(vdev, vq);
798eb9ad377SManos Pitsidianakis 
799eb9ad377SManos Pitsidianakis     if (!virtio_queue_ready(vq)) {
800eb9ad377SManos Pitsidianakis         return;
801eb9ad377SManos Pitsidianakis     }
802eb9ad377SManos Pitsidianakis 
803eb9ad377SManos Pitsidianakis     elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
804eb9ad377SManos Pitsidianakis     while (elem) {
805eb9ad377SManos Pitsidianakis         cmd = g_new0(virtio_snd_ctrl_command, 1);
806eb9ad377SManos Pitsidianakis         cmd->elem = elem;
807eb9ad377SManos Pitsidianakis         cmd->vq = vq;
808eb9ad377SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK);
809eb9ad377SManos Pitsidianakis         QTAILQ_INSERT_TAIL(&s->cmdq, cmd, next);
810eb9ad377SManos Pitsidianakis         elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
811eb9ad377SManos Pitsidianakis     }
812eb9ad377SManos Pitsidianakis 
813eb9ad377SManos Pitsidianakis     virtio_snd_process_cmdq(s);
814eb9ad377SManos Pitsidianakis }
815eb9ad377SManos Pitsidianakis 
816eb9ad377SManos Pitsidianakis /*
817eb9ad377SManos Pitsidianakis  * The event virtqueue handler.
818eb9ad377SManos Pitsidianakis  * Not implemented yet.
819eb9ad377SManos Pitsidianakis  *
820eb9ad377SManos Pitsidianakis  * @vdev: VirtIOSound device
821eb9ad377SManos Pitsidianakis  * @vq: event vq
822eb9ad377SManos Pitsidianakis  */
823eb9ad377SManos Pitsidianakis static void virtio_snd_handle_event(VirtIODevice *vdev, VirtQueue *vq)
824eb9ad377SManos Pitsidianakis {
825eb9ad377SManos Pitsidianakis     qemu_log_mask(LOG_UNIMP, "virtio_snd: event queue is unimplemented.\n");
826eb9ad377SManos Pitsidianakis     trace_virtio_snd_handle_event();
827eb9ad377SManos Pitsidianakis }
828eb9ad377SManos Pitsidianakis 
829d8d64acbSManos Pitsidianakis static inline void empty_invalid_queue(VirtIODevice *vdev, VirtQueue *vq)
830d8d64acbSManos Pitsidianakis {
831d8d64acbSManos Pitsidianakis     VirtIOSoundPCMBuffer *buffer = NULL;
832d8d64acbSManos Pitsidianakis     VirtIOSoundPCMStream *stream = NULL;
833d8d64acbSManos Pitsidianakis     virtio_snd_pcm_status resp = { 0 };
834d8d64acbSManos Pitsidianakis     VirtIOSound *vsnd = VIRTIO_SND(vdev);
835d8d64acbSManos Pitsidianakis     bool any = false;
836d8d64acbSManos Pitsidianakis 
837d8d64acbSManos Pitsidianakis     for (uint32_t i = 0; i < vsnd->snd_conf.streams; i++) {
838d8d64acbSManos Pitsidianakis         stream = vsnd->pcm->streams[i];
839d8d64acbSManos Pitsidianakis         if (stream) {
840d8d64acbSManos Pitsidianakis             any = false;
841d8d64acbSManos Pitsidianakis             WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
842d8d64acbSManos Pitsidianakis                 while (!QSIMPLEQ_EMPTY(&stream->invalid)) {
843d8d64acbSManos Pitsidianakis                     buffer = QSIMPLEQ_FIRST(&stream->invalid);
844d8d64acbSManos Pitsidianakis                     if (buffer->vq != vq) {
845d8d64acbSManos Pitsidianakis                         break;
846d8d64acbSManos Pitsidianakis                     }
847d8d64acbSManos Pitsidianakis                     any = true;
848d8d64acbSManos Pitsidianakis                     resp.status = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
849d8d64acbSManos Pitsidianakis                     iov_from_buf(buffer->elem->in_sg,
850d8d64acbSManos Pitsidianakis                                  buffer->elem->in_num,
851d8d64acbSManos Pitsidianakis                                  0,
852d8d64acbSManos Pitsidianakis                                  &resp,
853d8d64acbSManos Pitsidianakis                                  sizeof(virtio_snd_pcm_status));
854d8d64acbSManos Pitsidianakis                     virtqueue_push(vq,
855d8d64acbSManos Pitsidianakis                                    buffer->elem,
856d8d64acbSManos Pitsidianakis                                    sizeof(virtio_snd_pcm_status));
857d8d64acbSManos Pitsidianakis                     QSIMPLEQ_REMOVE_HEAD(&stream->invalid, entry);
858d8d64acbSManos Pitsidianakis                     virtio_snd_pcm_buffer_free(buffer);
859d8d64acbSManos Pitsidianakis                 }
860d8d64acbSManos Pitsidianakis                 if (any) {
861d8d64acbSManos Pitsidianakis                     /*
862d8d64acbSManos Pitsidianakis                      * Notify vq about virtio_snd_pcm_status responses.
863d8d64acbSManos Pitsidianakis                      * Buffer responses must be notified separately later.
864d8d64acbSManos Pitsidianakis                      */
865d8d64acbSManos Pitsidianakis                     virtio_notify(vdev, vq);
866d8d64acbSManos Pitsidianakis                 }
867d8d64acbSManos Pitsidianakis             }
868d8d64acbSManos Pitsidianakis         }
869d8d64acbSManos Pitsidianakis     }
870d8d64acbSManos Pitsidianakis }
871d8d64acbSManos Pitsidianakis 
872eb9ad377SManos Pitsidianakis /*
87318a75281SManos Pitsidianakis  * The tx virtqueue handler. Makes the buffers available to their respective
87418a75281SManos Pitsidianakis  * streams for consumption.
87518a75281SManos Pitsidianakis  *
87618a75281SManos Pitsidianakis  * @vdev: VirtIOSound device
87718a75281SManos Pitsidianakis  * @vq: tx virtqueue
87818a75281SManos Pitsidianakis  */
879d8d64acbSManos Pitsidianakis static void virtio_snd_handle_tx_xfer(VirtIODevice *vdev, VirtQueue *vq)
88018a75281SManos Pitsidianakis {
88118a75281SManos Pitsidianakis     VirtIOSound *s = VIRTIO_SND(vdev);
88218a75281SManos Pitsidianakis     VirtIOSoundPCMStream *stream = NULL;
88318a75281SManos Pitsidianakis     VirtIOSoundPCMBuffer *buffer;
88418a75281SManos Pitsidianakis     VirtQueueElement *elem;
88518a75281SManos Pitsidianakis     size_t msg_sz, size;
88618a75281SManos Pitsidianakis     virtio_snd_pcm_xfer hdr;
88718a75281SManos Pitsidianakis     uint32_t stream_id;
88818a75281SManos Pitsidianakis     /*
88918a75281SManos Pitsidianakis      * If any of the I/O messages are invalid, put them in stream->invalid and
89018a75281SManos Pitsidianakis      * return them after the for loop.
89118a75281SManos Pitsidianakis      */
89218a75281SManos Pitsidianakis     bool must_empty_invalid_queue = false;
89318a75281SManos Pitsidianakis 
89418a75281SManos Pitsidianakis     if (!virtio_queue_ready(vq)) {
89518a75281SManos Pitsidianakis         return;
89618a75281SManos Pitsidianakis     }
897d8d64acbSManos Pitsidianakis     trace_virtio_snd_handle_tx_xfer();
89818a75281SManos Pitsidianakis 
89918a75281SManos Pitsidianakis     for (;;) {
90018a75281SManos Pitsidianakis         elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
90118a75281SManos Pitsidianakis         if (!elem) {
90218a75281SManos Pitsidianakis             break;
90318a75281SManos Pitsidianakis         }
90418a75281SManos Pitsidianakis         /* get the message hdr object */
90518a75281SManos Pitsidianakis         msg_sz = iov_to_buf(elem->out_sg,
90618a75281SManos Pitsidianakis                             elem->out_num,
90718a75281SManos Pitsidianakis                             0,
90818a75281SManos Pitsidianakis                             &hdr,
90918a75281SManos Pitsidianakis                             sizeof(virtio_snd_pcm_xfer));
91018a75281SManos Pitsidianakis         if (msg_sz != sizeof(virtio_snd_pcm_xfer)) {
91118a75281SManos Pitsidianakis             goto tx_err;
91218a75281SManos Pitsidianakis         }
91318a75281SManos Pitsidianakis         stream_id = le32_to_cpu(hdr.stream_id);
91418a75281SManos Pitsidianakis 
91518a75281SManos Pitsidianakis         if (stream_id >= s->snd_conf.streams
91618a75281SManos Pitsidianakis             || s->pcm->streams[stream_id] == NULL) {
91718a75281SManos Pitsidianakis             goto tx_err;
91818a75281SManos Pitsidianakis         }
91918a75281SManos Pitsidianakis 
92018a75281SManos Pitsidianakis         stream = s->pcm->streams[stream_id];
92118a75281SManos Pitsidianakis         if (stream->info.direction != VIRTIO_SND_D_OUTPUT) {
92218a75281SManos Pitsidianakis             goto tx_err;
92318a75281SManos Pitsidianakis         }
92418a75281SManos Pitsidianakis 
92518a75281SManos Pitsidianakis         WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
92618a75281SManos Pitsidianakis             size = iov_size(elem->out_sg, elem->out_num) - msg_sz;
92718a75281SManos Pitsidianakis 
92818a75281SManos Pitsidianakis             buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer) + size);
92918a75281SManos Pitsidianakis             buffer->elem = elem;
93018a75281SManos Pitsidianakis             buffer->populated = false;
93118a75281SManos Pitsidianakis             buffer->vq = vq;
93218a75281SManos Pitsidianakis             buffer->size = size;
93318a75281SManos Pitsidianakis             buffer->offset = 0;
93418a75281SManos Pitsidianakis 
93518a75281SManos Pitsidianakis             QSIMPLEQ_INSERT_TAIL(&stream->queue, buffer, entry);
93618a75281SManos Pitsidianakis         }
93718a75281SManos Pitsidianakis         continue;
93818a75281SManos Pitsidianakis 
93918a75281SManos Pitsidianakis tx_err:
94018a75281SManos Pitsidianakis         WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
94118a75281SManos Pitsidianakis             must_empty_invalid_queue = true;
94218a75281SManos Pitsidianakis             buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer));
94318a75281SManos Pitsidianakis             buffer->elem = elem;
94418a75281SManos Pitsidianakis             buffer->vq = vq;
94518a75281SManos Pitsidianakis             QSIMPLEQ_INSERT_TAIL(&stream->invalid, buffer, entry);
94618a75281SManos Pitsidianakis         }
94718a75281SManos Pitsidianakis     }
94818a75281SManos Pitsidianakis 
94918a75281SManos Pitsidianakis     if (must_empty_invalid_queue) {
950d8d64acbSManos Pitsidianakis         empty_invalid_queue(vdev, vq);
95118a75281SManos Pitsidianakis     }
95218a75281SManos Pitsidianakis }
95318a75281SManos Pitsidianakis 
95418a75281SManos Pitsidianakis /*
955d8d64acbSManos Pitsidianakis  * The rx virtqueue handler. Makes the buffers available to their respective
956d8d64acbSManos Pitsidianakis  * streams for consumption.
9572880e676SManos Pitsidianakis  *
9582880e676SManos Pitsidianakis  * @vdev: VirtIOSound device
959d8d64acbSManos Pitsidianakis  * @vq: rx virtqueue
9602880e676SManos Pitsidianakis  */
961d8d64acbSManos Pitsidianakis static void virtio_snd_handle_rx_xfer(VirtIODevice *vdev, VirtQueue *vq)
962d8d64acbSManos Pitsidianakis {
963d8d64acbSManos Pitsidianakis     VirtIOSound *s = VIRTIO_SND(vdev);
964d8d64acbSManos Pitsidianakis     VirtIOSoundPCMStream *stream = NULL;
965d8d64acbSManos Pitsidianakis     VirtIOSoundPCMBuffer *buffer;
966d8d64acbSManos Pitsidianakis     VirtQueueElement *elem;
967d8d64acbSManos Pitsidianakis     size_t msg_sz, size;
968d8d64acbSManos Pitsidianakis     virtio_snd_pcm_xfer hdr;
969d8d64acbSManos Pitsidianakis     uint32_t stream_id;
970d8d64acbSManos Pitsidianakis     /*
971d8d64acbSManos Pitsidianakis      * if any of the I/O messages are invalid, put them in stream->invalid and
972d8d64acbSManos Pitsidianakis      * return them after the for loop.
973d8d64acbSManos Pitsidianakis      */
974d8d64acbSManos Pitsidianakis     bool must_empty_invalid_queue = false;
975d8d64acbSManos Pitsidianakis 
976d8d64acbSManos Pitsidianakis     if (!virtio_queue_ready(vq)) {
977d8d64acbSManos Pitsidianakis         return;
978d8d64acbSManos Pitsidianakis     }
979d8d64acbSManos Pitsidianakis     trace_virtio_snd_handle_rx_xfer();
980d8d64acbSManos Pitsidianakis 
981d8d64acbSManos Pitsidianakis     for (;;) {
982d8d64acbSManos Pitsidianakis         elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
983d8d64acbSManos Pitsidianakis         if (!elem) {
984d8d64acbSManos Pitsidianakis             break;
985d8d64acbSManos Pitsidianakis         }
986d8d64acbSManos Pitsidianakis         /* get the message hdr object */
987d8d64acbSManos Pitsidianakis         msg_sz = iov_to_buf(elem->out_sg,
988d8d64acbSManos Pitsidianakis                             elem->out_num,
989d8d64acbSManos Pitsidianakis                             0,
990d8d64acbSManos Pitsidianakis                             &hdr,
991d8d64acbSManos Pitsidianakis                             sizeof(virtio_snd_pcm_xfer));
992d8d64acbSManos Pitsidianakis         if (msg_sz != sizeof(virtio_snd_pcm_xfer)) {
993d8d64acbSManos Pitsidianakis             goto rx_err;
994d8d64acbSManos Pitsidianakis         }
995d8d64acbSManos Pitsidianakis         stream_id = le32_to_cpu(hdr.stream_id);
996d8d64acbSManos Pitsidianakis 
997d8d64acbSManos Pitsidianakis         if (stream_id >= s->snd_conf.streams
998d8d64acbSManos Pitsidianakis             || !s->pcm->streams[stream_id]) {
999d8d64acbSManos Pitsidianakis             goto rx_err;
1000d8d64acbSManos Pitsidianakis         }
1001d8d64acbSManos Pitsidianakis 
1002d8d64acbSManos Pitsidianakis         stream = s->pcm->streams[stream_id];
1003d8d64acbSManos Pitsidianakis         if (stream == NULL || stream->info.direction != VIRTIO_SND_D_INPUT) {
1004d8d64acbSManos Pitsidianakis             goto rx_err;
1005d8d64acbSManos Pitsidianakis         }
1006d8d64acbSManos Pitsidianakis         WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
1007d8d64acbSManos Pitsidianakis             size = iov_size(elem->in_sg, elem->in_num) -
1008d8d64acbSManos Pitsidianakis                 sizeof(virtio_snd_pcm_status);
1009d8d64acbSManos Pitsidianakis             buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer) + size);
1010d8d64acbSManos Pitsidianakis             buffer->elem = elem;
1011d8d64acbSManos Pitsidianakis             buffer->vq = vq;
1012d8d64acbSManos Pitsidianakis             buffer->size = 0;
1013d8d64acbSManos Pitsidianakis             buffer->offset = 0;
1014d8d64acbSManos Pitsidianakis             QSIMPLEQ_INSERT_TAIL(&stream->queue, buffer, entry);
1015d8d64acbSManos Pitsidianakis         }
1016d8d64acbSManos Pitsidianakis         continue;
1017d8d64acbSManos Pitsidianakis 
1018d8d64acbSManos Pitsidianakis rx_err:
1019d8d64acbSManos Pitsidianakis         WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
1020d8d64acbSManos Pitsidianakis             must_empty_invalid_queue = true;
1021d8d64acbSManos Pitsidianakis             buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer));
1022d8d64acbSManos Pitsidianakis             buffer->elem = elem;
1023d8d64acbSManos Pitsidianakis             buffer->vq = vq;
1024d8d64acbSManos Pitsidianakis             QSIMPLEQ_INSERT_TAIL(&stream->invalid, buffer, entry);
1025d8d64acbSManos Pitsidianakis         }
1026d8d64acbSManos Pitsidianakis     }
1027d8d64acbSManos Pitsidianakis 
1028d8d64acbSManos Pitsidianakis     if (must_empty_invalid_queue) {
1029d8d64acbSManos Pitsidianakis         empty_invalid_queue(vdev, vq);
1030d8d64acbSManos Pitsidianakis     }
1031d8d64acbSManos Pitsidianakis }
10322880e676SManos Pitsidianakis 
10332880e676SManos Pitsidianakis static uint64_t get_features(VirtIODevice *vdev, uint64_t features,
10342880e676SManos Pitsidianakis                              Error **errp)
10352880e676SManos Pitsidianakis {
10362880e676SManos Pitsidianakis     /*
10372880e676SManos Pitsidianakis      * virtio-v1.2-csd01, 5.14.3,
10382880e676SManos Pitsidianakis      * Feature Bits
10392880e676SManos Pitsidianakis      * None currently defined.
10402880e676SManos Pitsidianakis      */
10412880e676SManos Pitsidianakis     VirtIOSound *s = VIRTIO_SND(vdev);
10422880e676SManos Pitsidianakis     features |= s->features;
10432880e676SManos Pitsidianakis 
10442880e676SManos Pitsidianakis     trace_virtio_snd_get_features(vdev, features);
10452880e676SManos Pitsidianakis 
10462880e676SManos Pitsidianakis     return features;
10472880e676SManos Pitsidianakis }
10482880e676SManos Pitsidianakis 
10492880e676SManos Pitsidianakis static void
10502880e676SManos Pitsidianakis virtio_snd_vm_state_change(void *opaque, bool running,
10512880e676SManos Pitsidianakis                                        RunState state)
10522880e676SManos Pitsidianakis {
10532880e676SManos Pitsidianakis     if (running) {
10542880e676SManos Pitsidianakis         trace_virtio_snd_vm_state_running();
10552880e676SManos Pitsidianakis     } else {
10562880e676SManos Pitsidianakis         trace_virtio_snd_vm_state_stopped();
10572880e676SManos Pitsidianakis     }
10582880e676SManos Pitsidianakis }
10592880e676SManos Pitsidianakis 
10602880e676SManos Pitsidianakis static void virtio_snd_realize(DeviceState *dev, Error **errp)
10612880e676SManos Pitsidianakis {
10622880e676SManos Pitsidianakis     ERRP_GUARD();
10632880e676SManos Pitsidianakis     VirtIOSound *vsnd = VIRTIO_SND(dev);
10642880e676SManos Pitsidianakis     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
1065eb9ad377SManos Pitsidianakis     virtio_snd_pcm_set_params default_params = { 0 };
1066eb9ad377SManos Pitsidianakis     uint32_t status;
10672880e676SManos Pitsidianakis 
1068eb9ad377SManos Pitsidianakis     vsnd->pcm = NULL;
10692880e676SManos Pitsidianakis     vsnd->vmstate =
10702880e676SManos Pitsidianakis         qemu_add_vm_change_state_handler(virtio_snd_vm_state_change, vsnd);
10712880e676SManos Pitsidianakis 
10722880e676SManos Pitsidianakis     trace_virtio_snd_realize(vsnd);
10732880e676SManos Pitsidianakis 
1074eb9ad377SManos Pitsidianakis     vsnd->pcm = g_new0(VirtIOSoundPCM, 1);
1075eb9ad377SManos Pitsidianakis     vsnd->pcm->snd = vsnd;
1076eb9ad377SManos Pitsidianakis     vsnd->pcm->streams =
1077eb9ad377SManos Pitsidianakis         g_new0(VirtIOSoundPCMStream *, vsnd->snd_conf.streams);
1078eb9ad377SManos Pitsidianakis     vsnd->pcm->pcm_params =
1079eb9ad377SManos Pitsidianakis         g_new0(virtio_snd_pcm_set_params, vsnd->snd_conf.streams);
1080eb9ad377SManos Pitsidianakis 
10812880e676SManos Pitsidianakis     virtio_init(vdev, VIRTIO_ID_SOUND, sizeof(virtio_snd_config));
10822880e676SManos Pitsidianakis     virtio_add_feature(&vsnd->features, VIRTIO_F_VERSION_1);
10832880e676SManos Pitsidianakis 
10842880e676SManos Pitsidianakis     /* set number of jacks and streams */
10852880e676SManos Pitsidianakis     if (vsnd->snd_conf.jacks > 8) {
10862880e676SManos Pitsidianakis         error_setg(errp,
10872880e676SManos Pitsidianakis                    "Invalid number of jacks: %"PRIu32,
10882880e676SManos Pitsidianakis                    vsnd->snd_conf.jacks);
10892880e676SManos Pitsidianakis         return;
10902880e676SManos Pitsidianakis     }
10912880e676SManos Pitsidianakis     if (vsnd->snd_conf.streams < 1 || vsnd->snd_conf.streams > 10) {
10922880e676SManos Pitsidianakis         error_setg(errp,
10932880e676SManos Pitsidianakis                    "Invalid number of streams: %"PRIu32,
10942880e676SManos Pitsidianakis                     vsnd->snd_conf.streams);
10952880e676SManos Pitsidianakis         return;
10962880e676SManos Pitsidianakis     }
10972880e676SManos Pitsidianakis 
10982880e676SManos Pitsidianakis     if (vsnd->snd_conf.chmaps > VIRTIO_SND_CHMAP_MAX_SIZE) {
10992880e676SManos Pitsidianakis         error_setg(errp,
11002880e676SManos Pitsidianakis                    "Invalid number of channel maps: %"PRIu32,
11012880e676SManos Pitsidianakis                    vsnd->snd_conf.chmaps);
11022880e676SManos Pitsidianakis         return;
11032880e676SManos Pitsidianakis     }
11042880e676SManos Pitsidianakis 
11052880e676SManos Pitsidianakis     AUD_register_card("virtio-sound", &vsnd->card, errp);
11062880e676SManos Pitsidianakis 
1107eb9ad377SManos Pitsidianakis     /* set default params for all streams */
1108eb9ad377SManos Pitsidianakis     default_params.features = 0;
1109eb9ad377SManos Pitsidianakis     default_params.buffer_bytes = cpu_to_le32(8192);
1110eb9ad377SManos Pitsidianakis     default_params.period_bytes = cpu_to_le32(2048);
1111eb9ad377SManos Pitsidianakis     default_params.channels = 2;
1112eb9ad377SManos Pitsidianakis     default_params.format = VIRTIO_SND_PCM_FMT_S16;
1113eb9ad377SManos Pitsidianakis     default_params.rate = VIRTIO_SND_PCM_RATE_48000;
11142880e676SManos Pitsidianakis     vsnd->queues[VIRTIO_SND_VQ_CONTROL] =
1115eb9ad377SManos Pitsidianakis         virtio_add_queue(vdev, 64, virtio_snd_handle_ctrl);
11162880e676SManos Pitsidianakis     vsnd->queues[VIRTIO_SND_VQ_EVENT] =
1117eb9ad377SManos Pitsidianakis         virtio_add_queue(vdev, 64, virtio_snd_handle_event);
11182880e676SManos Pitsidianakis     vsnd->queues[VIRTIO_SND_VQ_TX] =
1119d8d64acbSManos Pitsidianakis         virtio_add_queue(vdev, 64, virtio_snd_handle_tx_xfer);
11202880e676SManos Pitsidianakis     vsnd->queues[VIRTIO_SND_VQ_RX] =
1121d8d64acbSManos Pitsidianakis         virtio_add_queue(vdev, 64, virtio_snd_handle_rx_xfer);
1122eb9ad377SManos Pitsidianakis     qemu_mutex_init(&vsnd->cmdq_mutex);
1123eb9ad377SManos Pitsidianakis     QTAILQ_INIT(&vsnd->cmdq);
1124eb9ad377SManos Pitsidianakis 
1125eb9ad377SManos Pitsidianakis     for (uint32_t i = 0; i < vsnd->snd_conf.streams; i++) {
1126eb9ad377SManos Pitsidianakis         status = virtio_snd_set_pcm_params(vsnd, i, &default_params);
1127eb9ad377SManos Pitsidianakis         if (status != cpu_to_le32(VIRTIO_SND_S_OK)) {
1128eb9ad377SManos Pitsidianakis             error_setg(errp,
1129*aaf851a2SMichael Tokarev                        "Can't initialize stream params, device responded with %s.",
1130eb9ad377SManos Pitsidianakis                        print_code(status));
1131eb9ad377SManos Pitsidianakis             return;
1132eb9ad377SManos Pitsidianakis         }
1133eb9ad377SManos Pitsidianakis         status = virtio_snd_pcm_prepare(vsnd, i);
1134eb9ad377SManos Pitsidianakis         if (status != cpu_to_le32(VIRTIO_SND_S_OK)) {
1135eb9ad377SManos Pitsidianakis             error_setg(errp,
1136eb9ad377SManos Pitsidianakis                        "Can't prepare streams, device responded with %s.",
1137eb9ad377SManos Pitsidianakis                        print_code(status));
1138eb9ad377SManos Pitsidianakis             return;
1139eb9ad377SManos Pitsidianakis         }
1140eb9ad377SManos Pitsidianakis     }
11412880e676SManos Pitsidianakis }
11422880e676SManos Pitsidianakis 
114318a75281SManos Pitsidianakis static inline void return_tx_buffer(VirtIOSoundPCMStream *stream,
114418a75281SManos Pitsidianakis                                     VirtIOSoundPCMBuffer *buffer)
114518a75281SManos Pitsidianakis {
114618a75281SManos Pitsidianakis     virtio_snd_pcm_status resp = { 0 };
114718a75281SManos Pitsidianakis     resp.status = cpu_to_le32(VIRTIO_SND_S_OK);
114818a75281SManos Pitsidianakis     resp.latency_bytes = cpu_to_le32((uint32_t)buffer->size);
114918a75281SManos Pitsidianakis     iov_from_buf(buffer->elem->in_sg,
115018a75281SManos Pitsidianakis                  buffer->elem->in_num,
115118a75281SManos Pitsidianakis                  0,
115218a75281SManos Pitsidianakis                  &resp,
115318a75281SManos Pitsidianakis                  sizeof(virtio_snd_pcm_status));
115418a75281SManos Pitsidianakis     virtqueue_push(buffer->vq,
115518a75281SManos Pitsidianakis                    buffer->elem,
115618a75281SManos Pitsidianakis                    sizeof(virtio_snd_pcm_status));
115718a75281SManos Pitsidianakis     virtio_notify(VIRTIO_DEVICE(stream->s), buffer->vq);
115818a75281SManos Pitsidianakis     QSIMPLEQ_REMOVE(&stream->queue,
115918a75281SManos Pitsidianakis                     buffer,
116018a75281SManos Pitsidianakis                     VirtIOSoundPCMBuffer,
116118a75281SManos Pitsidianakis                     entry);
116218a75281SManos Pitsidianakis     virtio_snd_pcm_buffer_free(buffer);
116318a75281SManos Pitsidianakis }
116418a75281SManos Pitsidianakis 
116518a75281SManos Pitsidianakis /*
116618a75281SManos Pitsidianakis  * AUD_* output callback.
116718a75281SManos Pitsidianakis  *
116818a75281SManos Pitsidianakis  * @data: VirtIOSoundPCMStream stream
116918a75281SManos Pitsidianakis  * @available: number of bytes that can be written with AUD_write()
117018a75281SManos Pitsidianakis  */
117118a75281SManos Pitsidianakis static void virtio_snd_pcm_out_cb(void *data, int available)
117218a75281SManos Pitsidianakis {
117318a75281SManos Pitsidianakis     VirtIOSoundPCMStream *stream = data;
117418a75281SManos Pitsidianakis     VirtIOSoundPCMBuffer *buffer;
117518a75281SManos Pitsidianakis     size_t size;
117618a75281SManos Pitsidianakis 
117718a75281SManos Pitsidianakis     WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
117818a75281SManos Pitsidianakis         while (!QSIMPLEQ_EMPTY(&stream->queue)) {
117918a75281SManos Pitsidianakis             buffer = QSIMPLEQ_FIRST(&stream->queue);
118018a75281SManos Pitsidianakis             if (!virtio_queue_ready(buffer->vq)) {
118118a75281SManos Pitsidianakis                 return;
118218a75281SManos Pitsidianakis             }
118318a75281SManos Pitsidianakis             if (!stream->active) {
118418a75281SManos Pitsidianakis                 /* Stream has stopped, so do not perform AUD_write. */
118518a75281SManos Pitsidianakis                 return_tx_buffer(stream, buffer);
118618a75281SManos Pitsidianakis                 continue;
118718a75281SManos Pitsidianakis             }
118818a75281SManos Pitsidianakis             if (!buffer->populated) {
118918a75281SManos Pitsidianakis                 iov_to_buf(buffer->elem->out_sg,
119018a75281SManos Pitsidianakis                            buffer->elem->out_num,
119118a75281SManos Pitsidianakis                            sizeof(virtio_snd_pcm_xfer),
119218a75281SManos Pitsidianakis                            buffer->data,
119318a75281SManos Pitsidianakis                            buffer->size);
119418a75281SManos Pitsidianakis                 buffer->populated = true;
119518a75281SManos Pitsidianakis             }
119618a75281SManos Pitsidianakis             for (;;) {
119718a75281SManos Pitsidianakis                 size = AUD_write(stream->voice.out,
119818a75281SManos Pitsidianakis                                  buffer->data + buffer->offset,
119918a75281SManos Pitsidianakis                                  MIN(buffer->size, available));
120018a75281SManos Pitsidianakis                 assert(size <= MIN(buffer->size, available));
120118a75281SManos Pitsidianakis                 if (size == 0) {
120218a75281SManos Pitsidianakis                     /* break out of both loops */
120318a75281SManos Pitsidianakis                     available = 0;
120418a75281SManos Pitsidianakis                     break;
120518a75281SManos Pitsidianakis                 }
120618a75281SManos Pitsidianakis                 buffer->size -= size;
120718a75281SManos Pitsidianakis                 buffer->offset += size;
120818a75281SManos Pitsidianakis                 available -= size;
120918a75281SManos Pitsidianakis                 if (buffer->size < 1) {
121018a75281SManos Pitsidianakis                     return_tx_buffer(stream, buffer);
121118a75281SManos Pitsidianakis                     break;
121218a75281SManos Pitsidianakis                 }
121318a75281SManos Pitsidianakis                 if (!available) {
121418a75281SManos Pitsidianakis                     break;
121518a75281SManos Pitsidianakis                 }
121618a75281SManos Pitsidianakis             }
121718a75281SManos Pitsidianakis             if (!available) {
121818a75281SManos Pitsidianakis                 break;
121918a75281SManos Pitsidianakis             }
122018a75281SManos Pitsidianakis         }
122118a75281SManos Pitsidianakis     }
122218a75281SManos Pitsidianakis }
122318a75281SManos Pitsidianakis 
122418a75281SManos Pitsidianakis /*
1225d8d64acbSManos Pitsidianakis  * Flush all buffer data from this input stream's queue into the driver's
1226d8d64acbSManos Pitsidianakis  * virtual queue.
1227d8d64acbSManos Pitsidianakis  *
1228d8d64acbSManos Pitsidianakis  * @stream: VirtIOSoundPCMStream *stream
1229d8d64acbSManos Pitsidianakis  */
1230d8d64acbSManos Pitsidianakis static inline void return_rx_buffer(VirtIOSoundPCMStream *stream,
1231d8d64acbSManos Pitsidianakis                                     VirtIOSoundPCMBuffer *buffer)
1232d8d64acbSManos Pitsidianakis {
1233d8d64acbSManos Pitsidianakis     virtio_snd_pcm_status resp = { 0 };
1234d8d64acbSManos Pitsidianakis     resp.status = cpu_to_le32(VIRTIO_SND_S_OK);
1235d8d64acbSManos Pitsidianakis     resp.latency_bytes = 0;
1236d8d64acbSManos Pitsidianakis     /* Copy data -if any- to guest */
1237d8d64acbSManos Pitsidianakis     iov_from_buf(buffer->elem->in_sg,
1238d8d64acbSManos Pitsidianakis                  buffer->elem->in_num,
1239d8d64acbSManos Pitsidianakis                  0,
1240d8d64acbSManos Pitsidianakis                  buffer->data,
1241d8d64acbSManos Pitsidianakis                  buffer->size);
1242d8d64acbSManos Pitsidianakis     iov_from_buf(buffer->elem->in_sg,
1243d8d64acbSManos Pitsidianakis                  buffer->elem->in_num,
1244d8d64acbSManos Pitsidianakis                  buffer->size,
1245d8d64acbSManos Pitsidianakis                  &resp,
1246d8d64acbSManos Pitsidianakis                  sizeof(virtio_snd_pcm_status));
1247d8d64acbSManos Pitsidianakis     virtqueue_push(buffer->vq,
1248d8d64acbSManos Pitsidianakis                    buffer->elem,
1249d8d64acbSManos Pitsidianakis                    sizeof(virtio_snd_pcm_status) + buffer->size);
1250d8d64acbSManos Pitsidianakis     virtio_notify(VIRTIO_DEVICE(stream->s), buffer->vq);
1251d8d64acbSManos Pitsidianakis     QSIMPLEQ_REMOVE(&stream->queue,
1252d8d64acbSManos Pitsidianakis                     buffer,
1253d8d64acbSManos Pitsidianakis                     VirtIOSoundPCMBuffer,
1254d8d64acbSManos Pitsidianakis                     entry);
1255d8d64acbSManos Pitsidianakis     virtio_snd_pcm_buffer_free(buffer);
1256d8d64acbSManos Pitsidianakis }
1257d8d64acbSManos Pitsidianakis 
1258d8d64acbSManos Pitsidianakis 
1259d8d64acbSManos Pitsidianakis /*
1260d8d64acbSManos Pitsidianakis  * AUD_* input callback.
1261d8d64acbSManos Pitsidianakis  *
1262d8d64acbSManos Pitsidianakis  * @data: VirtIOSoundPCMStream stream
1263d8d64acbSManos Pitsidianakis  * @available: number of bytes that can be read with AUD_read()
1264d8d64acbSManos Pitsidianakis  */
1265d8d64acbSManos Pitsidianakis static void virtio_snd_pcm_in_cb(void *data, int available)
1266d8d64acbSManos Pitsidianakis {
1267d8d64acbSManos Pitsidianakis     VirtIOSoundPCMStream *stream = data;
1268d8d64acbSManos Pitsidianakis     VirtIOSoundPCMBuffer *buffer;
1269d8d64acbSManos Pitsidianakis     size_t size;
1270d8d64acbSManos Pitsidianakis 
1271d8d64acbSManos Pitsidianakis     WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
1272d8d64acbSManos Pitsidianakis         while (!QSIMPLEQ_EMPTY(&stream->queue)) {
1273d8d64acbSManos Pitsidianakis             buffer = QSIMPLEQ_FIRST(&stream->queue);
1274d8d64acbSManos Pitsidianakis             if (!virtio_queue_ready(buffer->vq)) {
1275d8d64acbSManos Pitsidianakis                 return;
1276d8d64acbSManos Pitsidianakis             }
1277d8d64acbSManos Pitsidianakis             if (!stream->active) {
1278d8d64acbSManos Pitsidianakis                 /* Stream has stopped, so do not perform AUD_read. */
1279d8d64acbSManos Pitsidianakis                 return_rx_buffer(stream, buffer);
1280d8d64acbSManos Pitsidianakis                 continue;
1281d8d64acbSManos Pitsidianakis             }
1282d8d64acbSManos Pitsidianakis 
1283d8d64acbSManos Pitsidianakis             for (;;) {
1284d8d64acbSManos Pitsidianakis                 size = AUD_read(stream->voice.in,
1285d8d64acbSManos Pitsidianakis                         buffer->data + buffer->size,
1286d8d64acbSManos Pitsidianakis                         MIN(available, (stream->params.period_bytes -
1287d8d64acbSManos Pitsidianakis                                         buffer->size)));
1288d8d64acbSManos Pitsidianakis                 if (!size) {
1289d8d64acbSManos Pitsidianakis                     available = 0;
1290d8d64acbSManos Pitsidianakis                     break;
1291d8d64acbSManos Pitsidianakis                 }
1292d8d64acbSManos Pitsidianakis                 buffer->size += size;
1293d8d64acbSManos Pitsidianakis                 available -= size;
1294d8d64acbSManos Pitsidianakis                 if (buffer->size >= stream->params.period_bytes) {
1295d8d64acbSManos Pitsidianakis                     return_rx_buffer(stream, buffer);
1296d8d64acbSManos Pitsidianakis                     break;
1297d8d64acbSManos Pitsidianakis                 }
1298d8d64acbSManos Pitsidianakis                 if (!available) {
1299d8d64acbSManos Pitsidianakis                     break;
1300d8d64acbSManos Pitsidianakis                 }
1301d8d64acbSManos Pitsidianakis             }
1302d8d64acbSManos Pitsidianakis             if (!available) {
1303d8d64acbSManos Pitsidianakis                 break;
1304d8d64acbSManos Pitsidianakis             }
1305d8d64acbSManos Pitsidianakis         }
1306d8d64acbSManos Pitsidianakis     }
1307d8d64acbSManos Pitsidianakis }
1308d8d64acbSManos Pitsidianakis 
1309d8d64acbSManos Pitsidianakis /*
1310d8d64acbSManos Pitsidianakis  * Flush all buffer data from this output stream's queue into the driver's
1311d8d64acbSManos Pitsidianakis  * virtual queue.
131218a75281SManos Pitsidianakis  *
131318a75281SManos Pitsidianakis  * @stream: VirtIOSoundPCMStream *stream
131418a75281SManos Pitsidianakis  */
131518a75281SManos Pitsidianakis static inline void virtio_snd_pcm_flush(VirtIOSoundPCMStream *stream)
131618a75281SManos Pitsidianakis {
131718a75281SManos Pitsidianakis     VirtIOSoundPCMBuffer *buffer;
1318d8d64acbSManos Pitsidianakis     void (*cb)(VirtIOSoundPCMStream *, VirtIOSoundPCMBuffer *) =
1319d8d64acbSManos Pitsidianakis         (stream->info.direction == VIRTIO_SND_D_OUTPUT) ? return_tx_buffer :
1320d8d64acbSManos Pitsidianakis         return_rx_buffer;
132118a75281SManos Pitsidianakis 
132218a75281SManos Pitsidianakis     WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
132318a75281SManos Pitsidianakis         while (!QSIMPLEQ_EMPTY(&stream->queue)) {
132418a75281SManos Pitsidianakis             buffer = QSIMPLEQ_FIRST(&stream->queue);
1325d8d64acbSManos Pitsidianakis             cb(stream, buffer);
132618a75281SManos Pitsidianakis         }
132718a75281SManos Pitsidianakis     }
132818a75281SManos Pitsidianakis }
132918a75281SManos Pitsidianakis 
13302880e676SManos Pitsidianakis static void virtio_snd_unrealize(DeviceState *dev)
13312880e676SManos Pitsidianakis {
13322880e676SManos Pitsidianakis     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
13332880e676SManos Pitsidianakis     VirtIOSound *vsnd = VIRTIO_SND(dev);
1334eb9ad377SManos Pitsidianakis     VirtIOSoundPCMStream *stream;
13352880e676SManos Pitsidianakis 
13362880e676SManos Pitsidianakis     qemu_del_vm_change_state_handler(vsnd->vmstate);
13372880e676SManos Pitsidianakis     trace_virtio_snd_unrealize(vsnd);
13382880e676SManos Pitsidianakis 
1339eb9ad377SManos Pitsidianakis     if (vsnd->pcm) {
1340eb9ad377SManos Pitsidianakis         if (vsnd->pcm->streams) {
1341eb9ad377SManos Pitsidianakis             for (uint32_t i = 0; i < vsnd->snd_conf.streams; i++) {
1342eb9ad377SManos Pitsidianakis                 stream = vsnd->pcm->streams[i];
1343eb9ad377SManos Pitsidianakis                 if (stream) {
1344eb9ad377SManos Pitsidianakis                     virtio_snd_process_cmdq(stream->s);
1345eb9ad377SManos Pitsidianakis                     virtio_snd_pcm_close(stream);
134618a75281SManos Pitsidianakis                     qemu_mutex_destroy(&stream->queue_mutex);
1347eb9ad377SManos Pitsidianakis                     g_free(stream);
1348eb9ad377SManos Pitsidianakis                 }
1349eb9ad377SManos Pitsidianakis             }
1350eb9ad377SManos Pitsidianakis             g_free(vsnd->pcm->streams);
1351eb9ad377SManos Pitsidianakis         }
1352eb9ad377SManos Pitsidianakis         g_free(vsnd->pcm->pcm_params);
1353eb9ad377SManos Pitsidianakis         g_free(vsnd->pcm);
1354eb9ad377SManos Pitsidianakis         vsnd->pcm = NULL;
1355eb9ad377SManos Pitsidianakis     }
13562880e676SManos Pitsidianakis     AUD_remove_card(&vsnd->card);
1357eb9ad377SManos Pitsidianakis     qemu_mutex_destroy(&vsnd->cmdq_mutex);
13582880e676SManos Pitsidianakis     virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_CONTROL]);
13592880e676SManos Pitsidianakis     virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_EVENT]);
13602880e676SManos Pitsidianakis     virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_TX]);
13612880e676SManos Pitsidianakis     virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_RX]);
13622880e676SManos Pitsidianakis     virtio_cleanup(vdev);
13632880e676SManos Pitsidianakis }
13642880e676SManos Pitsidianakis 
13652880e676SManos Pitsidianakis 
1366eb9ad377SManos Pitsidianakis static void virtio_snd_reset(VirtIODevice *vdev)
1367eb9ad377SManos Pitsidianakis {
1368eb9ad377SManos Pitsidianakis     VirtIOSound *s = VIRTIO_SND(vdev);
1369eb9ad377SManos Pitsidianakis     virtio_snd_ctrl_command *cmd;
1370eb9ad377SManos Pitsidianakis 
1371eb9ad377SManos Pitsidianakis     WITH_QEMU_LOCK_GUARD(&s->cmdq_mutex) {
1372eb9ad377SManos Pitsidianakis         while (!QTAILQ_EMPTY(&s->cmdq)) {
1373eb9ad377SManos Pitsidianakis             cmd = QTAILQ_FIRST(&s->cmdq);
1374eb9ad377SManos Pitsidianakis             QTAILQ_REMOVE(&s->cmdq, cmd, next);
1375eb9ad377SManos Pitsidianakis             virtio_snd_ctrl_cmd_free(cmd);
1376eb9ad377SManos Pitsidianakis         }
1377eb9ad377SManos Pitsidianakis     }
1378eb9ad377SManos Pitsidianakis }
13792880e676SManos Pitsidianakis 
13802880e676SManos Pitsidianakis static void virtio_snd_class_init(ObjectClass *klass, void *data)
13812880e676SManos Pitsidianakis {
13822880e676SManos Pitsidianakis     DeviceClass *dc = DEVICE_CLASS(klass);
13832880e676SManos Pitsidianakis     VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
13842880e676SManos Pitsidianakis 
13852880e676SManos Pitsidianakis 
13862880e676SManos Pitsidianakis     set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
13872880e676SManos Pitsidianakis     device_class_set_props(dc, virtio_snd_properties);
13882880e676SManos Pitsidianakis 
13892880e676SManos Pitsidianakis     dc->vmsd = &vmstate_virtio_snd;
13902880e676SManos Pitsidianakis     vdc->vmsd = &vmstate_virtio_snd_device;
13912880e676SManos Pitsidianakis     vdc->realize = virtio_snd_realize;
13922880e676SManos Pitsidianakis     vdc->unrealize = virtio_snd_unrealize;
13932880e676SManos Pitsidianakis     vdc->get_config = virtio_snd_get_config;
13942880e676SManos Pitsidianakis     vdc->set_config = virtio_snd_set_config;
13952880e676SManos Pitsidianakis     vdc->get_features = get_features;
13962880e676SManos Pitsidianakis     vdc->reset = virtio_snd_reset;
13972880e676SManos Pitsidianakis     vdc->legacy_features = 0;
13982880e676SManos Pitsidianakis }
13992880e676SManos Pitsidianakis 
14002880e676SManos Pitsidianakis static const TypeInfo virtio_snd_types[] = {
14012880e676SManos Pitsidianakis     {
14022880e676SManos Pitsidianakis       .name          = TYPE_VIRTIO_SND,
14032880e676SManos Pitsidianakis       .parent        = TYPE_VIRTIO_DEVICE,
14042880e676SManos Pitsidianakis       .instance_size = sizeof(VirtIOSound),
14052880e676SManos Pitsidianakis       .class_init    = virtio_snd_class_init,
14062880e676SManos Pitsidianakis     }
14072880e676SManos Pitsidianakis };
14082880e676SManos Pitsidianakis 
14092880e676SManos Pitsidianakis DEFINE_TYPES(virtio_snd_types)
1410