xref: /qemu/hw/audio/virtio-snd.c (revision 8b4d80bb)
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"
228b4d80bbSPhilippe Mathieu-Daudé #include "qemu/lockable.h"
2342508261SPhilippe Mathieu-Daudé #include "exec/tswap.h"
242880e676SManos Pitsidianakis #include "sysemu/runstate.h"
252880e676SManos Pitsidianakis #include "trace.h"
262880e676SManos Pitsidianakis #include "qapi/error.h"
272880e676SManos Pitsidianakis #include "hw/audio/virtio-snd.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);
39f7856181SManos Pitsidianakis static void virtio_snd_unrealize(DeviceState *dev);
4018a75281SManos 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);
63eb9ad377SManos 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 = {
712880e676SManos Pitsidianakis     .name = TYPE_VIRTIO_SND,
72551ef0faSVolker Rümelin     .unmigratable = 1,
732880e676SManos Pitsidianakis     .minimum_version_id = VIRTIO_SOUND_VM_VERSION,
742880e676SManos Pitsidianakis     .version_id = VIRTIO_SOUND_VM_VERSION,
75856a6fe4SRichard Henderson     .fields = (const 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 }
1322880e676SManos Pitsidianakis 
133eb9ad377SManos 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 
14018a75281SManos 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 }
146eb9ad377SManos Pitsidianakis 
1472880e676SManos 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 
174eb9ad377SManos 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 
246633487dfSVolker Rümelin     cmd->payload_size = sizeof(virtio_snd_pcm_info) * count;
2470ff05dd2SManos Pitsidianakis     cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK);
2480ff05dd2SManos Pitsidianakis     iov_from_buf(cmd->elem->in_sg,
2490ff05dd2SManos Pitsidianakis                  cmd->elem->in_num,
2500ff05dd2SManos Pitsidianakis                  sizeof(virtio_snd_hdr),
2510ff05dd2SManos Pitsidianakis                  pcm_info,
252633487dfSVolker Rümelin                  cmd->payload_size);
2530ff05dd2SManos Pitsidianakis }
2540ff05dd2SManos Pitsidianakis 
2550ff05dd2SManos Pitsidianakis /*
256eb9ad377SManos Pitsidianakis  * Set the given stream params.
257eb9ad377SManos Pitsidianakis  * Called by both virtio_snd_handle_pcm_set_params and during device
258eb9ad377SManos Pitsidianakis  * initialization.
259eb9ad377SManos Pitsidianakis  * Returns the response status code. (VIRTIO_SND_S_*).
260eb9ad377SManos Pitsidianakis  *
261eb9ad377SManos Pitsidianakis  * @s: VirtIOSound device
262eb9ad377SManos Pitsidianakis  * @params: The PCM params as defined in the virtio specification
263eb9ad377SManos Pitsidianakis  */
264eb9ad377SManos Pitsidianakis static
virtio_snd_set_pcm_params(VirtIOSound * s,uint32_t stream_id,virtio_snd_pcm_set_params * params)265eb9ad377SManos Pitsidianakis uint32_t virtio_snd_set_pcm_params(VirtIOSound *s,
266eb9ad377SManos Pitsidianakis                                    uint32_t stream_id,
267eb9ad377SManos Pitsidianakis                                    virtio_snd_pcm_set_params *params)
268eb9ad377SManos Pitsidianakis {
269eb9ad377SManos Pitsidianakis     virtio_snd_pcm_set_params *st_params;
270eb9ad377SManos Pitsidianakis 
271eb9ad377SManos Pitsidianakis     if (stream_id >= s->snd_conf.streams || s->pcm->pcm_params == NULL) {
272eb9ad377SManos Pitsidianakis         /*
273eb9ad377SManos Pitsidianakis          * TODO: do we need to set DEVICE_NEEDS_RESET?
274eb9ad377SManos Pitsidianakis          */
275eb9ad377SManos Pitsidianakis         virtio_error(VIRTIO_DEVICE(s), "Streams have not been initialized.\n");
276eb9ad377SManos Pitsidianakis         return cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
277eb9ad377SManos Pitsidianakis     }
278eb9ad377SManos Pitsidianakis 
279eb9ad377SManos Pitsidianakis     st_params = virtio_snd_pcm_get_params(s, stream_id);
280eb9ad377SManos Pitsidianakis 
281eb9ad377SManos Pitsidianakis     if (params->channels < 1 || params->channels > AUDIO_MAX_CHANNELS) {
282eb9ad377SManos Pitsidianakis         error_report("Number of channels is not supported.");
283eb9ad377SManos Pitsidianakis         return cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
284eb9ad377SManos Pitsidianakis     }
285eb9ad377SManos Pitsidianakis     if (!(supported_formats & BIT(params->format))) {
286eb9ad377SManos Pitsidianakis         error_report("Stream format is not supported.");
287eb9ad377SManos Pitsidianakis         return cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
288eb9ad377SManos Pitsidianakis     }
289eb9ad377SManos Pitsidianakis     if (!(supported_rates & BIT(params->rate))) {
290eb9ad377SManos Pitsidianakis         error_report("Stream rate is not supported.");
291eb9ad377SManos Pitsidianakis         return cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
292eb9ad377SManos Pitsidianakis     }
293eb9ad377SManos Pitsidianakis 
294eb9ad377SManos Pitsidianakis     st_params->buffer_bytes = le32_to_cpu(params->buffer_bytes);
295eb9ad377SManos Pitsidianakis     st_params->period_bytes = le32_to_cpu(params->period_bytes);
296eb9ad377SManos Pitsidianakis     st_params->features = le32_to_cpu(params->features);
297eb9ad377SManos Pitsidianakis     /* the following are uint8_t, so there's no need to bswap the values. */
298eb9ad377SManos Pitsidianakis     st_params->channels = params->channels;
299eb9ad377SManos Pitsidianakis     st_params->format = params->format;
300eb9ad377SManos Pitsidianakis     st_params->rate = params->rate;
301eb9ad377SManos Pitsidianakis 
302eb9ad377SManos Pitsidianakis     return cpu_to_le32(VIRTIO_SND_S_OK);
303eb9ad377SManos Pitsidianakis }
304eb9ad377SManos Pitsidianakis 
305eb9ad377SManos Pitsidianakis /*
30664704ce0SManos Pitsidianakis  * Handles the VIRTIO_SND_R_PCM_SET_PARAMS request.
30764704ce0SManos Pitsidianakis  *
30864704ce0SManos Pitsidianakis  * @s: VirtIOSound device
30964704ce0SManos Pitsidianakis  * @cmd: The request command queue element from VirtIOSound cmdq field
31064704ce0SManos Pitsidianakis  */
virtio_snd_handle_pcm_set_params(VirtIOSound * s,virtio_snd_ctrl_command * cmd)31164704ce0SManos Pitsidianakis static void virtio_snd_handle_pcm_set_params(VirtIOSound *s,
31264704ce0SManos Pitsidianakis                                              virtio_snd_ctrl_command *cmd)
31364704ce0SManos Pitsidianakis {
31464704ce0SManos Pitsidianakis     virtio_snd_pcm_set_params req = { 0 };
31564704ce0SManos Pitsidianakis     uint32_t stream_id;
31664704ce0SManos Pitsidianakis     size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
31764704ce0SManos Pitsidianakis                                cmd->elem->out_num,
31864704ce0SManos Pitsidianakis                                0,
31964704ce0SManos Pitsidianakis                                &req,
32064704ce0SManos Pitsidianakis                                sizeof(virtio_snd_pcm_set_params));
32164704ce0SManos Pitsidianakis 
32264704ce0SManos Pitsidianakis     if (msg_sz != sizeof(virtio_snd_pcm_set_params)) {
32364704ce0SManos Pitsidianakis         /*
32464704ce0SManos Pitsidianakis          * TODO: do we need to set DEVICE_NEEDS_RESET?
32564704ce0SManos Pitsidianakis          */
32664704ce0SManos Pitsidianakis         qemu_log_mask(LOG_GUEST_ERROR,
32764704ce0SManos Pitsidianakis                 "%s: virtio-snd command size incorrect %zu vs \
32864704ce0SManos Pitsidianakis                 %zu\n", __func__, msg_sz, sizeof(virtio_snd_pcm_set_params));
32964704ce0SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
33064704ce0SManos Pitsidianakis         return;
33164704ce0SManos Pitsidianakis     }
33264704ce0SManos Pitsidianakis     stream_id = le32_to_cpu(req.hdr.stream_id);
33364704ce0SManos Pitsidianakis     trace_virtio_snd_handle_pcm_set_params(stream_id);
33464704ce0SManos Pitsidianakis     cmd->resp.code = virtio_snd_set_pcm_params(s, stream_id, &req);
33564704ce0SManos Pitsidianakis }
33664704ce0SManos Pitsidianakis 
33764704ce0SManos Pitsidianakis /*
338eb9ad377SManos Pitsidianakis  * Get a QEMU Audiosystem compatible format value from a VIRTIO_SND_PCM_FMT_*
339eb9ad377SManos Pitsidianakis  */
virtio_snd_get_qemu_format(uint32_t format)340eb9ad377SManos Pitsidianakis static AudioFormat virtio_snd_get_qemu_format(uint32_t format)
341eb9ad377SManos Pitsidianakis {
342eb9ad377SManos Pitsidianakis     #define CASE(FMT)               \
343eb9ad377SManos Pitsidianakis     case VIRTIO_SND_PCM_FMT_##FMT:  \
344eb9ad377SManos Pitsidianakis         return AUDIO_FORMAT_##FMT;
345eb9ad377SManos Pitsidianakis 
346eb9ad377SManos Pitsidianakis     switch (format) {
347eb9ad377SManos Pitsidianakis     CASE(U8)
348eb9ad377SManos Pitsidianakis     CASE(S8)
349eb9ad377SManos Pitsidianakis     CASE(U16)
350eb9ad377SManos Pitsidianakis     CASE(S16)
351eb9ad377SManos Pitsidianakis     CASE(U32)
352eb9ad377SManos Pitsidianakis     CASE(S32)
353eb9ad377SManos Pitsidianakis     case VIRTIO_SND_PCM_FMT_FLOAT:
354eb9ad377SManos Pitsidianakis         return AUDIO_FORMAT_F32;
355eb9ad377SManos Pitsidianakis     default:
356eb9ad377SManos Pitsidianakis         g_assert_not_reached();
357eb9ad377SManos Pitsidianakis     }
358eb9ad377SManos Pitsidianakis 
359eb9ad377SManos Pitsidianakis     #undef CASE
360eb9ad377SManos Pitsidianakis }
361eb9ad377SManos Pitsidianakis 
362eb9ad377SManos Pitsidianakis /*
363eb9ad377SManos Pitsidianakis  * Get a QEMU Audiosystem compatible frequency value from a
364eb9ad377SManos Pitsidianakis  * VIRTIO_SND_PCM_RATE_*
365eb9ad377SManos Pitsidianakis  */
virtio_snd_get_qemu_freq(uint32_t rate)366eb9ad377SManos Pitsidianakis static uint32_t virtio_snd_get_qemu_freq(uint32_t rate)
367eb9ad377SManos Pitsidianakis {
368eb9ad377SManos Pitsidianakis     #define CASE(RATE)               \
369eb9ad377SManos Pitsidianakis     case VIRTIO_SND_PCM_RATE_##RATE: \
370eb9ad377SManos Pitsidianakis         return RATE;
371eb9ad377SManos Pitsidianakis 
372eb9ad377SManos Pitsidianakis     switch (rate) {
373eb9ad377SManos Pitsidianakis     CASE(5512)
374eb9ad377SManos Pitsidianakis     CASE(8000)
375eb9ad377SManos Pitsidianakis     CASE(11025)
376eb9ad377SManos Pitsidianakis     CASE(16000)
377eb9ad377SManos Pitsidianakis     CASE(22050)
378eb9ad377SManos Pitsidianakis     CASE(32000)
379eb9ad377SManos Pitsidianakis     CASE(44100)
380eb9ad377SManos Pitsidianakis     CASE(48000)
381eb9ad377SManos Pitsidianakis     CASE(64000)
382eb9ad377SManos Pitsidianakis     CASE(88200)
383eb9ad377SManos Pitsidianakis     CASE(96000)
384eb9ad377SManos Pitsidianakis     CASE(176400)
385eb9ad377SManos Pitsidianakis     CASE(192000)
386eb9ad377SManos Pitsidianakis     CASE(384000)
387eb9ad377SManos Pitsidianakis     default:
388eb9ad377SManos Pitsidianakis         g_assert_not_reached();
389eb9ad377SManos Pitsidianakis     }
390eb9ad377SManos Pitsidianakis 
391eb9ad377SManos Pitsidianakis     #undef CASE
392eb9ad377SManos Pitsidianakis }
393eb9ad377SManos Pitsidianakis 
394eb9ad377SManos Pitsidianakis /*
395eb9ad377SManos Pitsidianakis  * Get QEMU Audiosystem compatible audsettings from virtio based pcm stream
396eb9ad377SManos Pitsidianakis  * params.
397eb9ad377SManos Pitsidianakis  */
virtio_snd_get_qemu_audsettings(audsettings * as,virtio_snd_pcm_set_params * params)398eb9ad377SManos Pitsidianakis static void virtio_snd_get_qemu_audsettings(audsettings *as,
399eb9ad377SManos Pitsidianakis                                             virtio_snd_pcm_set_params *params)
400eb9ad377SManos Pitsidianakis {
401eb9ad377SManos Pitsidianakis     as->nchannels = MIN(AUDIO_MAX_CHANNELS, params->channels);
402eb9ad377SManos Pitsidianakis     as->fmt = virtio_snd_get_qemu_format(params->format);
403eb9ad377SManos Pitsidianakis     as->freq = virtio_snd_get_qemu_freq(params->rate);
404eb9ad377SManos Pitsidianakis     as->endianness = target_words_bigendian() ? 1 : 0;
405eb9ad377SManos Pitsidianakis }
406eb9ad377SManos Pitsidianakis 
407eb9ad377SManos Pitsidianakis /*
408eb9ad377SManos Pitsidianakis  * Close a stream and free all its resources.
409eb9ad377SManos Pitsidianakis  *
410eb9ad377SManos Pitsidianakis  * @stream: VirtIOSoundPCMStream *stream
411eb9ad377SManos Pitsidianakis  */
virtio_snd_pcm_close(VirtIOSoundPCMStream * stream)412eb9ad377SManos Pitsidianakis static void virtio_snd_pcm_close(VirtIOSoundPCMStream *stream)
413eb9ad377SManos Pitsidianakis {
41418a75281SManos Pitsidianakis     if (stream) {
41518a75281SManos Pitsidianakis         virtio_snd_pcm_flush(stream);
416d8d64acbSManos Pitsidianakis         if (stream->info.direction == VIRTIO_SND_D_OUTPUT) {
41718a75281SManos Pitsidianakis             AUD_close_out(&stream->pcm->snd->card, stream->voice.out);
41818a75281SManos Pitsidianakis             stream->voice.out = NULL;
419d8d64acbSManos Pitsidianakis         } else if (stream->info.direction == VIRTIO_SND_D_INPUT) {
420d8d64acbSManos Pitsidianakis             AUD_close_in(&stream->pcm->snd->card, stream->voice.in);
421d8d64acbSManos Pitsidianakis             stream->voice.in = NULL;
42218a75281SManos Pitsidianakis         }
42318a75281SManos Pitsidianakis     }
424eb9ad377SManos Pitsidianakis }
425eb9ad377SManos Pitsidianakis 
426eb9ad377SManos Pitsidianakis /*
427eb9ad377SManos Pitsidianakis  * Prepares a VirtIOSound card stream.
428eb9ad377SManos Pitsidianakis  * Returns the response status code. (VIRTIO_SND_S_*).
429eb9ad377SManos Pitsidianakis  *
430eb9ad377SManos Pitsidianakis  * @s: VirtIOSound device
431eb9ad377SManos Pitsidianakis  * @stream_id: stream id
432eb9ad377SManos Pitsidianakis  */
virtio_snd_pcm_prepare(VirtIOSound * s,uint32_t stream_id)433eb9ad377SManos Pitsidianakis static uint32_t virtio_snd_pcm_prepare(VirtIOSound *s, uint32_t stream_id)
434eb9ad377SManos Pitsidianakis {
435eb9ad377SManos Pitsidianakis     audsettings as;
436eb9ad377SManos Pitsidianakis     virtio_snd_pcm_set_params *params;
437eb9ad377SManos Pitsidianakis     VirtIOSoundPCMStream *stream;
438eb9ad377SManos Pitsidianakis 
439eb9ad377SManos Pitsidianakis     if (s->pcm->streams == NULL ||
440eb9ad377SManos Pitsidianakis         s->pcm->pcm_params == NULL ||
441eb9ad377SManos Pitsidianakis         stream_id >= s->snd_conf.streams) {
442eb9ad377SManos Pitsidianakis         return cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
443eb9ad377SManos Pitsidianakis     }
444eb9ad377SManos Pitsidianakis 
445eb9ad377SManos Pitsidianakis     params = virtio_snd_pcm_get_params(s, stream_id);
446eb9ad377SManos Pitsidianakis     if (params == NULL) {
447eb9ad377SManos Pitsidianakis         return cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
448eb9ad377SManos Pitsidianakis     }
449eb9ad377SManos Pitsidianakis 
450eb9ad377SManos Pitsidianakis     stream = virtio_snd_pcm_get_stream(s, stream_id);
451eb9ad377SManos Pitsidianakis     if (stream == NULL) {
452eb9ad377SManos Pitsidianakis         stream = g_new0(VirtIOSoundPCMStream, 1);
453eb9ad377SManos Pitsidianakis         stream->active = false;
454eb9ad377SManos Pitsidianakis         stream->id = stream_id;
455eb9ad377SManos Pitsidianakis         stream->pcm = s->pcm;
456eb9ad377SManos Pitsidianakis         stream->s = s;
45718a75281SManos Pitsidianakis         qemu_mutex_init(&stream->queue_mutex);
45818a75281SManos Pitsidianakis         QSIMPLEQ_INIT(&stream->queue);
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;
481eb9ad377SManos 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);
49018a75281SManos 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);
497d8d64acbSManos Pitsidianakis         AUD_set_volume_in(stream->voice.in, 0, 255, 255);
49818a75281SManos Pitsidianakis     }
49918a75281SManos 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 
526eb9ad377SManos 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 
548e5788b8fSManos 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" :
579fa131d4aSManos Pitsidianakis             "VIRTIO_SND_R_PCM_STOP", stream_id);
58018a75281SManos Pitsidianakis 
581fa131d4aSManos 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) {
58718a75281SManos Pitsidianakis             AUD_set_active_out(stream->voice.out, start);
588d8d64acbSManos Pitsidianakis         } else {
589d8d64acbSManos Pitsidianakis             AUD_set_active_in(stream->voice.in, start);
59018a75281SManos Pitsidianakis         }
59118a75281SManos Pitsidianakis     } else {
59218a75281SManos 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 
599fa131d4aSManos 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     }
61418a75281SManos Pitsidianakis     return count;
61518a75281SManos Pitsidianakis }
61618a75281SManos Pitsidianakis 
61718a75281SManos Pitsidianakis /*
61818a75281SManos Pitsidianakis  * Handles VIRTIO_SND_R_PCM_RELEASE.
619d48800d7SManos Pitsidianakis  *
620d48800d7SManos Pitsidianakis  * @s: VirtIOSound device
621d48800d7SManos Pitsidianakis  * @cmd: The request command queue element from VirtIOSound cmdq field
622d48800d7SManos Pitsidianakis  */
virtio_snd_handle_pcm_release(VirtIOSound * s,virtio_snd_ctrl_command * cmd)623d48800d7SManos Pitsidianakis static void virtio_snd_handle_pcm_release(VirtIOSound *s,
624d48800d7SManos Pitsidianakis                                           virtio_snd_ctrl_command *cmd)
625d48800d7SManos Pitsidianakis {
626d48800d7SManos Pitsidianakis     uint32_t stream_id;
627d48800d7SManos Pitsidianakis     VirtIOSoundPCMStream *stream;
628d48800d7SManos Pitsidianakis     size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
629d48800d7SManos Pitsidianakis                                cmd->elem->out_num,
630d48800d7SManos Pitsidianakis                                sizeof(virtio_snd_hdr),
631d48800d7SManos Pitsidianakis                                &stream_id,
632d48800d7SManos Pitsidianakis                                sizeof(stream_id));
633d48800d7SManos Pitsidianakis 
634d48800d7SManos Pitsidianakis     if (msg_sz != sizeof(stream_id)) {
635d48800d7SManos Pitsidianakis         /*
636d48800d7SManos Pitsidianakis          * TODO: do we need to set DEVICE_NEEDS_RESET?
637d48800d7SManos Pitsidianakis          */
638d48800d7SManos Pitsidianakis         qemu_log_mask(LOG_GUEST_ERROR,
639d48800d7SManos Pitsidianakis                 "%s: virtio-snd command size incorrect %zu vs \
640d48800d7SManos Pitsidianakis                 %zu\n", __func__, msg_sz, sizeof(stream_id));
641d48800d7SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
642d48800d7SManos Pitsidianakis         return;
643d48800d7SManos Pitsidianakis     }
644d48800d7SManos Pitsidianakis 
645d48800d7SManos Pitsidianakis     stream_id = le32_to_cpu(stream_id);
646d48800d7SManos Pitsidianakis     trace_virtio_snd_handle_pcm_release(stream_id);
647d48800d7SManos Pitsidianakis     stream = virtio_snd_pcm_get_stream(s, stream_id);
648d48800d7SManos Pitsidianakis     if (stream == NULL) {
649d48800d7SManos Pitsidianakis         /*
650d48800d7SManos Pitsidianakis          * TODO: do we need to set DEVICE_NEEDS_RESET?
651d48800d7SManos Pitsidianakis          */
652d48800d7SManos Pitsidianakis         error_report("already released stream %"PRIu32, stream_id);
653d48800d7SManos Pitsidianakis         virtio_error(VIRTIO_DEVICE(s),
654d48800d7SManos Pitsidianakis                      "already released stream %"PRIu32,
655d48800d7SManos Pitsidianakis                      stream_id);
656d48800d7SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
657d48800d7SManos Pitsidianakis         return;
658d48800d7SManos Pitsidianakis     }
65918a75281SManos Pitsidianakis 
66018a75281SManos Pitsidianakis     if (virtio_snd_pcm_get_io_msgs_count(stream)) {
66118a75281SManos Pitsidianakis         /*
66218a75281SManos Pitsidianakis          * virtio-v1.2-csd01, 5.14.6.6.5.1,
66318a75281SManos Pitsidianakis          * Device Requirements: Stream Release
66418a75281SManos Pitsidianakis          *
66518a75281SManos Pitsidianakis          * - The device MUST complete all pending I/O messages for the
66618a75281SManos Pitsidianakis          *   specified stream ID.
66718a75281SManos Pitsidianakis          * - The device MUST NOT complete the control request while there
66818a75281SManos Pitsidianakis          *   are pending I/O messages for the specified stream ID.
66918a75281SManos Pitsidianakis          */
67018a75281SManos Pitsidianakis         trace_virtio_snd_pcm_stream_flush(stream_id);
67118a75281SManos Pitsidianakis         virtio_snd_pcm_flush(stream);
67218a75281SManos Pitsidianakis     }
67318a75281SManos Pitsidianakis 
674d48800d7SManos Pitsidianakis     cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK);
675d48800d7SManos Pitsidianakis }
676d48800d7SManos Pitsidianakis 
677d48800d7SManos Pitsidianakis /*
678eb9ad377SManos Pitsidianakis  * The actual processing done in virtio_snd_process_cmdq().
679eb9ad377SManos Pitsidianakis  *
680eb9ad377SManos Pitsidianakis  * @s: VirtIOSound device
681eb9ad377SManos Pitsidianakis  * @cmd: control command request
682eb9ad377SManos Pitsidianakis  */
683eb9ad377SManos Pitsidianakis static inline void
process_cmd(VirtIOSound * s,virtio_snd_ctrl_command * cmd)684eb9ad377SManos Pitsidianakis process_cmd(VirtIOSound *s, virtio_snd_ctrl_command *cmd)
685eb9ad377SManos Pitsidianakis {
686eb9ad377SManos Pitsidianakis     uint32_t code;
687eb9ad377SManos Pitsidianakis     size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
688eb9ad377SManos Pitsidianakis                                cmd->elem->out_num,
689eb9ad377SManos Pitsidianakis                                0,
690eb9ad377SManos Pitsidianakis                                &cmd->ctrl,
691eb9ad377SManos Pitsidianakis                                sizeof(virtio_snd_hdr));
692eb9ad377SManos Pitsidianakis 
693eb9ad377SManos Pitsidianakis     if (msg_sz != sizeof(virtio_snd_hdr)) {
694eb9ad377SManos Pitsidianakis         /*
695eb9ad377SManos Pitsidianakis          * TODO: do we need to set DEVICE_NEEDS_RESET?
696eb9ad377SManos Pitsidianakis          */
697eb9ad377SManos Pitsidianakis         qemu_log_mask(LOG_GUEST_ERROR,
698eb9ad377SManos Pitsidianakis                 "%s: virtio-snd command size incorrect %zu vs \
699eb9ad377SManos Pitsidianakis                 %zu\n", __func__, msg_sz, sizeof(virtio_snd_hdr));
700eb9ad377SManos Pitsidianakis         return;
701eb9ad377SManos Pitsidianakis     }
702eb9ad377SManos Pitsidianakis 
703eb9ad377SManos Pitsidianakis     code = le32_to_cpu(cmd->ctrl.code);
704eb9ad377SManos Pitsidianakis 
705eb9ad377SManos Pitsidianakis     trace_virtio_snd_handle_code(code, print_code(code));
706eb9ad377SManos Pitsidianakis 
707eb9ad377SManos Pitsidianakis     switch (code) {
708eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_JACK_INFO:
709eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_JACK_REMAP:
710eb9ad377SManos Pitsidianakis         qemu_log_mask(LOG_UNIMP,
711eb9ad377SManos Pitsidianakis                      "virtio_snd: jack functionality is unimplemented.\n");
712eb9ad377SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
713eb9ad377SManos Pitsidianakis         break;
714eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_PCM_INFO:
7150ff05dd2SManos Pitsidianakis         virtio_snd_handle_pcm_info(s, cmd);
7160ff05dd2SManos Pitsidianakis         break;
717fa131d4aSManos Pitsidianakis     case VIRTIO_SND_R_PCM_START:
718fa131d4aSManos Pitsidianakis         virtio_snd_handle_pcm_start_stop(s, cmd, true);
719fa131d4aSManos Pitsidianakis         break;
720fa131d4aSManos Pitsidianakis     case VIRTIO_SND_R_PCM_STOP:
721fa131d4aSManos Pitsidianakis         virtio_snd_handle_pcm_start_stop(s, cmd, false);
722fa131d4aSManos Pitsidianakis         break;
723eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_PCM_SET_PARAMS:
72464704ce0SManos Pitsidianakis         virtio_snd_handle_pcm_set_params(s, cmd);
72564704ce0SManos Pitsidianakis         break;
726eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_PCM_PREPARE:
727e5788b8fSManos Pitsidianakis         virtio_snd_handle_pcm_prepare(s, cmd);
728e5788b8fSManos Pitsidianakis         break;
729eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_PCM_RELEASE:
730d48800d7SManos Pitsidianakis         virtio_snd_handle_pcm_release(s, cmd);
731eb9ad377SManos Pitsidianakis         break;
732eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_CHMAP_INFO:
733eb9ad377SManos Pitsidianakis         qemu_log_mask(LOG_UNIMP,
734eb9ad377SManos Pitsidianakis                      "virtio_snd: chmap info functionality is unimplemented.\n");
735eb9ad377SManos Pitsidianakis         trace_virtio_snd_handle_chmap_info();
736eb9ad377SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
737eb9ad377SManos Pitsidianakis         break;
738eb9ad377SManos Pitsidianakis     default:
739eb9ad377SManos Pitsidianakis         /* error */
740eb9ad377SManos Pitsidianakis         error_report("virtio snd header not recognized: %"PRIu32, code);
741eb9ad377SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
742eb9ad377SManos Pitsidianakis     }
743eb9ad377SManos Pitsidianakis 
744eb9ad377SManos Pitsidianakis     iov_from_buf(cmd->elem->in_sg,
745eb9ad377SManos Pitsidianakis                  cmd->elem->in_num,
746eb9ad377SManos Pitsidianakis                  0,
747eb9ad377SManos Pitsidianakis                  &cmd->resp,
748eb9ad377SManos Pitsidianakis                  sizeof(virtio_snd_hdr));
749633487dfSVolker Rümelin     virtqueue_push(cmd->vq, cmd->elem,
750633487dfSVolker Rümelin                    sizeof(virtio_snd_hdr) + cmd->payload_size);
751eb9ad377SManos Pitsidianakis     virtio_notify(VIRTIO_DEVICE(s), cmd->vq);
752eb9ad377SManos Pitsidianakis }
753eb9ad377SManos Pitsidianakis 
754eb9ad377SManos Pitsidianakis /*
755eb9ad377SManos Pitsidianakis  * Consume all elements in command queue.
756eb9ad377SManos Pitsidianakis  *
757eb9ad377SManos Pitsidianakis  * @s: VirtIOSound device
758eb9ad377SManos Pitsidianakis  */
virtio_snd_process_cmdq(VirtIOSound * s)759eb9ad377SManos Pitsidianakis static void virtio_snd_process_cmdq(VirtIOSound *s)
760eb9ad377SManos Pitsidianakis {
761eb9ad377SManos Pitsidianakis     virtio_snd_ctrl_command *cmd;
762eb9ad377SManos Pitsidianakis 
763eb9ad377SManos Pitsidianakis     if (unlikely(qatomic_read(&s->processing_cmdq))) {
764eb9ad377SManos Pitsidianakis         return;
765eb9ad377SManos Pitsidianakis     }
766eb9ad377SManos Pitsidianakis 
767eb9ad377SManos Pitsidianakis     WITH_QEMU_LOCK_GUARD(&s->cmdq_mutex) {
768eb9ad377SManos Pitsidianakis         qatomic_set(&s->processing_cmdq, true);
769eb9ad377SManos Pitsidianakis         while (!QTAILQ_EMPTY(&s->cmdq)) {
770eb9ad377SManos Pitsidianakis             cmd = QTAILQ_FIRST(&s->cmdq);
771eb9ad377SManos Pitsidianakis 
772eb9ad377SManos Pitsidianakis             /* process command */
773eb9ad377SManos Pitsidianakis             process_cmd(s, cmd);
774eb9ad377SManos Pitsidianakis 
775eb9ad377SManos Pitsidianakis             QTAILQ_REMOVE(&s->cmdq, cmd, next);
776eb9ad377SManos Pitsidianakis 
777eb9ad377SManos Pitsidianakis             virtio_snd_ctrl_cmd_free(cmd);
778eb9ad377SManos Pitsidianakis         }
779eb9ad377SManos Pitsidianakis         qatomic_set(&s->processing_cmdq, false);
780eb9ad377SManos Pitsidianakis     }
781eb9ad377SManos Pitsidianakis }
782eb9ad377SManos Pitsidianakis 
783eb9ad377SManos Pitsidianakis /*
784eb9ad377SManos Pitsidianakis  * The control message handler. Pops an element from the control virtqueue,
785eb9ad377SManos Pitsidianakis  * and stores them to VirtIOSound's cmdq queue and finally calls
786eb9ad377SManos Pitsidianakis  * virtio_snd_process_cmdq() for processing.
787eb9ad377SManos Pitsidianakis  *
788eb9ad377SManos Pitsidianakis  * @vdev: VirtIOSound device
789eb9ad377SManos Pitsidianakis  * @vq: Control virtqueue
790eb9ad377SManos Pitsidianakis  */
virtio_snd_handle_ctrl(VirtIODevice * vdev,VirtQueue * vq)791eb9ad377SManos Pitsidianakis static void virtio_snd_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq)
792eb9ad377SManos Pitsidianakis {
793eb9ad377SManos Pitsidianakis     VirtIOSound *s = VIRTIO_SND(vdev);
794eb9ad377SManos Pitsidianakis     VirtQueueElement *elem;
795eb9ad377SManos Pitsidianakis     virtio_snd_ctrl_command *cmd;
796eb9ad377SManos Pitsidianakis 
797eb9ad377SManos Pitsidianakis     trace_virtio_snd_handle_ctrl(vdev, vq);
798eb9ad377SManos Pitsidianakis 
799eb9ad377SManos Pitsidianakis     if (!virtio_queue_ready(vq)) {
800eb9ad377SManos Pitsidianakis         return;
801eb9ad377SManos Pitsidianakis     }
802eb9ad377SManos Pitsidianakis 
803eb9ad377SManos Pitsidianakis     elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
804eb9ad377SManos Pitsidianakis     while (elem) {
805eb9ad377SManos Pitsidianakis         cmd = g_new0(virtio_snd_ctrl_command, 1);
806eb9ad377SManos Pitsidianakis         cmd->elem = elem;
807eb9ad377SManos Pitsidianakis         cmd->vq = vq;
808eb9ad377SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK);
809633487dfSVolker Rümelin         /* implicit cmd->payload_size = 0; */
810eb9ad377SManos Pitsidianakis         QTAILQ_INSERT_TAIL(&s->cmdq, cmd, next);
811eb9ad377SManos Pitsidianakis         elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
812eb9ad377SManos Pitsidianakis     }
813eb9ad377SManos Pitsidianakis 
814eb9ad377SManos Pitsidianakis     virtio_snd_process_cmdq(s);
815eb9ad377SManos Pitsidianakis }
816eb9ad377SManos Pitsidianakis 
817eb9ad377SManos Pitsidianakis /*
818eb9ad377SManos Pitsidianakis  * The event virtqueue handler.
819eb9ad377SManos Pitsidianakis  * Not implemented yet.
820eb9ad377SManos Pitsidianakis  *
821eb9ad377SManos Pitsidianakis  * @vdev: VirtIOSound device
822eb9ad377SManos Pitsidianakis  * @vq: event vq
823eb9ad377SManos Pitsidianakis  */
virtio_snd_handle_event(VirtIODevice * vdev,VirtQueue * vq)824eb9ad377SManos Pitsidianakis static void virtio_snd_handle_event(VirtIODevice *vdev, VirtQueue *vq)
825eb9ad377SManos Pitsidianakis {
826eb9ad377SManos Pitsidianakis     qemu_log_mask(LOG_UNIMP, "virtio_snd: event queue is unimplemented.\n");
827eb9ad377SManos Pitsidianakis     trace_virtio_snd_handle_event();
828eb9ad377SManos Pitsidianakis }
829eb9ad377SManos Pitsidianakis 
830731655f8SManos Pitsidianakis /*
831731655f8SManos Pitsidianakis  * Must only be called if vsnd->invalid is not empty.
832731655f8SManos Pitsidianakis  */
empty_invalid_queue(VirtIODevice * vdev,VirtQueue * vq)833d8d64acbSManos Pitsidianakis static inline void empty_invalid_queue(VirtIODevice *vdev, VirtQueue *vq)
834d8d64acbSManos Pitsidianakis {
835d8d64acbSManos Pitsidianakis     VirtIOSoundPCMBuffer *buffer = NULL;
836d8d64acbSManos Pitsidianakis     virtio_snd_pcm_status resp = { 0 };
837d8d64acbSManos Pitsidianakis     VirtIOSound *vsnd = VIRTIO_SND(vdev);
838d8d64acbSManos Pitsidianakis 
839731655f8SManos Pitsidianakis     g_assert(!QSIMPLEQ_EMPTY(&vsnd->invalid));
840731655f8SManos Pitsidianakis 
841731655f8SManos Pitsidianakis     while (!QSIMPLEQ_EMPTY(&vsnd->invalid)) {
842731655f8SManos Pitsidianakis         buffer = QSIMPLEQ_FIRST(&vsnd->invalid);
843731655f8SManos Pitsidianakis         /* If buffer->vq != vq, our logic is fundamentally wrong, so bail out */
844731655f8SManos Pitsidianakis         g_assert(buffer->vq == vq);
845731655f8SManos Pitsidianakis 
846d8d64acbSManos Pitsidianakis         resp.status = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
847d8d64acbSManos Pitsidianakis         iov_from_buf(buffer->elem->in_sg,
848d8d64acbSManos Pitsidianakis                      buffer->elem->in_num,
849d8d64acbSManos Pitsidianakis                      0,
850d8d64acbSManos Pitsidianakis                      &resp,
851d8d64acbSManos Pitsidianakis                      sizeof(virtio_snd_pcm_status));
852d8d64acbSManos Pitsidianakis         virtqueue_push(vq,
853d8d64acbSManos Pitsidianakis                        buffer->elem,
854d8d64acbSManos Pitsidianakis                        sizeof(virtio_snd_pcm_status));
855731655f8SManos Pitsidianakis         QSIMPLEQ_REMOVE_HEAD(&vsnd->invalid, entry);
856d8d64acbSManos Pitsidianakis         virtio_snd_pcm_buffer_free(buffer);
857d8d64acbSManos Pitsidianakis     }
858731655f8SManos Pitsidianakis     /* Notify vq about virtio_snd_pcm_status responses. */
859d8d64acbSManos Pitsidianakis     virtio_notify(vdev, vq);
860d8d64acbSManos Pitsidianakis }
861d8d64acbSManos Pitsidianakis 
862eb9ad377SManos Pitsidianakis /*
86318a75281SManos Pitsidianakis  * The tx virtqueue handler. Makes the buffers available to their respective
86418a75281SManos Pitsidianakis  * streams for consumption.
86518a75281SManos Pitsidianakis  *
86618a75281SManos Pitsidianakis  * @vdev: VirtIOSound device
86718a75281SManos Pitsidianakis  * @vq: tx virtqueue
86818a75281SManos Pitsidianakis  */
virtio_snd_handle_tx_xfer(VirtIODevice * vdev,VirtQueue * vq)869d8d64acbSManos Pitsidianakis static void virtio_snd_handle_tx_xfer(VirtIODevice *vdev, VirtQueue *vq)
87018a75281SManos Pitsidianakis {
871731655f8SManos Pitsidianakis     VirtIOSound *vsnd = VIRTIO_SND(vdev);
87218a75281SManos Pitsidianakis     VirtIOSoundPCMBuffer *buffer;
87318a75281SManos Pitsidianakis     VirtQueueElement *elem;
87418a75281SManos Pitsidianakis     size_t msg_sz, size;
87518a75281SManos Pitsidianakis     virtio_snd_pcm_xfer hdr;
87618a75281SManos Pitsidianakis     uint32_t stream_id;
87718a75281SManos Pitsidianakis     /*
878731655f8SManos Pitsidianakis      * If any of the I/O messages are invalid, put them in vsnd->invalid and
87918a75281SManos Pitsidianakis      * return them after the for loop.
88018a75281SManos Pitsidianakis      */
88118a75281SManos Pitsidianakis     bool must_empty_invalid_queue = false;
88218a75281SManos Pitsidianakis 
88318a75281SManos Pitsidianakis     if (!virtio_queue_ready(vq)) {
88418a75281SManos Pitsidianakis         return;
88518a75281SManos Pitsidianakis     }
886d8d64acbSManos Pitsidianakis     trace_virtio_snd_handle_tx_xfer();
88718a75281SManos Pitsidianakis 
888dcb0a1acSPhilippe Mathieu-Daudé     for (;;) {
889dcb0a1acSPhilippe Mathieu-Daudé         VirtIOSoundPCMStream *stream;
890dcb0a1acSPhilippe Mathieu-Daudé 
89118a75281SManos Pitsidianakis         elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
89218a75281SManos Pitsidianakis         if (!elem) {
89318a75281SManos Pitsidianakis             break;
89418a75281SManos Pitsidianakis         }
89518a75281SManos Pitsidianakis         /* get the message hdr object */
89618a75281SManos Pitsidianakis         msg_sz = iov_to_buf(elem->out_sg,
89718a75281SManos Pitsidianakis                             elem->out_num,
89818a75281SManos Pitsidianakis                             0,
89918a75281SManos Pitsidianakis                             &hdr,
90018a75281SManos Pitsidianakis                             sizeof(virtio_snd_pcm_xfer));
90118a75281SManos Pitsidianakis         if (msg_sz != sizeof(virtio_snd_pcm_xfer)) {
902731655f8SManos Pitsidianakis             goto tx_err;
90318a75281SManos Pitsidianakis         }
90418a75281SManos Pitsidianakis         stream_id = le32_to_cpu(hdr.stream_id);
90518a75281SManos Pitsidianakis 
906731655f8SManos Pitsidianakis         if (stream_id >= vsnd->snd_conf.streams
907731655f8SManos Pitsidianakis             || vsnd->pcm->streams[stream_id] == NULL) {
908731655f8SManos Pitsidianakis             goto tx_err;
90918a75281SManos Pitsidianakis         }
91018a75281SManos Pitsidianakis 
911731655f8SManos Pitsidianakis         stream = vsnd->pcm->streams[stream_id];
91218a75281SManos Pitsidianakis         if (stream->info.direction != VIRTIO_SND_D_OUTPUT) {
91318a75281SManos Pitsidianakis             goto tx_err;
91418a75281SManos Pitsidianakis         }
91518a75281SManos Pitsidianakis 
91618a75281SManos Pitsidianakis         WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
91718a75281SManos Pitsidianakis             size = iov_size(elem->out_sg, elem->out_num) - msg_sz;
91818a75281SManos Pitsidianakis 
91918a75281SManos Pitsidianakis             buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer) + size);
92018a75281SManos Pitsidianakis             buffer->elem = elem;
92118a75281SManos Pitsidianakis             buffer->populated = false;
92218a75281SManos Pitsidianakis             buffer->vq = vq;
92318a75281SManos Pitsidianakis             buffer->size = size;
92418a75281SManos Pitsidianakis             buffer->offset = 0;
92518a75281SManos Pitsidianakis 
92618a75281SManos Pitsidianakis             QSIMPLEQ_INSERT_TAIL(&stream->queue, buffer, entry);
92718a75281SManos Pitsidianakis         }
92818a75281SManos Pitsidianakis         continue;
92918a75281SManos Pitsidianakis 
93018a75281SManos Pitsidianakis tx_err:
93118a75281SManos Pitsidianakis         must_empty_invalid_queue = true;
93218a75281SManos Pitsidianakis         buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer));
93318a75281SManos Pitsidianakis         buffer->elem = elem;
93418a75281SManos Pitsidianakis         buffer->vq = vq;
935731655f8SManos Pitsidianakis         QSIMPLEQ_INSERT_TAIL(&vsnd->invalid, buffer, entry);
93618a75281SManos Pitsidianakis     }
93718a75281SManos Pitsidianakis 
93818a75281SManos Pitsidianakis     if (must_empty_invalid_queue) {
939d8d64acbSManos Pitsidianakis         empty_invalid_queue(vdev, vq);
94018a75281SManos Pitsidianakis     }
94118a75281SManos Pitsidianakis }
94218a75281SManos Pitsidianakis 
94318a75281SManos Pitsidianakis /*
944d8d64acbSManos Pitsidianakis  * The rx virtqueue handler. Makes the buffers available to their respective
945d8d64acbSManos Pitsidianakis  * streams for consumption.
9462880e676SManos Pitsidianakis  *
9472880e676SManos Pitsidianakis  * @vdev: VirtIOSound device
948d8d64acbSManos Pitsidianakis  * @vq: rx virtqueue
9492880e676SManos Pitsidianakis  */
virtio_snd_handle_rx_xfer(VirtIODevice * vdev,VirtQueue * vq)950d8d64acbSManos Pitsidianakis static void virtio_snd_handle_rx_xfer(VirtIODevice *vdev, VirtQueue *vq)
951d8d64acbSManos Pitsidianakis {
952731655f8SManos Pitsidianakis     VirtIOSound *vsnd = VIRTIO_SND(vdev);
953d8d64acbSManos Pitsidianakis     VirtIOSoundPCMBuffer *buffer;
954d8d64acbSManos Pitsidianakis     VirtQueueElement *elem;
955d8d64acbSManos Pitsidianakis     size_t msg_sz, size;
956d8d64acbSManos Pitsidianakis     virtio_snd_pcm_xfer hdr;
957d8d64acbSManos Pitsidianakis     uint32_t stream_id;
958d8d64acbSManos Pitsidianakis     /*
959731655f8SManos Pitsidianakis      * if any of the I/O messages are invalid, put them in vsnd->invalid and
960d8d64acbSManos Pitsidianakis      * return them after the for loop.
961d8d64acbSManos Pitsidianakis      */
962d8d64acbSManos Pitsidianakis     bool must_empty_invalid_queue = false;
963d8d64acbSManos Pitsidianakis 
964d8d64acbSManos Pitsidianakis     if (!virtio_queue_ready(vq)) {
965d8d64acbSManos Pitsidianakis         return;
966d8d64acbSManos Pitsidianakis     }
967d8d64acbSManos Pitsidianakis     trace_virtio_snd_handle_rx_xfer();
968d8d64acbSManos Pitsidianakis 
969dcb0a1acSPhilippe Mathieu-Daudé     for (;;) {
970dcb0a1acSPhilippe Mathieu-Daudé         VirtIOSoundPCMStream *stream;
971dcb0a1acSPhilippe Mathieu-Daudé 
972d8d64acbSManos Pitsidianakis         elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
973d8d64acbSManos Pitsidianakis         if (!elem) {
974d8d64acbSManos Pitsidianakis             break;
975d8d64acbSManos Pitsidianakis         }
976d8d64acbSManos Pitsidianakis         /* get the message hdr object */
977d8d64acbSManos Pitsidianakis         msg_sz = iov_to_buf(elem->out_sg,
978d8d64acbSManos Pitsidianakis                             elem->out_num,
979d8d64acbSManos Pitsidianakis                             0,
980d8d64acbSManos Pitsidianakis                             &hdr,
981d8d64acbSManos Pitsidianakis                             sizeof(virtio_snd_pcm_xfer));
982d8d64acbSManos Pitsidianakis         if (msg_sz != sizeof(virtio_snd_pcm_xfer)) {
983731655f8SManos Pitsidianakis             goto rx_err;
984d8d64acbSManos Pitsidianakis         }
985d8d64acbSManos Pitsidianakis         stream_id = le32_to_cpu(hdr.stream_id);
986d8d64acbSManos Pitsidianakis 
987731655f8SManos Pitsidianakis         if (stream_id >= vsnd->snd_conf.streams
988731655f8SManos Pitsidianakis             || !vsnd->pcm->streams[stream_id]) {
989731655f8SManos Pitsidianakis             goto rx_err;
990d8d64acbSManos Pitsidianakis         }
991d8d64acbSManos Pitsidianakis 
992731655f8SManos Pitsidianakis         stream = vsnd->pcm->streams[stream_id];
993d8d64acbSManos Pitsidianakis         if (stream == NULL || stream->info.direction != VIRTIO_SND_D_INPUT) {
994d8d64acbSManos Pitsidianakis             goto rx_err;
995d8d64acbSManos Pitsidianakis         }
996d8d64acbSManos Pitsidianakis         WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
997d8d64acbSManos Pitsidianakis             size = iov_size(elem->in_sg, elem->in_num) -
998d8d64acbSManos Pitsidianakis                 sizeof(virtio_snd_pcm_status);
999d8d64acbSManos Pitsidianakis             buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer) + size);
1000d8d64acbSManos Pitsidianakis             buffer->elem = elem;
1001d8d64acbSManos Pitsidianakis             buffer->vq = vq;
1002d8d64acbSManos Pitsidianakis             buffer->size = 0;
1003d8d64acbSManos Pitsidianakis             buffer->offset = 0;
1004d8d64acbSManos Pitsidianakis             QSIMPLEQ_INSERT_TAIL(&stream->queue, buffer, entry);
1005d8d64acbSManos Pitsidianakis         }
1006d8d64acbSManos Pitsidianakis         continue;
1007d8d64acbSManos Pitsidianakis 
1008d8d64acbSManos Pitsidianakis rx_err:
1009d8d64acbSManos Pitsidianakis         must_empty_invalid_queue = true;
1010d8d64acbSManos Pitsidianakis         buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer));
1011d8d64acbSManos Pitsidianakis         buffer->elem = elem;
1012d8d64acbSManos Pitsidianakis         buffer->vq = vq;
1013731655f8SManos Pitsidianakis         QSIMPLEQ_INSERT_TAIL(&vsnd->invalid, buffer, entry);
1014d8d64acbSManos Pitsidianakis     }
1015d8d64acbSManos Pitsidianakis 
1016d8d64acbSManos Pitsidianakis     if (must_empty_invalid_queue) {
1017d8d64acbSManos Pitsidianakis         empty_invalid_queue(vdev, vq);
1018d8d64acbSManos Pitsidianakis     }
1019d8d64acbSManos Pitsidianakis }
10202880e676SManos Pitsidianakis 
get_features(VirtIODevice * vdev,uint64_t features,Error ** errp)10212880e676SManos Pitsidianakis static uint64_t get_features(VirtIODevice *vdev, uint64_t features,
10222880e676SManos Pitsidianakis                              Error **errp)
10232880e676SManos Pitsidianakis {
10242880e676SManos Pitsidianakis     /*
10252880e676SManos Pitsidianakis      * virtio-v1.2-csd01, 5.14.3,
10262880e676SManos Pitsidianakis      * Feature Bits
10272880e676SManos Pitsidianakis      * None currently defined.
10282880e676SManos Pitsidianakis      */
10292880e676SManos Pitsidianakis     VirtIOSound *s = VIRTIO_SND(vdev);
10302880e676SManos Pitsidianakis     features |= s->features;
10312880e676SManos Pitsidianakis 
10322880e676SManos Pitsidianakis     trace_virtio_snd_get_features(vdev, features);
10332880e676SManos Pitsidianakis 
10342880e676SManos Pitsidianakis     return features;
10352880e676SManos Pitsidianakis }
10362880e676SManos Pitsidianakis 
10372880e676SManos Pitsidianakis static void
virtio_snd_vm_state_change(void * opaque,bool running,RunState state)10382880e676SManos Pitsidianakis virtio_snd_vm_state_change(void *opaque, bool running,
10392880e676SManos Pitsidianakis                                        RunState state)
10402880e676SManos Pitsidianakis {
10412880e676SManos Pitsidianakis     if (running) {
10422880e676SManos Pitsidianakis         trace_virtio_snd_vm_state_running();
10432880e676SManos Pitsidianakis     } else {
10442880e676SManos Pitsidianakis         trace_virtio_snd_vm_state_stopped();
10452880e676SManos Pitsidianakis     }
10462880e676SManos Pitsidianakis }
10472880e676SManos Pitsidianakis 
virtio_snd_realize(DeviceState * dev,Error ** errp)10482880e676SManos Pitsidianakis static void virtio_snd_realize(DeviceState *dev, Error **errp)
10492880e676SManos Pitsidianakis {
10502880e676SManos Pitsidianakis     ERRP_GUARD();
10512880e676SManos Pitsidianakis     VirtIOSound *vsnd = VIRTIO_SND(dev);
10522880e676SManos Pitsidianakis     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
1053eb9ad377SManos Pitsidianakis     virtio_snd_pcm_set_params default_params = { 0 };
1054eb9ad377SManos Pitsidianakis     uint32_t status;
10552880e676SManos Pitsidianakis 
10562880e676SManos Pitsidianakis     trace_virtio_snd_realize(vsnd);
10572880e676SManos Pitsidianakis 
1058f7856181SManos Pitsidianakis     /* check number of jacks and streams */
10592880e676SManos Pitsidianakis     if (vsnd->snd_conf.jacks > 8) {
10602880e676SManos Pitsidianakis         error_setg(errp,
10612880e676SManos Pitsidianakis                    "Invalid number of jacks: %"PRIu32,
10622880e676SManos Pitsidianakis                    vsnd->snd_conf.jacks);
10632880e676SManos Pitsidianakis         return;
10642880e676SManos Pitsidianakis     }
10652880e676SManos Pitsidianakis     if (vsnd->snd_conf.streams < 1 || vsnd->snd_conf.streams > 10) {
10662880e676SManos Pitsidianakis         error_setg(errp,
10672880e676SManos Pitsidianakis                    "Invalid number of streams: %"PRIu32,
10682880e676SManos Pitsidianakis                     vsnd->snd_conf.streams);
10692880e676SManos Pitsidianakis         return;
10702880e676SManos Pitsidianakis     }
10712880e676SManos Pitsidianakis 
10722880e676SManos Pitsidianakis     if (vsnd->snd_conf.chmaps > VIRTIO_SND_CHMAP_MAX_SIZE) {
10732880e676SManos Pitsidianakis         error_setg(errp,
10742880e676SManos Pitsidianakis                    "Invalid number of channel maps: %"PRIu32,
10752880e676SManos Pitsidianakis                    vsnd->snd_conf.chmaps);
10762880e676SManos Pitsidianakis         return;
10772880e676SManos Pitsidianakis     }
10782880e676SManos Pitsidianakis 
1079691d3d8bSManos Pitsidianakis     if (!AUD_register_card("virtio-sound", &vsnd->card, errp)) {
1080691d3d8bSManos Pitsidianakis         return;
1081691d3d8bSManos Pitsidianakis     }
10822880e676SManos Pitsidianakis 
1083f7856181SManos Pitsidianakis     vsnd->vmstate =
1084f7856181SManos Pitsidianakis         qemu_add_vm_change_state_handler(virtio_snd_vm_state_change, vsnd);
1085f7856181SManos Pitsidianakis 
1086f7856181SManos Pitsidianakis     vsnd->pcm = g_new0(VirtIOSoundPCM, 1);
1087f7856181SManos Pitsidianakis     vsnd->pcm->snd = vsnd;
1088f7856181SManos Pitsidianakis     vsnd->pcm->streams =
1089f7856181SManos Pitsidianakis         g_new0(VirtIOSoundPCMStream *, vsnd->snd_conf.streams);
1090f7856181SManos Pitsidianakis     vsnd->pcm->pcm_params =
1091f7856181SManos Pitsidianakis         g_new0(virtio_snd_pcm_set_params, vsnd->snd_conf.streams);
1092f7856181SManos Pitsidianakis 
1093f7856181SManos Pitsidianakis     virtio_init(vdev, VIRTIO_ID_SOUND, sizeof(virtio_snd_config));
1094f7856181SManos Pitsidianakis     virtio_add_feature(&vsnd->features, VIRTIO_F_VERSION_1);
1095f7856181SManos Pitsidianakis 
1096eb9ad377SManos Pitsidianakis     /* set default params for all streams */
1097eb9ad377SManos Pitsidianakis     default_params.features = 0;
1098eb9ad377SManos Pitsidianakis     default_params.buffer_bytes = cpu_to_le32(8192);
1099eb9ad377SManos Pitsidianakis     default_params.period_bytes = cpu_to_le32(2048);
1100eb9ad377SManos Pitsidianakis     default_params.channels = 2;
1101eb9ad377SManos Pitsidianakis     default_params.format = VIRTIO_SND_PCM_FMT_S16;
1102eb9ad377SManos Pitsidianakis     default_params.rate = VIRTIO_SND_PCM_RATE_48000;
11032880e676SManos Pitsidianakis     vsnd->queues[VIRTIO_SND_VQ_CONTROL] =
1104eb9ad377SManos Pitsidianakis         virtio_add_queue(vdev, 64, virtio_snd_handle_ctrl);
11052880e676SManos Pitsidianakis     vsnd->queues[VIRTIO_SND_VQ_EVENT] =
1106eb9ad377SManos Pitsidianakis         virtio_add_queue(vdev, 64, virtio_snd_handle_event);
11072880e676SManos Pitsidianakis     vsnd->queues[VIRTIO_SND_VQ_TX] =
1108d8d64acbSManos Pitsidianakis         virtio_add_queue(vdev, 64, virtio_snd_handle_tx_xfer);
11092880e676SManos Pitsidianakis     vsnd->queues[VIRTIO_SND_VQ_RX] =
1110d8d64acbSManos Pitsidianakis         virtio_add_queue(vdev, 64, virtio_snd_handle_rx_xfer);
1111eb9ad377SManos Pitsidianakis     qemu_mutex_init(&vsnd->cmdq_mutex);
1112eb9ad377SManos Pitsidianakis     QTAILQ_INIT(&vsnd->cmdq);
1113731655f8SManos Pitsidianakis     QSIMPLEQ_INIT(&vsnd->invalid);
1114eb9ad377SManos Pitsidianakis 
1115eb9ad377SManos Pitsidianakis     for (uint32_t i = 0; i < vsnd->snd_conf.streams; i++) {
1116eb9ad377SManos Pitsidianakis         status = virtio_snd_set_pcm_params(vsnd, i, &default_params);
1117eb9ad377SManos Pitsidianakis         if (status != cpu_to_le32(VIRTIO_SND_S_OK)) {
1118eb9ad377SManos Pitsidianakis             error_setg(errp,
1119aaf851a2SMichael Tokarev                        "Can't initialize stream params, device responded with %s.",
1120eb9ad377SManos Pitsidianakis                        print_code(status));
1121f7856181SManos Pitsidianakis             goto error_cleanup;
1122eb9ad377SManos Pitsidianakis         }
1123eb9ad377SManos Pitsidianakis         status = virtio_snd_pcm_prepare(vsnd, i);
1124eb9ad377SManos Pitsidianakis         if (status != cpu_to_le32(VIRTIO_SND_S_OK)) {
1125eb9ad377SManos Pitsidianakis             error_setg(errp,
1126eb9ad377SManos Pitsidianakis                        "Can't prepare streams, device responded with %s.",
1127eb9ad377SManos Pitsidianakis                        print_code(status));
1128f7856181SManos Pitsidianakis             goto error_cleanup;
1129f7856181SManos Pitsidianakis         }
1130f7856181SManos Pitsidianakis     }
1131f7856181SManos Pitsidianakis 
1132eb9ad377SManos Pitsidianakis     return;
1133f7856181SManos Pitsidianakis 
1134f7856181SManos Pitsidianakis error_cleanup:
1135f7856181SManos Pitsidianakis     virtio_snd_unrealize(dev);
11362880e676SManos Pitsidianakis }
11372880e676SManos Pitsidianakis 
return_tx_buffer(VirtIOSoundPCMStream * stream,VirtIOSoundPCMBuffer * buffer)113818a75281SManos Pitsidianakis static inline void return_tx_buffer(VirtIOSoundPCMStream *stream,
113918a75281SManos Pitsidianakis                                     VirtIOSoundPCMBuffer *buffer)
114018a75281SManos Pitsidianakis {
114118a75281SManos Pitsidianakis     virtio_snd_pcm_status resp = { 0 };
114218a75281SManos Pitsidianakis     resp.status = cpu_to_le32(VIRTIO_SND_S_OK);
114318a75281SManos Pitsidianakis     resp.latency_bytes = cpu_to_le32((uint32_t)buffer->size);
114418a75281SManos Pitsidianakis     iov_from_buf(buffer->elem->in_sg,
114518a75281SManos Pitsidianakis                  buffer->elem->in_num,
114618a75281SManos Pitsidianakis                  0,
114718a75281SManos Pitsidianakis                  &resp,
114818a75281SManos Pitsidianakis                  sizeof(virtio_snd_pcm_status));
114918a75281SManos Pitsidianakis     virtqueue_push(buffer->vq,
115018a75281SManos Pitsidianakis                    buffer->elem,
115118a75281SManos Pitsidianakis                    sizeof(virtio_snd_pcm_status));
115218a75281SManos Pitsidianakis     virtio_notify(VIRTIO_DEVICE(stream->s), buffer->vq);
115318a75281SManos Pitsidianakis     QSIMPLEQ_REMOVE(&stream->queue,
115418a75281SManos Pitsidianakis                     buffer,
115518a75281SManos Pitsidianakis                     VirtIOSoundPCMBuffer,
115618a75281SManos Pitsidianakis                     entry);
115718a75281SManos Pitsidianakis     virtio_snd_pcm_buffer_free(buffer);
115818a75281SManos Pitsidianakis }
115918a75281SManos Pitsidianakis 
116018a75281SManos Pitsidianakis /*
116118a75281SManos Pitsidianakis  * AUD_* output callback.
116218a75281SManos Pitsidianakis  *
116318a75281SManos Pitsidianakis  * @data: VirtIOSoundPCMStream stream
116418a75281SManos Pitsidianakis  * @available: number of bytes that can be written with AUD_write()
116518a75281SManos Pitsidianakis  */
virtio_snd_pcm_out_cb(void * data,int available)116618a75281SManos Pitsidianakis static void virtio_snd_pcm_out_cb(void *data, int available)
116718a75281SManos Pitsidianakis {
116818a75281SManos Pitsidianakis     VirtIOSoundPCMStream *stream = data;
116918a75281SManos Pitsidianakis     VirtIOSoundPCMBuffer *buffer;
117018a75281SManos Pitsidianakis     size_t size;
117118a75281SManos Pitsidianakis 
117218a75281SManos Pitsidianakis     WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
117318a75281SManos Pitsidianakis         while (!QSIMPLEQ_EMPTY(&stream->queue)) {
117418a75281SManos Pitsidianakis             buffer = QSIMPLEQ_FIRST(&stream->queue);
117518a75281SManos Pitsidianakis             if (!virtio_queue_ready(buffer->vq)) {
117618a75281SManos Pitsidianakis                 return;
117718a75281SManos Pitsidianakis             }
117818a75281SManos Pitsidianakis             if (!stream->active) {
117918a75281SManos Pitsidianakis                 /* Stream has stopped, so do not perform AUD_write. */
118018a75281SManos Pitsidianakis                 return_tx_buffer(stream, buffer);
118118a75281SManos Pitsidianakis                 continue;
118218a75281SManos Pitsidianakis             }
118318a75281SManos Pitsidianakis             if (!buffer->populated) {
118418a75281SManos Pitsidianakis                 iov_to_buf(buffer->elem->out_sg,
118518a75281SManos Pitsidianakis                            buffer->elem->out_num,
118618a75281SManos Pitsidianakis                            sizeof(virtio_snd_pcm_xfer),
118718a75281SManos Pitsidianakis                            buffer->data,
118818a75281SManos Pitsidianakis                            buffer->size);
118918a75281SManos Pitsidianakis                 buffer->populated = true;
119018a75281SManos Pitsidianakis             }
119118a75281SManos Pitsidianakis             for (;;) {
119218a75281SManos Pitsidianakis                 size = AUD_write(stream->voice.out,
119318a75281SManos Pitsidianakis                                  buffer->data + buffer->offset,
119418a75281SManos Pitsidianakis                                  MIN(buffer->size, available));
119518a75281SManos Pitsidianakis                 assert(size <= MIN(buffer->size, available));
119618a75281SManos Pitsidianakis                 if (size == 0) {
119718a75281SManos Pitsidianakis                     /* break out of both loops */
119818a75281SManos Pitsidianakis                     available = 0;
119918a75281SManos Pitsidianakis                     break;
120018a75281SManos Pitsidianakis                 }
120118a75281SManos Pitsidianakis                 buffer->size -= size;
120218a75281SManos Pitsidianakis                 buffer->offset += size;
120318a75281SManos Pitsidianakis                 available -= size;
120418a75281SManos Pitsidianakis                 if (buffer->size < 1) {
120518a75281SManos Pitsidianakis                     return_tx_buffer(stream, buffer);
120618a75281SManos Pitsidianakis                     break;
120718a75281SManos Pitsidianakis                 }
120818a75281SManos Pitsidianakis                 if (!available) {
120918a75281SManos Pitsidianakis                     break;
121018a75281SManos Pitsidianakis                 }
121118a75281SManos Pitsidianakis             }
121218a75281SManos Pitsidianakis             if (!available) {
121318a75281SManos Pitsidianakis                 break;
121418a75281SManos Pitsidianakis             }
121518a75281SManos Pitsidianakis         }
121618a75281SManos Pitsidianakis     }
121718a75281SManos Pitsidianakis }
121818a75281SManos Pitsidianakis 
121918a75281SManos Pitsidianakis /*
1220d8d64acbSManos Pitsidianakis  * Flush all buffer data from this input stream's queue into the driver's
1221d8d64acbSManos Pitsidianakis  * virtual queue.
1222d8d64acbSManos Pitsidianakis  *
1223d8d64acbSManos Pitsidianakis  * @stream: VirtIOSoundPCMStream *stream
1224d8d64acbSManos Pitsidianakis  */
return_rx_buffer(VirtIOSoundPCMStream * stream,VirtIOSoundPCMBuffer * buffer)1225d8d64acbSManos Pitsidianakis static inline void return_rx_buffer(VirtIOSoundPCMStream *stream,
1226d8d64acbSManos Pitsidianakis                                     VirtIOSoundPCMBuffer *buffer)
1227d8d64acbSManos Pitsidianakis {
1228d8d64acbSManos Pitsidianakis     virtio_snd_pcm_status resp = { 0 };
1229d8d64acbSManos Pitsidianakis     resp.status = cpu_to_le32(VIRTIO_SND_S_OK);
1230d8d64acbSManos Pitsidianakis     resp.latency_bytes = 0;
1231d8d64acbSManos Pitsidianakis     /* Copy data -if any- to guest */
1232d8d64acbSManos Pitsidianakis     iov_from_buf(buffer->elem->in_sg,
1233d8d64acbSManos Pitsidianakis                  buffer->elem->in_num,
1234d8d64acbSManos Pitsidianakis                  0,
1235d8d64acbSManos Pitsidianakis                  buffer->data,
1236d8d64acbSManos Pitsidianakis                  buffer->size);
1237d8d64acbSManos Pitsidianakis     iov_from_buf(buffer->elem->in_sg,
1238d8d64acbSManos Pitsidianakis                  buffer->elem->in_num,
1239d8d64acbSManos Pitsidianakis                  buffer->size,
1240d8d64acbSManos Pitsidianakis                  &resp,
1241d8d64acbSManos Pitsidianakis                  sizeof(virtio_snd_pcm_status));
1242d8d64acbSManos Pitsidianakis     virtqueue_push(buffer->vq,
1243d8d64acbSManos Pitsidianakis                    buffer->elem,
1244d8d64acbSManos Pitsidianakis                    sizeof(virtio_snd_pcm_status) + buffer->size);
1245d8d64acbSManos Pitsidianakis     virtio_notify(VIRTIO_DEVICE(stream->s), buffer->vq);
1246d8d64acbSManos Pitsidianakis     QSIMPLEQ_REMOVE(&stream->queue,
1247d8d64acbSManos Pitsidianakis                     buffer,
1248d8d64acbSManos Pitsidianakis                     VirtIOSoundPCMBuffer,
1249d8d64acbSManos Pitsidianakis                     entry);
1250d8d64acbSManos Pitsidianakis     virtio_snd_pcm_buffer_free(buffer);
1251d8d64acbSManos Pitsidianakis }
1252d8d64acbSManos Pitsidianakis 
1253d8d64acbSManos Pitsidianakis 
1254d8d64acbSManos Pitsidianakis /*
1255d8d64acbSManos Pitsidianakis  * AUD_* input callback.
1256d8d64acbSManos Pitsidianakis  *
1257d8d64acbSManos Pitsidianakis  * @data: VirtIOSoundPCMStream stream
1258d8d64acbSManos Pitsidianakis  * @available: number of bytes that can be read with AUD_read()
1259d8d64acbSManos Pitsidianakis  */
virtio_snd_pcm_in_cb(void * data,int available)1260d8d64acbSManos Pitsidianakis static void virtio_snd_pcm_in_cb(void *data, int available)
1261d8d64acbSManos Pitsidianakis {
1262d8d64acbSManos Pitsidianakis     VirtIOSoundPCMStream *stream = data;
1263d8d64acbSManos Pitsidianakis     VirtIOSoundPCMBuffer *buffer;
1264d8d64acbSManos Pitsidianakis     size_t size;
1265d8d64acbSManos Pitsidianakis 
1266d8d64acbSManos Pitsidianakis     WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
1267d8d64acbSManos Pitsidianakis         while (!QSIMPLEQ_EMPTY(&stream->queue)) {
1268d8d64acbSManos Pitsidianakis             buffer = QSIMPLEQ_FIRST(&stream->queue);
1269d8d64acbSManos Pitsidianakis             if (!virtio_queue_ready(buffer->vq)) {
1270d8d64acbSManos Pitsidianakis                 return;
1271d8d64acbSManos Pitsidianakis             }
1272d8d64acbSManos Pitsidianakis             if (!stream->active) {
1273d8d64acbSManos Pitsidianakis                 /* Stream has stopped, so do not perform AUD_read. */
1274d8d64acbSManos Pitsidianakis                 return_rx_buffer(stream, buffer);
1275d8d64acbSManos Pitsidianakis                 continue;
1276d8d64acbSManos Pitsidianakis             }
1277d8d64acbSManos Pitsidianakis 
1278d8d64acbSManos Pitsidianakis             for (;;) {
1279d8d64acbSManos Pitsidianakis                 size = AUD_read(stream->voice.in,
1280d8d64acbSManos Pitsidianakis                         buffer->data + buffer->size,
1281d8d64acbSManos Pitsidianakis                         MIN(available, (stream->params.period_bytes -
1282d8d64acbSManos Pitsidianakis                                         buffer->size)));
1283d8d64acbSManos Pitsidianakis                 if (!size) {
1284d8d64acbSManos Pitsidianakis                     available = 0;
1285d8d64acbSManos Pitsidianakis                     break;
1286d8d64acbSManos Pitsidianakis                 }
1287d8d64acbSManos Pitsidianakis                 buffer->size += size;
1288d8d64acbSManos Pitsidianakis                 available -= size;
1289d8d64acbSManos Pitsidianakis                 if (buffer->size >= stream->params.period_bytes) {
1290d8d64acbSManos Pitsidianakis                     return_rx_buffer(stream, buffer);
1291d8d64acbSManos Pitsidianakis                     break;
1292d8d64acbSManos Pitsidianakis                 }
1293d8d64acbSManos Pitsidianakis                 if (!available) {
1294d8d64acbSManos Pitsidianakis                     break;
1295d8d64acbSManos Pitsidianakis                 }
1296d8d64acbSManos Pitsidianakis             }
1297d8d64acbSManos Pitsidianakis             if (!available) {
1298d8d64acbSManos Pitsidianakis                 break;
1299d8d64acbSManos Pitsidianakis             }
1300d8d64acbSManos Pitsidianakis         }
1301d8d64acbSManos Pitsidianakis     }
1302d8d64acbSManos Pitsidianakis }
1303d8d64acbSManos Pitsidianakis 
1304d8d64acbSManos Pitsidianakis /*
1305d8d64acbSManos Pitsidianakis  * Flush all buffer data from this output stream's queue into the driver's
1306d8d64acbSManos Pitsidianakis  * virtual queue.
130718a75281SManos Pitsidianakis  *
130818a75281SManos Pitsidianakis  * @stream: VirtIOSoundPCMStream *stream
130918a75281SManos Pitsidianakis  */
virtio_snd_pcm_flush(VirtIOSoundPCMStream * stream)131018a75281SManos Pitsidianakis static inline void virtio_snd_pcm_flush(VirtIOSoundPCMStream *stream)
131118a75281SManos Pitsidianakis {
131218a75281SManos Pitsidianakis     VirtIOSoundPCMBuffer *buffer;
1313d8d64acbSManos Pitsidianakis     void (*cb)(VirtIOSoundPCMStream *, VirtIOSoundPCMBuffer *) =
1314d8d64acbSManos Pitsidianakis         (stream->info.direction == VIRTIO_SND_D_OUTPUT) ? return_tx_buffer :
1315d8d64acbSManos Pitsidianakis         return_rx_buffer;
131618a75281SManos Pitsidianakis 
131718a75281SManos Pitsidianakis     WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
131818a75281SManos Pitsidianakis         while (!QSIMPLEQ_EMPTY(&stream->queue)) {
131918a75281SManos Pitsidianakis             buffer = QSIMPLEQ_FIRST(&stream->queue);
1320d8d64acbSManos Pitsidianakis             cb(stream, buffer);
132118a75281SManos Pitsidianakis         }
132218a75281SManos Pitsidianakis     }
132318a75281SManos Pitsidianakis }
132418a75281SManos Pitsidianakis 
virtio_snd_unrealize(DeviceState * dev)13252880e676SManos Pitsidianakis static void virtio_snd_unrealize(DeviceState *dev)
13262880e676SManos Pitsidianakis {
13272880e676SManos Pitsidianakis     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
13282880e676SManos Pitsidianakis     VirtIOSound *vsnd = VIRTIO_SND(dev);
1329eb9ad377SManos Pitsidianakis     VirtIOSoundPCMStream *stream;
13302880e676SManos Pitsidianakis 
13312880e676SManos Pitsidianakis     qemu_del_vm_change_state_handler(vsnd->vmstate);
13322880e676SManos Pitsidianakis     trace_virtio_snd_unrealize(vsnd);
13332880e676SManos Pitsidianakis 
1334eb9ad377SManos Pitsidianakis     if (vsnd->pcm) {
1335eb9ad377SManos Pitsidianakis         if (vsnd->pcm->streams) {
1336eb9ad377SManos Pitsidianakis             for (uint32_t i = 0; i < vsnd->snd_conf.streams; i++) {
1337eb9ad377SManos Pitsidianakis                 stream = vsnd->pcm->streams[i];
1338eb9ad377SManos Pitsidianakis                 if (stream) {
1339eb9ad377SManos Pitsidianakis                     virtio_snd_process_cmdq(stream->s);
1340eb9ad377SManos Pitsidianakis                     virtio_snd_pcm_close(stream);
134118a75281SManos Pitsidianakis                     qemu_mutex_destroy(&stream->queue_mutex);
1342eb9ad377SManos Pitsidianakis                     g_free(stream);
1343eb9ad377SManos Pitsidianakis                 }
1344eb9ad377SManos Pitsidianakis             }
1345eb9ad377SManos Pitsidianakis             g_free(vsnd->pcm->streams);
1346eb9ad377SManos Pitsidianakis         }
1347eb9ad377SManos Pitsidianakis         g_free(vsnd->pcm->pcm_params);
1348eb9ad377SManos Pitsidianakis         g_free(vsnd->pcm);
1349eb9ad377SManos Pitsidianakis         vsnd->pcm = NULL;
1350eb9ad377SManos Pitsidianakis     }
13512880e676SManos Pitsidianakis     AUD_remove_card(&vsnd->card);
1352eb9ad377SManos Pitsidianakis     qemu_mutex_destroy(&vsnd->cmdq_mutex);
13532880e676SManos Pitsidianakis     virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_CONTROL]);
13542880e676SManos Pitsidianakis     virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_EVENT]);
13552880e676SManos Pitsidianakis     virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_TX]);
13562880e676SManos Pitsidianakis     virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_RX]);
13572880e676SManos Pitsidianakis     virtio_cleanup(vdev);
13582880e676SManos Pitsidianakis }
13592880e676SManos Pitsidianakis 
13602880e676SManos Pitsidianakis 
virtio_snd_reset(VirtIODevice * vdev)1361eb9ad377SManos Pitsidianakis static void virtio_snd_reset(VirtIODevice *vdev)
1362eb9ad377SManos Pitsidianakis {
1363731655f8SManos Pitsidianakis     VirtIOSound *vsnd = VIRTIO_SND(vdev);
1364eb9ad377SManos Pitsidianakis     virtio_snd_ctrl_command *cmd;
1365eb9ad377SManos Pitsidianakis 
1366731655f8SManos Pitsidianakis     /*
1367731655f8SManos Pitsidianakis      * Sanity check that the invalid buffer message queue is emptied at the end
1368731655f8SManos Pitsidianakis      * of every virtio_snd_handle_tx_xfer/virtio_snd_handle_rx_xfer call, and
1369731655f8SManos Pitsidianakis      * must be empty otherwise.
1370731655f8SManos Pitsidianakis      */
1371731655f8SManos Pitsidianakis     g_assert(QSIMPLEQ_EMPTY(&vsnd->invalid));
1372731655f8SManos Pitsidianakis 
1373731655f8SManos Pitsidianakis     WITH_QEMU_LOCK_GUARD(&vsnd->cmdq_mutex) {
1374731655f8SManos Pitsidianakis         while (!QTAILQ_EMPTY(&vsnd->cmdq)) {
1375731655f8SManos Pitsidianakis             cmd = QTAILQ_FIRST(&vsnd->cmdq);
1376731655f8SManos Pitsidianakis             QTAILQ_REMOVE(&vsnd->cmdq, cmd, next);
1377eb9ad377SManos Pitsidianakis             virtio_snd_ctrl_cmd_free(cmd);
1378eb9ad377SManos Pitsidianakis         }
1379eb9ad377SManos Pitsidianakis     }
1380eb9ad377SManos Pitsidianakis }
13812880e676SManos Pitsidianakis 
virtio_snd_class_init(ObjectClass * klass,void * data)13822880e676SManos Pitsidianakis static void virtio_snd_class_init(ObjectClass *klass, void *data)
13832880e676SManos Pitsidianakis {
13842880e676SManos Pitsidianakis     DeviceClass *dc = DEVICE_CLASS(klass);
13852880e676SManos Pitsidianakis     VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
13862880e676SManos Pitsidianakis 
13872880e676SManos Pitsidianakis 
13882880e676SManos Pitsidianakis     set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
13892880e676SManos Pitsidianakis     device_class_set_props(dc, virtio_snd_properties);
13902880e676SManos Pitsidianakis 
13912880e676SManos Pitsidianakis     dc->vmsd = &vmstate_virtio_snd;
13922880e676SManos Pitsidianakis     vdc->vmsd = &vmstate_virtio_snd_device;
13932880e676SManos Pitsidianakis     vdc->realize = virtio_snd_realize;
13942880e676SManos Pitsidianakis     vdc->unrealize = virtio_snd_unrealize;
13952880e676SManos Pitsidianakis     vdc->get_config = virtio_snd_get_config;
13962880e676SManos Pitsidianakis     vdc->set_config = virtio_snd_set_config;
13972880e676SManos Pitsidianakis     vdc->get_features = get_features;
13982880e676SManos Pitsidianakis     vdc->reset = virtio_snd_reset;
13992880e676SManos Pitsidianakis     vdc->legacy_features = 0;
14002880e676SManos Pitsidianakis }
14012880e676SManos Pitsidianakis 
14022880e676SManos Pitsidianakis static const TypeInfo virtio_snd_types[] = {
14032880e676SManos Pitsidianakis     {
14042880e676SManos Pitsidianakis       .name          = TYPE_VIRTIO_SND,
14052880e676SManos Pitsidianakis       .parent        = TYPE_VIRTIO_DEVICE,
14062880e676SManos Pitsidianakis       .instance_size = sizeof(VirtIOSound),
14072880e676SManos Pitsidianakis       .class_init    = virtio_snd_class_init,
14082880e676SManos Pitsidianakis     }
14092880e676SManos Pitsidianakis };
14102880e676SManos Pitsidianakis 
14112880e676SManos Pitsidianakis DEFINE_TYPES(virtio_snd_types)
1412