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