diff mbox series

[v8,10/12] virtio-sound: implement audio output (TX)

Message ID c94a9c1e65bb6bb43c58e5ccb982948424a3f3f2.1693252037.git.manos.pitsidianakis@linaro.org (mailing list archive)
State New, archived
Headers show
Series Add VIRTIO sound card | expand

Commit Message

Manos Pitsidianakis Aug. 28, 2023, 7:55 p.m. UTC
Handle output IO messages in the transmit (TX) virtqueue.

It allocates a VirtIOSoundPCMBlock for each IO message and copies the
data buffer to it. When the IO buffer is written to the host's sound
card, the guest will be notified that it has been consumed.

The lifetime of an IO message is:

1. Guest sends IO message to TX virtqueue.
2. QEMU adds it to the appropriate stream's IO buffer queue.
3. Sometime later, the host audio backend calls the output callback,
   virtio_snd_pcm_out_cb(), which is defined with an AUD_open_out()
   call. The callback gets an available number of bytes the backend can
   receive. Then it writes data from the IO buffer queue to the backend.
   If at any time a buffer is exhausted, it is returned to the guest as
   completed.
4. If the guest releases the stream, its buffer queue is flushed by
   attempting to write any leftover data to the audio backend and
   releasing all IO messages back to the guest. This is how according to
   the spec the guest knows the release was successful.

Based-on: https://github.com/OpenSynergy/qemu/commit/5a2f350eec5d157b90d9c7b40a8e603f4da92471
Signed-off-by: Igor Skalkin <Igor.Skalkin@opensynergy.com>
Signed-off-by: Anton Yakovlev <Anton.Yakovlev@opensynergy.com>
Signed-off-by: Emmanouil Pitsidianakis <manos.pitsidianakis@linaro.org>
---
 hw/virtio/trace-events         |   2 +
 hw/virtio/virtio-snd.c         | 262 ++++++++++++++++++++++++++++++++-
 include/hw/virtio/virtio-snd.h |  11 ++
 3 files changed, 271 insertions(+), 4 deletions(-)

Comments

Alex Bennée Aug. 30, 2023, 1:39 p.m. UTC | #1
Emmanouil Pitsidianakis <manos.pitsidianakis@linaro.org> writes:

> Handle output IO messages in the transmit (TX) virtqueue.
>
> It allocates a VirtIOSoundPCMBlock for each IO message and copies the
> data buffer to it. When the IO buffer is written to the host's sound
> card, the guest will be notified that it has been consumed.
>
> The lifetime of an IO message is:
>
> 1. Guest sends IO message to TX virtqueue.
> 2. QEMU adds it to the appropriate stream's IO buffer queue.
> 3. Sometime later, the host audio backend calls the output callback,
>    virtio_snd_pcm_out_cb(), which is defined with an AUD_open_out()
>    call. The callback gets an available number of bytes the backend can
>    receive. Then it writes data from the IO buffer queue to the backend.
>    If at any time a buffer is exhausted, it is returned to the guest as
>    completed.
> 4. If the guest releases the stream, its buffer queue is flushed by
>    attempting to write any leftover data to the audio backend and
>    releasing all IO messages back to the guest. This is how according to
>    the spec the guest knows the release was successful.
>
> Based-on: https://github.com/OpenSynergy/qemu/commit/5a2f350eec5d157b90d9c7b40a8e603f4da92471
> Signed-off-by: Igor Skalkin <Igor.Skalkin@opensynergy.com>
> Signed-off-by: Anton Yakovlev <Anton.Yakovlev@opensynergy.com>
> Signed-off-by: Emmanouil Pitsidianakis
> <manos.pitsidianakis@linaro.org>

Reviewed-by: Alex Bennée <alex.bennee@linaro.org>
Philippe Mathieu-Daudé Sept. 4, 2023, 10:26 a.m. UTC | #2
On 28/8/23 21:55, Emmanouil Pitsidianakis wrote:
> Handle output IO messages in the transmit (TX) virtqueue.
> 
> It allocates a VirtIOSoundPCMBlock for each IO message and copies the
> data buffer to it. When the IO buffer is written to the host's sound
> card, the guest will be notified that it has been consumed.
> 
> The lifetime of an IO message is:
> 
> 1. Guest sends IO message to TX virtqueue.
> 2. QEMU adds it to the appropriate stream's IO buffer queue.
> 3. Sometime later, the host audio backend calls the output callback,
>     virtio_snd_pcm_out_cb(), which is defined with an AUD_open_out()
>     call. The callback gets an available number of bytes the backend can
>     receive. Then it writes data from the IO buffer queue to the backend.
>     If at any time a buffer is exhausted, it is returned to the guest as
>     completed.
> 4. If the guest releases the stream, its buffer queue is flushed by
>     attempting to write any leftover data to the audio backend and
>     releasing all IO messages back to the guest. This is how according to
>     the spec the guest knows the release was successful.
> 
> Based-on: https://github.com/OpenSynergy/qemu/commit/5a2f350eec5d157b90d9c7b40a8e603f4da92471
> Signed-off-by: Igor Skalkin <Igor.Skalkin@opensynergy.com>
> Signed-off-by: Anton Yakovlev <Anton.Yakovlev@opensynergy.com>
> Signed-off-by: Emmanouil Pitsidianakis <manos.pitsidianakis@linaro.org>
> ---
>   hw/virtio/trace-events         |   2 +
>   hw/virtio/virtio-snd.c         | 262 ++++++++++++++++++++++++++++++++-
>   include/hw/virtio/virtio-snd.h |  11 ++
>   3 files changed, 271 insertions(+), 4 deletions(-)


>   /*
> - * Handles VIRTIO_SND_R_PCM_RELEASE. Releases the buffer resources allocated to
> - * a stream.
> + * Returns the number of I/O messages that are being processed.
> + *
> + * @stream: VirtIOSoundPCMStream
> + */
> +static size_t virtio_snd_pcm_get_pending_io_msgs(VirtIOSoundPCMStream *stream)
> +{
> +    VirtIOSoundPCMBlock *block;
> +    VirtIOSoundPCMBlock *next;
> +    size_t size = 0;
> +
> +    WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
> +        QSIMPLEQ_FOREACH_SAFE(block, &stream->queue, entry, next) {
> +            size += 1;

Can you add a comment explaining this magic size?

> +        }
> +    }
> +    return size;
> +}


> +/*
> + * 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(VirtIODevice *vdev, VirtQueue *vq)
> +{
> +    VirtIOSound *s = VIRTIO_SND(vdev);
> +    VirtIOSoundPCMStream *stream = NULL;
> +    VirtQueueElement *elem;
> +    size_t sz;
> +    virtio_snd_pcm_xfer hdr;
> +    virtio_snd_pcm_status resp = { 0 };

virtio_snd_pcm_status has multiple fields, so better zero-initialize
all of them with '{ }'.

> +
> +    trace_virtio_snd_handle_xfer();
> +
> +    for (;;) {
> +        elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
> +        if (!elem) {
> +            break;
> +        }
> +        /* get the message hdr object */
> +        sz = iov_to_buf(elem->out_sg,
> +                        elem->out_num,
> +                        0,
> +                        &hdr,
> +                        sizeof(hdr));
> +        if (sz != sizeof(hdr)
> +            || hdr.stream_id >= s->snd_conf.streams
> +            || !s->pcm->streams[hdr.stream_id]) {
> +            goto tx_err;
> +        }
> +
> +        stream = s->pcm->streams[hdr.stream_id];
> +        if (stream->direction != VIRTIO_SND_D_OUTPUT) {
> +            goto tx_err;
> +        }
> +
> +        WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
> +            virtio_snd_pcm_read_write(stream,
> +                    vq,
> +                    elem,
> +                    hdr.stream_id == VIRTIO_SND_D_INPUT);
> +
> +            resp.status = VIRTIO_SND_S_OK;
> +            iov_from_buf(elem->in_sg,
> +                         elem->in_num,
> +                         0,
> +                         &resp,
> +                         sizeof(resp));
> +        }
> +        continue;
> +
> +tx_err:
> +        WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
> +            resp.status = VIRTIO_SND_S_BAD_MSG;
> +            iov_from_buf(elem->in_sg,
> +                         elem->in_num,
> +                         0,
> +                         &resp,
> +                         sizeof(resp));
> +        }
> +    }
> +
> +    /*
> +     * Notify vq about virtio_snd_pcm_status responses.
> +     * Buffer responses must be notified separately later.
> +     */
> +    virtio_notify(VIRTIO_DEVICE(s), vq);
> +}


> +/*
> + * 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;
> +    VirtIOSoundPCMBlock *block;
> +    VirtIOSoundPCMBlock *next;
> +    size_t size;
> +
> +    WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
> +        QSIMPLEQ_FOREACH_SAFE(block, &stream->queue, entry, next) {
> +            for (;;) {
> +                size = MIN(block->size, available);
> +                size = AUD_write(stream->voice.out,
> +                        block->data + block->offset,
> +                        size);

If AUD_write() returns 0, is this an infinite loop?

> +                block->size -= size;
> +                block->offset += size;
> +                if (!block->size) {
> +                    virtqueue_push(block->vq,
> +                            block->elem,
> +                            sizeof(block->elem));
> +                    virtio_notify(VIRTIO_DEVICE(stream->s),
> +                            block->vq);
> +                    QSIMPLEQ_REMOVE_HEAD(&stream->queue, entry);
> +                    g_free(block);
> +                    available -= size;
> +                    break;
> +                }
> +
> +                available -= size;
> +                if (!available) {
> +                    break;
> +                }
> +            }
> +            if (!available) {
> +                break;
> +            }
> +        }
> +    }
> +}
> +
> +/*
> + * Flush all buffer data from this stream's queue into the driver's virtual
> + * queue.
> + *
> + * @stream: VirtIOSoundPCMStream *stream
> + */
> +static void virtio_snd_pcm_flush(VirtIOSoundPCMStream *stream)
> +{
> +    VirtIOSoundPCMBlock *block;
> +    VirtIOSoundPCMBlock *next;
> +
> +    WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
> +        QSIMPLEQ_FOREACH_SAFE(block, &stream->queue, entry, next) {
> +            AUD_write(stream->voice.out, block->data + block->offset, block->size);

Is it OK to ignore AUD_write() returning < block->size?
If so, can you add a comment please?

> +            virtqueue_push(block->vq, block->elem, sizeof(block->elem));
> +            virtio_notify(VIRTIO_DEVICE(stream->s), block->vq);
> +            QSIMPLEQ_REMOVE(&stream->queue, block, VirtIOSoundPCMBlock, entry);
> +        }
> +    }
> +}
Manos Pitsidianakis Sept. 4, 2023, 10:34 a.m. UTC | #3
On Mon, 04 Sep 2023 13:26, Philippe Mathieu-Daudé <philmd@linaro.org> wrote:
>>   /*
>> - * Handles VIRTIO_SND_R_PCM_RELEASE. Releases the buffer resources allocated to
>> - * a stream.
>> + * Returns the number of I/O messages that are being processed.
>> + *
>> + * @stream: VirtIOSoundPCMStream
>> + */
>> +static size_t virtio_snd_pcm_get_pending_io_msgs(VirtIOSoundPCMStream *stream)
>> +{
>> +    VirtIOSoundPCMBlock *block;
>> +    VirtIOSoundPCMBlock *next;
>> +    size_t size = 0;
>> +
>> +    WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
>> +        QSIMPLEQ_FOREACH_SAFE(block, &stream->queue, entry, next) {
>> +            size += 1;
>
>Can you add a comment explaining this magic size?

It's not magic, it's simply how many messages there are as explained in 
the function doc comment. This was previously bytes hence `size`. I will 
change the variable name to `count`.

>> +static void virtio_snd_handle_tx(VirtIODevice *vdev, VirtQueue *vq)
>> +{
>> +    VirtIOSound *s = VIRTIO_SND(vdev);
>> +    VirtIOSoundPCMStream *stream = NULL;
>> +    VirtQueueElement *elem;
>> +    size_t sz;
>> +    virtio_snd_pcm_xfer hdr;
>> +    virtio_snd_pcm_status resp = { 0 };
>
>virtio_snd_pcm_status has multiple fields, so better zero-initialize
>all of them with '{ }'.

I don't understand why, virtio_snd_pcm_status has two int fields hence { 
0 } zero-initializes all of them.

>> +/*
>> + * 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;
>> +    VirtIOSoundPCMBlock *block;
>> +    VirtIOSoundPCMBlock *next;
>> +    size_t size;
>> +
>> +    WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
>> +        QSIMPLEQ_FOREACH_SAFE(block, &stream->queue, entry, next) {
>> +            for (;;) {
>> +                size = MIN(block->size, available);
>> +                size = AUD_write(stream->voice.out,
>> +                        block->data + block->offset,
>> +                        size);
>
>If AUD_write() returns 0, is this an infinite loop?

Hm since we have available > 0 bytes this wouldn't theoretically happen, 
but I see there are code paths that return 0 on bugs/failures, I will 
add the check.

>> +                block->size -= size;
>> +                block->offset += size;
>> +                if (!block->size) {
>> +                    virtqueue_push(block->vq,
>> +                            block->elem,
>> +                            sizeof(block->elem));
>> +                    virtio_notify(VIRTIO_DEVICE(stream->s),
>> +                            block->vq);
>> +                    QSIMPLEQ_REMOVE_HEAD(&stream->queue, entry);
>> +                    g_free(block);
>> +                    available -= size;
>> +                    break;
>> +                }
>> +
>> +                available -= size;
>> +                if (!available) {
>> +                    break;
>> +                }
>> +            }
>> +            if (!available) {
>> +                break;
>> +            }
>> +        }
>> +    }
>> +}
>> +
>> +/*
>> + * Flush all buffer data from this stream's queue into the driver's virtual
>> + * queue.
>> + *
>> + * @stream: VirtIOSoundPCMStream *stream
>> + */
>> +static void virtio_snd_pcm_flush(VirtIOSoundPCMStream *stream)
>> +{
>> +    VirtIOSoundPCMBlock *block;
>> +    VirtIOSoundPCMBlock *next;
>> +
>> +    WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
>> +        QSIMPLEQ_FOREACH_SAFE(block, &stream->queue, entry, next) {
>> +            AUD_write(stream->voice.out, block->data + block->offset, block->size);
>
>Is it OK to ignore AUD_write() returning < block->size?
>If so, can you add a comment please?

This is a flush event with a timeout so it should complete asap. As 
mentioned in another reply it might be better to copy the data to a 
buffer in order not to lose any audio bytes.

Thank you for the feedback,
Manos
Philippe Mathieu-Daudé Sept. 4, 2023, 11:39 a.m. UTC | #4
On 4/9/23 12:34, Manos Pitsidianakis wrote:
> On Mon, 04 Sep 2023 13:26, Philippe Mathieu-Daudé <philmd@linaro.org> 
> wrote:
>>>   /*
>>> - * Handles VIRTIO_SND_R_PCM_RELEASE. Releases the buffer resources 
>>> allocated to
>>> - * a stream.
>>> + * Returns the number of I/O messages that are being processed.
>>> + *
>>> + * @stream: VirtIOSoundPCMStream
>>> + */
>>> +static size_t 
>>> virtio_snd_pcm_get_pending_io_msgs(VirtIOSoundPCMStream *stream)
>>> +{
>>> +    VirtIOSoundPCMBlock *block;
>>> +    VirtIOSoundPCMBlock *next;
>>> +    size_t size = 0;
>>> +
>>> +    WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
>>> +        QSIMPLEQ_FOREACH_SAFE(block, &stream->queue, entry, next) {
>>> +            size += 1;
>>
>> Can you add a comment explaining this magic size?
> 
> It's not magic, it's simply how many messages there are as explained in 
> the function doc comment. This was previously bytes hence `size`. I will 
> change the variable name to `count`.

Ah OK. 'msg_processed'?

>>> +static void virtio_snd_handle_tx(VirtIODevice *vdev, VirtQueue *vq)
>>> +{
>>> +    VirtIOSound *s = VIRTIO_SND(vdev);
>>> +    VirtIOSoundPCMStream *stream = NULL;
>>> +    VirtQueueElement *elem;
>>> +    size_t sz;
>>> +    virtio_snd_pcm_xfer hdr;
>>> +    virtio_snd_pcm_status resp = { 0 };
>>
>> virtio_snd_pcm_status has multiple fields, so better zero-initialize
>> all of them with '{ }'.
> 
> I don't understand why, virtio_snd_pcm_status has two int fields hence { 
> 0 } zero-initializes all of them.

Hmm I'm too busy right now to check the C standard. Looking at QEMU
code base, most of the time we use nothing or N * 0 to initialize N
fields:

$ git grep '{ }' | wc -l
      536
$ git grep -E '{ ?0, 0 ?}' | wc -l
       50
$ git grep '{ }' | wc -l
      536

Anyway, not a blocker.

>>> +/*
>>> + * 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;
>>> +    VirtIOSoundPCMBlock *block;
>>> +    VirtIOSoundPCMBlock *next;
>>> +    size_t size;
>>> +
>>> +    WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
>>> +        QSIMPLEQ_FOREACH_SAFE(block, &stream->queue, entry, next) {
>>> +            for (;;) {
>>> +                size = MIN(block->size, available);
>>> +                size = AUD_write(stream->voice.out,
>>> +                        block->data + block->offset,
>>> +                        size);
>>
>> If AUD_write() returns 0, is this an infinite loop?
> 
> Hm since we have available > 0 bytes this wouldn't theoretically happen, 
> but I see there are code paths that return 0 on bugs/failures, I will 
> add the check.

Thanks.

>>> +                block->size -= size;
>>> +                block->offset += size;
>>> +                if (!block->size) {
>>> +                    virtqueue_push(block->vq,
>>> +                            block->elem,
>>> +                            sizeof(block->elem));
>>> +                    virtio_notify(VIRTIO_DEVICE(stream->s),
>>> +                            block->vq);
>>> +                    QSIMPLEQ_REMOVE_HEAD(&stream->queue, entry);
>>> +                    g_free(block);
>>> +                    available -= size;
>>> +                    break;
>>> +                }
>>> +
>>> +                available -= size;
>>> +                if (!available) {
>>> +                    break;
>>> +                }
>>> +            }
>>> +            if (!available) {
>>> +                break;
>>> +            }
>>> +        }
>>> +    }
>>> +}
>>> +
>>> +/*
>>> + * Flush all buffer data from this stream's queue into the driver's 
>>> virtual
>>> + * queue.
>>> + *
>>> + * @stream: VirtIOSoundPCMStream *stream
>>> + */
>>> +static void virtio_snd_pcm_flush(VirtIOSoundPCMStream *stream)
>>> +{
>>> +    VirtIOSoundPCMBlock *block;
>>> +    VirtIOSoundPCMBlock *next;
>>> +
>>> +    WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
>>> +        QSIMPLEQ_FOREACH_SAFE(block, &stream->queue, entry, next) {
>>> +            AUD_write(stream->voice.out, block->data + 
>>> block->offset, block->size);
>>
>> Is it OK to ignore AUD_write() returning < block->size?
>> If so, can you add a comment please?
> 
> This is a flush event with a timeout so it should complete asap. As 
> mentioned in another reply it might be better to copy the data to a 
> buffer in order not to lose any audio bytes.

Maybe this could do:

   /*
    * This is a flush event with a timeout which should complete ASAP.
    * TODO: Copy the data to a buffer in order not to lose any audio
    * bytes. (Meanwhile we just discard any write failure).
    */

Regards,

Phil.
Volker Rümelin Sept. 4, 2023, 9:34 p.m. UTC | #5
Am 04.09.23 um 12:34 schrieb Manos Pitsidianakis:
> On Mon, 04 Sep 2023 13:26, Philippe Mathieu-Daudé <philmd@linaro.org> 
> wrote:
>
>>> +/*
>>> + * 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;
>>> +    VirtIOSoundPCMBlock *block;
>>> +    VirtIOSoundPCMBlock *next;
>>> +    size_t size;
>>> +
>>> +    WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
>>> +        QSIMPLEQ_FOREACH_SAFE(block, &stream->queue, entry, next) {
>>> +            for (;;) {
>>> +                size = MIN(block->size, available);
>>> +                size = AUD_write(stream->voice.out,
>>> +                        block->data + block->offset,
>>> +                        size);
>>
>> If AUD_write() returns 0, is this an infinite loop?
>
> Hm since we have available > 0 bytes this wouldn't theoretically 
> happen, but I see there are code paths that return 0 on bugs/failures, 
> I will add the check.

Before QEMU 8.0.0 it was possible that AUD_write() couldn't write the 
last audio frame and sometimes 'available' was just miscalculated. Since 
commit e1e6a6fcc9 ("audio: handle leftover audio frame from upsampling") 
AUD_write() writes all 'available' bytes.

With best regards,
Volker

>
>>> +                block->size -= size;
>>> +                block->offset += size;
>>> +                if (!block->size) {
>>> +                    virtqueue_push(block->vq,
>>> +                            block->elem,
>>> +                            sizeof(block->elem));
>>> + virtio_notify(VIRTIO_DEVICE(stream->s),
>>> +                            block->vq);
>>> + QSIMPLEQ_REMOVE_HEAD(&stream->queue, entry);
>>> +                    g_free(block);
>>> +                    available -= size;
>>> +                    break;
>>> +                }
>>> +
>>> +                available -= size;
>>> +                if (!available) {
>>> +                    break;
>>> +                }
>>> +            }
>>> +            if (!available) {
>>> +                break;
>>> +            }
>>> +        }
>>> +    }
>>> +}
>>> +
>>> +/*
>>> + * Flush all buffer data from this stream's queue into the driver's 
>>> virtual
>>> + * queue.
>>> + *
>>> + * @stream: VirtIOSoundPCMStream *stream
>>> + */
>>> +static void virtio_snd_pcm_flush(VirtIOSoundPCMStream *stream)
>>> +{
>>> +    VirtIOSoundPCMBlock *block;
>>> +    VirtIOSoundPCMBlock *next;
>>> +
>>> +    WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
>>> +        QSIMPLEQ_FOREACH_SAFE(block, &stream->queue, entry, next) {
>>> +            AUD_write(stream->voice.out, block->data + 
>>> block->offset, block->size);
>>
Volker Rümelin Sept. 5, 2023, 7:10 a.m. UTC | #6
Am 04.09.23 um 23:34 schrieb Volker Rümelin:
> Am 04.09.23 um 12:34 schrieb Manos Pitsidianakis:
>> On Mon, 04 Sep 2023 13:26, Philippe Mathieu-Daudé <philmd@linaro.org> 
>> wrote:
>>
>>>> +/*
>>>> + * 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;
>>>> +    VirtIOSoundPCMBlock *block;
>>>> +    VirtIOSoundPCMBlock *next;
>>>> +    size_t size;
>>>> +
>>>> +    WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
>>>> +        QSIMPLEQ_FOREACH_SAFE(block, &stream->queue, entry, next) {
>>>> +            for (;;) {
>>>> +                size = MIN(block->size, available);
>>>> +                size = AUD_write(stream->voice.out,
>>>> +                        block->data + block->offset,
>>>> +                        size);
>>>
>>> If AUD_write() returns 0, is this an infinite loop?
>>
>> Hm since we have available > 0 bytes this wouldn't theoretically 
>> happen, but I see there are code paths that return 0 on 
>> bugs/failures, I will add the check.
>
> Before QEMU 8.0.0 it was possible that AUD_write() couldn't write the 
> last audio frame and sometimes 'available' was just miscalculated. 
> Since commit e1e6a6fcc9 ("audio: handle leftover audio frame from 
> upsampling") AUD_write() writes all 'available' bytes.
>

I thought about this again. The error check is necessary.

> With best regards,
> Volker
>
>>
>>>> +                block->size -= size;
>>>> +                block->offset += size;
>>>> +                if (!block->size) {
>>>> +                    virtqueue_push(block->vq,
>>>> +                            block->elem,
>>>> +                            sizeof(block->elem));
>>>> + virtio_notify(VIRTIO_DEVICE(stream->s),
>>>> +                            block->vq);
>>>> + QSIMPLEQ_REMOVE_HEAD(&stream->queue, entry);
>>>> +                    g_free(block);
>>>> +                    available -= size;
>>>> +                    break;
>>>> +                }
>>>> +
>>>> +                available -= size;
>>>> +                if (!available) {
>>>> +                    break;
>>>> +                }
>>>> +            }
>>>> +            if (!available) {
>>>> +                break;
>>>> +            }
>>>> +        }
>>>> +    }
>>>> +}
>>>> +
>>>> +/*
>>>> + * Flush all buffer data from this stream's queue into the 
>>>> driver's virtual
>>>> + * queue.
>>>> + *
>>>> + * @stream: VirtIOSoundPCMStream *stream
>>>> + */
>>>> +static void virtio_snd_pcm_flush(VirtIOSoundPCMStream *stream)
>>>> +{
>>>> +    VirtIOSoundPCMBlock *block;
>>>> +    VirtIOSoundPCMBlock *next;
>>>> +
>>>> +    WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
>>>> +        QSIMPLEQ_FOREACH_SAFE(block, &stream->queue, entry, next) {
>>>> +            AUD_write(stream->voice.out, block->data + 
>>>> block->offset, block->size);
>>>
>
diff mbox series

Patch

diff --git a/hw/virtio/trace-events b/hw/virtio/trace-events
index 60ab62a80d..3b95e745c2 100644
--- a/hw/virtio/trace-events
+++ b/hw/virtio/trace-events
@@ -171,3 +171,5 @@  virtio_snd_handle_pcm_release(uint32_t stream) "VIRTIO_SND_PCM_RELEASE called fo
 virtio_snd_handle_code(uint32_t val, const char *code) "ctrl code msg val = %"PRIu32" == %s"
 virtio_snd_handle_chmap_info(void) "VIRTIO_SND_CHMAP_INFO called"
 virtio_snd_handle_event(void) "event queue callback called"
+virtio_snd_pcm_stream_flush(uint32_t stream) "flushing stream %"PRIu32
+virtio_snd_handle_xfer(void) "tx/rx queue callback called"
diff --git a/hw/virtio/virtio-snd.c b/hw/virtio/virtio-snd.c
index e4fc832c39..4859ce4bf6 100644
--- a/hw/virtio/virtio-snd.c
+++ b/hw/virtio/virtio-snd.c
@@ -31,6 +31,15 @@ 
 #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 uint32_t
+virtio_snd_pcm_read_write(VirtIOSoundPCMStream *stream,
+                          VirtQueue *vq,
+                          VirtQueueElement *element,
+                          bool read);
+
 static uint32_t supported_formats = BIT(VIRTIO_SND_PCM_FMT_S8)
                                   | BIT(VIRTIO_SND_PCM_FMT_U8)
                                   | BIT(VIRTIO_SND_PCM_FMT_S16)
@@ -368,7 +377,24 @@  static void virtio_snd_get_qemu_audsettings(audsettings *as,
  */
 static void virtio_snd_pcm_close(VirtIOSoundPCMStream *stream)
 {
+    VirtIOSoundPCMBlock *block, *next;
+
     if (stream) {
+        WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
+            QSIMPLEQ_FOREACH_SAFE(block, &stream->queue, entry, next) {
+                virtqueue_push(block->vq,
+                        block->elem,
+                        sizeof(block->elem));
+                virtio_notify(VIRTIO_DEVICE(stream->s),
+                        block->vq);
+                QSIMPLEQ_REMOVE_HEAD(&stream->queue, entry);
+                g_free(block);
+            }
+        }
+        if (stream->direction == VIRTIO_SND_D_OUTPUT) {
+            AUD_close_out(&stream->pcm->snd->card, stream->voice.out);
+            stream->voice.out = NULL;
+        }
         qemu_mutex_destroy(&stream->queue_mutex);
         g_free(stream);
     }
@@ -422,6 +448,17 @@  static uint32_t virtio_snd_pcm_prepare(VirtIOSound *s, uint32_t stream_id)
     stream->positions[0] = VIRTIO_SND_CHMAP_FL;
     stream->positions[1] = VIRTIO_SND_CHMAP_FR;
 
+    if (stream->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);
+    } else {
+        qemu_log_mask(LOG_UNIMP, "virtio_snd: input/capture is unimplemented.");
+    }
+
     stream->as = as;
     stream->desired_as = stream->as;
     qemu_mutex_init(&stream->queue_mutex);
@@ -465,6 +502,7 @@  static void virtio_snd_handle_pcm_start_stop(VirtIOSound *s,
                                              bool start)
 {
     VirtIOSoundPCMStream *stream;
+    VirtIOSoundPCMBlock *block, *next;
     virtio_snd_pcm_hdr req;
     size_t sz = iov_to_buf(cmd->elem->out_sg,
                            cmd->elem->out_num,
@@ -481,15 +519,48 @@  static void virtio_snd_handle_pcm_start_stop(VirtIOSound *s,
             "VIRTIO_SND_R_PCM_STOP", req.stream_id);
 
     stream = virtio_snd_pcm_get_stream(s, req.stream_id);
-    if (!stream) {
+    if (stream) {
+        if (stream->direction == VIRTIO_SND_D_OUTPUT) {
+            AUD_set_active_out(stream->voice.out, start);
+        }
+        /* remove previous buffers. */
+        WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
+            QSIMPLEQ_FOREACH_SAFE(block, &stream->queue, entry, next) {
+                virtqueue_push(block->vq,
+                               block->elem,
+                               sizeof(block->elem));
+                virtio_notify(VIRTIO_DEVICE(stream->s), block->vq);
+                QSIMPLEQ_REMOVE_HEAD(&stream->queue, entry);
+                g_free(block);
+            }
+        }
+    } else {
         error_report("Invalid stream id: %"PRIu32, req.stream_id);
         cmd->resp.code = VIRTIO_SND_S_BAD_MSG;
     }
 }
 
 /*
- * Handles VIRTIO_SND_R_PCM_RELEASE. Releases the buffer resources allocated to
- * a stream.
+ * Returns the number of I/O messages that are being processed.
+ *
+ * @stream: VirtIOSoundPCMStream
+ */
+static size_t virtio_snd_pcm_get_pending_io_msgs(VirtIOSoundPCMStream *stream)
+{
+    VirtIOSoundPCMBlock *block;
+    VirtIOSoundPCMBlock *next;
+    size_t size = 0;
+
+    WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
+        QSIMPLEQ_FOREACH_SAFE(block, &stream->queue, entry, next) {
+            size += 1;
+        }
+    }
+    return size;
+}
+
+/*
+ * Handles VIRTIO_SND_R_PCM_RELEASE.
  *
  * @s: VirtIOSound device
  * @cmd: The request command queue element from VirtIOSound cmdq field
@@ -520,6 +591,22 @@  static void virtio_snd_handle_pcm_release(VirtIOSound *s,
         cmd->resp.code = VIRTIO_SND_S_BAD_MSG;
         return;
     }
+
+    if (virtio_snd_pcm_get_pending_io_msgs(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.
+         */
+        virtio_snd_process_cmdq(stream->s);
+        trace_virtio_snd_pcm_stream_flush(stream_id);
+        virtio_snd_pcm_flush(stream);
+    }
+
     cmd->resp.code = VIRTIO_SND_S_OK;
 }
 
@@ -669,6 +756,79 @@  static void virtio_snd_handle_event(VirtIODevice *vdev, VirtQueue *vq)
     trace_virtio_snd_handle_event();
 }
 
+/*
+ * 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(VirtIODevice *vdev, VirtQueue *vq)
+{
+    VirtIOSound *s = VIRTIO_SND(vdev);
+    VirtIOSoundPCMStream *stream = NULL;
+    VirtQueueElement *elem;
+    size_t sz;
+    virtio_snd_pcm_xfer hdr;
+    virtio_snd_pcm_status resp = { 0 };
+
+    trace_virtio_snd_handle_xfer();
+
+    for (;;) {
+        elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
+        if (!elem) {
+            break;
+        }
+        /* get the message hdr object */
+        sz = iov_to_buf(elem->out_sg,
+                        elem->out_num,
+                        0,
+                        &hdr,
+                        sizeof(hdr));
+        if (sz != sizeof(hdr)
+            || hdr.stream_id >= s->snd_conf.streams
+            || !s->pcm->streams[hdr.stream_id]) {
+            goto tx_err;
+        }
+
+        stream = s->pcm->streams[hdr.stream_id];
+        if (stream->direction != VIRTIO_SND_D_OUTPUT) {
+            goto tx_err;
+        }
+
+        WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
+            virtio_snd_pcm_read_write(stream,
+                    vq,
+                    elem,
+                    hdr.stream_id == VIRTIO_SND_D_INPUT);
+
+            resp.status = VIRTIO_SND_S_OK;
+            iov_from_buf(elem->in_sg,
+                         elem->in_num,
+                         0,
+                         &resp,
+                         sizeof(resp));
+        }
+        continue;
+
+tx_err:
+        WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
+            resp.status = VIRTIO_SND_S_BAD_MSG;
+            iov_from_buf(elem->in_sg,
+                         elem->in_num,
+                         0,
+                         &resp,
+                         sizeof(resp));
+        }
+    }
+
+    /*
+     * Notify vq about virtio_snd_pcm_status responses.
+     * Buffer responses must be notified separately later.
+     */
+    virtio_notify(VIRTIO_DEVICE(s), vq);
+}
+
 /*
  * Stub buffer virtqueue handler.
  *
@@ -770,7 +930,7 @@  static void virtio_snd_realize(DeviceState *dev, Error **errp)
     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_xfer);
+        virtio_add_queue(vdev, 64, virtio_snd_handle_tx);
     vsnd->queues[VIRTIO_SND_VQ_RX] =
         virtio_add_queue(vdev, 64, virtio_snd_handle_xfer);
     qemu_mutex_init(&vsnd->cmdq_mutex);
@@ -795,6 +955,73 @@  static void virtio_snd_realize(DeviceState *dev, Error **errp)
     }
 }
 
+/*
+ * 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;
+    VirtIOSoundPCMBlock *block;
+    VirtIOSoundPCMBlock *next;
+    size_t size;
+
+    WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
+        QSIMPLEQ_FOREACH_SAFE(block, &stream->queue, entry, next) {
+            for (;;) {
+                size = MIN(block->size, available);
+                size = AUD_write(stream->voice.out,
+                        block->data + block->offset,
+                        size);
+                block->size -= size;
+                block->offset += size;
+                if (!block->size) {
+                    virtqueue_push(block->vq,
+                            block->elem,
+                            sizeof(block->elem));
+                    virtio_notify(VIRTIO_DEVICE(stream->s),
+                            block->vq);
+                    QSIMPLEQ_REMOVE_HEAD(&stream->queue, entry);
+                    g_free(block);
+                    available -= size;
+                    break;
+                }
+
+                available -= size;
+                if (!available) {
+                    break;
+                }
+            }
+            if (!available) {
+                break;
+            }
+        }
+    }
+}
+
+/*
+ * Flush all buffer data from this stream's queue into the driver's virtual
+ * queue.
+ *
+ * @stream: VirtIOSoundPCMStream *stream
+ */
+static void virtio_snd_pcm_flush(VirtIOSoundPCMStream *stream)
+{
+    VirtIOSoundPCMBlock *block;
+    VirtIOSoundPCMBlock *next;
+
+    WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
+        QSIMPLEQ_FOREACH_SAFE(block, &stream->queue, entry, next) {
+            AUD_write(stream->voice.out, block->data + block->offset, block->size);
+            virtqueue_push(block->vq, block->elem, sizeof(block->elem));
+            virtio_notify(VIRTIO_DEVICE(stream->s), block->vq);
+            QSIMPLEQ_REMOVE(&stream->queue, block, VirtIOSoundPCMBlock, entry);
+        }
+    }
+}
+
 static void virtio_snd_unrealize(DeviceState *dev)
 {
     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
@@ -818,6 +1045,7 @@  static void virtio_snd_unrealize(DeviceState *dev)
             }
             g_free(vsnd->pcm->streams);
         }
+        g_free(vsnd->pcm->pcm_params);
         g_free(vsnd->pcm);
         vsnd->pcm = NULL;
     }
@@ -827,6 +1055,32 @@  static void virtio_snd_unrealize(DeviceState *dev)
 }
 
 
+static uint32_t
+virtio_snd_pcm_read_write(VirtIOSoundPCMStream *stream,
+                          VirtQueue *vq,
+                          VirtQueueElement *element,
+                          bool read)
+{
+    VirtIOSoundPCMBlock *fragment;
+    size_t size = iov_size(element->out_sg, element->out_num) -
+        sizeof(virtio_snd_pcm_xfer);
+
+    fragment = g_malloc0(sizeof(VirtIOSoundPCMBlock) + size);
+    fragment->elem = element;
+    fragment->vq = vq;
+    fragment->size = size;
+    fragment->offset = 0;
+
+    iov_to_buf(element->out_sg, element->out_num,
+               sizeof(virtio_snd_pcm_xfer),
+               fragment->data,
+               size);
+
+    QSIMPLEQ_INSERT_TAIL(&stream->queue, fragment, entry);
+
+    return fragment->size;
+}
+
 static void virtio_snd_reset(VirtIODevice *vdev)
 {
     VirtIOSound *s = VIRTIO_SND(vdev);
diff --git a/include/hw/virtio/virtio-snd.h b/include/hw/virtio/virtio-snd.h
index b7046418cf..0350df9ab7 100644
--- a/include/hw/virtio/virtio-snd.h
+++ b/include/hw/virtio/virtio-snd.h
@@ -79,6 +79,8 @@  typedef struct VirtIOSoundPCMParams VirtIOSoundPCMParams;
 
 typedef struct VirtIOSoundPCM VirtIOSoundPCM;
 
+typedef struct VirtIOSoundPCMBlock VirtIOSoundPCMBlock;
+
 /* Stream params */
 struct VirtIOSoundPCMParams {
     uint32_t features;
@@ -89,6 +91,15 @@  struct VirtIOSoundPCMParams {
     uint8_t rate;
 };
 
+struct VirtIOSoundPCMBlock {
+    QSIMPLEQ_ENTRY(VirtIOSoundPCMBlock) entry;
+    VirtQueueElement *elem;
+    VirtQueue *vq;
+    size_t size;
+    uint64_t offset;
+    uint8_t data[];
+};
+
 struct VirtIOSoundPCM {
     VirtIOSound *snd;
     VirtIOSoundPCMParams *pcm_params;