xref: /qemu/hw/audio/virtio-snd.c (revision fa131d4a)
12880e676SManos Pitsidianakis /*
22880e676SManos Pitsidianakis  * VIRTIO Sound Device conforming to
32880e676SManos Pitsidianakis  *
42880e676SManos Pitsidianakis  * "Virtual I/O Device (VIRTIO) Version 1.2
52880e676SManos Pitsidianakis  * Committee Specification Draft 01
62880e676SManos Pitsidianakis  * 09 May 2022"
72880e676SManos Pitsidianakis  *
82880e676SManos Pitsidianakis  * <https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html#x1-52900014>
92880e676SManos Pitsidianakis  *
102880e676SManos Pitsidianakis  * Copyright (c) 2023 Emmanouil Pitsidianakis <manos.pitsidianakis@linaro.org>
112880e676SManos Pitsidianakis  * Copyright (C) 2019 OpenSynergy GmbH
122880e676SManos Pitsidianakis  *
132880e676SManos Pitsidianakis  * This work is licensed under the terms of the GNU GPL, version 2 or
142880e676SManos Pitsidianakis  * (at your option) any later version.  See the COPYING file in the
152880e676SManos Pitsidianakis  * top-level directory.
162880e676SManos Pitsidianakis  */
172880e676SManos Pitsidianakis 
182880e676SManos Pitsidianakis #include "qemu/osdep.h"
192880e676SManos Pitsidianakis #include "qemu/iov.h"
202880e676SManos Pitsidianakis #include "qemu/log.h"
212880e676SManos Pitsidianakis #include "qemu/error-report.h"
222880e676SManos Pitsidianakis #include "include/qemu/lockable.h"
232880e676SManos Pitsidianakis #include "sysemu/runstate.h"
242880e676SManos Pitsidianakis #include "trace.h"
252880e676SManos Pitsidianakis #include "qapi/error.h"
262880e676SManos Pitsidianakis #include "hw/audio/virtio-snd.h"
272880e676SManos Pitsidianakis #include "hw/core/cpu.h"
282880e676SManos Pitsidianakis 
292880e676SManos Pitsidianakis #define VIRTIO_SOUND_VM_VERSION 1
302880e676SManos Pitsidianakis #define VIRTIO_SOUND_JACK_DEFAULT 0
312880e676SManos Pitsidianakis #define VIRTIO_SOUND_STREAM_DEFAULT 1
322880e676SManos Pitsidianakis #define VIRTIO_SOUND_CHMAP_DEFAULT 0
332880e676SManos Pitsidianakis #define VIRTIO_SOUND_HDA_FN_NID 0
342880e676SManos Pitsidianakis 
35eb9ad377SManos Pitsidianakis static uint32_t supported_formats = BIT(VIRTIO_SND_PCM_FMT_S8)
36eb9ad377SManos Pitsidianakis                                   | BIT(VIRTIO_SND_PCM_FMT_U8)
37eb9ad377SManos Pitsidianakis                                   | BIT(VIRTIO_SND_PCM_FMT_S16)
38eb9ad377SManos Pitsidianakis                                   | BIT(VIRTIO_SND_PCM_FMT_U16)
39eb9ad377SManos Pitsidianakis                                   | BIT(VIRTIO_SND_PCM_FMT_S32)
40eb9ad377SManos Pitsidianakis                                   | BIT(VIRTIO_SND_PCM_FMT_U32)
41eb9ad377SManos Pitsidianakis                                   | BIT(VIRTIO_SND_PCM_FMT_FLOAT);
42eb9ad377SManos Pitsidianakis 
43eb9ad377SManos Pitsidianakis static uint32_t supported_rates = BIT(VIRTIO_SND_PCM_RATE_5512)
44eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_8000)
45eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_11025)
46eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_16000)
47eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_22050)
48eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_32000)
49eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_44100)
50eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_48000)
51eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_64000)
52eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_88200)
53eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_96000)
54eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_176400)
55eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_192000)
56eb9ad377SManos Pitsidianakis                                 | BIT(VIRTIO_SND_PCM_RATE_384000);
57eb9ad377SManos Pitsidianakis 
582880e676SManos Pitsidianakis static const VMStateDescription vmstate_virtio_snd_device = {
592880e676SManos Pitsidianakis     .name = TYPE_VIRTIO_SND,
602880e676SManos Pitsidianakis     .version_id = VIRTIO_SOUND_VM_VERSION,
612880e676SManos Pitsidianakis     .minimum_version_id = VIRTIO_SOUND_VM_VERSION,
622880e676SManos Pitsidianakis };
632880e676SManos Pitsidianakis 
642880e676SManos Pitsidianakis static const VMStateDescription vmstate_virtio_snd = {
652880e676SManos Pitsidianakis     .name = TYPE_VIRTIO_SND,
662880e676SManos Pitsidianakis     .minimum_version_id = VIRTIO_SOUND_VM_VERSION,
672880e676SManos Pitsidianakis     .version_id = VIRTIO_SOUND_VM_VERSION,
682880e676SManos Pitsidianakis     .fields = (VMStateField[]) {
692880e676SManos Pitsidianakis         VMSTATE_VIRTIO_DEVICE,
702880e676SManos Pitsidianakis         VMSTATE_END_OF_LIST()
712880e676SManos Pitsidianakis     },
722880e676SManos Pitsidianakis };
732880e676SManos Pitsidianakis 
742880e676SManos Pitsidianakis static Property virtio_snd_properties[] = {
752880e676SManos Pitsidianakis     DEFINE_AUDIO_PROPERTIES(VirtIOSound, card),
762880e676SManos Pitsidianakis     DEFINE_PROP_UINT32("jacks", VirtIOSound, snd_conf.jacks,
772880e676SManos Pitsidianakis                        VIRTIO_SOUND_JACK_DEFAULT),
782880e676SManos Pitsidianakis     DEFINE_PROP_UINT32("streams", VirtIOSound, snd_conf.streams,
792880e676SManos Pitsidianakis                        VIRTIO_SOUND_STREAM_DEFAULT),
802880e676SManos Pitsidianakis     DEFINE_PROP_UINT32("chmaps", VirtIOSound, snd_conf.chmaps,
812880e676SManos Pitsidianakis                        VIRTIO_SOUND_CHMAP_DEFAULT),
822880e676SManos Pitsidianakis     DEFINE_PROP_END_OF_LIST(),
832880e676SManos Pitsidianakis };
842880e676SManos Pitsidianakis 
852880e676SManos Pitsidianakis static void
862880e676SManos Pitsidianakis virtio_snd_get_config(VirtIODevice *vdev, uint8_t *config)
872880e676SManos Pitsidianakis {
882880e676SManos Pitsidianakis     VirtIOSound *s = VIRTIO_SND(vdev);
892880e676SManos Pitsidianakis     virtio_snd_config *sndconfig =
902880e676SManos Pitsidianakis         (virtio_snd_config *)config;
912880e676SManos Pitsidianakis     trace_virtio_snd_get_config(vdev,
922880e676SManos Pitsidianakis                                 s->snd_conf.jacks,
932880e676SManos Pitsidianakis                                 s->snd_conf.streams,
942880e676SManos Pitsidianakis                                 s->snd_conf.chmaps);
952880e676SManos Pitsidianakis 
962880e676SManos Pitsidianakis     memcpy(sndconfig, &s->snd_conf, sizeof(s->snd_conf));
972880e676SManos Pitsidianakis     cpu_to_le32s(&sndconfig->jacks);
982880e676SManos Pitsidianakis     cpu_to_le32s(&sndconfig->streams);
992880e676SManos Pitsidianakis     cpu_to_le32s(&sndconfig->chmaps);
1002880e676SManos Pitsidianakis 
1012880e676SManos Pitsidianakis }
1022880e676SManos Pitsidianakis 
1032880e676SManos Pitsidianakis static void
1042880e676SManos Pitsidianakis virtio_snd_set_config(VirtIODevice *vdev, const uint8_t *config)
1052880e676SManos Pitsidianakis {
1062880e676SManos Pitsidianakis     VirtIOSound *s = VIRTIO_SND(vdev);
1072880e676SManos Pitsidianakis     const virtio_snd_config *sndconfig =
1082880e676SManos Pitsidianakis         (const virtio_snd_config *)config;
1092880e676SManos Pitsidianakis 
1102880e676SManos Pitsidianakis 
1112880e676SManos Pitsidianakis    trace_virtio_snd_set_config(vdev,
1122880e676SManos Pitsidianakis                                s->snd_conf.jacks,
1132880e676SManos Pitsidianakis                                sndconfig->jacks,
1142880e676SManos Pitsidianakis                                s->snd_conf.streams,
1152880e676SManos Pitsidianakis                                sndconfig->streams,
1162880e676SManos Pitsidianakis                                s->snd_conf.chmaps,
1172880e676SManos Pitsidianakis                                sndconfig->chmaps);
1182880e676SManos Pitsidianakis 
1192880e676SManos Pitsidianakis     memcpy(&s->snd_conf, sndconfig, sizeof(virtio_snd_config));
1202880e676SManos Pitsidianakis     le32_to_cpus(&s->snd_conf.jacks);
1212880e676SManos Pitsidianakis     le32_to_cpus(&s->snd_conf.streams);
1222880e676SManos Pitsidianakis     le32_to_cpus(&s->snd_conf.chmaps);
1232880e676SManos Pitsidianakis 
1242880e676SManos Pitsidianakis }
1252880e676SManos Pitsidianakis 
126eb9ad377SManos Pitsidianakis static void
127eb9ad377SManos Pitsidianakis virtio_snd_ctrl_cmd_free(virtio_snd_ctrl_command *cmd)
128eb9ad377SManos Pitsidianakis {
129eb9ad377SManos Pitsidianakis     g_free(cmd->elem);
130eb9ad377SManos Pitsidianakis     g_free(cmd);
131eb9ad377SManos Pitsidianakis }
132eb9ad377SManos Pitsidianakis 
1332880e676SManos Pitsidianakis /*
134eb9ad377SManos Pitsidianakis  * Get a specific stream from the virtio sound card device.
135eb9ad377SManos Pitsidianakis  * Returns NULL if @stream_id is invalid or not allocated.
136eb9ad377SManos Pitsidianakis  *
137eb9ad377SManos Pitsidianakis  * @s: VirtIOSound device
138eb9ad377SManos Pitsidianakis  * @stream_id: stream id
139eb9ad377SManos Pitsidianakis  */
140eb9ad377SManos Pitsidianakis static VirtIOSoundPCMStream *virtio_snd_pcm_get_stream(VirtIOSound *s,
141eb9ad377SManos Pitsidianakis                                                        uint32_t stream_id)
142eb9ad377SManos Pitsidianakis {
143eb9ad377SManos Pitsidianakis     return stream_id >= s->snd_conf.streams ? NULL :
144eb9ad377SManos Pitsidianakis         s->pcm->streams[stream_id];
145eb9ad377SManos Pitsidianakis }
146eb9ad377SManos Pitsidianakis 
147eb9ad377SManos Pitsidianakis /*
148eb9ad377SManos Pitsidianakis  * Get params for a specific stream.
149eb9ad377SManos Pitsidianakis  *
150eb9ad377SManos Pitsidianakis  * @s: VirtIOSound device
151eb9ad377SManos Pitsidianakis  * @stream_id: stream id
152eb9ad377SManos Pitsidianakis  */
153eb9ad377SManos Pitsidianakis static virtio_snd_pcm_set_params *virtio_snd_pcm_get_params(VirtIOSound *s,
154eb9ad377SManos Pitsidianakis                                                             uint32_t stream_id)
155eb9ad377SManos Pitsidianakis {
156eb9ad377SManos Pitsidianakis     return stream_id >= s->snd_conf.streams ? NULL
157eb9ad377SManos Pitsidianakis         : &s->pcm->pcm_params[stream_id];
158eb9ad377SManos Pitsidianakis }
159eb9ad377SManos Pitsidianakis 
160eb9ad377SManos Pitsidianakis /*
1610ff05dd2SManos Pitsidianakis  * Handle the VIRTIO_SND_R_PCM_INFO request.
1620ff05dd2SManos Pitsidianakis  * The function writes the info structs to the request element.
1630ff05dd2SManos Pitsidianakis  *
1640ff05dd2SManos Pitsidianakis  * @s: VirtIOSound device
1650ff05dd2SManos Pitsidianakis  * @cmd: The request command queue element from VirtIOSound cmdq field
1660ff05dd2SManos Pitsidianakis  */
1670ff05dd2SManos Pitsidianakis static void virtio_snd_handle_pcm_info(VirtIOSound *s,
1680ff05dd2SManos Pitsidianakis                                        virtio_snd_ctrl_command *cmd)
1690ff05dd2SManos Pitsidianakis {
1700ff05dd2SManos Pitsidianakis     uint32_t stream_id, start_id, count, size;
1710ff05dd2SManos Pitsidianakis     virtio_snd_pcm_info val;
1720ff05dd2SManos Pitsidianakis     virtio_snd_query_info req;
1730ff05dd2SManos Pitsidianakis     VirtIOSoundPCMStream *stream = NULL;
1740ff05dd2SManos Pitsidianakis     g_autofree virtio_snd_pcm_info *pcm_info = NULL;
1750ff05dd2SManos Pitsidianakis     size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
1760ff05dd2SManos Pitsidianakis                                cmd->elem->out_num,
1770ff05dd2SManos Pitsidianakis                                0,
1780ff05dd2SManos Pitsidianakis                                &req,
1790ff05dd2SManos Pitsidianakis                                sizeof(virtio_snd_query_info));
1800ff05dd2SManos Pitsidianakis 
1810ff05dd2SManos Pitsidianakis     if (msg_sz != sizeof(virtio_snd_query_info)) {
1820ff05dd2SManos Pitsidianakis         /*
1830ff05dd2SManos Pitsidianakis          * TODO: do we need to set DEVICE_NEEDS_RESET?
1840ff05dd2SManos Pitsidianakis          */
1850ff05dd2SManos Pitsidianakis         qemu_log_mask(LOG_GUEST_ERROR,
1860ff05dd2SManos Pitsidianakis                 "%s: virtio-snd command size incorrect %zu vs \
1870ff05dd2SManos Pitsidianakis                 %zu\n", __func__, msg_sz, sizeof(virtio_snd_query_info));
1880ff05dd2SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
1890ff05dd2SManos Pitsidianakis         return;
1900ff05dd2SManos Pitsidianakis     }
1910ff05dd2SManos Pitsidianakis 
1920ff05dd2SManos Pitsidianakis     start_id = le32_to_cpu(req.start_id);
1930ff05dd2SManos Pitsidianakis     count = le32_to_cpu(req.count);
1940ff05dd2SManos Pitsidianakis     size = le32_to_cpu(req.size);
1950ff05dd2SManos Pitsidianakis 
1960ff05dd2SManos Pitsidianakis     if (iov_size(cmd->elem->in_sg, cmd->elem->in_num) <
1970ff05dd2SManos Pitsidianakis         sizeof(virtio_snd_hdr) + size * count) {
1980ff05dd2SManos Pitsidianakis         /*
1990ff05dd2SManos Pitsidianakis          * TODO: do we need to set DEVICE_NEEDS_RESET?
2000ff05dd2SManos Pitsidianakis          */
2010ff05dd2SManos Pitsidianakis         error_report("pcm info: buffer too small, got: %zu, needed: %zu",
2020ff05dd2SManos Pitsidianakis                 iov_size(cmd->elem->in_sg, cmd->elem->in_num),
2030ff05dd2SManos Pitsidianakis                 sizeof(virtio_snd_pcm_info));
2040ff05dd2SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
2050ff05dd2SManos Pitsidianakis         return;
2060ff05dd2SManos Pitsidianakis     }
2070ff05dd2SManos Pitsidianakis 
2080ff05dd2SManos Pitsidianakis     pcm_info = g_new0(virtio_snd_pcm_info, count);
2090ff05dd2SManos Pitsidianakis     for (uint32_t i = 0; i < count; i++) {
2100ff05dd2SManos Pitsidianakis         stream_id = i + start_id;
2110ff05dd2SManos Pitsidianakis         trace_virtio_snd_handle_pcm_info(stream_id);
2120ff05dd2SManos Pitsidianakis         stream = virtio_snd_pcm_get_stream(s, stream_id);
2130ff05dd2SManos Pitsidianakis         if (!stream) {
2140ff05dd2SManos Pitsidianakis             error_report("Invalid stream id: %"PRIu32, stream_id);
2150ff05dd2SManos Pitsidianakis             cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
2160ff05dd2SManos Pitsidianakis             return;
2170ff05dd2SManos Pitsidianakis         }
2180ff05dd2SManos Pitsidianakis         val = stream->info;
2190ff05dd2SManos Pitsidianakis         val.hdr.hda_fn_nid = cpu_to_le32(val.hdr.hda_fn_nid);
2200ff05dd2SManos Pitsidianakis         val.features = cpu_to_le32(val.features);
2210ff05dd2SManos Pitsidianakis         val.formats = cpu_to_le64(val.formats);
2220ff05dd2SManos Pitsidianakis         val.rates = cpu_to_le64(val.rates);
2230ff05dd2SManos Pitsidianakis         /*
2240ff05dd2SManos Pitsidianakis          * 5.14.6.6.2.1 Device Requirements: Stream Information The device MUST
2250ff05dd2SManos Pitsidianakis          * NOT set undefined feature, format, rate and direction values. The
2260ff05dd2SManos Pitsidianakis          * device MUST initialize the padding bytes to 0.
2270ff05dd2SManos Pitsidianakis          */
2280ff05dd2SManos Pitsidianakis         pcm_info[i] = val;
2290ff05dd2SManos Pitsidianakis         memset(&pcm_info[i].padding, 0, 5);
2300ff05dd2SManos Pitsidianakis     }
2310ff05dd2SManos Pitsidianakis 
2320ff05dd2SManos Pitsidianakis     cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK);
2330ff05dd2SManos Pitsidianakis     iov_from_buf(cmd->elem->in_sg,
2340ff05dd2SManos Pitsidianakis                  cmd->elem->in_num,
2350ff05dd2SManos Pitsidianakis                  sizeof(virtio_snd_hdr),
2360ff05dd2SManos Pitsidianakis                  pcm_info,
2370ff05dd2SManos Pitsidianakis                  sizeof(virtio_snd_pcm_info) * count);
2380ff05dd2SManos Pitsidianakis }
2390ff05dd2SManos Pitsidianakis 
2400ff05dd2SManos Pitsidianakis /*
241eb9ad377SManos Pitsidianakis  * Set the given stream params.
242eb9ad377SManos Pitsidianakis  * Called by both virtio_snd_handle_pcm_set_params and during device
243eb9ad377SManos Pitsidianakis  * initialization.
244eb9ad377SManos Pitsidianakis  * Returns the response status code. (VIRTIO_SND_S_*).
245eb9ad377SManos Pitsidianakis  *
246eb9ad377SManos Pitsidianakis  * @s: VirtIOSound device
247eb9ad377SManos Pitsidianakis  * @params: The PCM params as defined in the virtio specification
248eb9ad377SManos Pitsidianakis  */
249eb9ad377SManos Pitsidianakis static
250eb9ad377SManos Pitsidianakis uint32_t virtio_snd_set_pcm_params(VirtIOSound *s,
251eb9ad377SManos Pitsidianakis                                    uint32_t stream_id,
252eb9ad377SManos Pitsidianakis                                    virtio_snd_pcm_set_params *params)
253eb9ad377SManos Pitsidianakis {
254eb9ad377SManos Pitsidianakis     virtio_snd_pcm_set_params *st_params;
255eb9ad377SManos Pitsidianakis 
256eb9ad377SManos Pitsidianakis     if (stream_id >= s->snd_conf.streams || s->pcm->pcm_params == NULL) {
257eb9ad377SManos Pitsidianakis         /*
258eb9ad377SManos Pitsidianakis          * TODO: do we need to set DEVICE_NEEDS_RESET?
259eb9ad377SManos Pitsidianakis          */
260eb9ad377SManos Pitsidianakis         virtio_error(VIRTIO_DEVICE(s), "Streams have not been initialized.\n");
261eb9ad377SManos Pitsidianakis         return cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
262eb9ad377SManos Pitsidianakis     }
263eb9ad377SManos Pitsidianakis 
264eb9ad377SManos Pitsidianakis     st_params = virtio_snd_pcm_get_params(s, stream_id);
265eb9ad377SManos Pitsidianakis 
266eb9ad377SManos Pitsidianakis     if (params->channels < 1 || params->channels > AUDIO_MAX_CHANNELS) {
267eb9ad377SManos Pitsidianakis         error_report("Number of channels is not supported.");
268eb9ad377SManos Pitsidianakis         return cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
269eb9ad377SManos Pitsidianakis     }
270eb9ad377SManos Pitsidianakis     if (!(supported_formats & BIT(params->format))) {
271eb9ad377SManos Pitsidianakis         error_report("Stream format is not supported.");
272eb9ad377SManos Pitsidianakis         return cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
273eb9ad377SManos Pitsidianakis     }
274eb9ad377SManos Pitsidianakis     if (!(supported_rates & BIT(params->rate))) {
275eb9ad377SManos Pitsidianakis         error_report("Stream rate is not supported.");
276eb9ad377SManos Pitsidianakis         return cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
277eb9ad377SManos Pitsidianakis     }
278eb9ad377SManos Pitsidianakis 
279eb9ad377SManos Pitsidianakis     st_params->buffer_bytes = le32_to_cpu(params->buffer_bytes);
280eb9ad377SManos Pitsidianakis     st_params->period_bytes = le32_to_cpu(params->period_bytes);
281eb9ad377SManos Pitsidianakis     st_params->features = le32_to_cpu(params->features);
282eb9ad377SManos Pitsidianakis     /* the following are uint8_t, so there's no need to bswap the values. */
283eb9ad377SManos Pitsidianakis     st_params->channels = params->channels;
284eb9ad377SManos Pitsidianakis     st_params->format = params->format;
285eb9ad377SManos Pitsidianakis     st_params->rate = params->rate;
286eb9ad377SManos Pitsidianakis 
287eb9ad377SManos Pitsidianakis     return cpu_to_le32(VIRTIO_SND_S_OK);
288eb9ad377SManos Pitsidianakis }
289eb9ad377SManos Pitsidianakis 
290eb9ad377SManos Pitsidianakis /*
291eb9ad377SManos Pitsidianakis  * Get a QEMU Audiosystem compatible format value from a VIRTIO_SND_PCM_FMT_*
292eb9ad377SManos Pitsidianakis  */
293eb9ad377SManos Pitsidianakis static AudioFormat virtio_snd_get_qemu_format(uint32_t format)
294eb9ad377SManos Pitsidianakis {
295eb9ad377SManos Pitsidianakis     #define CASE(FMT)               \
296eb9ad377SManos Pitsidianakis     case VIRTIO_SND_PCM_FMT_##FMT:  \
297eb9ad377SManos Pitsidianakis         return AUDIO_FORMAT_##FMT;
298eb9ad377SManos Pitsidianakis 
299eb9ad377SManos Pitsidianakis     switch (format) {
300eb9ad377SManos Pitsidianakis     CASE(U8)
301eb9ad377SManos Pitsidianakis     CASE(S8)
302eb9ad377SManos Pitsidianakis     CASE(U16)
303eb9ad377SManos Pitsidianakis     CASE(S16)
304eb9ad377SManos Pitsidianakis     CASE(U32)
305eb9ad377SManos Pitsidianakis     CASE(S32)
306eb9ad377SManos Pitsidianakis     case VIRTIO_SND_PCM_FMT_FLOAT:
307eb9ad377SManos Pitsidianakis         return AUDIO_FORMAT_F32;
308eb9ad377SManos Pitsidianakis     default:
309eb9ad377SManos Pitsidianakis         g_assert_not_reached();
310eb9ad377SManos Pitsidianakis     }
311eb9ad377SManos Pitsidianakis 
312eb9ad377SManos Pitsidianakis     #undef CASE
313eb9ad377SManos Pitsidianakis }
314eb9ad377SManos Pitsidianakis 
315eb9ad377SManos Pitsidianakis /*
316eb9ad377SManos Pitsidianakis  * Get a QEMU Audiosystem compatible frequency value from a
317eb9ad377SManos Pitsidianakis  * VIRTIO_SND_PCM_RATE_*
318eb9ad377SManos Pitsidianakis  */
319eb9ad377SManos Pitsidianakis static uint32_t virtio_snd_get_qemu_freq(uint32_t rate)
320eb9ad377SManos Pitsidianakis {
321eb9ad377SManos Pitsidianakis     #define CASE(RATE)               \
322eb9ad377SManos Pitsidianakis     case VIRTIO_SND_PCM_RATE_##RATE: \
323eb9ad377SManos Pitsidianakis         return RATE;
324eb9ad377SManos Pitsidianakis 
325eb9ad377SManos Pitsidianakis     switch (rate) {
326eb9ad377SManos Pitsidianakis     CASE(5512)
327eb9ad377SManos Pitsidianakis     CASE(8000)
328eb9ad377SManos Pitsidianakis     CASE(11025)
329eb9ad377SManos Pitsidianakis     CASE(16000)
330eb9ad377SManos Pitsidianakis     CASE(22050)
331eb9ad377SManos Pitsidianakis     CASE(32000)
332eb9ad377SManos Pitsidianakis     CASE(44100)
333eb9ad377SManos Pitsidianakis     CASE(48000)
334eb9ad377SManos Pitsidianakis     CASE(64000)
335eb9ad377SManos Pitsidianakis     CASE(88200)
336eb9ad377SManos Pitsidianakis     CASE(96000)
337eb9ad377SManos Pitsidianakis     CASE(176400)
338eb9ad377SManos Pitsidianakis     CASE(192000)
339eb9ad377SManos Pitsidianakis     CASE(384000)
340eb9ad377SManos Pitsidianakis     default:
341eb9ad377SManos Pitsidianakis         g_assert_not_reached();
342eb9ad377SManos Pitsidianakis     }
343eb9ad377SManos Pitsidianakis 
344eb9ad377SManos Pitsidianakis     #undef CASE
345eb9ad377SManos Pitsidianakis }
346eb9ad377SManos Pitsidianakis 
347eb9ad377SManos Pitsidianakis /*
348eb9ad377SManos Pitsidianakis  * Get QEMU Audiosystem compatible audsettings from virtio based pcm stream
349eb9ad377SManos Pitsidianakis  * params.
350eb9ad377SManos Pitsidianakis  */
351eb9ad377SManos Pitsidianakis static void virtio_snd_get_qemu_audsettings(audsettings *as,
352eb9ad377SManos Pitsidianakis                                             virtio_snd_pcm_set_params *params)
353eb9ad377SManos Pitsidianakis {
354eb9ad377SManos Pitsidianakis     as->nchannels = MIN(AUDIO_MAX_CHANNELS, params->channels);
355eb9ad377SManos Pitsidianakis     as->fmt = virtio_snd_get_qemu_format(params->format);
356eb9ad377SManos Pitsidianakis     as->freq = virtio_snd_get_qemu_freq(params->rate);
357eb9ad377SManos Pitsidianakis     as->endianness = target_words_bigendian() ? 1 : 0;
358eb9ad377SManos Pitsidianakis }
359eb9ad377SManos Pitsidianakis 
360eb9ad377SManos Pitsidianakis /*
361eb9ad377SManos Pitsidianakis  * Close a stream and free all its resources.
362eb9ad377SManos Pitsidianakis  *
363eb9ad377SManos Pitsidianakis  * @stream: VirtIOSoundPCMStream *stream
364eb9ad377SManos Pitsidianakis  */
365eb9ad377SManos Pitsidianakis static void virtio_snd_pcm_close(VirtIOSoundPCMStream *stream)
366eb9ad377SManos Pitsidianakis {
367eb9ad377SManos Pitsidianakis }
368eb9ad377SManos Pitsidianakis 
369eb9ad377SManos Pitsidianakis /*
370eb9ad377SManos Pitsidianakis  * Prepares a VirtIOSound card stream.
371eb9ad377SManos Pitsidianakis  * Returns the response status code. (VIRTIO_SND_S_*).
372eb9ad377SManos Pitsidianakis  *
373eb9ad377SManos Pitsidianakis  * @s: VirtIOSound device
374eb9ad377SManos Pitsidianakis  * @stream_id: stream id
375eb9ad377SManos Pitsidianakis  */
376eb9ad377SManos Pitsidianakis static uint32_t virtio_snd_pcm_prepare(VirtIOSound *s, uint32_t stream_id)
377eb9ad377SManos Pitsidianakis {
378eb9ad377SManos Pitsidianakis     audsettings as;
379eb9ad377SManos Pitsidianakis     virtio_snd_pcm_set_params *params;
380eb9ad377SManos Pitsidianakis     VirtIOSoundPCMStream *stream;
381eb9ad377SManos Pitsidianakis 
382eb9ad377SManos Pitsidianakis     if (s->pcm->streams == NULL ||
383eb9ad377SManos Pitsidianakis         s->pcm->pcm_params == NULL ||
384eb9ad377SManos Pitsidianakis         stream_id >= s->snd_conf.streams) {
385eb9ad377SManos Pitsidianakis         return cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
386eb9ad377SManos Pitsidianakis     }
387eb9ad377SManos Pitsidianakis 
388eb9ad377SManos Pitsidianakis     params = virtio_snd_pcm_get_params(s, stream_id);
389eb9ad377SManos Pitsidianakis     if (params == NULL) {
390eb9ad377SManos Pitsidianakis         return cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
391eb9ad377SManos Pitsidianakis     }
392eb9ad377SManos Pitsidianakis 
393eb9ad377SManos Pitsidianakis     stream = virtio_snd_pcm_get_stream(s, stream_id);
394eb9ad377SManos Pitsidianakis     if (stream == NULL) {
395eb9ad377SManos Pitsidianakis         stream = g_new0(VirtIOSoundPCMStream, 1);
396eb9ad377SManos Pitsidianakis         stream->active = false;
397eb9ad377SManos Pitsidianakis         stream->id = stream_id;
398eb9ad377SManos Pitsidianakis         stream->pcm = s->pcm;
399eb9ad377SManos Pitsidianakis         stream->s = s;
400eb9ad377SManos Pitsidianakis 
401eb9ad377SManos Pitsidianakis         /*
402eb9ad377SManos Pitsidianakis          * stream_id >= s->snd_conf.streams was checked before so this is
403eb9ad377SManos Pitsidianakis          * in-bounds
404eb9ad377SManos Pitsidianakis          */
405eb9ad377SManos Pitsidianakis         s->pcm->streams[stream_id] = stream;
406eb9ad377SManos Pitsidianakis     }
407eb9ad377SManos Pitsidianakis 
408eb9ad377SManos Pitsidianakis     virtio_snd_get_qemu_audsettings(&as, params);
409eb9ad377SManos Pitsidianakis     stream->info.direction = stream_id < s->snd_conf.streams / 2 +
410eb9ad377SManos Pitsidianakis         (s->snd_conf.streams & 1) ? VIRTIO_SND_D_OUTPUT : VIRTIO_SND_D_INPUT;
411eb9ad377SManos Pitsidianakis     stream->info.hdr.hda_fn_nid = VIRTIO_SOUND_HDA_FN_NID;
412eb9ad377SManos Pitsidianakis     stream->info.features = 0;
413eb9ad377SManos Pitsidianakis     stream->info.channels_min = 1;
414eb9ad377SManos Pitsidianakis     stream->info.channels_max = as.nchannels;
415eb9ad377SManos Pitsidianakis     stream->info.formats = supported_formats;
416eb9ad377SManos Pitsidianakis     stream->info.rates = supported_rates;
417eb9ad377SManos Pitsidianakis     stream->params = *params;
418eb9ad377SManos Pitsidianakis 
419eb9ad377SManos Pitsidianakis     stream->positions[0] = VIRTIO_SND_CHMAP_FL;
420eb9ad377SManos Pitsidianakis     stream->positions[1] = VIRTIO_SND_CHMAP_FR;
421eb9ad377SManos Pitsidianakis     stream->as = as;
422eb9ad377SManos Pitsidianakis 
423eb9ad377SManos Pitsidianakis     return cpu_to_le32(VIRTIO_SND_S_OK);
424eb9ad377SManos Pitsidianakis }
425eb9ad377SManos Pitsidianakis 
426eb9ad377SManos Pitsidianakis static const char *print_code(uint32_t code)
427eb9ad377SManos Pitsidianakis {
428eb9ad377SManos Pitsidianakis     #define CASE(CODE)            \
429eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_##CODE:     \
430eb9ad377SManos Pitsidianakis         return "VIRTIO_SND_R_"#CODE
431eb9ad377SManos Pitsidianakis 
432eb9ad377SManos Pitsidianakis     switch (code) {
433eb9ad377SManos Pitsidianakis     CASE(JACK_INFO);
434eb9ad377SManos Pitsidianakis     CASE(JACK_REMAP);
435eb9ad377SManos Pitsidianakis     CASE(PCM_INFO);
436eb9ad377SManos Pitsidianakis     CASE(PCM_SET_PARAMS);
437eb9ad377SManos Pitsidianakis     CASE(PCM_PREPARE);
438eb9ad377SManos Pitsidianakis     CASE(PCM_RELEASE);
439eb9ad377SManos Pitsidianakis     CASE(PCM_START);
440eb9ad377SManos Pitsidianakis     CASE(PCM_STOP);
441eb9ad377SManos Pitsidianakis     CASE(CHMAP_INFO);
442eb9ad377SManos Pitsidianakis     default:
443eb9ad377SManos Pitsidianakis         return "invalid code";
444eb9ad377SManos Pitsidianakis     }
445eb9ad377SManos Pitsidianakis 
446eb9ad377SManos Pitsidianakis     #undef CASE
447eb9ad377SManos Pitsidianakis };
448eb9ad377SManos Pitsidianakis 
449eb9ad377SManos Pitsidianakis /*
450fa131d4aSManos Pitsidianakis  * Handles VIRTIO_SND_R_PCM_START.
451fa131d4aSManos Pitsidianakis  *
452fa131d4aSManos Pitsidianakis  * @s: VirtIOSound device
453fa131d4aSManos Pitsidianakis  * @cmd: The request command queue element from VirtIOSound cmdq field
454fa131d4aSManos Pitsidianakis  * @start: whether to start or stop the device
455fa131d4aSManos Pitsidianakis  */
456fa131d4aSManos Pitsidianakis static void virtio_snd_handle_pcm_start_stop(VirtIOSound *s,
457fa131d4aSManos Pitsidianakis                                              virtio_snd_ctrl_command *cmd,
458fa131d4aSManos Pitsidianakis                                              bool start)
459fa131d4aSManos Pitsidianakis {
460fa131d4aSManos Pitsidianakis     VirtIOSoundPCMStream *stream;
461fa131d4aSManos Pitsidianakis     virtio_snd_pcm_hdr req;
462fa131d4aSManos Pitsidianakis     uint32_t stream_id;
463fa131d4aSManos Pitsidianakis     size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
464fa131d4aSManos Pitsidianakis                                cmd->elem->out_num,
465fa131d4aSManos Pitsidianakis                                0,
466fa131d4aSManos Pitsidianakis                                &req,
467fa131d4aSManos Pitsidianakis                                sizeof(virtio_snd_pcm_hdr));
468fa131d4aSManos Pitsidianakis 
469fa131d4aSManos Pitsidianakis     if (msg_sz != sizeof(virtio_snd_pcm_hdr)) {
470fa131d4aSManos Pitsidianakis         qemu_log_mask(LOG_GUEST_ERROR,
471fa131d4aSManos Pitsidianakis                 "%s: virtio-snd command size incorrect %zu vs \
472fa131d4aSManos Pitsidianakis                 %zu\n", __func__, msg_sz, sizeof(virtio_snd_pcm_hdr));
473fa131d4aSManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
474fa131d4aSManos Pitsidianakis         return;
475fa131d4aSManos Pitsidianakis     }
476fa131d4aSManos Pitsidianakis 
477fa131d4aSManos Pitsidianakis     stream_id = le32_to_cpu(req.stream_id);
478fa131d4aSManos Pitsidianakis     cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK);
479fa131d4aSManos Pitsidianakis     trace_virtio_snd_handle_pcm_start_stop(start ? "VIRTIO_SND_R_PCM_START" :
480fa131d4aSManos Pitsidianakis             "VIRTIO_SND_R_PCM_STOP", stream_id);
481fa131d4aSManos Pitsidianakis     stream = virtio_snd_pcm_get_stream(s, stream_id);
482fa131d4aSManos Pitsidianakis     if (stream == NULL) {
483fa131d4aSManos Pitsidianakis         error_report("Invalid stream id: %"PRIu32, req.stream_id);
484fa131d4aSManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
485fa131d4aSManos Pitsidianakis         return;
486fa131d4aSManos Pitsidianakis     }
487fa131d4aSManos Pitsidianakis     stream->active = start;
488fa131d4aSManos Pitsidianakis }
489fa131d4aSManos Pitsidianakis 
490fa131d4aSManos Pitsidianakis /*
491eb9ad377SManos Pitsidianakis  * The actual processing done in virtio_snd_process_cmdq().
492eb9ad377SManos Pitsidianakis  *
493eb9ad377SManos Pitsidianakis  * @s: VirtIOSound device
494eb9ad377SManos Pitsidianakis  * @cmd: control command request
495eb9ad377SManos Pitsidianakis  */
496eb9ad377SManos Pitsidianakis static inline void
497eb9ad377SManos Pitsidianakis process_cmd(VirtIOSound *s, virtio_snd_ctrl_command *cmd)
498eb9ad377SManos Pitsidianakis {
499eb9ad377SManos Pitsidianakis     uint32_t code;
500eb9ad377SManos Pitsidianakis     size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
501eb9ad377SManos Pitsidianakis                                cmd->elem->out_num,
502eb9ad377SManos Pitsidianakis                                0,
503eb9ad377SManos Pitsidianakis                                &cmd->ctrl,
504eb9ad377SManos Pitsidianakis                                sizeof(virtio_snd_hdr));
505eb9ad377SManos Pitsidianakis 
506eb9ad377SManos Pitsidianakis     if (msg_sz != sizeof(virtio_snd_hdr)) {
507eb9ad377SManos Pitsidianakis         /*
508eb9ad377SManos Pitsidianakis          * TODO: do we need to set DEVICE_NEEDS_RESET?
509eb9ad377SManos Pitsidianakis          */
510eb9ad377SManos Pitsidianakis         qemu_log_mask(LOG_GUEST_ERROR,
511eb9ad377SManos Pitsidianakis                 "%s: virtio-snd command size incorrect %zu vs \
512eb9ad377SManos Pitsidianakis                 %zu\n", __func__, msg_sz, sizeof(virtio_snd_hdr));
513eb9ad377SManos Pitsidianakis         return;
514eb9ad377SManos Pitsidianakis     }
515eb9ad377SManos Pitsidianakis 
516eb9ad377SManos Pitsidianakis     code = le32_to_cpu(cmd->ctrl.code);
517eb9ad377SManos Pitsidianakis 
518eb9ad377SManos Pitsidianakis     trace_virtio_snd_handle_code(code, print_code(code));
519eb9ad377SManos Pitsidianakis 
520eb9ad377SManos Pitsidianakis     switch (code) {
521eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_JACK_INFO:
522eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_JACK_REMAP:
523eb9ad377SManos Pitsidianakis         qemu_log_mask(LOG_UNIMP,
524eb9ad377SManos Pitsidianakis                      "virtio_snd: jack functionality is unimplemented.\n");
525eb9ad377SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
526eb9ad377SManos Pitsidianakis         break;
527eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_PCM_INFO:
5280ff05dd2SManos Pitsidianakis         virtio_snd_handle_pcm_info(s, cmd);
5290ff05dd2SManos Pitsidianakis         break;
530fa131d4aSManos Pitsidianakis     case VIRTIO_SND_R_PCM_START:
531fa131d4aSManos Pitsidianakis         virtio_snd_handle_pcm_start_stop(s, cmd, true);
532fa131d4aSManos Pitsidianakis         break;
533fa131d4aSManos Pitsidianakis     case VIRTIO_SND_R_PCM_STOP:
534fa131d4aSManos Pitsidianakis         virtio_snd_handle_pcm_start_stop(s, cmd, false);
535fa131d4aSManos Pitsidianakis         break;
536eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_PCM_SET_PARAMS:
537eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_PCM_PREPARE:
538eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_PCM_RELEASE:
539eb9ad377SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
540eb9ad377SManos Pitsidianakis         break;
541eb9ad377SManos Pitsidianakis     case VIRTIO_SND_R_CHMAP_INFO:
542eb9ad377SManos Pitsidianakis         qemu_log_mask(LOG_UNIMP,
543eb9ad377SManos Pitsidianakis                      "virtio_snd: chmap info functionality is unimplemented.\n");
544eb9ad377SManos Pitsidianakis         trace_virtio_snd_handle_chmap_info();
545eb9ad377SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
546eb9ad377SManos Pitsidianakis         break;
547eb9ad377SManos Pitsidianakis     default:
548eb9ad377SManos Pitsidianakis         /* error */
549eb9ad377SManos Pitsidianakis         error_report("virtio snd header not recognized: %"PRIu32, code);
550eb9ad377SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
551eb9ad377SManos Pitsidianakis     }
552eb9ad377SManos Pitsidianakis 
553eb9ad377SManos Pitsidianakis     iov_from_buf(cmd->elem->in_sg,
554eb9ad377SManos Pitsidianakis                  cmd->elem->in_num,
555eb9ad377SManos Pitsidianakis                  0,
556eb9ad377SManos Pitsidianakis                  &cmd->resp,
557eb9ad377SManos Pitsidianakis                  sizeof(virtio_snd_hdr));
558eb9ad377SManos Pitsidianakis     virtqueue_push(cmd->vq, cmd->elem, sizeof(virtio_snd_hdr));
559eb9ad377SManos Pitsidianakis     virtio_notify(VIRTIO_DEVICE(s), cmd->vq);
560eb9ad377SManos Pitsidianakis }
561eb9ad377SManos Pitsidianakis 
562eb9ad377SManos Pitsidianakis /*
563eb9ad377SManos Pitsidianakis  * Consume all elements in command queue.
564eb9ad377SManos Pitsidianakis  *
565eb9ad377SManos Pitsidianakis  * @s: VirtIOSound device
566eb9ad377SManos Pitsidianakis  */
567eb9ad377SManos Pitsidianakis static void virtio_snd_process_cmdq(VirtIOSound *s)
568eb9ad377SManos Pitsidianakis {
569eb9ad377SManos Pitsidianakis     virtio_snd_ctrl_command *cmd;
570eb9ad377SManos Pitsidianakis 
571eb9ad377SManos Pitsidianakis     if (unlikely(qatomic_read(&s->processing_cmdq))) {
572eb9ad377SManos Pitsidianakis         return;
573eb9ad377SManos Pitsidianakis     }
574eb9ad377SManos Pitsidianakis 
575eb9ad377SManos Pitsidianakis     WITH_QEMU_LOCK_GUARD(&s->cmdq_mutex) {
576eb9ad377SManos Pitsidianakis         qatomic_set(&s->processing_cmdq, true);
577eb9ad377SManos Pitsidianakis         while (!QTAILQ_EMPTY(&s->cmdq)) {
578eb9ad377SManos Pitsidianakis             cmd = QTAILQ_FIRST(&s->cmdq);
579eb9ad377SManos Pitsidianakis 
580eb9ad377SManos Pitsidianakis             /* process command */
581eb9ad377SManos Pitsidianakis             process_cmd(s, cmd);
582eb9ad377SManos Pitsidianakis 
583eb9ad377SManos Pitsidianakis             QTAILQ_REMOVE(&s->cmdq, cmd, next);
584eb9ad377SManos Pitsidianakis 
585eb9ad377SManos Pitsidianakis             virtio_snd_ctrl_cmd_free(cmd);
586eb9ad377SManos Pitsidianakis         }
587eb9ad377SManos Pitsidianakis         qatomic_set(&s->processing_cmdq, false);
588eb9ad377SManos Pitsidianakis     }
589eb9ad377SManos Pitsidianakis }
590eb9ad377SManos Pitsidianakis 
591eb9ad377SManos Pitsidianakis /*
592eb9ad377SManos Pitsidianakis  * The control message handler. Pops an element from the control virtqueue,
593eb9ad377SManos Pitsidianakis  * and stores them to VirtIOSound's cmdq queue and finally calls
594eb9ad377SManos Pitsidianakis  * virtio_snd_process_cmdq() for processing.
595eb9ad377SManos Pitsidianakis  *
596eb9ad377SManos Pitsidianakis  * @vdev: VirtIOSound device
597eb9ad377SManos Pitsidianakis  * @vq: Control virtqueue
598eb9ad377SManos Pitsidianakis  */
599eb9ad377SManos Pitsidianakis static void virtio_snd_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq)
600eb9ad377SManos Pitsidianakis {
601eb9ad377SManos Pitsidianakis     VirtIOSound *s = VIRTIO_SND(vdev);
602eb9ad377SManos Pitsidianakis     VirtQueueElement *elem;
603eb9ad377SManos Pitsidianakis     virtio_snd_ctrl_command *cmd;
604eb9ad377SManos Pitsidianakis 
605eb9ad377SManos Pitsidianakis     trace_virtio_snd_handle_ctrl(vdev, vq);
606eb9ad377SManos Pitsidianakis 
607eb9ad377SManos Pitsidianakis     if (!virtio_queue_ready(vq)) {
608eb9ad377SManos Pitsidianakis         return;
609eb9ad377SManos Pitsidianakis     }
610eb9ad377SManos Pitsidianakis 
611eb9ad377SManos Pitsidianakis     elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
612eb9ad377SManos Pitsidianakis     while (elem) {
613eb9ad377SManos Pitsidianakis         cmd = g_new0(virtio_snd_ctrl_command, 1);
614eb9ad377SManos Pitsidianakis         cmd->elem = elem;
615eb9ad377SManos Pitsidianakis         cmd->vq = vq;
616eb9ad377SManos Pitsidianakis         cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK);
617eb9ad377SManos Pitsidianakis         QTAILQ_INSERT_TAIL(&s->cmdq, cmd, next);
618eb9ad377SManos Pitsidianakis         elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
619eb9ad377SManos Pitsidianakis     }
620eb9ad377SManos Pitsidianakis 
621eb9ad377SManos Pitsidianakis     virtio_snd_process_cmdq(s);
622eb9ad377SManos Pitsidianakis }
623eb9ad377SManos Pitsidianakis 
624eb9ad377SManos Pitsidianakis /*
625eb9ad377SManos Pitsidianakis  * The event virtqueue handler.
626eb9ad377SManos Pitsidianakis  * Not implemented yet.
627eb9ad377SManos Pitsidianakis  *
628eb9ad377SManos Pitsidianakis  * @vdev: VirtIOSound device
629eb9ad377SManos Pitsidianakis  * @vq: event vq
630eb9ad377SManos Pitsidianakis  */
631eb9ad377SManos Pitsidianakis static void virtio_snd_handle_event(VirtIODevice *vdev, VirtQueue *vq)
632eb9ad377SManos Pitsidianakis {
633eb9ad377SManos Pitsidianakis     qemu_log_mask(LOG_UNIMP, "virtio_snd: event queue is unimplemented.\n");
634eb9ad377SManos Pitsidianakis     trace_virtio_snd_handle_event();
635eb9ad377SManos Pitsidianakis }
636eb9ad377SManos Pitsidianakis 
637eb9ad377SManos Pitsidianakis /*
638eb9ad377SManos Pitsidianakis  * Stub buffer virtqueue handler.
6392880e676SManos Pitsidianakis  *
6402880e676SManos Pitsidianakis  * @vdev: VirtIOSound device
6412880e676SManos Pitsidianakis  * @vq: virtqueue
6422880e676SManos Pitsidianakis  */
643eb9ad377SManos Pitsidianakis static void virtio_snd_handle_xfer(VirtIODevice *vdev, VirtQueue *vq) {}
6442880e676SManos Pitsidianakis 
6452880e676SManos Pitsidianakis static uint64_t get_features(VirtIODevice *vdev, uint64_t features,
6462880e676SManos Pitsidianakis                              Error **errp)
6472880e676SManos Pitsidianakis {
6482880e676SManos Pitsidianakis     /*
6492880e676SManos Pitsidianakis      * virtio-v1.2-csd01, 5.14.3,
6502880e676SManos Pitsidianakis      * Feature Bits
6512880e676SManos Pitsidianakis      * None currently defined.
6522880e676SManos Pitsidianakis      */
6532880e676SManos Pitsidianakis     VirtIOSound *s = VIRTIO_SND(vdev);
6542880e676SManos Pitsidianakis     features |= s->features;
6552880e676SManos Pitsidianakis 
6562880e676SManos Pitsidianakis     trace_virtio_snd_get_features(vdev, features);
6572880e676SManos Pitsidianakis 
6582880e676SManos Pitsidianakis     return features;
6592880e676SManos Pitsidianakis }
6602880e676SManos Pitsidianakis 
6612880e676SManos Pitsidianakis static void
6622880e676SManos Pitsidianakis virtio_snd_vm_state_change(void *opaque, bool running,
6632880e676SManos Pitsidianakis                                        RunState state)
6642880e676SManos Pitsidianakis {
6652880e676SManos Pitsidianakis     if (running) {
6662880e676SManos Pitsidianakis         trace_virtio_snd_vm_state_running();
6672880e676SManos Pitsidianakis     } else {
6682880e676SManos Pitsidianakis         trace_virtio_snd_vm_state_stopped();
6692880e676SManos Pitsidianakis     }
6702880e676SManos Pitsidianakis }
6712880e676SManos Pitsidianakis 
6722880e676SManos Pitsidianakis static void virtio_snd_realize(DeviceState *dev, Error **errp)
6732880e676SManos Pitsidianakis {
6742880e676SManos Pitsidianakis     ERRP_GUARD();
6752880e676SManos Pitsidianakis     VirtIOSound *vsnd = VIRTIO_SND(dev);
6762880e676SManos Pitsidianakis     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
677eb9ad377SManos Pitsidianakis     virtio_snd_pcm_set_params default_params = { 0 };
678eb9ad377SManos Pitsidianakis     uint32_t status;
6792880e676SManos Pitsidianakis 
680eb9ad377SManos Pitsidianakis     vsnd->pcm = NULL;
6812880e676SManos Pitsidianakis     vsnd->vmstate =
6822880e676SManos Pitsidianakis         qemu_add_vm_change_state_handler(virtio_snd_vm_state_change, vsnd);
6832880e676SManos Pitsidianakis 
6842880e676SManos Pitsidianakis     trace_virtio_snd_realize(vsnd);
6852880e676SManos Pitsidianakis 
686eb9ad377SManos Pitsidianakis     vsnd->pcm = g_new0(VirtIOSoundPCM, 1);
687eb9ad377SManos Pitsidianakis     vsnd->pcm->snd = vsnd;
688eb9ad377SManos Pitsidianakis     vsnd->pcm->streams =
689eb9ad377SManos Pitsidianakis         g_new0(VirtIOSoundPCMStream *, vsnd->snd_conf.streams);
690eb9ad377SManos Pitsidianakis     vsnd->pcm->pcm_params =
691eb9ad377SManos Pitsidianakis         g_new0(virtio_snd_pcm_set_params, vsnd->snd_conf.streams);
692eb9ad377SManos Pitsidianakis 
6932880e676SManos Pitsidianakis     virtio_init(vdev, VIRTIO_ID_SOUND, sizeof(virtio_snd_config));
6942880e676SManos Pitsidianakis     virtio_add_feature(&vsnd->features, VIRTIO_F_VERSION_1);
6952880e676SManos Pitsidianakis 
6962880e676SManos Pitsidianakis     /* set number of jacks and streams */
6972880e676SManos Pitsidianakis     if (vsnd->snd_conf.jacks > 8) {
6982880e676SManos Pitsidianakis         error_setg(errp,
6992880e676SManos Pitsidianakis                    "Invalid number of jacks: %"PRIu32,
7002880e676SManos Pitsidianakis                    vsnd->snd_conf.jacks);
7012880e676SManos Pitsidianakis         return;
7022880e676SManos Pitsidianakis     }
7032880e676SManos Pitsidianakis     if (vsnd->snd_conf.streams < 1 || vsnd->snd_conf.streams > 10) {
7042880e676SManos Pitsidianakis         error_setg(errp,
7052880e676SManos Pitsidianakis                    "Invalid number of streams: %"PRIu32,
7062880e676SManos Pitsidianakis                     vsnd->snd_conf.streams);
7072880e676SManos Pitsidianakis         return;
7082880e676SManos Pitsidianakis     }
7092880e676SManos Pitsidianakis 
7102880e676SManos Pitsidianakis     if (vsnd->snd_conf.chmaps > VIRTIO_SND_CHMAP_MAX_SIZE) {
7112880e676SManos Pitsidianakis         error_setg(errp,
7122880e676SManos Pitsidianakis                    "Invalid number of channel maps: %"PRIu32,
7132880e676SManos Pitsidianakis                    vsnd->snd_conf.chmaps);
7142880e676SManos Pitsidianakis         return;
7152880e676SManos Pitsidianakis     }
7162880e676SManos Pitsidianakis 
7172880e676SManos Pitsidianakis     AUD_register_card("virtio-sound", &vsnd->card, errp);
7182880e676SManos Pitsidianakis 
719eb9ad377SManos Pitsidianakis     /* set default params for all streams */
720eb9ad377SManos Pitsidianakis     default_params.features = 0;
721eb9ad377SManos Pitsidianakis     default_params.buffer_bytes = cpu_to_le32(8192);
722eb9ad377SManos Pitsidianakis     default_params.period_bytes = cpu_to_le32(2048);
723eb9ad377SManos Pitsidianakis     default_params.channels = 2;
724eb9ad377SManos Pitsidianakis     default_params.format = VIRTIO_SND_PCM_FMT_S16;
725eb9ad377SManos Pitsidianakis     default_params.rate = VIRTIO_SND_PCM_RATE_48000;
7262880e676SManos Pitsidianakis     vsnd->queues[VIRTIO_SND_VQ_CONTROL] =
727eb9ad377SManos Pitsidianakis         virtio_add_queue(vdev, 64, virtio_snd_handle_ctrl);
7282880e676SManos Pitsidianakis     vsnd->queues[VIRTIO_SND_VQ_EVENT] =
729eb9ad377SManos Pitsidianakis         virtio_add_queue(vdev, 64, virtio_snd_handle_event);
7302880e676SManos Pitsidianakis     vsnd->queues[VIRTIO_SND_VQ_TX] =
731eb9ad377SManos Pitsidianakis         virtio_add_queue(vdev, 64, virtio_snd_handle_xfer);
7322880e676SManos Pitsidianakis     vsnd->queues[VIRTIO_SND_VQ_RX] =
733eb9ad377SManos Pitsidianakis         virtio_add_queue(vdev, 64, virtio_snd_handle_xfer);
734eb9ad377SManos Pitsidianakis     qemu_mutex_init(&vsnd->cmdq_mutex);
735eb9ad377SManos Pitsidianakis     QTAILQ_INIT(&vsnd->cmdq);
736eb9ad377SManos Pitsidianakis 
737eb9ad377SManos Pitsidianakis     for (uint32_t i = 0; i < vsnd->snd_conf.streams; i++) {
738eb9ad377SManos Pitsidianakis         status = virtio_snd_set_pcm_params(vsnd, i, &default_params);
739eb9ad377SManos Pitsidianakis         if (status != cpu_to_le32(VIRTIO_SND_S_OK)) {
740eb9ad377SManos Pitsidianakis             error_setg(errp,
741eb9ad377SManos Pitsidianakis                        "Can't initalize stream params, device responded with %s.",
742eb9ad377SManos Pitsidianakis                        print_code(status));
743eb9ad377SManos Pitsidianakis             return;
744eb9ad377SManos Pitsidianakis         }
745eb9ad377SManos Pitsidianakis         status = virtio_snd_pcm_prepare(vsnd, i);
746eb9ad377SManos Pitsidianakis         if (status != cpu_to_le32(VIRTIO_SND_S_OK)) {
747eb9ad377SManos Pitsidianakis             error_setg(errp,
748eb9ad377SManos Pitsidianakis                        "Can't prepare streams, device responded with %s.",
749eb9ad377SManos Pitsidianakis                        print_code(status));
750eb9ad377SManos Pitsidianakis             return;
751eb9ad377SManos Pitsidianakis         }
752eb9ad377SManos Pitsidianakis     }
7532880e676SManos Pitsidianakis }
7542880e676SManos Pitsidianakis 
7552880e676SManos Pitsidianakis static void virtio_snd_unrealize(DeviceState *dev)
7562880e676SManos Pitsidianakis {
7572880e676SManos Pitsidianakis     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
7582880e676SManos Pitsidianakis     VirtIOSound *vsnd = VIRTIO_SND(dev);
759eb9ad377SManos Pitsidianakis     VirtIOSoundPCMStream *stream;
7602880e676SManos Pitsidianakis 
7612880e676SManos Pitsidianakis     qemu_del_vm_change_state_handler(vsnd->vmstate);
7622880e676SManos Pitsidianakis     trace_virtio_snd_unrealize(vsnd);
7632880e676SManos Pitsidianakis 
764eb9ad377SManos Pitsidianakis     if (vsnd->pcm) {
765eb9ad377SManos Pitsidianakis         if (vsnd->pcm->streams) {
766eb9ad377SManos Pitsidianakis             for (uint32_t i = 0; i < vsnd->snd_conf.streams; i++) {
767eb9ad377SManos Pitsidianakis                 stream = vsnd->pcm->streams[i];
768eb9ad377SManos Pitsidianakis                 if (stream) {
769eb9ad377SManos Pitsidianakis                     virtio_snd_process_cmdq(stream->s);
770eb9ad377SManos Pitsidianakis                     virtio_snd_pcm_close(stream);
771eb9ad377SManos Pitsidianakis                     g_free(stream);
772eb9ad377SManos Pitsidianakis                 }
773eb9ad377SManos Pitsidianakis             }
774eb9ad377SManos Pitsidianakis             g_free(vsnd->pcm->streams);
775eb9ad377SManos Pitsidianakis         }
776eb9ad377SManos Pitsidianakis         g_free(vsnd->pcm->pcm_params);
777eb9ad377SManos Pitsidianakis         g_free(vsnd->pcm);
778eb9ad377SManos Pitsidianakis         vsnd->pcm = NULL;
779eb9ad377SManos Pitsidianakis     }
7802880e676SManos Pitsidianakis     AUD_remove_card(&vsnd->card);
781eb9ad377SManos Pitsidianakis     qemu_mutex_destroy(&vsnd->cmdq_mutex);
7822880e676SManos Pitsidianakis     virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_CONTROL]);
7832880e676SManos Pitsidianakis     virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_EVENT]);
7842880e676SManos Pitsidianakis     virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_TX]);
7852880e676SManos Pitsidianakis     virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_RX]);
7862880e676SManos Pitsidianakis     virtio_cleanup(vdev);
7872880e676SManos Pitsidianakis }
7882880e676SManos Pitsidianakis 
7892880e676SManos Pitsidianakis 
790eb9ad377SManos Pitsidianakis static void virtio_snd_reset(VirtIODevice *vdev)
791eb9ad377SManos Pitsidianakis {
792eb9ad377SManos Pitsidianakis     VirtIOSound *s = VIRTIO_SND(vdev);
793eb9ad377SManos Pitsidianakis     virtio_snd_ctrl_command *cmd;
794eb9ad377SManos Pitsidianakis 
795eb9ad377SManos Pitsidianakis     WITH_QEMU_LOCK_GUARD(&s->cmdq_mutex) {
796eb9ad377SManos Pitsidianakis         while (!QTAILQ_EMPTY(&s->cmdq)) {
797eb9ad377SManos Pitsidianakis             cmd = QTAILQ_FIRST(&s->cmdq);
798eb9ad377SManos Pitsidianakis             QTAILQ_REMOVE(&s->cmdq, cmd, next);
799eb9ad377SManos Pitsidianakis             virtio_snd_ctrl_cmd_free(cmd);
800eb9ad377SManos Pitsidianakis         }
801eb9ad377SManos Pitsidianakis     }
802eb9ad377SManos Pitsidianakis }
8032880e676SManos Pitsidianakis 
8042880e676SManos Pitsidianakis static void virtio_snd_class_init(ObjectClass *klass, void *data)
8052880e676SManos Pitsidianakis {
8062880e676SManos Pitsidianakis     DeviceClass *dc = DEVICE_CLASS(klass);
8072880e676SManos Pitsidianakis     VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
8082880e676SManos Pitsidianakis 
8092880e676SManos Pitsidianakis 
8102880e676SManos Pitsidianakis     set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
8112880e676SManos Pitsidianakis     device_class_set_props(dc, virtio_snd_properties);
8122880e676SManos Pitsidianakis 
8132880e676SManos Pitsidianakis     dc->vmsd = &vmstate_virtio_snd;
8142880e676SManos Pitsidianakis     vdc->vmsd = &vmstate_virtio_snd_device;
8152880e676SManos Pitsidianakis     vdc->realize = virtio_snd_realize;
8162880e676SManos Pitsidianakis     vdc->unrealize = virtio_snd_unrealize;
8172880e676SManos Pitsidianakis     vdc->get_config = virtio_snd_get_config;
8182880e676SManos Pitsidianakis     vdc->set_config = virtio_snd_set_config;
8192880e676SManos Pitsidianakis     vdc->get_features = get_features;
8202880e676SManos Pitsidianakis     vdc->reset = virtio_snd_reset;
8212880e676SManos Pitsidianakis     vdc->legacy_features = 0;
8222880e676SManos Pitsidianakis }
8232880e676SManos Pitsidianakis 
8242880e676SManos Pitsidianakis static const TypeInfo virtio_snd_types[] = {
8252880e676SManos Pitsidianakis     {
8262880e676SManos Pitsidianakis       .name          = TYPE_VIRTIO_SND,
8272880e676SManos Pitsidianakis       .parent        = TYPE_VIRTIO_DEVICE,
8282880e676SManos Pitsidianakis       .instance_size = sizeof(VirtIOSound),
8292880e676SManos Pitsidianakis       .class_init    = virtio_snd_class_init,
8302880e676SManos Pitsidianakis     }
8312880e676SManos Pitsidianakis };
8322880e676SManos Pitsidianakis 
8332880e676SManos Pitsidianakis DEFINE_TYPES(virtio_snd_types)
834