symbian-qemu-0.9.1-12/qemu-symbian-svp/hw/virtio-audio.c
changeset 1 2fb8b9db1c86
child 71 d00bf4f57250
equal deleted inserted replaced
0:ffa851df0825 1:2fb8b9db1c86
       
     1 /*
       
     2  * Virtio Audio Device
       
     3  *
       
     4  * Copyright 2009 CodeSourcery
       
     5  *
       
     6  * Permission is hereby granted, free of charge, to any person obtaining a copy
       
     7  * of this software and associated documentation files (the "Software"), to deal
       
     8  * in the Software without restriction, including without limitation the rights
       
     9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       
    10  * copies of the Software, and to permit persons to whom the Software is
       
    11  * furnished to do so, subject to the following conditions:
       
    12  *
       
    13  * The above copyright notice and this permission notice shall be included in
       
    14  * all copies or substantial portions of the Software.
       
    15  *
       
    16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       
    17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       
    18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
       
    19  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       
    20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       
    21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
       
    22  * THE SOFTWARE.
       
    23  */
       
    24 
       
    25 #include "virtio-audio.h"
       
    26 
       
    27 //#define DEBUG_VIRTIO_AUDIO
       
    28 
       
    29 #ifdef DEBUG_VIRTIO_AUDIO
       
    30 #define DPRINTF(fmt, args...) \
       
    31 do { printf("virtio-audio: " fmt , ##args); } while (0)
       
    32 #define BADF(fmt, args...) \
       
    33 do { fprintf(stderr, "virtio-audio: error: " fmt , ##args); exit(1);} while (0)
       
    34 #else
       
    35 #define DPRINTF(fmt, args...) do {} while(0)
       
    36 #define BADF(fmt, args...) \
       
    37 do { fprintf(stderr, "virtio-audio: error: " fmt , ##args);} while (0)
       
    38 #endif
       
    39 
       
    40 #define NUM_STREAMS 2
       
    41 
       
    42 typedef struct {
       
    43     struct VirtIOAudio *dev;
       
    44     VirtQueue *data_vq;
       
    45     struct audsettings fmt;
       
    46     SWVoiceOut *out_voice;
       
    47     SWVoiceIn *in_voice;
       
    48     VirtQueueElement elem;
       
    49     int data_left;
       
    50     int data_offset;
       
    51     int has_buffer;
       
    52 } VirtIOAudioStream;
       
    53 
       
    54 typedef struct VirtIOAudio
       
    55 {
       
    56     VirtIODevice vdev;
       
    57     QEMUSoundCard card;
       
    58     VirtQueue *cmd_vq;
       
    59     VirtIOAudioStream stream[NUM_STREAMS];
       
    60 } VirtIOAudio;
       
    61 
       
    62 static VirtIOAudio *to_virtio_audio(VirtIODevice *vdev)
       
    63 {
       
    64     return (VirtIOAudio *)vdev;
       
    65 }
       
    66 
       
    67 static void virtio_audio_get_config(VirtIODevice *vdev, uint8_t *config)
       
    68 {
       
    69     struct virtio_audio_cfg audio_cfg;
       
    70     
       
    71     audio_cfg.num_streams = NUM_STREAMS;
       
    72     memcpy(config, &audio_cfg, sizeof(audio_cfg));
       
    73 }
       
    74 
       
    75 static uint32_t virtio_audio_get_features(VirtIODevice *vdev)
       
    76 {
       
    77     uint32_t features = 0;
       
    78 
       
    79     return features;
       
    80 }
       
    81 
       
    82 static void virtio_audio_set_features(VirtIODevice *vdev, uint32_t features)
       
    83 {
       
    84 }
       
    85 
       
    86 static int virtio_audio_fill(VirtIOAudioStream *stream,
       
    87                              int offset, int total_len)
       
    88 {
       
    89     uint8_t *p;
       
    90     int to_write;
       
    91     int written;
       
    92     int size;
       
    93     int n;
       
    94     struct iovec *iov;
       
    95     int iov_len;
       
    96 
       
    97     if (stream->in_voice) {
       
    98         iov_len = stream->elem.in_num;
       
    99         iov = stream->elem.in_sg;
       
   100     } else {
       
   101         iov_len = stream->elem.out_num;
       
   102         iov = stream->elem.out_sg;
       
   103     }
       
   104     written = 0;
       
   105     for (n = 0; total_len > 0 && n < iov_len; n++) {
       
   106         p = iov[n].iov_base;
       
   107         to_write = iov[n].iov_len;
       
   108         if (offset) {
       
   109             if (offset >= to_write) {
       
   110                 offset -= to_write;
       
   111                 continue;
       
   112             }
       
   113             p += offset;
       
   114             to_write -= offset;
       
   115             offset = 0;
       
   116         }
       
   117         if (to_write > total_len)
       
   118             to_write = total_len;
       
   119         while (to_write) {
       
   120             if (stream->in_voice) {
       
   121                 size = AUD_read(stream->in_voice, p, to_write);
       
   122             } else {
       
   123                 size = AUD_write(stream->out_voice, p, to_write);
       
   124             }
       
   125             DPRINTF("Copied %d/%d\n", size, to_write);
       
   126             if (size == 0) {
       
   127                 total_len = 0;
       
   128                 break;
       
   129             }
       
   130             to_write -= size;
       
   131             total_len -= size;
       
   132             written += size;
       
   133         }
       
   134     }
       
   135     return written;
       
   136 }
       
   137 
       
   138 static void virtio_audio_callback(void *opaque, int avail)
       
   139 {
       
   140     VirtIOAudioStream *stream = opaque;
       
   141     int n;
       
   142 
       
   143     DPRINTF("Callback (%d)\n", avail);
       
   144     while (avail) {
       
   145         while (stream->data_left == 0) {
       
   146             if (stream->has_buffer) {
       
   147                 virtqueue_push(stream->data_vq, &stream->elem, 0);
       
   148                 virtio_notify(&stream->dev->vdev, stream->data_vq);
       
   149                 stream->has_buffer = 0;
       
   150             }
       
   151             if (!virtqueue_pop(stream->data_vq, &stream->elem)) {
       
   152                 /* Buffer underrun.  */
       
   153                 stream->has_buffer = 0;
       
   154                 DPRINTF("Underrun\n");
       
   155                 break;
       
   156             }
       
   157             stream->data_offset = 0;
       
   158             stream->data_left = 0;
       
   159             stream->has_buffer = 1;
       
   160             if (stream->in_voice) {
       
   161                 for (n = 0; n < stream->elem.in_num; n++)
       
   162                     stream->data_left += stream->elem.in_sg[n].iov_len;
       
   163             } else {
       
   164                 for (n = 0; n < stream->elem.out_num; n++)
       
   165                     stream->data_left += stream->elem.out_sg[n].iov_len;
       
   166             }
       
   167         }
       
   168         if (stream->data_left == 0)
       
   169             break;
       
   170         n = virtio_audio_fill(stream, stream->data_offset, avail);
       
   171         stream->data_left -= n;
       
   172         stream->data_offset += n;
       
   173         avail -= n;
       
   174         if (!n)
       
   175             break;
       
   176     }
       
   177     if (stream->data_left == 0 && stream->has_buffer) {
       
   178         virtqueue_push(stream->data_vq, &stream->elem, 0);
       
   179         virtio_notify(&stream->dev->vdev, stream->data_vq);
       
   180         stream->has_buffer = 0;
       
   181     }
       
   182 }
       
   183 
       
   184 static void virtio_audio_cmd_result(uint32_t value, VirtQueueElement *elem,
       
   185                                     size_t *out_bytes)
       
   186 {
       
   187     size_t offset = *out_bytes;
       
   188     int len;
       
   189     int n;
       
   190 
       
   191     DPRINTF("cmd result %d\n", value);
       
   192     for (n = 0; n < elem->in_num; n++) {
       
   193         len = elem->in_sg[n].iov_len;
       
   194         if (len < offset) {
       
   195             offset -= len;
       
   196             len = 0;
       
   197         } else {
       
   198             if (len  - offset < 4) {
       
   199                 BADF("buffer too short\n");
       
   200                 return;
       
   201             }
       
   202             stl_p(elem->in_sg[n].iov_base + offset, value);
       
   203             (*out_bytes) += 4;
       
   204             return;
       
   205         }
       
   206     }
       
   207     BADF("No space left\n");
       
   208 }
       
   209 
       
   210 /* Command queue.  */
       
   211 static void virtio_audio_handle_cmd(VirtIODevice *vdev, VirtQueue *vq)
       
   212 {
       
   213     VirtIOAudio *s = to_virtio_audio(vdev);
       
   214     VirtIOAudioStream *stream;
       
   215     VirtQueueElement elem;
       
   216     int out_n;
       
   217     uint32_t *p;
       
   218     int len;
       
   219     size_t out_bytes;
       
   220     uint32_t value;
       
   221 
       
   222     while (virtqueue_pop(s->cmd_vq, &elem)) {
       
   223         for (out_n = 0; out_n < elem.out_num; out_n++) {
       
   224             p = (uint32_t *)elem.out_sg[out_n].iov_base;
       
   225             len = elem.out_sg[out_n].iov_len;
       
   226             while (len > 0) {
       
   227                 if (len < 12) {
       
   228                     BADF("Bad command length\n");
       
   229                     break;
       
   230                 }
       
   231                 DPRINTF("Command %d %d %d\n",
       
   232                         ldl_p(p), ldl_p(p + 1), ldl_p (p + 2));
       
   233                 value = ldl_p(p + 1);
       
   234                 if (value >= NUM_STREAMS)
       
   235                     break;
       
   236                 stream = &s->stream[value];
       
   237                 value = ldl_p(p + 2);
       
   238                 switch (ldl_p(p)) {
       
   239                 case VIRTIO_AUDIO_CMD_SET_ENDIAN:
       
   240                     stream->fmt.endianness = value;
       
   241                     break;
       
   242                 case VIRTIO_AUDIO_CMD_SET_CHANNELS:
       
   243                     stream->fmt.nchannels = value;
       
   244                     break;
       
   245                 case VIRTIO_AUDIO_CMD_SET_FMT:
       
   246                     stream->fmt.fmt = value;
       
   247                     break;
       
   248                 case VIRTIO_AUDIO_CMD_SET_FREQ:
       
   249                     stream->fmt.freq = value;
       
   250                     break;
       
   251                 case VIRTIO_AUDIO_CMD_INIT:
       
   252                     if (value & 1) {
       
   253                         if (stream->out_voice) {
       
   254                             AUD_close_out(&s->card, stream->out_voice);
       
   255                             stream->out_voice = NULL;
       
   256                         }
       
   257                         stream->in_voice =
       
   258                           AUD_open_in(&s->card, stream->in_voice,
       
   259                                       "virtio-audio.in",
       
   260                                       stream,
       
   261                                       virtio_audio_callback,
       
   262                                       &stream->fmt);
       
   263                         virtio_audio_cmd_result(0, &elem, &out_bytes);
       
   264                     } else {
       
   265                         if (stream->out_voice) {
       
   266                             AUD_close_in(&s->card, stream->in_voice);
       
   267                             stream->in_voice = NULL;
       
   268                         }
       
   269                         stream->out_voice =
       
   270                           AUD_open_out(&s->card, stream->out_voice,
       
   271                                        "virtio-audio.out",
       
   272                                        stream,
       
   273                                        virtio_audio_callback,
       
   274                                        &stream->fmt);
       
   275                         value = AUD_get_buffer_size_out(stream->out_voice);
       
   276                         virtio_audio_cmd_result(value, &elem, &out_bytes);
       
   277                     }
       
   278                     break;
       
   279                 case VIRTIO_AUDIO_CMD_RUN:
       
   280                     if (stream->in_voice) {
       
   281                         AUD_set_active_in(stream->in_voice, value);
       
   282                     } else {
       
   283                         AUD_set_active_out(stream->out_voice, value);
       
   284                     }
       
   285                     break;
       
   286                 }
       
   287                 p += 3;
       
   288                 len -= 12;
       
   289             }
       
   290         }
       
   291         virtqueue_push(s->cmd_vq, &elem, out_bytes);
       
   292     }
       
   293 }
       
   294 
       
   295 static void virtio_audio_handle_data(VirtIODevice *vdev, VirtQueue *vq)
       
   296 {
       
   297 }
       
   298 
       
   299 
       
   300 static void virtio_audio_save(QEMUFile *f, void *opaque)
       
   301 {
       
   302     VirtIOAudio *s = opaque;
       
   303     VirtIOAudioStream *stream;
       
   304     int i;
       
   305     int mode;
       
   306 
       
   307     virtio_save(&s->vdev, f);
       
   308 
       
   309     for (i = 0; i < NUM_STREAMS; i++) {
       
   310         stream = &s->stream[i];
       
   311 
       
   312         if (stream->in_voice) {
       
   313             mode = 2;
       
   314             if (AUD_is_active_in(stream->in_voice))
       
   315                 mode |= 1;
       
   316         } else if (stream->out_voice) {
       
   317             mode |= 4;
       
   318             if (AUD_is_active_out(stream->out_voice))
       
   319                 mode |= 1;
       
   320         } else {
       
   321             mode = 0;
       
   322         }
       
   323         qemu_put_byte(f, mode);
       
   324         qemu_put_byte(f, stream->fmt.endianness);
       
   325         qemu_put_be16(f, stream->fmt.nchannels);
       
   326         qemu_put_be32(f, stream->fmt.fmt);
       
   327         qemu_put_be32(f, stream->fmt.freq);
       
   328     }
       
   329 }
       
   330 
       
   331 static int virtio_audio_load(QEMUFile *f, void *opaque, int version_id)
       
   332 {
       
   333     VirtIOAudio *s = opaque;
       
   334     VirtIOAudioStream *stream;
       
   335     int i;
       
   336     int mode;
       
   337 
       
   338     if (version_id != 1)
       
   339         return -EINVAL;
       
   340 
       
   341     /* FIXME: Do bad things happen if there is a transfer in progress?  */
       
   342 
       
   343     virtio_load(&s->vdev, f);
       
   344 
       
   345     for (i = 0; i < NUM_STREAMS; i++) {
       
   346         stream = &s->stream[i];
       
   347 
       
   348         stream->has_buffer = 0;
       
   349         stream->data_left = 0;
       
   350         if (stream->in_voice) {
       
   351             AUD_close_in(&s->card, stream->in_voice);
       
   352             stream->in_voice = NULL;
       
   353         }
       
   354         if (stream->out_voice) {
       
   355             AUD_close_out(&s->card, stream->out_voice);
       
   356             stream->out_voice = NULL;
       
   357         }
       
   358         mode = qemu_get_byte(f);
       
   359         stream->fmt.endianness = qemu_get_byte(f);
       
   360         stream->fmt.nchannels = qemu_get_be16(f);
       
   361         stream->fmt.fmt = qemu_get_be32(f);
       
   362         stream->fmt.freq = qemu_get_be32(f);
       
   363         if (mode & 2) {
       
   364             stream->in_voice = AUD_open_in(&s->card, stream->in_voice,
       
   365                                            "virtio-audio.in",
       
   366                                            stream,
       
   367                                            virtio_audio_callback,
       
   368                                            &stream->fmt);
       
   369             AUD_set_active_in(stream->in_voice, mode & 1);
       
   370         } else if (mode & 4) {
       
   371             stream->out_voice = AUD_open_out(&s->card, stream->out_voice,
       
   372                                              "virtio-audio.out",
       
   373                                              stream,
       
   374                                              virtio_audio_callback,
       
   375                                              &stream->fmt);
       
   376             AUD_set_active_out(stream->out_voice, mode & 1);
       
   377         }
       
   378     }
       
   379 
       
   380     return 0;
       
   381 }
       
   382 
       
   383 void virtio_audio_init(VirtIOBindFn bind, void *bind_arg, AudioState *audio)
       
   384 {
       
   385     VirtIOAudio *s;
       
   386     int i;
       
   387 
       
   388     s = (VirtIOAudio *)bind(bind_arg, "virtio-audio", 0, VIRTIO_ID_AUDIO,
       
   389                             sizeof(struct virtio_audio_cfg),
       
   390                             sizeof(VirtIOAudio));
       
   391     if (!s)
       
   392         return;
       
   393 
       
   394     s->vdev.get_config = virtio_audio_get_config;
       
   395     s->vdev.get_features = virtio_audio_get_features;
       
   396     s->vdev.set_features = virtio_audio_set_features;
       
   397     s->cmd_vq = virtio_add_queue(&s->vdev, 64, virtio_audio_handle_cmd);
       
   398     for (i = 0; i < NUM_STREAMS; i++) {
       
   399         s->stream[i].data_vq = virtio_add_queue(&s->vdev, 128,
       
   400                                                 virtio_audio_handle_data);
       
   401         s->stream[i].dev = s;
       
   402     }
       
   403 
       
   404     AUD_register_card(audio, "virtio-audio", &s->card);
       
   405 
       
   406     register_savevm("virtio-audio", -1, 1,
       
   407                     virtio_audio_save, virtio_audio_load, s);
       
   408 }