diff -r ffa851df0825 -r 2fb8b9db1c86 symbian-qemu-0.9.1-12/qemu-symbian-svp/hw/virtio-audio.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/symbian-qemu-0.9.1-12/qemu-symbian-svp/hw/virtio-audio.c Fri Jul 31 15:01:17 2009 +0100 @@ -0,0 +1,408 @@ +/* + * Virtio Audio Device + * + * Copyright 2009 CodeSourcery + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "virtio-audio.h" + +//#define DEBUG_VIRTIO_AUDIO + +#ifdef DEBUG_VIRTIO_AUDIO +#define DPRINTF(fmt, args...) \ +do { printf("virtio-audio: " fmt , ##args); } while (0) +#define BADF(fmt, args...) \ +do { fprintf(stderr, "virtio-audio: error: " fmt , ##args); exit(1);} while (0) +#else +#define DPRINTF(fmt, args...) do {} while(0) +#define BADF(fmt, args...) \ +do { fprintf(stderr, "virtio-audio: error: " fmt , ##args);} while (0) +#endif + +#define NUM_STREAMS 2 + +typedef struct { + struct VirtIOAudio *dev; + VirtQueue *data_vq; + struct audsettings fmt; + SWVoiceOut *out_voice; + SWVoiceIn *in_voice; + VirtQueueElement elem; + int data_left; + int data_offset; + int has_buffer; +} VirtIOAudioStream; + +typedef struct VirtIOAudio +{ + VirtIODevice vdev; + QEMUSoundCard card; + VirtQueue *cmd_vq; + VirtIOAudioStream stream[NUM_STREAMS]; +} VirtIOAudio; + +static VirtIOAudio *to_virtio_audio(VirtIODevice *vdev) +{ + return (VirtIOAudio *)vdev; +} + +static void virtio_audio_get_config(VirtIODevice *vdev, uint8_t *config) +{ + struct virtio_audio_cfg audio_cfg; + + audio_cfg.num_streams = NUM_STREAMS; + memcpy(config, &audio_cfg, sizeof(audio_cfg)); +} + +static uint32_t virtio_audio_get_features(VirtIODevice *vdev) +{ + uint32_t features = 0; + + return features; +} + +static void virtio_audio_set_features(VirtIODevice *vdev, uint32_t features) +{ +} + +static int virtio_audio_fill(VirtIOAudioStream *stream, + int offset, int total_len) +{ + uint8_t *p; + int to_write; + int written; + int size; + int n; + struct iovec *iov; + int iov_len; + + if (stream->in_voice) { + iov_len = stream->elem.in_num; + iov = stream->elem.in_sg; + } else { + iov_len = stream->elem.out_num; + iov = stream->elem.out_sg; + } + written = 0; + for (n = 0; total_len > 0 && n < iov_len; n++) { + p = iov[n].iov_base; + to_write = iov[n].iov_len; + if (offset) { + if (offset >= to_write) { + offset -= to_write; + continue; + } + p += offset; + to_write -= offset; + offset = 0; + } + if (to_write > total_len) + to_write = total_len; + while (to_write) { + if (stream->in_voice) { + size = AUD_read(stream->in_voice, p, to_write); + } else { + size = AUD_write(stream->out_voice, p, to_write); + } + DPRINTF("Copied %d/%d\n", size, to_write); + if (size == 0) { + total_len = 0; + break; + } + to_write -= size; + total_len -= size; + written += size; + } + } + return written; +} + +static void virtio_audio_callback(void *opaque, int avail) +{ + VirtIOAudioStream *stream = opaque; + int n; + + DPRINTF("Callback (%d)\n", avail); + while (avail) { + while (stream->data_left == 0) { + if (stream->has_buffer) { + virtqueue_push(stream->data_vq, &stream->elem, 0); + virtio_notify(&stream->dev->vdev, stream->data_vq); + stream->has_buffer = 0; + } + if (!virtqueue_pop(stream->data_vq, &stream->elem)) { + /* Buffer underrun. */ + stream->has_buffer = 0; + DPRINTF("Underrun\n"); + break; + } + stream->data_offset = 0; + stream->data_left = 0; + stream->has_buffer = 1; + if (stream->in_voice) { + for (n = 0; n < stream->elem.in_num; n++) + stream->data_left += stream->elem.in_sg[n].iov_len; + } else { + for (n = 0; n < stream->elem.out_num; n++) + stream->data_left += stream->elem.out_sg[n].iov_len; + } + } + if (stream->data_left == 0) + break; + n = virtio_audio_fill(stream, stream->data_offset, avail); + stream->data_left -= n; + stream->data_offset += n; + avail -= n; + if (!n) + break; + } + if (stream->data_left == 0 && stream->has_buffer) { + virtqueue_push(stream->data_vq, &stream->elem, 0); + virtio_notify(&stream->dev->vdev, stream->data_vq); + stream->has_buffer = 0; + } +} + +static void virtio_audio_cmd_result(uint32_t value, VirtQueueElement *elem, + size_t *out_bytes) +{ + size_t offset = *out_bytes; + int len; + int n; + + DPRINTF("cmd result %d\n", value); + for (n = 0; n < elem->in_num; n++) { + len = elem->in_sg[n].iov_len; + if (len < offset) { + offset -= len; + len = 0; + } else { + if (len - offset < 4) { + BADF("buffer too short\n"); + return; + } + stl_p(elem->in_sg[n].iov_base + offset, value); + (*out_bytes) += 4; + return; + } + } + BADF("No space left\n"); +} + +/* Command queue. */ +static void virtio_audio_handle_cmd(VirtIODevice *vdev, VirtQueue *vq) +{ + VirtIOAudio *s = to_virtio_audio(vdev); + VirtIOAudioStream *stream; + VirtQueueElement elem; + int out_n; + uint32_t *p; + int len; + size_t out_bytes; + uint32_t value; + + while (virtqueue_pop(s->cmd_vq, &elem)) { + for (out_n = 0; out_n < elem.out_num; out_n++) { + p = (uint32_t *)elem.out_sg[out_n].iov_base; + len = elem.out_sg[out_n].iov_len; + while (len > 0) { + if (len < 12) { + BADF("Bad command length\n"); + break; + } + DPRINTF("Command %d %d %d\n", + ldl_p(p), ldl_p(p + 1), ldl_p (p + 2)); + value = ldl_p(p + 1); + if (value >= NUM_STREAMS) + break; + stream = &s->stream[value]; + value = ldl_p(p + 2); + switch (ldl_p(p)) { + case VIRTIO_AUDIO_CMD_SET_ENDIAN: + stream->fmt.endianness = value; + break; + case VIRTIO_AUDIO_CMD_SET_CHANNELS: + stream->fmt.nchannels = value; + break; + case VIRTIO_AUDIO_CMD_SET_FMT: + stream->fmt.fmt = value; + break; + case VIRTIO_AUDIO_CMD_SET_FREQ: + stream->fmt.freq = value; + break; + case VIRTIO_AUDIO_CMD_INIT: + if (value & 1) { + if (stream->out_voice) { + AUD_close_out(&s->card, stream->out_voice); + stream->out_voice = NULL; + } + stream->in_voice = + AUD_open_in(&s->card, stream->in_voice, + "virtio-audio.in", + stream, + virtio_audio_callback, + &stream->fmt); + virtio_audio_cmd_result(0, &elem, &out_bytes); + } else { + if (stream->out_voice) { + AUD_close_in(&s->card, stream->in_voice); + stream->in_voice = NULL; + } + stream->out_voice = + AUD_open_out(&s->card, stream->out_voice, + "virtio-audio.out", + stream, + virtio_audio_callback, + &stream->fmt); + value = AUD_get_buffer_size_out(stream->out_voice); + virtio_audio_cmd_result(value, &elem, &out_bytes); + } + break; + case VIRTIO_AUDIO_CMD_RUN: + if (stream->in_voice) { + AUD_set_active_in(stream->in_voice, value); + } else { + AUD_set_active_out(stream->out_voice, value); + } + break; + } + p += 3; + len -= 12; + } + } + virtqueue_push(s->cmd_vq, &elem, out_bytes); + } +} + +static void virtio_audio_handle_data(VirtIODevice *vdev, VirtQueue *vq) +{ +} + + +static void virtio_audio_save(QEMUFile *f, void *opaque) +{ + VirtIOAudio *s = opaque; + VirtIOAudioStream *stream; + int i; + int mode; + + virtio_save(&s->vdev, f); + + for (i = 0; i < NUM_STREAMS; i++) { + stream = &s->stream[i]; + + if (stream->in_voice) { + mode = 2; + if (AUD_is_active_in(stream->in_voice)) + mode |= 1; + } else if (stream->out_voice) { + mode |= 4; + if (AUD_is_active_out(stream->out_voice)) + mode |= 1; + } else { + mode = 0; + } + qemu_put_byte(f, mode); + qemu_put_byte(f, stream->fmt.endianness); + qemu_put_be16(f, stream->fmt.nchannels); + qemu_put_be32(f, stream->fmt.fmt); + qemu_put_be32(f, stream->fmt.freq); + } +} + +static int virtio_audio_load(QEMUFile *f, void *opaque, int version_id) +{ + VirtIOAudio *s = opaque; + VirtIOAudioStream *stream; + int i; + int mode; + + if (version_id != 1) + return -EINVAL; + + /* FIXME: Do bad things happen if there is a transfer in progress? */ + + virtio_load(&s->vdev, f); + + for (i = 0; i < NUM_STREAMS; i++) { + stream = &s->stream[i]; + + stream->has_buffer = 0; + stream->data_left = 0; + if (stream->in_voice) { + AUD_close_in(&s->card, stream->in_voice); + stream->in_voice = NULL; + } + if (stream->out_voice) { + AUD_close_out(&s->card, stream->out_voice); + stream->out_voice = NULL; + } + mode = qemu_get_byte(f); + stream->fmt.endianness = qemu_get_byte(f); + stream->fmt.nchannels = qemu_get_be16(f); + stream->fmt.fmt = qemu_get_be32(f); + stream->fmt.freq = qemu_get_be32(f); + if (mode & 2) { + stream->in_voice = AUD_open_in(&s->card, stream->in_voice, + "virtio-audio.in", + stream, + virtio_audio_callback, + &stream->fmt); + AUD_set_active_in(stream->in_voice, mode & 1); + } else if (mode & 4) { + stream->out_voice = AUD_open_out(&s->card, stream->out_voice, + "virtio-audio.out", + stream, + virtio_audio_callback, + &stream->fmt); + AUD_set_active_out(stream->out_voice, mode & 1); + } + } + + return 0; +} + +void virtio_audio_init(VirtIOBindFn bind, void *bind_arg, AudioState *audio) +{ + VirtIOAudio *s; + int i; + + s = (VirtIOAudio *)bind(bind_arg, "virtio-audio", 0, VIRTIO_ID_AUDIO, + sizeof(struct virtio_audio_cfg), + sizeof(VirtIOAudio)); + if (!s) + return; + + s->vdev.get_config = virtio_audio_get_config; + s->vdev.get_features = virtio_audio_get_features; + s->vdev.set_features = virtio_audio_set_features; + s->cmd_vq = virtio_add_queue(&s->vdev, 64, virtio_audio_handle_cmd); + for (i = 0; i < NUM_STREAMS; i++) { + s->stream[i].data_vq = virtio_add_queue(&s->vdev, 128, + virtio_audio_handle_data); + s->stream[i].dev = s; + } + + AUD_register_card(audio, "virtio-audio", &s->card); + + register_savevm("virtio-audio", -1, 1, + virtio_audio_save, virtio_audio_load, s); +}