alsa: poll mode handling

Signed-off-by: malc <av1474@comtv.ru>
diff --git a/audio/alsaaudio.c b/audio/alsaaudio.c
index 5ea49a6..28c245d 100644
--- a/audio/alsaaudio.c
+++ b/audio/alsaaudio.c
@@ -23,6 +23,7 @@
  */
 #include <alsa/asoundlib.h>
 #include "qemu-common.h"
+#include "qemu-char.h"
 #include "audio.h"
 
 #if QEMU_GNUC_PREREQ(4, 3)
@@ -32,16 +33,24 @@
 #define AUDIO_CAP "alsa"
 #include "audio_int.h"
 
+struct pollhlp {
+    snd_pcm_t *handle;
+    struct pollfd *pfds;
+    int count;
+};
+
 typedef struct ALSAVoiceOut {
     HWVoiceOut hw;
     void *pcm_buf;
     snd_pcm_t *handle;
+    struct pollhlp pollhlp;
 } ALSAVoiceOut;
 
 typedef struct ALSAVoiceIn {
     HWVoiceIn hw;
     snd_pcm_t *handle;
     void *pcm_buf;
+    struct pollhlp pollhlp;
 } ALSAVoiceIn;
 
 static struct {
@@ -123,6 +132,156 @@
     *handlep = NULL;
 }
 
+static int alsa_recover (snd_pcm_t *handle)
+{
+    int err = snd_pcm_prepare (handle);
+    if (err < 0) {
+        alsa_logerr (err, "Failed to prepare handle %p\n", handle);
+        return -1;
+    }
+    return 0;
+}
+
+static int alsa_resume (snd_pcm_t *handle)
+{
+    int err = snd_pcm_resume (handle);
+    if (err < 0) {
+        alsa_logerr (err, "Failed to resume handle %p\n", handle);
+        return -1;
+    }
+    return 0;
+}
+
+static void alsa_poll_handler (void *opaque)
+{
+    int err, count;
+    snd_pcm_state_t state;
+    struct pollhlp *hlp = opaque;
+    unsigned short revents;
+
+    count = poll (hlp->pfds, hlp->count, 0);
+    if (count < 0) {
+        dolog ("alsa_poll_handler: poll %s\n", strerror (errno));
+        return;
+    }
+
+    if (!count) {
+        return;
+    }
+
+    /* XXX: ALSA example uses initial count, not the one returned by
+       poll, correct? */
+    err = snd_pcm_poll_descriptors_revents (hlp->handle, hlp->pfds,
+                                            hlp->count, &revents);
+    if (err < 0) {
+        alsa_logerr (err, "snd_pcm_poll_descriptors_revents");
+        return;
+    }
+
+    if (!(revents & POLLOUT)) {
+        if (conf.verbose) {
+            dolog ("revents = %d\n", revents);
+        }
+        return;
+    }
+
+    state = snd_pcm_state (hlp->handle);
+    switch (state) {
+    case SND_PCM_STATE_XRUN:
+        alsa_recover (hlp->handle);
+        break;
+
+    case SND_PCM_STATE_SUSPENDED:
+        alsa_resume (hlp->handle);
+        break;
+
+    case SND_PCM_STATE_PREPARED:
+        audio_run ("alsa run (prepared)");
+        break;
+
+    case SND_PCM_STATE_RUNNING:
+        audio_run ("alsa run (running)");
+        break;
+
+    default:
+        dolog ("Unexpected state %d\n", state);
+    }
+}
+
+static int alsa_poll_helper (snd_pcm_t *handle, struct pollhlp *hlp)
+{
+    int i, count, err;
+    struct pollfd *pfds;
+
+    count = snd_pcm_poll_descriptors_count (handle);
+    if (count <= 0) {
+        dolog ("Could not initialize poll mode\n"
+               "Invalid number of poll descriptors %d\n", count);
+        return -1;
+    }
+
+    pfds = audio_calloc ("alsa_poll_helper", count, sizeof (*pfds));
+    if (!pfds) {
+        dolog ("Could not initialize poll mode\n");
+        return -1;
+    }
+
+    err = snd_pcm_poll_descriptors (handle, pfds, count);
+    if (err < 0) {
+        alsa_logerr (err, "Could not initialize poll mode\n"
+                     "Could not obtain poll descriptors\n");
+        qemu_free (pfds);
+        return -1;
+    }
+
+    for (i = 0; i < count; ++i) {
+        if (pfds[i].events & POLLIN) {
+            err = qemu_set_fd_handler (pfds[i].fd, alsa_poll_handler,
+                                       NULL, hlp);
+        }
+        if (pfds[i].events & POLLOUT) {
+            if (conf.verbose) {
+                dolog ("POLLOUT %d %d\n", i, pfds[i].fd);
+            }
+            err = qemu_set_fd_handler (pfds[i].fd, NULL,
+                                       alsa_poll_handler, hlp);
+        }
+        if (conf.verbose) {
+            dolog ("Set handler events=%#x index=%d fd=%d err=%d\n",
+                   pfds[i].events, i, pfds[i].fd, err);
+        }
+
+        if (err) {
+            dolog ("Failed to set handler events=%#x index=%d fd=%d err=%d\n",
+                   pfds[i].events, i, pfds[i].fd, err);
+
+            while (i--) {
+                qemu_set_fd_handler (pfds[i].fd, NULL, NULL, NULL);
+            }
+            qemu_free (pfds);
+            return -1;
+        }
+    }
+    hlp->pfds = pfds;
+    hlp->count = count;
+    hlp->handle = handle;
+    return 0;
+}
+
+static int alsa_poll_out (HWVoiceOut *hw)
+{
+    ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw;
+
+    return alsa_poll_helper (alsa->handle, &alsa->pollhlp);
+}
+
+static int alsa_poll_in (HWVoiceIn *hw)
+{
+    ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw;
+
+    return alsa_poll_helper (alsa->handle, &alsa->pollhlp);
+}
+
 static int alsa_write (SWVoiceOut *sw, void *buf, int len)
 {
     return audio_pcm_sw_write (sw, buf, len);
@@ -493,26 +652,6 @@
     return -1;
 }
 
-static int alsa_recover (snd_pcm_t *handle)
-{
-    int err = snd_pcm_prepare (handle);
-    if (err < 0) {
-        alsa_logerr (err, "Failed to prepare handle %p\n", handle);
-        return -1;
-    }
-    return 0;
-}
-
-static int alsa_resume (snd_pcm_t *handle)
-{
-    int err = snd_pcm_resume (handle);
-    if (err < 0) {
-        alsa_logerr (err, "Failed to resume handle %p\n", handle);
-        return -1;
-    }
-    return 0;
-}
-
 static snd_pcm_sframes_t alsa_get_avail (snd_pcm_t *handle)
 {
     snd_pcm_sframes_t avail;
@@ -626,6 +765,22 @@
     return decr;
 }
 
+static void alsa_fini_poll (struct pollhlp *hlp)
+{
+    int i;
+    struct pollfd *pfds = hlp->pfds;
+
+    if (pfds) {
+        for (i = 0; i < hlp->count; ++i) {
+            qemu_set_fd_handler (pfds[i].fd, NULL, NULL, NULL);
+        }
+        qemu_free (pfds);
+    }
+    hlp->pfds = NULL;
+    hlp->count = 0;
+    hlp->handle = NULL;
+}
+
 static void alsa_fini_out (HWVoiceOut *hw)
 {
     ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw;
@@ -637,6 +792,8 @@
         qemu_free (alsa->pcm_buf);
         alsa->pcm_buf = NULL;
     }
+
+    alsa_fini_poll (&alsa->pollhlp);
 }
 
 static int alsa_init_out (HWVoiceOut *hw, struct audsettings *as)
@@ -705,11 +862,21 @@
 
 static int alsa_ctl_out (HWVoiceOut *hw, int cmd, ...)
 {
+    va_list ap;
+    int poll_mode;
     ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw;
 
+    va_start (ap, cmd);
+    poll_mode = va_arg (ap, int);
+    va_end (ap);
+
     switch (cmd) {
     case VOICE_ENABLE:
         ldebug ("enabling voice\n");
+        if (poll_mode && alsa_poll_out (hw)) {
+            poll_mode = 0;
+        }
+        hw->poll_mode = poll_mode;
         return alsa_voice_ctl (alsa->handle, "playback", 0);
 
     case VOICE_DISABLE:
@@ -772,6 +939,7 @@
         qemu_free (alsa->pcm_buf);
         alsa->pcm_buf = NULL;
     }
+    alsa_fini_poll (&alsa->pollhlp);
 }
 
 static int alsa_run_in (HWVoiceIn *hw)
@@ -909,15 +1077,30 @@
 
 static int alsa_ctl_in (HWVoiceIn *hw, int cmd, ...)
 {
+    va_list ap;
+    int poll_mode;
     ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw;
 
+    va_start (ap, cmd);
+    poll_mode = va_arg (ap, int);
+    va_end (ap);
+
     switch (cmd) {
     case VOICE_ENABLE:
         ldebug ("enabling voice\n");
+        if (poll_mode && alsa_poll_in (hw)) {
+            poll_mode = 0;
+        }
+        hw->poll_mode = poll_mode;
+
         return alsa_voice_ctl (alsa->handle, "capture", 0);
 
     case VOICE_DISABLE:
         ldebug ("disabling voice\n");
+        if (hw->poll_mode) {
+            hw->poll_mode = 0;
+            alsa_fini_poll (&alsa->pollhlp);
+        }
         return alsa_voice_ctl (alsa->handle, "capture", 1);
     }
 
@@ -1014,7 +1197,7 @@
     .fini_in  = alsa_fini_in,
     .run_in   = alsa_run_in,
     .read     = alsa_read,
-    .ctl_in   = alsa_ctl_in
+    .ctl_in   = alsa_ctl_in,
 };
 
 struct audio_driver alsa_audio_driver = {