| /* |
| * VIRTIO Sound Device conforming to |
| * |
| * "Virtual I/O Device (VIRTIO) Version 1.2 |
| * Committee Specification Draft 01 |
| * 09 May 2022" |
| * |
| * <https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html#x1-52900014> |
| * |
| * Copyright (c) 2023 Emmanouil Pitsidianakis <manos.pitsidianakis@linaro.org> |
| * Copyright (C) 2019 OpenSynergy GmbH |
| * |
| * This work is licensed under the terms of the GNU GPL, version 2 or |
| * (at your option) any later version. See the COPYING file in the |
| * top-level directory. |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "qemu/iov.h" |
| #include "qemu/log.h" |
| #include "qemu/error-report.h" |
| #include "qemu/lockable.h" |
| #include "exec/tswap.h" |
| #include "sysemu/runstate.h" |
| #include "trace.h" |
| #include "qapi/error.h" |
| #include "hw/audio/virtio-snd.h" |
| |
| #define VIRTIO_SOUND_VM_VERSION 1 |
| #define VIRTIO_SOUND_JACK_DEFAULT 0 |
| #define VIRTIO_SOUND_STREAM_DEFAULT 2 |
| #define VIRTIO_SOUND_CHMAP_DEFAULT 0 |
| #define VIRTIO_SOUND_HDA_FN_NID 0 |
| |
| static void virtio_snd_pcm_out_cb(void *data, int available); |
| static void virtio_snd_process_cmdq(VirtIOSound *s); |
| static void virtio_snd_pcm_flush(VirtIOSoundPCMStream *stream); |
| static void virtio_snd_pcm_in_cb(void *data, int available); |
| static void virtio_snd_unrealize(DeviceState *dev); |
| |
| static uint32_t supported_formats = BIT(VIRTIO_SND_PCM_FMT_S8) |
| | BIT(VIRTIO_SND_PCM_FMT_U8) |
| | BIT(VIRTIO_SND_PCM_FMT_S16) |
| | BIT(VIRTIO_SND_PCM_FMT_U16) |
| | BIT(VIRTIO_SND_PCM_FMT_S32) |
| | BIT(VIRTIO_SND_PCM_FMT_U32) |
| | BIT(VIRTIO_SND_PCM_FMT_FLOAT); |
| |
| static uint32_t supported_rates = BIT(VIRTIO_SND_PCM_RATE_5512) |
| | BIT(VIRTIO_SND_PCM_RATE_8000) |
| | BIT(VIRTIO_SND_PCM_RATE_11025) |
| | BIT(VIRTIO_SND_PCM_RATE_16000) |
| | BIT(VIRTIO_SND_PCM_RATE_22050) |
| | BIT(VIRTIO_SND_PCM_RATE_32000) |
| | BIT(VIRTIO_SND_PCM_RATE_44100) |
| | BIT(VIRTIO_SND_PCM_RATE_48000) |
| | BIT(VIRTIO_SND_PCM_RATE_64000) |
| | BIT(VIRTIO_SND_PCM_RATE_88200) |
| | BIT(VIRTIO_SND_PCM_RATE_96000) |
| | BIT(VIRTIO_SND_PCM_RATE_176400) |
| | BIT(VIRTIO_SND_PCM_RATE_192000) |
| | BIT(VIRTIO_SND_PCM_RATE_384000); |
| |
| static const VMStateDescription vmstate_virtio_snd_device = { |
| .name = TYPE_VIRTIO_SND, |
| .version_id = VIRTIO_SOUND_VM_VERSION, |
| .minimum_version_id = VIRTIO_SOUND_VM_VERSION, |
| }; |
| |
| static const VMStateDescription vmstate_virtio_snd = { |
| .name = TYPE_VIRTIO_SND, |
| .unmigratable = 1, |
| .minimum_version_id = VIRTIO_SOUND_VM_VERSION, |
| .version_id = VIRTIO_SOUND_VM_VERSION, |
| .fields = (const VMStateField[]) { |
| VMSTATE_VIRTIO_DEVICE, |
| VMSTATE_END_OF_LIST() |
| }, |
| }; |
| |
| static Property virtio_snd_properties[] = { |
| DEFINE_AUDIO_PROPERTIES(VirtIOSound, card), |
| DEFINE_PROP_UINT32("jacks", VirtIOSound, snd_conf.jacks, |
| VIRTIO_SOUND_JACK_DEFAULT), |
| DEFINE_PROP_UINT32("streams", VirtIOSound, snd_conf.streams, |
| VIRTIO_SOUND_STREAM_DEFAULT), |
| DEFINE_PROP_UINT32("chmaps", VirtIOSound, snd_conf.chmaps, |
| VIRTIO_SOUND_CHMAP_DEFAULT), |
| DEFINE_PROP_END_OF_LIST(), |
| }; |
| |
| static void |
| virtio_snd_get_config(VirtIODevice *vdev, uint8_t *config) |
| { |
| VirtIOSound *s = VIRTIO_SND(vdev); |
| virtio_snd_config *sndconfig = |
| (virtio_snd_config *)config; |
| trace_virtio_snd_get_config(vdev, |
| s->snd_conf.jacks, |
| s->snd_conf.streams, |
| s->snd_conf.chmaps); |
| |
| memcpy(sndconfig, &s->snd_conf, sizeof(s->snd_conf)); |
| cpu_to_le32s(&sndconfig->jacks); |
| cpu_to_le32s(&sndconfig->streams); |
| cpu_to_le32s(&sndconfig->chmaps); |
| |
| } |
| |
| static void |
| virtio_snd_set_config(VirtIODevice *vdev, const uint8_t *config) |
| { |
| VirtIOSound *s = VIRTIO_SND(vdev); |
| const virtio_snd_config *sndconfig = |
| (const virtio_snd_config *)config; |
| |
| |
| trace_virtio_snd_set_config(vdev, |
| s->snd_conf.jacks, |
| sndconfig->jacks, |
| s->snd_conf.streams, |
| sndconfig->streams, |
| s->snd_conf.chmaps, |
| sndconfig->chmaps); |
| |
| memcpy(&s->snd_conf, sndconfig, sizeof(virtio_snd_config)); |
| le32_to_cpus(&s->snd_conf.jacks); |
| le32_to_cpus(&s->snd_conf.streams); |
| le32_to_cpus(&s->snd_conf.chmaps); |
| |
| } |
| |
| static void |
| virtio_snd_pcm_buffer_free(VirtIOSoundPCMBuffer *buffer) |
| { |
| g_free(buffer->elem); |
| g_free(buffer); |
| } |
| |
| static void |
| virtio_snd_ctrl_cmd_free(virtio_snd_ctrl_command *cmd) |
| { |
| g_free(cmd->elem); |
| g_free(cmd); |
| } |
| |
| /* |
| * Get a specific stream from the virtio sound card device. |
| * Returns NULL if @stream_id is invalid or not allocated. |
| * |
| * @s: VirtIOSound device |
| * @stream_id: stream id |
| */ |
| static VirtIOSoundPCMStream *virtio_snd_pcm_get_stream(VirtIOSound *s, |
| uint32_t stream_id) |
| { |
| return stream_id >= s->snd_conf.streams ? NULL : |
| s->pcm->streams[stream_id]; |
| } |
| |
| /* |
| * Get params for a specific stream. |
| * |
| * @s: VirtIOSound device |
| * @stream_id: stream id |
| */ |
| static virtio_snd_pcm_set_params *virtio_snd_pcm_get_params(VirtIOSound *s, |
| uint32_t stream_id) |
| { |
| return stream_id >= s->snd_conf.streams ? NULL |
| : &s->pcm->pcm_params[stream_id]; |
| } |
| |
| /* |
| * Handle the VIRTIO_SND_R_PCM_INFO request. |
| * The function writes the info structs to the request element. |
| * |
| * @s: VirtIOSound device |
| * @cmd: The request command queue element from VirtIOSound cmdq field |
| */ |
| static void virtio_snd_handle_pcm_info(VirtIOSound *s, |
| virtio_snd_ctrl_command *cmd) |
| { |
| uint32_t stream_id, start_id, count, size; |
| virtio_snd_pcm_info val; |
| virtio_snd_query_info req; |
| VirtIOSoundPCMStream *stream = NULL; |
| g_autofree virtio_snd_pcm_info *pcm_info = NULL; |
| size_t msg_sz = iov_to_buf(cmd->elem->out_sg, |
| cmd->elem->out_num, |
| 0, |
| &req, |
| sizeof(virtio_snd_query_info)); |
| |
| if (msg_sz != sizeof(virtio_snd_query_info)) { |
| /* |
| * TODO: do we need to set DEVICE_NEEDS_RESET? |
| */ |
| qemu_log_mask(LOG_GUEST_ERROR, |
| "%s: virtio-snd command size incorrect %zu vs \ |
| %zu\n", __func__, msg_sz, sizeof(virtio_snd_query_info)); |
| cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG); |
| return; |
| } |
| |
| start_id = le32_to_cpu(req.start_id); |
| count = le32_to_cpu(req.count); |
| size = le32_to_cpu(req.size); |
| |
| if (iov_size(cmd->elem->in_sg, cmd->elem->in_num) < |
| sizeof(virtio_snd_hdr) + size * count) { |
| /* |
| * TODO: do we need to set DEVICE_NEEDS_RESET? |
| */ |
| error_report("pcm info: buffer too small, got: %zu, needed: %zu", |
| iov_size(cmd->elem->in_sg, cmd->elem->in_num), |
| sizeof(virtio_snd_pcm_info)); |
| cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG); |
| return; |
| } |
| |
| pcm_info = g_new0(virtio_snd_pcm_info, count); |
| for (uint32_t i = 0; i < count; i++) { |
| stream_id = i + start_id; |
| trace_virtio_snd_handle_pcm_info(stream_id); |
| stream = virtio_snd_pcm_get_stream(s, stream_id); |
| if (!stream) { |
| error_report("Invalid stream id: %"PRIu32, stream_id); |
| cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG); |
| return; |
| } |
| val = stream->info; |
| val.hdr.hda_fn_nid = cpu_to_le32(val.hdr.hda_fn_nid); |
| val.features = cpu_to_le32(val.features); |
| val.formats = cpu_to_le64(val.formats); |
| val.rates = cpu_to_le64(val.rates); |
| /* |
| * 5.14.6.6.2.1 Device Requirements: Stream Information The device MUST |
| * NOT set undefined feature, format, rate and direction values. The |
| * device MUST initialize the padding bytes to 0. |
| */ |
| pcm_info[i] = val; |
| memset(&pcm_info[i].padding, 0, 5); |
| } |
| |
| cmd->payload_size = sizeof(virtio_snd_pcm_info) * count; |
| cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK); |
| iov_from_buf(cmd->elem->in_sg, |
| cmd->elem->in_num, |
| sizeof(virtio_snd_hdr), |
| pcm_info, |
| cmd->payload_size); |
| } |
| |
| /* |
| * Set the given stream params. |
| * Called by both virtio_snd_handle_pcm_set_params and during device |
| * initialization. |
| * Returns the response status code. (VIRTIO_SND_S_*). |
| * |
| * @s: VirtIOSound device |
| * @params: The PCM params as defined in the virtio specification |
| */ |
| static |
| uint32_t virtio_snd_set_pcm_params(VirtIOSound *s, |
| uint32_t stream_id, |
| virtio_snd_pcm_set_params *params) |
| { |
| virtio_snd_pcm_set_params *st_params; |
| |
| if (stream_id >= s->snd_conf.streams || s->pcm->pcm_params == NULL) { |
| /* |
| * TODO: do we need to set DEVICE_NEEDS_RESET? |
| */ |
| virtio_error(VIRTIO_DEVICE(s), "Streams have not been initialized.\n"); |
| return cpu_to_le32(VIRTIO_SND_S_BAD_MSG); |
| } |
| |
| st_params = virtio_snd_pcm_get_params(s, stream_id); |
| |
| if (params->channels < 1 || params->channels > AUDIO_MAX_CHANNELS) { |
| error_report("Number of channels is not supported."); |
| return cpu_to_le32(VIRTIO_SND_S_NOT_SUPP); |
| } |
| if (BIT(params->format) > sizeof(supported_formats) || |
| !(supported_formats & BIT(params->format))) { |
| error_report("Stream format is not supported."); |
| return cpu_to_le32(VIRTIO_SND_S_NOT_SUPP); |
| } |
| if (BIT(params->rate) > sizeof(supported_rates) || |
| !(supported_rates & BIT(params->rate))) { |
| error_report("Stream rate is not supported."); |
| return cpu_to_le32(VIRTIO_SND_S_NOT_SUPP); |
| } |
| |
| st_params->buffer_bytes = le32_to_cpu(params->buffer_bytes); |
| st_params->period_bytes = le32_to_cpu(params->period_bytes); |
| st_params->features = le32_to_cpu(params->features); |
| /* the following are uint8_t, so there's no need to bswap the values. */ |
| st_params->channels = params->channels; |
| st_params->format = params->format; |
| st_params->rate = params->rate; |
| |
| return cpu_to_le32(VIRTIO_SND_S_OK); |
| } |
| |
| /* |
| * Handles the VIRTIO_SND_R_PCM_SET_PARAMS request. |
| * |
| * @s: VirtIOSound device |
| * @cmd: The request command queue element from VirtIOSound cmdq field |
| */ |
| static void virtio_snd_handle_pcm_set_params(VirtIOSound *s, |
| virtio_snd_ctrl_command *cmd) |
| { |
| virtio_snd_pcm_set_params req = { 0 }; |
| uint32_t stream_id; |
| size_t msg_sz = iov_to_buf(cmd->elem->out_sg, |
| cmd->elem->out_num, |
| 0, |
| &req, |
| sizeof(virtio_snd_pcm_set_params)); |
| |
| if (msg_sz != sizeof(virtio_snd_pcm_set_params)) { |
| /* |
| * TODO: do we need to set DEVICE_NEEDS_RESET? |
| */ |
| qemu_log_mask(LOG_GUEST_ERROR, |
| "%s: virtio-snd command size incorrect %zu vs \ |
| %zu\n", __func__, msg_sz, sizeof(virtio_snd_pcm_set_params)); |
| cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG); |
| return; |
| } |
| stream_id = le32_to_cpu(req.hdr.stream_id); |
| trace_virtio_snd_handle_pcm_set_params(stream_id); |
| cmd->resp.code = virtio_snd_set_pcm_params(s, stream_id, &req); |
| } |
| |
| /* |
| * Get a QEMU Audiosystem compatible format value from a VIRTIO_SND_PCM_FMT_* |
| */ |
| static AudioFormat virtio_snd_get_qemu_format(uint32_t format) |
| { |
| #define CASE(FMT) \ |
| case VIRTIO_SND_PCM_FMT_##FMT: \ |
| return AUDIO_FORMAT_##FMT; |
| |
| switch (format) { |
| CASE(U8) |
| CASE(S8) |
| CASE(U16) |
| CASE(S16) |
| CASE(U32) |
| CASE(S32) |
| case VIRTIO_SND_PCM_FMT_FLOAT: |
| return AUDIO_FORMAT_F32; |
| default: |
| g_assert_not_reached(); |
| } |
| |
| #undef CASE |
| } |
| |
| /* |
| * Get a QEMU Audiosystem compatible frequency value from a |
| * VIRTIO_SND_PCM_RATE_* |
| */ |
| static uint32_t virtio_snd_get_qemu_freq(uint32_t rate) |
| { |
| #define CASE(RATE) \ |
| case VIRTIO_SND_PCM_RATE_##RATE: \ |
| return RATE; |
| |
| switch (rate) { |
| CASE(5512) |
| CASE(8000) |
| CASE(11025) |
| CASE(16000) |
| CASE(22050) |
| CASE(32000) |
| CASE(44100) |
| CASE(48000) |
| CASE(64000) |
| CASE(88200) |
| CASE(96000) |
| CASE(176400) |
| CASE(192000) |
| CASE(384000) |
| default: |
| g_assert_not_reached(); |
| } |
| |
| #undef CASE |
| } |
| |
| /* |
| * Get QEMU Audiosystem compatible audsettings from virtio based pcm stream |
| * params. |
| */ |
| static void virtio_snd_get_qemu_audsettings(audsettings *as, |
| virtio_snd_pcm_set_params *params) |
| { |
| as->nchannels = MIN(AUDIO_MAX_CHANNELS, params->channels); |
| as->fmt = virtio_snd_get_qemu_format(params->format); |
| as->freq = virtio_snd_get_qemu_freq(params->rate); |
| as->endianness = 0; /* Conforming to VIRTIO 1.0: always little endian. */ |
| } |
| |
| /* |
| * Close a stream and free all its resources. |
| * |
| * @stream: VirtIOSoundPCMStream *stream |
| */ |
| static void virtio_snd_pcm_close(VirtIOSoundPCMStream *stream) |
| { |
| if (stream) { |
| virtio_snd_pcm_flush(stream); |
| if (stream->info.direction == VIRTIO_SND_D_OUTPUT) { |
| AUD_close_out(&stream->pcm->snd->card, stream->voice.out); |
| stream->voice.out = NULL; |
| } else if (stream->info.direction == VIRTIO_SND_D_INPUT) { |
| AUD_close_in(&stream->pcm->snd->card, stream->voice.in); |
| stream->voice.in = NULL; |
| } |
| } |
| } |
| |
| /* |
| * Prepares a VirtIOSound card stream. |
| * Returns the response status code. (VIRTIO_SND_S_*). |
| * |
| * @s: VirtIOSound device |
| * @stream_id: stream id |
| */ |
| static uint32_t virtio_snd_pcm_prepare(VirtIOSound *s, uint32_t stream_id) |
| { |
| audsettings as; |
| virtio_snd_pcm_set_params *params; |
| VirtIOSoundPCMStream *stream; |
| |
| if (s->pcm->streams == NULL || |
| s->pcm->pcm_params == NULL || |
| stream_id >= s->snd_conf.streams) { |
| return cpu_to_le32(VIRTIO_SND_S_BAD_MSG); |
| } |
| |
| params = virtio_snd_pcm_get_params(s, stream_id); |
| if (params == NULL) { |
| return cpu_to_le32(VIRTIO_SND_S_BAD_MSG); |
| } |
| |
| stream = virtio_snd_pcm_get_stream(s, stream_id); |
| if (stream == NULL) { |
| stream = g_new0(VirtIOSoundPCMStream, 1); |
| stream->active = false; |
| stream->id = stream_id; |
| stream->pcm = s->pcm; |
| stream->s = s; |
| qemu_mutex_init(&stream->queue_mutex); |
| QSIMPLEQ_INIT(&stream->queue); |
| |
| /* |
| * stream_id >= s->snd_conf.streams was checked before so this is |
| * in-bounds |
| */ |
| s->pcm->streams[stream_id] = stream; |
| } |
| |
| virtio_snd_get_qemu_audsettings(&as, params); |
| stream->info.direction = stream_id < s->snd_conf.streams / 2 + |
| (s->snd_conf.streams & 1) ? VIRTIO_SND_D_OUTPUT : VIRTIO_SND_D_INPUT; |
| stream->info.hdr.hda_fn_nid = VIRTIO_SOUND_HDA_FN_NID; |
| stream->info.features = 0; |
| stream->info.channels_min = 1; |
| stream->info.channels_max = as.nchannels; |
| stream->info.formats = supported_formats; |
| stream->info.rates = supported_rates; |
| stream->params = *params; |
| |
| stream->positions[0] = VIRTIO_SND_CHMAP_FL; |
| stream->positions[1] = VIRTIO_SND_CHMAP_FR; |
| stream->as = as; |
| |
| if (stream->info.direction == VIRTIO_SND_D_OUTPUT) { |
| stream->voice.out = AUD_open_out(&s->card, |
| stream->voice.out, |
| "virtio-sound.out", |
| stream, |
| virtio_snd_pcm_out_cb, |
| &as); |
| AUD_set_volume_out(stream->voice.out, 0, 255, 255); |
| } else { |
| stream->voice.in = AUD_open_in(&s->card, |
| stream->voice.in, |
| "virtio-sound.in", |
| stream, |
| virtio_snd_pcm_in_cb, |
| &as); |
| AUD_set_volume_in(stream->voice.in, 0, 255, 255); |
| } |
| |
| return cpu_to_le32(VIRTIO_SND_S_OK); |
| } |
| |
| static const char *print_code(uint32_t code) |
| { |
| #define CASE(CODE) \ |
| case VIRTIO_SND_R_##CODE: \ |
| return "VIRTIO_SND_R_"#CODE |
| |
| switch (code) { |
| CASE(JACK_INFO); |
| CASE(JACK_REMAP); |
| CASE(PCM_INFO); |
| CASE(PCM_SET_PARAMS); |
| CASE(PCM_PREPARE); |
| CASE(PCM_RELEASE); |
| CASE(PCM_START); |
| CASE(PCM_STOP); |
| CASE(CHMAP_INFO); |
| default: |
| return "invalid code"; |
| } |
| |
| #undef CASE |
| }; |
| |
| /* |
| * Handles VIRTIO_SND_R_PCM_PREPARE. |
| * |
| * @s: VirtIOSound device |
| * @cmd: The request command queue element from VirtIOSound cmdq field |
| */ |
| static void virtio_snd_handle_pcm_prepare(VirtIOSound *s, |
| virtio_snd_ctrl_command *cmd) |
| { |
| uint32_t stream_id; |
| size_t msg_sz = iov_to_buf(cmd->elem->out_sg, |
| cmd->elem->out_num, |
| sizeof(virtio_snd_hdr), |
| &stream_id, |
| sizeof(stream_id)); |
| |
| stream_id = le32_to_cpu(stream_id); |
| cmd->resp.code = msg_sz == sizeof(stream_id) |
| ? virtio_snd_pcm_prepare(s, stream_id) |
| : cpu_to_le32(VIRTIO_SND_S_BAD_MSG); |
| } |
| |
| /* |
| * Handles VIRTIO_SND_R_PCM_START. |
| * |
| * @s: VirtIOSound device |
| * @cmd: The request command queue element from VirtIOSound cmdq field |
| * @start: whether to start or stop the device |
| */ |
| static void virtio_snd_handle_pcm_start_stop(VirtIOSound *s, |
| virtio_snd_ctrl_command *cmd, |
| bool start) |
| { |
| VirtIOSoundPCMStream *stream; |
| virtio_snd_pcm_hdr req; |
| uint32_t stream_id; |
| size_t msg_sz = iov_to_buf(cmd->elem->out_sg, |
| cmd->elem->out_num, |
| 0, |
| &req, |
| sizeof(virtio_snd_pcm_hdr)); |
| |
| if (msg_sz != sizeof(virtio_snd_pcm_hdr)) { |
| qemu_log_mask(LOG_GUEST_ERROR, |
| "%s: virtio-snd command size incorrect %zu vs \ |
| %zu\n", __func__, msg_sz, sizeof(virtio_snd_pcm_hdr)); |
| cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG); |
| return; |
| } |
| |
| stream_id = le32_to_cpu(req.stream_id); |
| cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK); |
| trace_virtio_snd_handle_pcm_start_stop(start ? "VIRTIO_SND_R_PCM_START" : |
| "VIRTIO_SND_R_PCM_STOP", stream_id); |
| |
| stream = virtio_snd_pcm_get_stream(s, stream_id); |
| if (stream) { |
| WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) { |
| stream->active = start; |
| } |
| if (stream->info.direction == VIRTIO_SND_D_OUTPUT) { |
| AUD_set_active_out(stream->voice.out, start); |
| } else { |
| AUD_set_active_in(stream->voice.in, start); |
| } |
| } else { |
| error_report("Invalid stream id: %"PRIu32, stream_id); |
| cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG); |
| return; |
| } |
| stream->active = start; |
| } |
| |
| /* |
| * Returns the number of I/O messages that are being processed. |
| * |
| * @stream: VirtIOSoundPCMStream |
| */ |
| static size_t virtio_snd_pcm_get_io_msgs_count(VirtIOSoundPCMStream *stream) |
| { |
| VirtIOSoundPCMBuffer *buffer, *next; |
| size_t count = 0; |
| |
| WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) { |
| QSIMPLEQ_FOREACH_SAFE(buffer, &stream->queue, entry, next) { |
| count += 1; |
| } |
| } |
| return count; |
| } |
| |
| /* |
| * Handles VIRTIO_SND_R_PCM_RELEASE. |
| * |
| * @s: VirtIOSound device |
| * @cmd: The request command queue element from VirtIOSound cmdq field |
| */ |
| static void virtio_snd_handle_pcm_release(VirtIOSound *s, |
| virtio_snd_ctrl_command *cmd) |
| { |
| uint32_t stream_id; |
| VirtIOSoundPCMStream *stream; |
| size_t msg_sz = iov_to_buf(cmd->elem->out_sg, |
| cmd->elem->out_num, |
| sizeof(virtio_snd_hdr), |
| &stream_id, |
| sizeof(stream_id)); |
| |
| if (msg_sz != sizeof(stream_id)) { |
| /* |
| * TODO: do we need to set DEVICE_NEEDS_RESET? |
| */ |
| qemu_log_mask(LOG_GUEST_ERROR, |
| "%s: virtio-snd command size incorrect %zu vs \ |
| %zu\n", __func__, msg_sz, sizeof(stream_id)); |
| cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG); |
| return; |
| } |
| |
| stream_id = le32_to_cpu(stream_id); |
| trace_virtio_snd_handle_pcm_release(stream_id); |
| stream = virtio_snd_pcm_get_stream(s, stream_id); |
| if (stream == NULL) { |
| /* |
| * TODO: do we need to set DEVICE_NEEDS_RESET? |
| */ |
| error_report("already released stream %"PRIu32, stream_id); |
| virtio_error(VIRTIO_DEVICE(s), |
| "already released stream %"PRIu32, |
| stream_id); |
| cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG); |
| return; |
| } |
| |
| if (virtio_snd_pcm_get_io_msgs_count(stream)) { |
| /* |
| * virtio-v1.2-csd01, 5.14.6.6.5.1, |
| * Device Requirements: Stream Release |
| * |
| * - The device MUST complete all pending I/O messages for the |
| * specified stream ID. |
| * - The device MUST NOT complete the control request while there |
| * are pending I/O messages for the specified stream ID. |
| */ |
| trace_virtio_snd_pcm_stream_flush(stream_id); |
| virtio_snd_pcm_flush(stream); |
| } |
| |
| cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK); |
| } |
| |
| /* |
| * The actual processing done in virtio_snd_process_cmdq(). |
| * |
| * @s: VirtIOSound device |
| * @cmd: control command request |
| */ |
| static inline void |
| process_cmd(VirtIOSound *s, virtio_snd_ctrl_command *cmd) |
| { |
| uint32_t code; |
| size_t msg_sz = iov_to_buf(cmd->elem->out_sg, |
| cmd->elem->out_num, |
| 0, |
| &cmd->ctrl, |
| sizeof(virtio_snd_hdr)); |
| |
| if (msg_sz != sizeof(virtio_snd_hdr)) { |
| /* |
| * TODO: do we need to set DEVICE_NEEDS_RESET? |
| */ |
| qemu_log_mask(LOG_GUEST_ERROR, |
| "%s: virtio-snd command size incorrect %zu vs \ |
| %zu\n", __func__, msg_sz, sizeof(virtio_snd_hdr)); |
| return; |
| } |
| |
| code = le32_to_cpu(cmd->ctrl.code); |
| |
| trace_virtio_snd_handle_code(code, print_code(code)); |
| |
| switch (code) { |
| case VIRTIO_SND_R_JACK_INFO: |
| case VIRTIO_SND_R_JACK_REMAP: |
| qemu_log_mask(LOG_UNIMP, |
| "virtio_snd: jack functionality is unimplemented.\n"); |
| cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_NOT_SUPP); |
| break; |
| case VIRTIO_SND_R_PCM_INFO: |
| virtio_snd_handle_pcm_info(s, cmd); |
| break; |
| case VIRTIO_SND_R_PCM_START: |
| virtio_snd_handle_pcm_start_stop(s, cmd, true); |
| break; |
| case VIRTIO_SND_R_PCM_STOP: |
| virtio_snd_handle_pcm_start_stop(s, cmd, false); |
| break; |
| case VIRTIO_SND_R_PCM_SET_PARAMS: |
| virtio_snd_handle_pcm_set_params(s, cmd); |
| break; |
| case VIRTIO_SND_R_PCM_PREPARE: |
| virtio_snd_handle_pcm_prepare(s, cmd); |
| break; |
| case VIRTIO_SND_R_PCM_RELEASE: |
| virtio_snd_handle_pcm_release(s, cmd); |
| break; |
| case VIRTIO_SND_R_CHMAP_INFO: |
| qemu_log_mask(LOG_UNIMP, |
| "virtio_snd: chmap info functionality is unimplemented.\n"); |
| trace_virtio_snd_handle_chmap_info(); |
| cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_NOT_SUPP); |
| break; |
| default: |
| /* error */ |
| error_report("virtio snd header not recognized: %"PRIu32, code); |
| cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG); |
| } |
| |
| iov_from_buf(cmd->elem->in_sg, |
| cmd->elem->in_num, |
| 0, |
| &cmd->resp, |
| sizeof(virtio_snd_hdr)); |
| virtqueue_push(cmd->vq, cmd->elem, |
| sizeof(virtio_snd_hdr) + cmd->payload_size); |
| virtio_notify(VIRTIO_DEVICE(s), cmd->vq); |
| } |
| |
| /* |
| * Consume all elements in command queue. |
| * |
| * @s: VirtIOSound device |
| */ |
| static void virtio_snd_process_cmdq(VirtIOSound *s) |
| { |
| virtio_snd_ctrl_command *cmd; |
| |
| if (unlikely(qatomic_read(&s->processing_cmdq))) { |
| return; |
| } |
| |
| WITH_QEMU_LOCK_GUARD(&s->cmdq_mutex) { |
| qatomic_set(&s->processing_cmdq, true); |
| while (!QTAILQ_EMPTY(&s->cmdq)) { |
| cmd = QTAILQ_FIRST(&s->cmdq); |
| |
| /* process command */ |
| process_cmd(s, cmd); |
| |
| QTAILQ_REMOVE(&s->cmdq, cmd, next); |
| |
| virtio_snd_ctrl_cmd_free(cmd); |
| } |
| qatomic_set(&s->processing_cmdq, false); |
| } |
| } |
| |
| /* |
| * The control message handler. Pops an element from the control virtqueue, |
| * and stores them to VirtIOSound's cmdq queue and finally calls |
| * virtio_snd_process_cmdq() for processing. |
| * |
| * @vdev: VirtIOSound device |
| * @vq: Control virtqueue |
| */ |
| static void virtio_snd_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq) |
| { |
| VirtIOSound *s = VIRTIO_SND(vdev); |
| VirtQueueElement *elem; |
| virtio_snd_ctrl_command *cmd; |
| |
| trace_virtio_snd_handle_ctrl(vdev, vq); |
| |
| if (!virtio_queue_ready(vq)) { |
| return; |
| } |
| |
| elem = virtqueue_pop(vq, sizeof(VirtQueueElement)); |
| while (elem) { |
| cmd = g_new0(virtio_snd_ctrl_command, 1); |
| cmd->elem = elem; |
| cmd->vq = vq; |
| cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK); |
| /* implicit cmd->payload_size = 0; */ |
| QTAILQ_INSERT_TAIL(&s->cmdq, cmd, next); |
| elem = virtqueue_pop(vq, sizeof(VirtQueueElement)); |
| } |
| |
| virtio_snd_process_cmdq(s); |
| } |
| |
| /* |
| * The event virtqueue handler. |
| * Not implemented yet. |
| * |
| * @vdev: VirtIOSound device |
| * @vq: event vq |
| */ |
| static void virtio_snd_handle_event(VirtIODevice *vdev, VirtQueue *vq) |
| { |
| qemu_log_mask(LOG_UNIMP, "virtio_snd: event queue is unimplemented.\n"); |
| trace_virtio_snd_handle_event(); |
| } |
| |
| /* |
| * Must only be called if vsnd->invalid is not empty. |
| */ |
| static inline void empty_invalid_queue(VirtIODevice *vdev, VirtQueue *vq) |
| { |
| VirtIOSoundPCMBuffer *buffer = NULL; |
| virtio_snd_pcm_status resp = { 0 }; |
| VirtIOSound *vsnd = VIRTIO_SND(vdev); |
| |
| g_assert(!QSIMPLEQ_EMPTY(&vsnd->invalid)); |
| |
| while (!QSIMPLEQ_EMPTY(&vsnd->invalid)) { |
| buffer = QSIMPLEQ_FIRST(&vsnd->invalid); |
| /* If buffer->vq != vq, our logic is fundamentally wrong, so bail out */ |
| g_assert(buffer->vq == vq); |
| |
| resp.status = cpu_to_le32(VIRTIO_SND_S_BAD_MSG); |
| iov_from_buf(buffer->elem->in_sg, |
| buffer->elem->in_num, |
| 0, |
| &resp, |
| sizeof(virtio_snd_pcm_status)); |
| virtqueue_push(vq, |
| buffer->elem, |
| sizeof(virtio_snd_pcm_status)); |
| QSIMPLEQ_REMOVE_HEAD(&vsnd->invalid, entry); |
| virtio_snd_pcm_buffer_free(buffer); |
| } |
| /* Notify vq about virtio_snd_pcm_status responses. */ |
| virtio_notify(vdev, vq); |
| } |
| |
| /* |
| * The tx virtqueue handler. Makes the buffers available to their respective |
| * streams for consumption. |
| * |
| * @vdev: VirtIOSound device |
| * @vq: tx virtqueue |
| */ |
| static void virtio_snd_handle_tx_xfer(VirtIODevice *vdev, VirtQueue *vq) |
| { |
| VirtIOSound *vsnd = VIRTIO_SND(vdev); |
| VirtIOSoundPCMBuffer *buffer; |
| VirtQueueElement *elem; |
| size_t msg_sz, size; |
| virtio_snd_pcm_xfer hdr; |
| uint32_t stream_id; |
| /* |
| * If any of the I/O messages are invalid, put them in vsnd->invalid and |
| * return them after the for loop. |
| */ |
| bool must_empty_invalid_queue = false; |
| |
| if (!virtio_queue_ready(vq)) { |
| return; |
| } |
| trace_virtio_snd_handle_tx_xfer(); |
| |
| for (;;) { |
| VirtIOSoundPCMStream *stream; |
| |
| elem = virtqueue_pop(vq, sizeof(VirtQueueElement)); |
| if (!elem) { |
| break; |
| } |
| /* get the message hdr object */ |
| msg_sz = iov_to_buf(elem->out_sg, |
| elem->out_num, |
| 0, |
| &hdr, |
| sizeof(virtio_snd_pcm_xfer)); |
| if (msg_sz != sizeof(virtio_snd_pcm_xfer)) { |
| goto tx_err; |
| } |
| stream_id = le32_to_cpu(hdr.stream_id); |
| |
| if (stream_id >= vsnd->snd_conf.streams |
| || vsnd->pcm->streams[stream_id] == NULL) { |
| goto tx_err; |
| } |
| |
| stream = vsnd->pcm->streams[stream_id]; |
| if (stream->info.direction != VIRTIO_SND_D_OUTPUT) { |
| goto tx_err; |
| } |
| |
| WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) { |
| size = iov_size(elem->out_sg, elem->out_num) - msg_sz; |
| |
| buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer) + size); |
| buffer->elem = elem; |
| buffer->populated = false; |
| buffer->vq = vq; |
| buffer->size = size; |
| buffer->offset = 0; |
| |
| QSIMPLEQ_INSERT_TAIL(&stream->queue, buffer, entry); |
| } |
| continue; |
| |
| tx_err: |
| must_empty_invalid_queue = true; |
| buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer)); |
| buffer->elem = elem; |
| buffer->vq = vq; |
| QSIMPLEQ_INSERT_TAIL(&vsnd->invalid, buffer, entry); |
| } |
| |
| if (must_empty_invalid_queue) { |
| empty_invalid_queue(vdev, vq); |
| } |
| } |
| |
| /* |
| * The rx virtqueue handler. Makes the buffers available to their respective |
| * streams for consumption. |
| * |
| * @vdev: VirtIOSound device |
| * @vq: rx virtqueue |
| */ |
| static void virtio_snd_handle_rx_xfer(VirtIODevice *vdev, VirtQueue *vq) |
| { |
| VirtIOSound *vsnd = VIRTIO_SND(vdev); |
| VirtIOSoundPCMBuffer *buffer; |
| VirtQueueElement *elem; |
| size_t msg_sz, size; |
| virtio_snd_pcm_xfer hdr; |
| uint32_t stream_id; |
| /* |
| * if any of the I/O messages are invalid, put them in vsnd->invalid and |
| * return them after the for loop. |
| */ |
| bool must_empty_invalid_queue = false; |
| |
| if (!virtio_queue_ready(vq)) { |
| return; |
| } |
| trace_virtio_snd_handle_rx_xfer(); |
| |
| for (;;) { |
| VirtIOSoundPCMStream *stream; |
| |
| elem = virtqueue_pop(vq, sizeof(VirtQueueElement)); |
| if (!elem) { |
| break; |
| } |
| /* get the message hdr object */ |
| msg_sz = iov_to_buf(elem->out_sg, |
| elem->out_num, |
| 0, |
| &hdr, |
| sizeof(virtio_snd_pcm_xfer)); |
| if (msg_sz != sizeof(virtio_snd_pcm_xfer)) { |
| goto rx_err; |
| } |
| stream_id = le32_to_cpu(hdr.stream_id); |
| |
| if (stream_id >= vsnd->snd_conf.streams |
| || !vsnd->pcm->streams[stream_id]) { |
| goto rx_err; |
| } |
| |
| stream = vsnd->pcm->streams[stream_id]; |
| if (stream == NULL || stream->info.direction != VIRTIO_SND_D_INPUT) { |
| goto rx_err; |
| } |
| WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) { |
| size = iov_size(elem->in_sg, elem->in_num) - |
| sizeof(virtio_snd_pcm_status); |
| buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer) + size); |
| buffer->elem = elem; |
| buffer->vq = vq; |
| buffer->size = 0; |
| buffer->offset = 0; |
| QSIMPLEQ_INSERT_TAIL(&stream->queue, buffer, entry); |
| } |
| continue; |
| |
| rx_err: |
| must_empty_invalid_queue = true; |
| buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer)); |
| buffer->elem = elem; |
| buffer->vq = vq; |
| QSIMPLEQ_INSERT_TAIL(&vsnd->invalid, buffer, entry); |
| } |
| |
| if (must_empty_invalid_queue) { |
| empty_invalid_queue(vdev, vq); |
| } |
| } |
| |
| static uint64_t get_features(VirtIODevice *vdev, uint64_t features, |
| Error **errp) |
| { |
| /* |
| * virtio-v1.2-csd01, 5.14.3, |
| * Feature Bits |
| * None currently defined. |
| */ |
| VirtIOSound *s = VIRTIO_SND(vdev); |
| features |= s->features; |
| |
| trace_virtio_snd_get_features(vdev, features); |
| |
| return features; |
| } |
| |
| static void |
| virtio_snd_vm_state_change(void *opaque, bool running, |
| RunState state) |
| { |
| if (running) { |
| trace_virtio_snd_vm_state_running(); |
| } else { |
| trace_virtio_snd_vm_state_stopped(); |
| } |
| } |
| |
| static void virtio_snd_realize(DeviceState *dev, Error **errp) |
| { |
| ERRP_GUARD(); |
| VirtIOSound *vsnd = VIRTIO_SND(dev); |
| VirtIODevice *vdev = VIRTIO_DEVICE(dev); |
| virtio_snd_pcm_set_params default_params = { 0 }; |
| uint32_t status; |
| |
| trace_virtio_snd_realize(vsnd); |
| |
| /* check number of jacks and streams */ |
| if (vsnd->snd_conf.jacks > 8) { |
| error_setg(errp, |
| "Invalid number of jacks: %"PRIu32, |
| vsnd->snd_conf.jacks); |
| return; |
| } |
| if (vsnd->snd_conf.streams < 1 || vsnd->snd_conf.streams > 10) { |
| error_setg(errp, |
| "Invalid number of streams: %"PRIu32, |
| vsnd->snd_conf.streams); |
| return; |
| } |
| |
| if (vsnd->snd_conf.chmaps > VIRTIO_SND_CHMAP_MAX_SIZE) { |
| error_setg(errp, |
| "Invalid number of channel maps: %"PRIu32, |
| vsnd->snd_conf.chmaps); |
| return; |
| } |
| |
| if (!AUD_register_card("virtio-sound", &vsnd->card, errp)) { |
| return; |
| } |
| |
| vsnd->vmstate = |
| qemu_add_vm_change_state_handler(virtio_snd_vm_state_change, vsnd); |
| |
| vsnd->pcm = g_new0(VirtIOSoundPCM, 1); |
| vsnd->pcm->snd = vsnd; |
| vsnd->pcm->streams = |
| g_new0(VirtIOSoundPCMStream *, vsnd->snd_conf.streams); |
| vsnd->pcm->pcm_params = |
| g_new0(virtio_snd_pcm_set_params, vsnd->snd_conf.streams); |
| |
| virtio_init(vdev, VIRTIO_ID_SOUND, sizeof(virtio_snd_config)); |
| virtio_add_feature(&vsnd->features, VIRTIO_F_VERSION_1); |
| |
| /* set default params for all streams */ |
| default_params.features = 0; |
| default_params.buffer_bytes = cpu_to_le32(8192); |
| default_params.period_bytes = cpu_to_le32(2048); |
| default_params.channels = 2; |
| default_params.format = VIRTIO_SND_PCM_FMT_S16; |
| default_params.rate = VIRTIO_SND_PCM_RATE_48000; |
| vsnd->queues[VIRTIO_SND_VQ_CONTROL] = |
| virtio_add_queue(vdev, 64, virtio_snd_handle_ctrl); |
| vsnd->queues[VIRTIO_SND_VQ_EVENT] = |
| virtio_add_queue(vdev, 64, virtio_snd_handle_event); |
| vsnd->queues[VIRTIO_SND_VQ_TX] = |
| virtio_add_queue(vdev, 64, virtio_snd_handle_tx_xfer); |
| vsnd->queues[VIRTIO_SND_VQ_RX] = |
| virtio_add_queue(vdev, 64, virtio_snd_handle_rx_xfer); |
| qemu_mutex_init(&vsnd->cmdq_mutex); |
| QTAILQ_INIT(&vsnd->cmdq); |
| QSIMPLEQ_INIT(&vsnd->invalid); |
| |
| for (uint32_t i = 0; i < vsnd->snd_conf.streams; i++) { |
| status = virtio_snd_set_pcm_params(vsnd, i, &default_params); |
| if (status != cpu_to_le32(VIRTIO_SND_S_OK)) { |
| error_setg(errp, |
| "Can't initialize stream params, device responded with %s.", |
| print_code(status)); |
| goto error_cleanup; |
| } |
| status = virtio_snd_pcm_prepare(vsnd, i); |
| if (status != cpu_to_le32(VIRTIO_SND_S_OK)) { |
| error_setg(errp, |
| "Can't prepare streams, device responded with %s.", |
| print_code(status)); |
| goto error_cleanup; |
| } |
| } |
| |
| return; |
| |
| error_cleanup: |
| virtio_snd_unrealize(dev); |
| } |
| |
| static inline void return_tx_buffer(VirtIOSoundPCMStream *stream, |
| VirtIOSoundPCMBuffer *buffer) |
| { |
| virtio_snd_pcm_status resp = { 0 }; |
| resp.status = cpu_to_le32(VIRTIO_SND_S_OK); |
| resp.latency_bytes = cpu_to_le32((uint32_t)buffer->size); |
| iov_from_buf(buffer->elem->in_sg, |
| buffer->elem->in_num, |
| 0, |
| &resp, |
| sizeof(virtio_snd_pcm_status)); |
| virtqueue_push(buffer->vq, |
| buffer->elem, |
| sizeof(virtio_snd_pcm_status)); |
| virtio_notify(VIRTIO_DEVICE(stream->s), buffer->vq); |
| QSIMPLEQ_REMOVE(&stream->queue, |
| buffer, |
| VirtIOSoundPCMBuffer, |
| entry); |
| virtio_snd_pcm_buffer_free(buffer); |
| } |
| |
| /* |
| * AUD_* output callback. |
| * |
| * @data: VirtIOSoundPCMStream stream |
| * @available: number of bytes that can be written with AUD_write() |
| */ |
| static void virtio_snd_pcm_out_cb(void *data, int available) |
| { |
| VirtIOSoundPCMStream *stream = data; |
| VirtIOSoundPCMBuffer *buffer; |
| size_t size; |
| |
| WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) { |
| while (!QSIMPLEQ_EMPTY(&stream->queue)) { |
| buffer = QSIMPLEQ_FIRST(&stream->queue); |
| if (!virtio_queue_ready(buffer->vq)) { |
| return; |
| } |
| if (!stream->active) { |
| /* Stream has stopped, so do not perform AUD_write. */ |
| return_tx_buffer(stream, buffer); |
| continue; |
| } |
| if (!buffer->populated) { |
| iov_to_buf(buffer->elem->out_sg, |
| buffer->elem->out_num, |
| sizeof(virtio_snd_pcm_xfer), |
| buffer->data, |
| buffer->size); |
| buffer->populated = true; |
| } |
| for (;;) { |
| size = AUD_write(stream->voice.out, |
| buffer->data + buffer->offset, |
| MIN(buffer->size, available)); |
| assert(size <= MIN(buffer->size, available)); |
| if (size == 0) { |
| /* break out of both loops */ |
| available = 0; |
| break; |
| } |
| buffer->size -= size; |
| buffer->offset += size; |
| available -= size; |
| if (buffer->size < 1) { |
| return_tx_buffer(stream, buffer); |
| break; |
| } |
| if (!available) { |
| break; |
| } |
| } |
| if (!available) { |
| break; |
| } |
| } |
| } |
| } |
| |
| /* |
| * Flush all buffer data from this input stream's queue into the driver's |
| * virtual queue. |
| * |
| * @stream: VirtIOSoundPCMStream *stream |
| */ |
| static inline void return_rx_buffer(VirtIOSoundPCMStream *stream, |
| VirtIOSoundPCMBuffer *buffer) |
| { |
| virtio_snd_pcm_status resp = { 0 }; |
| resp.status = cpu_to_le32(VIRTIO_SND_S_OK); |
| resp.latency_bytes = 0; |
| /* Copy data -if any- to guest */ |
| iov_from_buf(buffer->elem->in_sg, |
| buffer->elem->in_num, |
| 0, |
| buffer->data, |
| buffer->size); |
| iov_from_buf(buffer->elem->in_sg, |
| buffer->elem->in_num, |
| buffer->size, |
| &resp, |
| sizeof(virtio_snd_pcm_status)); |
| virtqueue_push(buffer->vq, |
| buffer->elem, |
| sizeof(virtio_snd_pcm_status) + buffer->size); |
| virtio_notify(VIRTIO_DEVICE(stream->s), buffer->vq); |
| QSIMPLEQ_REMOVE(&stream->queue, |
| buffer, |
| VirtIOSoundPCMBuffer, |
| entry); |
| virtio_snd_pcm_buffer_free(buffer); |
| } |
| |
| |
| /* |
| * AUD_* input callback. |
| * |
| * @data: VirtIOSoundPCMStream stream |
| * @available: number of bytes that can be read with AUD_read() |
| */ |
| static void virtio_snd_pcm_in_cb(void *data, int available) |
| { |
| VirtIOSoundPCMStream *stream = data; |
| VirtIOSoundPCMBuffer *buffer; |
| size_t size, max_size; |
| |
| WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) { |
| while (!QSIMPLEQ_EMPTY(&stream->queue)) { |
| buffer = QSIMPLEQ_FIRST(&stream->queue); |
| if (!virtio_queue_ready(buffer->vq)) { |
| return; |
| } |
| if (!stream->active) { |
| /* Stream has stopped, so do not perform AUD_read. */ |
| return_rx_buffer(stream, buffer); |
| continue; |
| } |
| |
| max_size = iov_size(buffer->elem->in_sg, buffer->elem->in_num); |
| for (;;) { |
| if (buffer->size >= max_size) { |
| return_rx_buffer(stream, buffer); |
| break; |
| } |
| size = AUD_read(stream->voice.in, |
| buffer->data + buffer->size, |
| MIN(available, (stream->params.period_bytes - |
| buffer->size))); |
| if (!size) { |
| available = 0; |
| break; |
| } |
| buffer->size += size; |
| available -= size; |
| if (buffer->size >= stream->params.period_bytes) { |
| return_rx_buffer(stream, buffer); |
| break; |
| } |
| if (!available) { |
| break; |
| } |
| } |
| if (!available) { |
| break; |
| } |
| } |
| } |
| } |
| |
| /* |
| * Flush all buffer data from this output stream's queue into the driver's |
| * virtual queue. |
| * |
| * @stream: VirtIOSoundPCMStream *stream |
| */ |
| static inline void virtio_snd_pcm_flush(VirtIOSoundPCMStream *stream) |
| { |
| VirtIOSoundPCMBuffer *buffer; |
| void (*cb)(VirtIOSoundPCMStream *, VirtIOSoundPCMBuffer *) = |
| (stream->info.direction == VIRTIO_SND_D_OUTPUT) ? return_tx_buffer : |
| return_rx_buffer; |
| |
| WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) { |
| while (!QSIMPLEQ_EMPTY(&stream->queue)) { |
| buffer = QSIMPLEQ_FIRST(&stream->queue); |
| cb(stream, buffer); |
| } |
| } |
| } |
| |
| static void virtio_snd_unrealize(DeviceState *dev) |
| { |
| VirtIODevice *vdev = VIRTIO_DEVICE(dev); |
| VirtIOSound *vsnd = VIRTIO_SND(dev); |
| VirtIOSoundPCMStream *stream; |
| |
| qemu_del_vm_change_state_handler(vsnd->vmstate); |
| trace_virtio_snd_unrealize(vsnd); |
| |
| if (vsnd->pcm) { |
| if (vsnd->pcm->streams) { |
| for (uint32_t i = 0; i < vsnd->snd_conf.streams; i++) { |
| stream = vsnd->pcm->streams[i]; |
| if (stream) { |
| virtio_snd_process_cmdq(stream->s); |
| virtio_snd_pcm_close(stream); |
| qemu_mutex_destroy(&stream->queue_mutex); |
| g_free(stream); |
| } |
| } |
| g_free(vsnd->pcm->streams); |
| } |
| g_free(vsnd->pcm->pcm_params); |
| g_free(vsnd->pcm); |
| vsnd->pcm = NULL; |
| } |
| AUD_remove_card(&vsnd->card); |
| qemu_mutex_destroy(&vsnd->cmdq_mutex); |
| virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_CONTROL]); |
| virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_EVENT]); |
| virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_TX]); |
| virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_RX]); |
| virtio_cleanup(vdev); |
| } |
| |
| |
| static void virtio_snd_reset(VirtIODevice *vdev) |
| { |
| VirtIOSound *vsnd = VIRTIO_SND(vdev); |
| virtio_snd_ctrl_command *cmd; |
| |
| /* |
| * Sanity check that the invalid buffer message queue is emptied at the end |
| * of every virtio_snd_handle_tx_xfer/virtio_snd_handle_rx_xfer call, and |
| * must be empty otherwise. |
| */ |
| g_assert(QSIMPLEQ_EMPTY(&vsnd->invalid)); |
| |
| WITH_QEMU_LOCK_GUARD(&vsnd->cmdq_mutex) { |
| while (!QTAILQ_EMPTY(&vsnd->cmdq)) { |
| cmd = QTAILQ_FIRST(&vsnd->cmdq); |
| QTAILQ_REMOVE(&vsnd->cmdq, cmd, next); |
| virtio_snd_ctrl_cmd_free(cmd); |
| } |
| } |
| } |
| |
| static void virtio_snd_class_init(ObjectClass *klass, void *data) |
| { |
| DeviceClass *dc = DEVICE_CLASS(klass); |
| VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); |
| |
| |
| set_bit(DEVICE_CATEGORY_SOUND, dc->categories); |
| device_class_set_props(dc, virtio_snd_properties); |
| |
| dc->vmsd = &vmstate_virtio_snd; |
| vdc->vmsd = &vmstate_virtio_snd_device; |
| vdc->realize = virtio_snd_realize; |
| vdc->unrealize = virtio_snd_unrealize; |
| vdc->get_config = virtio_snd_get_config; |
| vdc->set_config = virtio_snd_set_config; |
| vdc->get_features = get_features; |
| vdc->reset = virtio_snd_reset; |
| vdc->legacy_features = 0; |
| } |
| |
| static const TypeInfo virtio_snd_types[] = { |
| { |
| .name = TYPE_VIRTIO_SND, |
| .parent = TYPE_VIRTIO_DEVICE, |
| .instance_size = sizeof(VirtIOSound), |
| .class_init = virtio_snd_class_init, |
| } |
| }; |
| |
| DEFINE_TYPES(virtio_snd_types) |