OSDN Git Service

pcm: dsnoop: Added "hw_ptr_alignment" option in configuration for slave pointer alignment
[android-x86/external-alsa-lib.git] / src / pcm / pcm_direct.c
index 7447570..54d9900 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  PCM - Direct Stream Mixing
- *  Copyright (c) 2003 by Jaroslav Kysela <perex@suse.cz>
+ *  Copyright (c) 2003 by Jaroslav Kysela <perex@perex.cz>
  *
  *
  *   This library is free software; you can redistribute it and/or modify
@@ -15,7 +15,7 @@
  *
  *   You should have received a copy of the GNU Lesser General Public
  *   License along with this library; if not, write to the Free Software
- *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  *
  */
   
@@ -30,7 +30,7 @@
 #include <grp.h>
 #include <sys/ioctl.h>
 #include <sys/mman.h>
-#include <sys/poll.h>
+#include <poll.h>
 #include <sys/shm.h>
 #include <sys/sem.h>
 #include <sys/wait.h>
@@ -82,6 +82,8 @@ int snd_pcm_direct_semaphore_create_or_connect(snd_pcm_direct_t *dmix)
        return 0;
 }
 
+#define SND_PCM_DIRECT_MAGIC   (0xa15ad300 + sizeof(snd_pcm_direct_share_t))
+
 /*
  *  global shared memory area 
  */
@@ -89,13 +91,20 @@ int snd_pcm_direct_semaphore_create_or_connect(snd_pcm_direct_t *dmix)
 int snd_pcm_direct_shm_create_or_connect(snd_pcm_direct_t *dmix)
 {
        struct shmid_ds buf;
-       int tmpid, err;
+       int tmpid, err, first_instance = 0;
        
 retryget:
        dmix->shmid = shmget(dmix->ipc_key, sizeof(snd_pcm_direct_share_t),
-                            IPC_CREAT | dmix->ipc_perm);
+                            dmix->ipc_perm);
+       if (dmix->shmid < 0 && errno == ENOENT) {
+               if ((dmix->shmid = shmget(dmix->ipc_key, sizeof(snd_pcm_direct_share_t),
+                                            IPC_CREAT | IPC_EXCL | dmix->ipc_perm)) != -1)
+                       first_instance = 1;
+               else if (errno == EEXIST)
+                       goto retryget;
+       }
        err = -errno;
-       if (dmix->shmid < 0){
+       if (dmix->shmid < 0) {
                if (errno == EINVAL)
                if ((tmpid = shmget(dmix->ipc_key, 0, dmix->ipc_perm)) != -1)
                if (!shmctl(tmpid, IPC_STAT, &buf))
@@ -107,21 +116,29 @@ retryget:
        }
        dmix->shmptr = shmat(dmix->shmid, 0, 0);
        if (dmix->shmptr == (void *) -1) {
+               err = -errno;
                snd_pcm_direct_shm_discard(dmix);
-               return -errno;
+               return err;
        }
        mlock(dmix->shmptr, sizeof(snd_pcm_direct_share_t));
        if (shmctl(dmix->shmid, IPC_STAT, &buf) < 0) {
+               err = -errno;
                snd_pcm_direct_shm_discard(dmix);
-               return -errno;
+               return err;
        }
-       if (buf.shm_nattch == 1) {      /* we're the first user, clear the segment */
+       if (first_instance) {   /* we're the first user, clear the segment */
                memset(dmix->shmptr, 0, sizeof(snd_pcm_direct_share_t));
                if (dmix->ipc_gid >= 0) {
                        buf.shm_perm.gid = dmix->ipc_gid;
                        shmctl(dmix->shmid, IPC_SET, &buf);
                }
+               dmix->shmptr->magic = SND_PCM_DIRECT_MAGIC;
                return 1;
+       } else {
+               if (dmix->shmptr->magic != SND_PCM_DIRECT_MAGIC) {
+                       snd_pcm_direct_shm_discard(dmix);
+                       return -EINVAL;
+               }
        }
        return 0;
 }
@@ -498,10 +515,12 @@ int snd_pcm_direct_async(snd_pcm_t *pcm, int sig, pid_t pid)
 }
 
 /* empty the timer read queue */
-void snd_pcm_direct_clear_timer_queue(snd_pcm_direct_t *dmix)
+int snd_pcm_direct_clear_timer_queue(snd_pcm_direct_t *dmix)
 {
+       int changed = 0;
        if (dmix->timer_need_poll) {
                while (poll(&dmix->timer_fd, 1, 0) > 0) {
+                       changed++;
                        /* we don't need the value */
                        if (dmix->tread) {
                                snd_timer_tread_t rbuf[4];
@@ -516,24 +535,161 @@ void snd_pcm_direct_clear_timer_queue(snd_pcm_direct_t *dmix)
                        snd_timer_tread_t rbuf[4];
                        int len;
                        while ((len = snd_timer_read(dmix->timer, rbuf,
-                                                    sizeof(rbuf))) > 0 &&
+                                                    sizeof(rbuf))) > 0
+                                                    && (++changed) &&
                               len != sizeof(rbuf[0]))
                                ;
                } else {
                        snd_timer_read_t rbuf;
                        while (snd_timer_read(dmix->timer, &rbuf, sizeof(rbuf)) > 0)
-                               ;
+                               changed++;
                }
        }
+       return changed;
 }
 
 int snd_pcm_direct_timer_stop(snd_pcm_direct_t *dmix)
 {
        snd_timer_stop(dmix->timer);
-       snd_pcm_direct_clear_timer_queue(dmix);
        return 0;
 }
 
+/*
+ * Recover slave on XRUN.
+ * Even if direct plugins disable xrun detection, there might be an xrun
+ * raised directly by some drivers.
+ * The first client recovers slave pcm.
+ * Each client needs to execute sw xrun handling afterwards
+ */
+int snd_pcm_direct_slave_recover(snd_pcm_direct_t *direct)
+{
+       int ret;
+       int semerr;
+
+       semerr = snd_pcm_direct_semaphore_down(direct,
+                                                  DIRECT_IPC_SEM_CLIENT);
+       if (semerr < 0) {
+               SNDERR("SEMDOWN FAILED with err %d", semerr);
+               return semerr;
+       }
+
+       if (snd_pcm_state(direct->spcm) != SND_PCM_STATE_XRUN) {
+               /* ignore... someone else already did recovery */
+               semerr = snd_pcm_direct_semaphore_up(direct,
+                                                    DIRECT_IPC_SEM_CLIENT);
+               if (semerr < 0) {
+                       SNDERR("SEMUP FAILED with err %d", semerr);
+                       return semerr;
+               }
+               return 0;
+       }
+
+       ret = snd_pcm_prepare(direct->spcm);
+       if (ret < 0) {
+               SNDERR("recover: unable to prepare slave");
+               semerr = snd_pcm_direct_semaphore_up(direct,
+                                                    DIRECT_IPC_SEM_CLIENT);
+               if (semerr < 0) {
+                       SNDERR("SEMUP FAILED with err %d", semerr);
+                       return semerr;
+               }
+               return ret;
+       }
+
+       if (direct->type == SND_PCM_TYPE_DSHARE) {
+               const snd_pcm_channel_area_t *dst_areas;
+               dst_areas = snd_pcm_mmap_areas(direct->spcm);
+               snd_pcm_areas_silence(dst_areas, 0, direct->spcm->channels,
+                                     direct->spcm->buffer_size,
+                                     direct->spcm->format);
+       }
+
+       ret = snd_pcm_start(direct->spcm);
+       if (ret < 0) {
+               SNDERR("recover: unable to start slave");
+               semerr = snd_pcm_direct_semaphore_up(direct,
+                                                    DIRECT_IPC_SEM_CLIENT);
+               if (semerr < 0) {
+                       SNDERR("SEMUP FAILED with err %d", semerr);
+                       return semerr;
+               }
+               return ret;
+       }
+       direct->shmptr->s.recoveries++;
+       semerr = snd_pcm_direct_semaphore_up(direct,
+                                                DIRECT_IPC_SEM_CLIENT);
+       if (semerr < 0) {
+               SNDERR("SEMUP FAILED with err %d", semerr);
+               return semerr;
+       }
+       return 0;
+}
+
+/*
+ * enter xrun state, if slave xrun occurred
+ * @return: 0 - no xrun >0: xrun happened
+ */
+int snd_pcm_direct_client_chk_xrun(snd_pcm_direct_t *direct, snd_pcm_t *pcm)
+{
+       if (direct->shmptr->s.recoveries != direct->recoveries) {
+               /* no matter how many xruns we missed -
+                * so don't increment but just update to actual counter
+                */
+               direct->recoveries = direct->shmptr->s.recoveries;
+               pcm->fast_ops->drop(pcm);
+               /* trigger_tstamp update is missing in drop callbacks */
+               gettimestamp(&direct->trigger_tstamp, pcm->tstamp_type);
+               /* no timer clear:
+                * if slave already entered xrun again the event is lost.
+                * snd_pcm_direct_clear_timer_queue(direct);
+                */
+               direct->state = SND_PCM_STATE_XRUN;
+               return 1;
+       }
+       return 0;
+}
+
+/*
+ * This is the only operation guaranteed to be called before entering poll().
+ * Direct plugins use fd of snd_timer to poll on, these timers do NOT check
+ * state of substream in kernel by intention.
+ * Only the enter to xrun might be notified once (SND_TIMER_EVENT_MSTOP).
+ * If xrun event was not correctly handled or was ignored it will never be
+ * evaluated again afterwards.
+ * This will result in snd_pcm_wait() always returning timeout.
+ * In contrast poll() on pcm hardware checks ALSA state and will immediately
+ * return POLLERR on XRUN.
+ *
+ * To prevent timeout and applications endlessly spinning without xrun
+ * detected we add a state check here which may trigger the xrun sequence.
+ *
+ * return count of filled descriptors or negative error code
+ */
+int snd_pcm_direct_poll_descriptors(snd_pcm_t *pcm, struct pollfd *pfds,
+                                   unsigned int space)
+{
+       if (pcm->poll_fd < 0) {
+               SNDMSG("poll_fd < 0");
+               return -EIO;
+       }
+       if (space >= 1 && pfds) {
+               pfds->fd = pcm->poll_fd;
+               pfds->events = pcm->poll_events | POLLERR | POLLNVAL;
+       } else {
+               return 0;
+       }
+
+       /* this will also evaluate slave state and enter xrun if necessary */
+       /* using __snd_pcm_state() since this function is called inside lock */
+       switch (__snd_pcm_state(pcm)) {
+       case SND_PCM_STATE_XRUN:
+               return -EPIPE;
+       default:
+               break;
+       }
+       return 1;
+}
+
 int snd_pcm_direct_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents)
 {
        snd_pcm_direct_t *dmix = pcm->private_data;
@@ -541,10 +697,12 @@ int snd_pcm_direct_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned in
        int empty = 0;
 
        assert(pfds && nfds == 1 && revents);
+
+timer_changed:
        events = pfds[0].revents;
        if (events & POLLIN) {
                snd_pcm_uframes_t avail;
-               snd_pcm_avail_update(pcm);
+               __snd_pcm_avail_update(pcm);
                if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
                        events |= POLLOUT;
                        events &= ~POLLIN;
@@ -556,17 +714,34 @@ int snd_pcm_direct_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned in
        }
        switch (snd_pcm_state(dmix->spcm)) {
        case SND_PCM_STATE_XRUN:
+               /* recover slave and update client state to xrun
+                * before returning POLLERR
+                */
+               snd_pcm_direct_slave_recover(dmix);
+               snd_pcm_direct_client_chk_xrun(dmix, pcm);
+               /* fallthrough */
        case SND_PCM_STATE_SUSPENDED:
+       case SND_PCM_STATE_SETUP:
                events |= POLLERR;
                break;
        default:
                if (empty) {
-                       snd_pcm_direct_clear_timer_queue(dmix);
+                       /* here we have a race condition:
+                        * if period event arrived after the avail_update call
+                        * above we might clear this event with the following
+                        * clear_timer_queue.
+                        * There is no way to do this in atomic manner, so we
+                        * need to recheck avail_update if we successfully
+                        * cleared a poll event.
+                        */
+                       if (snd_pcm_direct_clear_timer_queue(dmix))
+                               goto timer_changed;
                        events &= ~(POLLOUT|POLLIN);
                        /* additional check */
-                       switch (snd_pcm_state(pcm)) {
+                       switch (__snd_pcm_state(pcm)) {
                        case SND_PCM_STATE_XRUN:
                        case SND_PCM_STATE_SUSPENDED:
+                       case SND_PCM_STATE_SETUP:
                                events |= POLLERR;
                                break;
                        default:
@@ -581,16 +756,19 @@ int snd_pcm_direct_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned in
 
 int snd_pcm_direct_info(snd_pcm_t *pcm, snd_pcm_info_t * info)
 {
-       // snd_pcm_direct_t *dmix = pcm->private_data;
+       snd_pcm_direct_t *dmix = pcm->private_data;
+
+       if (dmix->spcm && !dmix->shmptr->use_server)
+               return snd_pcm_info(dmix->spcm, info);
 
        memset(info, 0, sizeof(*info));
        info->stream = pcm->stream;
        info->card = -1;
        /* FIXME: fill this with something more useful: we know the hardware name */
        if (pcm->name) {
-               strncpy((char *)info->id, pcm->name, sizeof(info->id));
-               strncpy((char *)info->name, pcm->name, sizeof(info->name));
-               strncpy((char *)info->subname, pcm->name, sizeof(info->subname));
+               snd_strlcpy((char *)info->id, pcm->name, sizeof(info->id));
+               snd_strlcpy((char *)info->name, pcm->name, sizeof(info->name));
+               snd_strlcpy((char *)info->subname, pcm->name, sizeof(info->subname));
        }
        info->subdevices_count = 1;
        return 0;
@@ -639,12 +817,35 @@ static int hw_param_interval_refine_minmax(snd_pcm_hw_params_t *params,
        return hw_param_interval_refine_one(params, var, &t);
 }
 
+/* this code is used 'as-is' from the alsa kernel code */
+static int snd_interval_step(struct snd_interval *i, unsigned int min,
+                            unsigned int step)
+{
+       unsigned int n;
+       int changed = 0;
+       n = (i->min - min) % step;
+       if (n != 0 || i->openmin) {
+               i->min += step - n;
+               changed = 1;
+       }
+       n = (i->max - min) % step;
+       if (n != 0 || i->openmax) {
+               i->max -= n;
+               changed = 1;
+       }
+       if (snd_interval_checkempty(i)) {
+               i->empty = 1;
+               return -EINVAL;
+       }
+       return changed;
+}
+
 #undef REFINE_DEBUG
 
 int snd_pcm_direct_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
 {
        snd_pcm_direct_t *dshare = pcm->private_data;
-       static snd_mask_t access = { .bits = { 
+       static const snd_mask_t access = { .bits = { 
                                        (1<<SNDRV_PCM_ACCESS_MMAP_INTERLEAVED) |
                                        (1<<SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED) |
                                        (1<<SNDRV_PCM_ACCESS_RW_INTERLEAVED) |
@@ -689,15 +890,16 @@ int snd_pcm_direct_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
                                           &dshare->shmptr->hw.rate);
        if (err < 0)
                return err;
-       err = hw_param_interval_refine_one(params, SND_PCM_HW_PARAM_PERIOD_SIZE,
-                                          &dshare->shmptr->hw.period_size);
-       if (err < 0)
-               return err;
-       err = hw_param_interval_refine_one(params, SND_PCM_HW_PARAM_PERIOD_TIME,
-                                          &dshare->shmptr->hw.period_time);
-       if (err < 0)
-               return err;
-       if (! dshare->variable_buffer_size) {
+
+       if (dshare->max_periods < 0) {
+               err = hw_param_interval_refine_one(params, SND_PCM_HW_PARAM_PERIOD_SIZE,
+                                                  &dshare->shmptr->hw.period_size);
+               if (err < 0)
+                       return err;
+               err = hw_param_interval_refine_one(params, SND_PCM_HW_PARAM_PERIOD_TIME,
+                                                  &dshare->shmptr->hw.period_time);
+               if (err < 0)
+                       return err;
                err = hw_param_interval_refine_one(params, SND_PCM_HW_PARAM_BUFFER_SIZE,
                                                   &dshare->shmptr->hw.buffer_size);
                if (err < 0)
@@ -709,15 +911,42 @@ int snd_pcm_direct_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
        } else if (params->rmask & ((1<<SND_PCM_HW_PARAM_PERIODS)|
                                    (1<<SND_PCM_HW_PARAM_BUFFER_BYTES)|
                                    (1<<SND_PCM_HW_PARAM_BUFFER_SIZE)|
-                                   (1<<SND_PCM_HW_PARAM_BUFFER_TIME))) {
+                                   (1<<SND_PCM_HW_PARAM_BUFFER_TIME)|
+                                   (1<<SND_PCM_HW_PARAM_PERIOD_TIME)|
+                                   (1<<SND_PCM_HW_PARAM_PERIOD_SIZE)|
+                                   (1<<SND_PCM_HW_PARAM_PERIOD_BYTES))) {
+               snd_interval_t period_size = dshare->shmptr->hw.period_size;
+               snd_interval_t period_time = dshare->shmptr->hw.period_time;
                int changed;
+               unsigned int max_periods = dshare->max_periods;
+               if (max_periods < 2)
+                       max_periods = dshare->slave_buffer_size / dshare->slave_period_size;
+
+               /* make sure buffer size does not exceed slave buffer size */
+               err = hw_param_interval_refine_minmax(params, SND_PCM_HW_PARAM_BUFFER_SIZE,
+                                       2 * dshare->slave_period_size, dshare->slave_buffer_size);
+               if (err < 0)
+                       return err;
+               if (dshare->var_periodsize) {
+                       /* more tolerant settings... */
+                       if (dshare->shmptr->hw.buffer_size.max / 2 > period_size.max)
+                               period_size.max = dshare->shmptr->hw.buffer_size.max / 2;
+                       if (dshare->shmptr->hw.buffer_time.max / 2 > period_time.max)
+                               period_time.max = dshare->shmptr->hw.buffer_time.max / 2;
+               }
+
+               err = hw_param_interval_refine_one(params, SND_PCM_HW_PARAM_PERIOD_SIZE,
+                                                  &period_size);
+               if (err < 0)
+                       return err;
+               err = hw_param_interval_refine_one(params, SND_PCM_HW_PARAM_PERIOD_TIME,
+                                                  &period_time);
+               if (err < 0)
+                       return err;
                do {
                        changed = 0;
-                       /* Set min/max size to [2:1024] since INT_MAX as the
-                        * upper-limit results in a too big buffer on some apps.
-                        */
                        err = hw_param_interval_refine_minmax(params, SND_PCM_HW_PARAM_PERIODS,
-                                                             2, 1024);
+                                                             2, max_periods);
                        if (err < 0)
                                return err;
                        changed |= err;
@@ -725,8 +954,16 @@ int snd_pcm_direct_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
                        if (err < 0)
                                return err;
                        changed |= err;
+                       err = snd_interval_step(hw_param_interval(params, SND_PCM_HW_PARAM_PERIOD_SIZE),
+                                                               0, dshare->slave_period_size);
+                       if (err < 0)
+                               return err;
+                       changed |= err;
+                       if (err)
+                               params->rmask |= (1 << SND_PCM_HW_PARAM_PERIOD_SIZE);
                } while (changed);
        }
+       dshare->timer_ticks = hw_param_interval(params, SND_PCM_HW_PARAM_PERIOD_SIZE)->max / dshare->slave_period_size;
        params->info = dshare->shmptr->s.info;
 #ifdef REFINE_DEBUG
        snd_output_puts(log, "DMIX REFINE (end):\n");
@@ -775,23 +1012,117 @@ int snd_pcm_direct_munmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
        return 0;
 }
 
-int snd_pcm_direct_resume(snd_pcm_t *pcm)
+snd_pcm_chmap_query_t **snd_pcm_direct_query_chmaps(snd_pcm_t *pcm)
+{
+       snd_pcm_direct_t *dmix = pcm->private_data;
+       return snd_pcm_query_chmaps(dmix->spcm);
+}
+
+snd_pcm_chmap_t *snd_pcm_direct_get_chmap(snd_pcm_t *pcm)
+{
+       snd_pcm_direct_t *dmix = pcm->private_data;
+       return snd_pcm_get_chmap(dmix->spcm);
+}
+
+int snd_pcm_direct_set_chmap(snd_pcm_t *pcm, const snd_pcm_chmap_t *map)
+{
+       snd_pcm_direct_t *dmix = pcm->private_data;
+       return snd_pcm_set_chmap(dmix->spcm, map);
+}
+
+int snd_pcm_direct_prepare(snd_pcm_t *pcm)
 {
        snd_pcm_direct_t *dmix = pcm->private_data;
        int err;
-       
-       snd_pcm_direct_semaphore_down(dmix, DIRECT_IPC_SEM_CLIENT);
-       err = snd_pcm_resume(dmix->spcm);
-       if (err == -ENOSYS) {
-               /* FIXME: error handling? */
-               snd_pcm_prepare(dmix->spcm);
+
+       switch (snd_pcm_state(dmix->spcm)) {
+       case SND_PCM_STATE_SETUP:
+       case SND_PCM_STATE_XRUN:
+       case SND_PCM_STATE_SUSPENDED:
+               err = snd_pcm_prepare(dmix->spcm);
+               if (err < 0)
+                       return err;
                snd_pcm_start(dmix->spcm);
-               err = 0;
+               break;
+       case SND_PCM_STATE_OPEN:
+       case SND_PCM_STATE_DISCONNECTED:
+               return -EBADFD;
+       default:
+               break;
+       }
+       snd_pcm_direct_check_interleave(dmix, pcm);
+       dmix->state = SND_PCM_STATE_PREPARED;
+       dmix->appl_ptr = dmix->last_appl_ptr = 0;
+       dmix->hw_ptr = 0;
+       return snd_pcm_direct_set_timer_params(dmix);
+}
+
+int snd_pcm_direct_resume(snd_pcm_t *pcm)
+{
+       snd_pcm_direct_t *dmix = pcm->private_data;
+       snd_pcm_t *spcm = dmix->spcm;
+
+       snd_pcm_direct_semaphore_down(dmix, DIRECT_IPC_SEM_CLIENT);
+       /* some buggy drivers require the device resumed before prepared;
+        * when a device has RESUME flag and is in SUSPENDED state, resume
+        * here but immediately drop to bring it to a sane active state.
+        */
+       if ((spcm->info & SND_PCM_INFO_RESUME) &&
+           snd_pcm_state(spcm) == SND_PCM_STATE_SUSPENDED) {
+               snd_pcm_resume(spcm);
+               snd_pcm_drop(spcm);
+               snd_pcm_direct_timer_stop(dmix);
+               snd_pcm_direct_clear_timer_queue(dmix);
+               snd_pcm_areas_silence(snd_pcm_mmap_areas(spcm), 0,
+                                     spcm->channels, spcm->buffer_size,
+                                     spcm->format);
+               snd_pcm_prepare(spcm);
+               snd_pcm_start(spcm);
        }
        snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT);
-       return err;
+       return -ENOSYS;
+}
+
+#define COPY_SLAVE(field) (dmix->shmptr->s.field = spcm->field)
+
+/* copy the slave setting */
+static void save_slave_setting(snd_pcm_direct_t *dmix, snd_pcm_t *spcm)
+{
+       spcm->info &= ~SND_PCM_INFO_PAUSE;
+
+       COPY_SLAVE(access);
+       COPY_SLAVE(format);
+       COPY_SLAVE(subformat);
+       COPY_SLAVE(channels);
+       COPY_SLAVE(rate);
+       COPY_SLAVE(period_size);
+       COPY_SLAVE(period_time);
+       COPY_SLAVE(periods);
+       COPY_SLAVE(tstamp_mode);
+       COPY_SLAVE(tstamp_type);
+       COPY_SLAVE(period_step);
+       COPY_SLAVE(avail_min);
+       COPY_SLAVE(start_threshold);
+       COPY_SLAVE(stop_threshold);
+       COPY_SLAVE(silence_threshold);
+       COPY_SLAVE(silence_size);
+       COPY_SLAVE(boundary);
+       COPY_SLAVE(info);
+       COPY_SLAVE(msbits);
+       COPY_SLAVE(rate_num);
+       COPY_SLAVE(rate_den);
+       COPY_SLAVE(hw_flags);
+       COPY_SLAVE(fifo_size);
+       COPY_SLAVE(buffer_size);
+       COPY_SLAVE(buffer_time);
+       COPY_SLAVE(sample_bits);
+       COPY_SLAVE(frame_bits);
+
+       dmix->shmptr->s.info &= ~SND_PCM_INFO_RESUME;
 }
 
+#undef COPY_SLAVE
+
 /*
  * this function initializes hardware and starts playback operation with
  * no stop threshold (it operates all time without xrun checking)
@@ -799,82 +1130,79 @@ int snd_pcm_direct_resume(snd_pcm_t *pcm)
  */
 int snd_pcm_direct_initialize_slave(snd_pcm_direct_t *dmix, snd_pcm_t *spcm, struct slave_params *params)
 {
-       snd_pcm_hw_params_t *hw_params;
-       snd_pcm_sw_params_t *sw_params;
+       snd_pcm_hw_params_t hw_params = {0};
+       snd_pcm_sw_params_t sw_params = {0};
        int ret, buffer_is_not_initialized;
        snd_pcm_uframes_t boundary;
        struct pollfd fd;
        int loops = 10;
 
-       snd_pcm_hw_params_alloca(&hw_params);
-       snd_pcm_sw_params_alloca(&sw_params);
-
       __again:
        if (loops-- <= 0) {
                SNDERR("unable to find a valid configuration for slave");
                return -EINVAL;
        }
-       ret = snd_pcm_hw_params_any(spcm, hw_params);
+       ret = snd_pcm_hw_params_any(spcm, &hw_params);
        if (ret < 0) {
                SNDERR("snd_pcm_hw_params_any failed");
                return ret;
        }
-       ret = snd_pcm_hw_params_set_access(spcm, hw_params, SND_PCM_ACCESS_MMAP_INTERLEAVED);
+       ret = snd_pcm_hw_params_set_access(spcm, &hw_params,
+                                          SND_PCM_ACCESS_MMAP_INTERLEAVED);
        if (ret < 0) {
-               ret = snd_pcm_hw_params_set_access(spcm, hw_params, SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
+               ret = snd_pcm_hw_params_set_access(spcm, &hw_params,
+                                       SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
                if (ret < 0) {
                        SNDERR("slave plugin does not support mmap interleaved or mmap noninterleaved access");
                        return ret;
                }
        }
-       ret = snd_pcm_hw_params_set_format(spcm, hw_params, params->format);
+       if (params->format == SND_PCM_FORMAT_UNKNOWN)
+               ret = -EINVAL;
+       else
+               ret = snd_pcm_hw_params_set_format(spcm, &hw_params,
+                                                  params->format);
        if (ret < 0) {
+               static const snd_pcm_format_t dmix_formats[] = {
+                       SND_PCM_FORMAT_S32,
+                       SND_PCM_FORMAT_S32 ^ SND_PCM_FORMAT_S32_LE ^
+                                                       SND_PCM_FORMAT_S32_BE,
+                       SND_PCM_FORMAT_S16,
+                       SND_PCM_FORMAT_S16 ^ SND_PCM_FORMAT_S16_LE ^
+                                                       SND_PCM_FORMAT_S16_BE,
+                       SND_PCM_FORMAT_S24_LE,
+                       SND_PCM_FORMAT_S24_3LE,
+                       SND_PCM_FORMAT_U8,
+               };
                snd_pcm_format_t format;
-               if (dmix->type == SND_PCM_TYPE_DMIX) {
-                       switch (params->format) {
-                       case SND_PCM_FORMAT_S32_LE:
-                       case SND_PCM_FORMAT_S32_BE:
-                       case SND_PCM_FORMAT_S16_LE:
-                       case SND_PCM_FORMAT_S16_BE:
-                       case SND_PCM_FORMAT_S24_3LE:
+               unsigned int i;
+
+               for (i = 0; i < ARRAY_SIZE(dmix_formats); ++i) {
+                       format = dmix_formats[i];
+                       ret = snd_pcm_hw_params_set_format(spcm, &hw_params,
+                                                          format);
+                       if (ret >= 0)
                                break;
-                       default:
-                               SNDERR("invalid format");
-                               return -EINVAL;
-                       }
                }
-               format = params->format;
-               ret = snd_pcm_hw_params_set_format(spcm, hw_params, format);
+               if (ret < 0 && dmix->type != SND_PCM_TYPE_DMIX) {
+                       /* TODO: try to choose a good format */
+                       ret = INTERNAL(snd_pcm_hw_params_set_format_first)(spcm,
+                                                       &hw_params, &format);
+               }
                if (ret < 0) {
                        SNDERR("requested or auto-format is not available");
                        return ret;
                }
                params->format = format;
        }
-       ret = snd_pcm_hw_params_set_channels(spcm, hw_params, params->channels);
+       ret = INTERNAL(snd_pcm_hw_params_set_channels_near)(spcm, &hw_params,
+                                       (unsigned int *)&params->channels);
        if (ret < 0) {
-               unsigned int min, max;
-               ret = INTERNAL(snd_pcm_hw_params_get_channels_min)(hw_params, &min);
-               if (ret < 0) {
-                       SNDERR("cannot obtain minimal count of channels");
-                       return ret;
-               }
-               ret = INTERNAL(snd_pcm_hw_params_get_channels_min)(hw_params, &max);
-               if (ret < 0) {
-                       SNDERR("cannot obtain maximal count of channels");
-                       return ret;
-               }
-               if (min == max) {
-                       ret = snd_pcm_hw_params_set_channels(spcm, hw_params, min);
-                       if (ret >= 0)
-                               params->channels = min;
-               }
-               if (ret < 0) {
-                       SNDERR("requested count of channels is not available");
-                       return ret;
-               }
+               SNDERR("requested count of channels is not available");
+               return ret;
        }
-       ret = INTERNAL(snd_pcm_hw_params_set_rate_near)(spcm, hw_params, (unsigned int *)&params->rate, 0);
+       ret = INTERNAL(snd_pcm_hw_params_set_rate_near)(spcm, &hw_params,
+                                       (unsigned int *)&params->rate, 0);
        if (ret < 0) {
                SNDERR("requested rate is not available");
                return ret;
@@ -882,13 +1210,15 @@ int snd_pcm_direct_initialize_slave(snd_pcm_direct_t *dmix, snd_pcm_t *spcm, str
 
        buffer_is_not_initialized = 0;
        if (params->buffer_time > 0) {
-               ret = INTERNAL(snd_pcm_hw_params_set_buffer_time_near)(spcm, hw_params, (unsigned int *)&params->buffer_time, 0);
+               ret = INTERNAL(snd_pcm_hw_params_set_buffer_time_near)(spcm,
+                       &hw_params, (unsigned int *)&params->buffer_time, 0);
                if (ret < 0) {
                        SNDERR("unable to set buffer time");
                        return ret;
                }
        } else if (params->buffer_size > 0) {
-               ret = INTERNAL(snd_pcm_hw_params_set_buffer_size_near)(spcm, hw_params, (snd_pcm_uframes_t *)&params->buffer_size);
+               ret = INTERNAL(snd_pcm_hw_params_set_buffer_size_near)(spcm,
+                       &hw_params, (snd_pcm_uframes_t *)&params->buffer_size);
                if (ret < 0) {
                        SNDERR("unable to set buffer size");
                        return ret;
@@ -898,13 +1228,16 @@ int snd_pcm_direct_initialize_slave(snd_pcm_direct_t *dmix, snd_pcm_t *spcm, str
        }
 
        if (params->period_time > 0) {
-               ret = INTERNAL(snd_pcm_hw_params_set_period_time_near)(spcm, hw_params, (unsigned int *)&params->period_time, 0);
+               ret = INTERNAL(snd_pcm_hw_params_set_period_time_near)(spcm,
+                       &hw_params, (unsigned int *)&params->period_time, 0);
                if (ret < 0) {
                        SNDERR("unable to set period_time");
                        return ret;
                }
        } else if (params->period_size > 0) {
-               ret = INTERNAL(snd_pcm_hw_params_set_period_size_near)(spcm, hw_params, (snd_pcm_uframes_t *)&params->period_size, 0);
+               ret = INTERNAL(snd_pcm_hw_params_set_period_size_near)(spcm,
+                       &hw_params, (snd_pcm_uframes_t *)&params->period_size,
+                       0);
                if (ret < 0) {
                        SNDERR("unable to set period_size");
                        return ret;
@@ -913,7 +1246,8 @@ int snd_pcm_direct_initialize_slave(snd_pcm_direct_t *dmix, snd_pcm_t *spcm, str
        
        if (buffer_is_not_initialized && params->periods > 0) {
                unsigned int periods = params->periods;
-               ret = INTERNAL(snd_pcm_hw_params_set_periods_near)(spcm, hw_params, &params->periods, 0);
+               ret = INTERNAL(snd_pcm_hw_params_set_periods_near)(spcm,
+                                       &hw_params, &params->periods, 0);
                if (ret < 0) {
                        SNDERR("unable to set requested periods");
                        return ret;
@@ -932,48 +1266,68 @@ int snd_pcm_direct_initialize_slave(snd_pcm_direct_t *dmix, snd_pcm_t *spcm, str
                }
        }
        
-       ret = snd_pcm_hw_params(spcm, hw_params);
+       ret = snd_pcm_hw_params(spcm, &hw_params);
        if (ret < 0) {
                SNDERR("unable to install hw params");
                return ret;
        }
 
        /* store some hw_params values to shared info */
-       dmix->shmptr->hw.format = snd_mask_value(hw_param_mask(hw_params, SND_PCM_HW_PARAM_FORMAT));
-       dmix->shmptr->hw.rate = *hw_param_interval(hw_params, SND_PCM_HW_PARAM_RATE);
-       dmix->shmptr->hw.buffer_size = *hw_param_interval(hw_params, SND_PCM_HW_PARAM_BUFFER_SIZE);
-       dmix->shmptr->hw.buffer_time = *hw_param_interval(hw_params, SND_PCM_HW_PARAM_BUFFER_TIME);
-       dmix->shmptr->hw.period_size = *hw_param_interval(hw_params, SND_PCM_HW_PARAM_PERIOD_SIZE);
-       dmix->shmptr->hw.period_time = *hw_param_interval(hw_params, SND_PCM_HW_PARAM_PERIOD_TIME);
-       dmix->shmptr->hw.periods = *hw_param_interval(hw_params, SND_PCM_HW_PARAM_PERIODS);
-
-
-       ret = snd_pcm_sw_params_current(spcm, sw_params);
+       dmix->shmptr->hw.format =
+               snd_mask_value(hw_param_mask(&hw_params,
+                                            SND_PCM_HW_PARAM_FORMAT));
+       dmix->shmptr->hw.rate =
+               *hw_param_interval(&hw_params, SND_PCM_HW_PARAM_RATE);
+       dmix->shmptr->hw.buffer_size =
+               *hw_param_interval(&hw_params, SND_PCM_HW_PARAM_BUFFER_SIZE);
+       dmix->shmptr->hw.buffer_time =
+               *hw_param_interval(&hw_params, SND_PCM_HW_PARAM_BUFFER_TIME);
+       dmix->shmptr->hw.period_size =
+               *hw_param_interval(&hw_params, SND_PCM_HW_PARAM_PERIOD_SIZE);
+       dmix->shmptr->hw.period_time =
+               *hw_param_interval(&hw_params, SND_PCM_HW_PARAM_PERIOD_TIME);
+       dmix->shmptr->hw.periods =
+               *hw_param_interval(&hw_params, SND_PCM_HW_PARAM_PERIODS);
+
+
+       ret = snd_pcm_sw_params_current(spcm, &sw_params);
        if (ret < 0) {
                SNDERR("unable to get current sw_params");
                return ret;
        }
 
-       ret = snd_pcm_sw_params_get_boundary(sw_params, &boundary);
+       ret = snd_pcm_sw_params_get_boundary(&sw_params, &boundary);
        if (ret < 0) {
                SNDERR("unable to get boundary");
                return ret;
        }
-       ret = snd_pcm_sw_params_set_stop_threshold(spcm, sw_params, boundary);
+       ret = snd_pcm_sw_params_set_stop_threshold(spcm, &sw_params, boundary);
        if (ret < 0) {
                SNDERR("unable to set stop threshold");
                return ret;
        }
 
-       if (dmix->type != SND_PCM_TYPE_DMIX)
+       /* set timestamp mode to MMAP
+        * the slave timestamp is copied appropriately in dsnoop/dmix/dshare
+        * based on the tstamp_mode of each client
+        */
+       ret = snd_pcm_sw_params_set_tstamp_mode(spcm, &sw_params,
+                                               SND_PCM_TSTAMP_ENABLE);
+       if (ret < 0) {
+               SNDERR("unable to tstamp mode MMAP");
+               return ret;
+       }
+
+       if (dmix->type != SND_PCM_TYPE_DMIX &&
+           dmix->type != SND_PCM_TYPE_DSHARE)
                goto __skip_silencing;
 
-       ret = snd_pcm_sw_params_set_silence_threshold(spcm, sw_params, 0);
+       ret = snd_pcm_sw_params_set_silence_threshold(spcm, &sw_params, 0);
        if (ret < 0) {
                SNDERR("unable to set silence threshold");
                return ret;
        }
-       ret = snd_pcm_sw_params_set_silence_size(spcm, sw_params, boundary);
+       ret = snd_pcm_sw_params_set_silence_size(spcm, &sw_params, boundary);
        if (ret < 0) {
                SNDERR("unable to set silence threshold (please upgrade to 0.9.0rc8+ driver)");
                return ret;
@@ -981,7 +1335,7 @@ int snd_pcm_direct_initialize_slave(snd_pcm_direct_t *dmix, snd_pcm_t *spcm, str
 
       __skip_silencing:
 
-       ret = snd_pcm_sw_params(spcm, sw_params);
+       ret = snd_pcm_sw_params(spcm, &sw_params);
        if (ret < 0) {
                SNDERR("unable to install sw params (please upgrade to 0.9.0rc8+ driver)");
                return ret;
@@ -990,7 +1344,8 @@ int snd_pcm_direct_initialize_slave(snd_pcm_direct_t *dmix, snd_pcm_t *spcm, str
        if (dmix->type == SND_PCM_TYPE_DSHARE) {
                const snd_pcm_channel_area_t *dst_areas;
                dst_areas = snd_pcm_mmap_areas(spcm);
-               snd_pcm_areas_silence(dst_areas, 0, spcm->channels, spcm->buffer_size, spcm->format);
+               snd_pcm_areas_silence(dst_areas, 0, spcm->channels,
+                                     spcm->buffer_size, spcm->format);
        }
        
        ret = snd_pcm_start(spcm);
@@ -1006,15 +1361,7 @@ int snd_pcm_direct_initialize_slave(snd_pcm_direct_t *dmix, snd_pcm_t *spcm, str
        snd_pcm_poll_descriptors(spcm, &fd, 1);
        dmix->hw_fd = fd.fd;
        
-       dmix->shmptr->s.boundary = spcm->boundary;
-       dmix->shmptr->s.buffer_size = spcm->buffer_size;
-       dmix->shmptr->s.period_size = spcm->period_size;
-       dmix->shmptr->s.sample_bits = spcm->sample_bits;
-       dmix->shmptr->s.channels = spcm->channels;
-       dmix->shmptr->s.rate = spcm->rate;
-       dmix->shmptr->s.format = spcm->format;
-       dmix->shmptr->s.info = spcm->info & ~SND_PCM_INFO_PAUSE;
-       dmix->shmptr->s.msbits = spcm->msbits;
+       save_slave_setting(dmix, spcm);
 
        /* Currently, we assume that each dmix client has the same
         * hw_params setting.
@@ -1028,6 +1375,14 @@ int snd_pcm_direct_initialize_slave(snd_pcm_direct_t *dmix, snd_pcm_t *spcm, str
        dmix->slave_boundary = spcm->boundary;
 
        spcm->donot_close = 1;
+
+       {
+               int ver = 0;
+               ioctl(spcm->poll_fd, SNDRV_PCM_IOCTL_PVERSION, &ver);
+               if (ver < SNDRV_PROTOCOL_VERSION(2, 0, 8))
+                       dmix->shmptr->use_server = 1;
+       }
+
        return 0;
 }
 
@@ -1039,27 +1394,29 @@ int snd_pcm_direct_initialize_slave(snd_pcm_direct_t *dmix, snd_pcm_t *spcm, str
 int snd_pcm_direct_initialize_poll_fd(snd_pcm_direct_t *dmix)
 {
        int ret;
-       snd_pcm_info_t *info;
+       snd_pcm_info_t info = {0};
        char name[128];
        int capture = dmix->type == SND_PCM_TYPE_DSNOOP ? 1 : 0;
 
        dmix->tread = 1;
        dmix->timer_need_poll = 0;
-       snd_pcm_info_alloca(&info);
-       ret = snd_pcm_info(dmix->spcm, info);
+       dmix->timer_ticks = 1;
+       ret = snd_pcm_info(dmix->spcm, &info);
        if (ret < 0) {
                SNDERR("unable to info for slave pcm");
                return ret;
        }
        sprintf(name, "hw:CLASS=%i,SCLASS=0,CARD=%i,DEV=%i,SUBDEV=%i",
-                               (int)SND_TIMER_CLASS_PCM, 
-                               snd_pcm_info_get_card(info),
-                               snd_pcm_info_get_device(info),
-                               snd_pcm_info_get_subdevice(info) * 2 + capture);
-       ret = snd_timer_open(&dmix->timer, name, SND_TIMER_OPEN_NONBLOCK | SND_TIMER_OPEN_TREAD);
+               (int)SND_TIMER_CLASS_PCM,
+               snd_pcm_info_get_card(&info),
+               snd_pcm_info_get_device(&info),
+               snd_pcm_info_get_subdevice(&info) * 2 + capture);
+       ret = snd_timer_open(&dmix->timer, name,
+                            SND_TIMER_OPEN_NONBLOCK | SND_TIMER_OPEN_TREAD);
        if (ret < 0) {
-               dmix->tread = 1;
-               ret = snd_timer_open(&dmix->timer, name, SND_TIMER_OPEN_NONBLOCK);
+               dmix->tread = 0;
+               ret = snd_timer_open(&dmix->timer, name,
+                                    SND_TIMER_OPEN_NONBLOCK);
                if (ret < 0) {
                        SNDERR("unable to open timer '%s'", name);
                        return ret;
@@ -1067,14 +1424,16 @@ int snd_pcm_direct_initialize_poll_fd(snd_pcm_direct_t *dmix)
        }
 
        if (snd_timer_poll_descriptors_count(dmix->timer) != 1) {
-               SNDERR("unable to use timer with fd more than one!!!", name);
+               SNDERR("unable to use timer '%s' with more than one fd!", name);
                return ret;
        }
        snd_timer_poll_descriptors(dmix->timer, &dmix->timer_fd, 1);
        dmix->poll_fd = dmix->timer_fd.fd;
 
-       dmix->timer_event_suspend = 1<<SND_TIMER_EVENT_MSUSPEND;
-       dmix->timer_event_resume = 1<<SND_TIMER_EVENT_MRESUME;
+       dmix->timer_events = (1<<SND_TIMER_EVENT_MSUSPEND) |
+                            (1<<SND_TIMER_EVENT_MRESUME) |
+                            (1<<SND_TIMER_EVENT_MSTOP) |
+                            (1<<SND_TIMER_EVENT_STOP);
 
        /*
         * Some hacks for older kernel drivers
@@ -1093,9 +1452,15 @@ int snd_pcm_direct_initialize_poll_fd(snd_pcm_direct_t *dmix)
                 * suspend/resume events.
                 */
                if (ver < SNDRV_PROTOCOL_VERSION(2, 0, 5)) {
-                       dmix->timer_event_suspend = 1<<SND_TIMER_EVENT_MPAUSE;
-                       dmix->timer_event_resume = 1<<SND_TIMER_EVENT_MCONTINUE;
+                       dmix->timer_events &= ~((1<<SND_TIMER_EVENT_MSUSPEND) |
+                                               (1<<SND_TIMER_EVENT_MRESUME));
+                       dmix->timer_events |= (1<<SND_TIMER_EVENT_MPAUSE) |
+                                             (1<<SND_TIMER_EVENT_MCONTINUE);
                }
+               /* In older versions, use SND_TIMER_EVENT_START too.
+                */
+               if (ver < SNDRV_PROTOCOL_VERSION(2, 0, 6))
+                       dmix->timer_events |= 1<<SND_TIMER_EVENT_START;
        }
        return 0;
 }
@@ -1110,6 +1475,46 @@ static snd_pcm_uframes_t recalc_boundary_size(unsigned long long bsize, snd_pcm_
        return (snd_pcm_uframes_t)bsize;
 }
 
+#define COPY_SLAVE(field) (spcm->field = dmix->shmptr->s.field)
+
+/* copy the slave setting */
+static void copy_slave_setting(snd_pcm_direct_t *dmix, snd_pcm_t *spcm)
+{
+       COPY_SLAVE(access);
+       COPY_SLAVE(format);
+       COPY_SLAVE(subformat);
+       COPY_SLAVE(channels);
+       COPY_SLAVE(rate);
+       COPY_SLAVE(period_size);
+       COPY_SLAVE(period_time);
+       COPY_SLAVE(periods);
+       COPY_SLAVE(tstamp_mode);
+       COPY_SLAVE(tstamp_type);
+       COPY_SLAVE(period_step);
+       COPY_SLAVE(avail_min);
+       COPY_SLAVE(start_threshold);
+       COPY_SLAVE(stop_threshold);
+       COPY_SLAVE(silence_threshold);
+       COPY_SLAVE(silence_size);
+       COPY_SLAVE(boundary);
+       COPY_SLAVE(info);
+       COPY_SLAVE(msbits);
+       COPY_SLAVE(rate_num);
+       COPY_SLAVE(rate_den);
+       COPY_SLAVE(hw_flags);
+       COPY_SLAVE(fifo_size);
+       COPY_SLAVE(buffer_size);
+       COPY_SLAVE(buffer_time);
+       COPY_SLAVE(sample_bits);
+       COPY_SLAVE(frame_bits);
+
+       spcm->info &= ~SND_PCM_INFO_PAUSE;
+       spcm->boundary = recalc_boundary_size(dmix->shmptr->s.boundary, spcm->buffer_size);
+}
+
+#undef COPY_SLAVE
+
+
 /*
  * open a slave PCM as secondary client (dup'ed fd)
  */
@@ -1118,7 +1523,7 @@ int snd_pcm_direct_open_secondary_client(snd_pcm_t **spcmp, snd_pcm_direct_t *dm
        int ret;
        snd_pcm_t *spcm;
 
-       ret = snd_pcm_hw_open_fd(spcmp, client_name, dmix->hw_fd, 0, 0);
+       ret = snd_pcm_hw_open_fd(spcmp, client_name, dmix->hw_fd, 0);
        if (ret < 0) {
                SNDERR("unable to open hardware");
                return ret;
@@ -1127,13 +1532,36 @@ int snd_pcm_direct_open_secondary_client(snd_pcm_t **spcmp, snd_pcm_direct_t *dm
        spcm = *spcmp;
        spcm->donot_close = 1;
        spcm->setup = 1;
-       /* we copy the slave setting */
-       spcm->buffer_size = dmix->shmptr->s.buffer_size;
-       spcm->sample_bits = dmix->shmptr->s.sample_bits;
-       spcm->channels = dmix->shmptr->s.channels;
-       spcm->format = dmix->shmptr->s.format;
-       spcm->boundary = recalc_boundary_size(dmix->shmptr->s.boundary, spcm->buffer_size);
-       spcm->info = dmix->shmptr->s.info;
+
+       copy_slave_setting(dmix, spcm);
+
+       /* Use the slave setting as SPCM, so far */
+       dmix->slave_buffer_size = spcm->buffer_size;
+       dmix->slave_period_size = dmix->shmptr->s.period_size;
+       dmix->slave_boundary = spcm->boundary;
+       dmix->recoveries = dmix->shmptr->s.recoveries;
+
+       ret = snd_pcm_mmap(spcm);
+       if (ret < 0) {
+               SNDERR("unable to mmap channels");
+               return ret;
+       }
+       return 0;
+}
+
+/*
+ * open a slave PCM as secondary client (dup'ed fd)
+ */
+int snd_pcm_direct_initialize_secondary_slave(snd_pcm_direct_t *dmix,
+                                             snd_pcm_t *spcm,
+                                             struct slave_params *params ATTRIBUTE_UNUSED)
+{
+       int ret;
+
+       spcm->donot_close = 1;
+       spcm->setup = 1;
+
+       copy_slave_setting(dmix, spcm);
 
        /* Use the slave setting as SPCM, so far */
        dmix->slave_buffer_size = spcm->buffer_size;
@@ -1150,25 +1578,23 @@ int snd_pcm_direct_open_secondary_client(snd_pcm_t **spcmp, snd_pcm_direct_t *dm
 
 int snd_pcm_direct_set_timer_params(snd_pcm_direct_t *dmix)
 {
-       snd_timer_params_t *params;
+       snd_timer_params_t params = {0};
        unsigned int filter;
        int ret;
 
-       snd_timer_params_alloca(&params);
-       snd_timer_params_set_auto_start(params, 1);
+       snd_timer_params_set_auto_start(&params, 1);
        if (dmix->type != SND_PCM_TYPE_DSNOOP)
-               snd_timer_params_set_early_event(params, 1);
-       snd_timer_params_set_ticks(params, 1);
+               snd_timer_params_set_early_event(&params, 1);
+       snd_timer_params_set_ticks(&params, dmix->timer_ticks);
        if (dmix->tread) {
                filter = (1<<SND_TIMER_EVENT_TICK) |
-                        dmix->timer_event_suspend |
-                        dmix->timer_event_resume;
-               snd_timer_params_set_filter(params, filter);
+                        dmix->timer_events;
+               INTERNAL(snd_timer_params_set_filter)(&params, filter);
        }
-       ret = snd_timer_params(dmix->timer, params);
+       ret = snd_timer_params(dmix->timer, &params);
        if (ret < 0) {
                SNDERR("unable to set timer parameters");
-                return ret;
+               return ret;
        }
        return 0;
 }
@@ -1183,7 +1609,7 @@ int snd_pcm_direct_check_interleave(snd_pcm_direct_t *dmix, snd_pcm_t *pcm)
        const snd_pcm_channel_area_t *dst_areas;
        const snd_pcm_channel_area_t *src_areas;
 
-       bits = snd_pcm_format_physical_width(dmix->type);
+       bits = snd_pcm_format_physical_width(pcm->format);
        if ((bits % 8) != 0)
                interleaved = 0;
        channels = dmix->channels;
@@ -1223,10 +1649,13 @@ int snd_pcm_direct_check_interleave(snd_pcm_direct_t *dmix, snd_pcm_t *pcm)
  * id == client channel
  * value == slave's channel
  */
-int snd_pcm_direct_parse_bindings(snd_pcm_direct_t *dmix, snd_config_t *cfg)
+int snd_pcm_direct_parse_bindings(snd_pcm_direct_t *dmix,
+                                 struct slave_params *params,
+                                 snd_config_t *cfg)
 {
        snd_config_iterator_t i, next;
        unsigned int chn, chn1, count = 0;
+       unsigned int *bindings;
        int err;
 
        dmix->channels = UINT_MAX;
@@ -1256,11 +1685,11 @@ int snd_pcm_direct_parse_bindings(snd_pcm_direct_t *dmix, snd_config_t *cfg)
                SNDERR("client channel out of range");
                return -EINVAL;
        }
-       dmix->bindings = malloc(count * sizeof(unsigned int));
-       if (dmix->bindings == NULL)
+       bindings = malloc(count * sizeof(unsigned int));
+       if (bindings == NULL)
                return -ENOMEM;
        for (chn = 0; chn < count; chn++)
-               dmix->bindings[chn] = UINT_MAX;         /* don't route */
+               bindings[chn] = UINT_MAX;               /* don't route */
        snd_config_for_each(i, next, cfg) {
                snd_config_t *n = snd_config_iterator_entry(i);
                const char *id;
@@ -1270,23 +1699,33 @@ int snd_pcm_direct_parse_bindings(snd_pcm_direct_t *dmix, snd_config_t *cfg)
                safe_strtol(id, &cchannel);
                if (snd_config_get_integer(n, &schannel) < 0) {
                        SNDERR("unable to get slave channel (should be integer type) in binding: %s\n", id);
+                       free(bindings);
+                       return -EINVAL;
+               }
+               if (schannel < 0 || schannel >= params->channels) {
+                       SNDERR("invalid slave channel number %ld in binding to %ld",
+                              schannel, cchannel);
+                       free(bindings);
                        return -EINVAL;
                }
-               dmix->bindings[cchannel] = schannel;
+               bindings[cchannel] = schannel;
        }
-       if (dmix->type == SND_PCM_TYPE_DSNOOP)
+       if (dmix->type == SND_PCM_TYPE_DSNOOP ||
+           ! dmix->bindings)
                goto __skip_same_dst;
        for (chn = 0; chn < count; chn++) {
                for (chn1 = 0; chn1 < count; chn1++) {
                        if (chn == chn1)
                                continue;
-                       if (dmix->bindings[chn] == dmix->bindings[chn1]) {
-                               SNDERR("unable to route channels %d,%d to same destination %d", chn, chn1, dmix->bindings[chn]);
+                       if (bindings[chn] == dmix->bindings[chn1]) {
+                               SNDERR("unable to route channels %d,%d to same destination %d", chn, chn1, bindings[chn]);
+                               free(bindings);
                                return -EINVAL;
                        }
                }
        }
       __skip_same_dst:
+       dmix->bindings = bindings;
        dmix->channels = count;
        return 0;
 }
@@ -1301,12 +1740,12 @@ static int _snd_pcm_direct_get_slave_ipc_offset(snd_config_t *root,
                                                int hop)
 {
        snd_config_iterator_t i, next;
+       snd_config_t *pcm_conf, *pcm_conf2;
        int err;
        long card = 0, device = 0, subdevice = 0;
        const char *str;
 
        if (snd_config_get_string(sconf, &str) >= 0) {
-               snd_config_t *pcm_conf;
                if (hop > SND_CONF_MAX_HOPS) {
                        SNDERR("Too many definition levels (looped?)");
                        return -EINVAL;
@@ -1323,6 +1762,38 @@ static int _snd_pcm_direct_get_slave_ipc_offset(snd_config_t *root,
                return err;
        }
 
+#if 0  /* for debug purposes */
+       {
+               snd_output_t *out;
+               snd_output_stdio_attach(&out, stderr, 0);
+               snd_config_save(sconf, out);
+               snd_output_close(out);
+       }
+#endif
+
+       if (snd_config_search(sconf, "slave", &pcm_conf) >= 0) {
+               if (snd_config_search(pcm_conf, "pcm", &pcm_conf) >= 0) {
+                       return _snd_pcm_direct_get_slave_ipc_offset(root,
+                                                                  pcm_conf,
+                                                                  direction,
+                                                                  hop + 1);
+               } else {
+                       if (snd_config_get_string(pcm_conf, &str) >= 0 &&
+                           snd_config_search_definition(root, "pcm_slave",
+                                                   str, &pcm_conf) >= 0) {
+                               if (snd_config_search(pcm_conf, "pcm",
+                                                       &pcm_conf2) >= 0) {
+                                       err =
+                                        _snd_pcm_direct_get_slave_ipc_offset(
+                                            root, pcm_conf2, direction, hop + 1);
+                                       snd_config_delete(pcm_conf);
+                                       return err;
+                               }
+                               snd_config_delete(pcm_conf);
+                       }
+               }
+       }
+
        snd_config_for_each(i, next, sconf) {
                snd_config_t *n = snd_config_iterator_entry(i);
                const char *id, *str;
@@ -1382,17 +1853,19 @@ static int _snd_pcm_direct_get_slave_ipc_offset(snd_config_t *root,
        return (direction << 1) + (device << 2) + (subdevice << 8) + (card << 12);
 }
 
-int snd_pcm_direct_get_slave_ipc_offset(snd_config_t *root,
+static int snd_pcm_direct_get_slave_ipc_offset(snd_config_t *root,
                                        snd_config_t *sconf,
                                        int direction)
 {
        return _snd_pcm_direct_get_slave_ipc_offset(root, sconf, direction, 0);
 }
 
-int snd_pcm_direct_parse_open_conf(snd_config_t *conf, struct snd_pcm_direct_open_conf *rec)
+int snd_pcm_direct_parse_open_conf(snd_config_t *root, snd_config_t *conf,
+                                  int stream, struct snd_pcm_direct_open_conf *rec)
 {
        snd_config_iterator_t i, next;
        int ipc_key_add_uid = 0;
+       snd_config_t *n;
        int err;
 
        rec->slave = NULL;
@@ -1400,12 +1873,23 @@ int snd_pcm_direct_parse_open_conf(snd_config_t *conf, struct snd_pcm_direct_ope
        rec->ipc_key = 0;
        rec->ipc_perm = 0600;
        rec->ipc_gid = -1;
-       rec->slowptr = 0;
-       rec->variable_buffer_size = 0;
+       rec->slowptr = 1;
+       rec->max_periods = 0;
+       rec->var_periodsize = 0;
+       rec->direct_memory_access = 1;
+       rec->hw_ptr_alignment = SND_PCM_HW_PTR_ALIGNMENT_AUTO;
+
+       /* read defaults */
+       if (snd_config_search(root, "defaults.pcm.dmix_max_periods", &n) >= 0) {
+               long val;
+               err = snd_config_get_integer(n, &val);
+               if (err >= 0)
+                       rec->max_periods = val;
+       }
 
        snd_config_for_each(i, next, conf) {
-               snd_config_t *n = snd_config_iterator_entry(i);
                const char *id;
+               n = snd_config_iterator_entry(i);
                if (snd_config_get_id(n, &id) < 0)
                        continue;
                if (snd_pcm_conf_generic_id(id))
@@ -1422,20 +1906,39 @@ int snd_pcm_direct_parse_open_conf(snd_config_t *conf, struct snd_pcm_direct_ope
                        continue;
                }
                if (strcmp(id, "ipc_perm") == 0) {
-                       char *perm;
-                       char *endp;
-                       err = snd_config_get_ascii(n, &perm);
+                       long perm;
+                       err = snd_config_get_integer(n, &perm);
                        if (err < 0) {
-                               SNDERR("The field ipc_perm must be a valid file permission");
+                               SNDERR("Invalid type for %s", id);
                                return err;
                        }
-                       if (isdigit(*perm) == 0) {
+                       if ((perm & ~0777) != 0) {
                                SNDERR("The field ipc_perm must be a valid file permission");
-                               free(perm);
                                return -EINVAL;
                        }
-                       rec->ipc_perm = strtol(perm, &endp, 8);
-                       free(perm);
+                       rec->ipc_perm = perm;
+                       continue;
+               }
+               if (strcmp(id, "hw_ptr_alignment") == 0) {
+                       const char *str;
+                       err = snd_config_get_string(n, &str);
+                       if (err < 0) {
+                               SNDERR("Invalid type for %s", id);
+                               return -EINVAL;
+                       }
+                       if (strcmp(str, "no") == 0)
+                               rec->hw_ptr_alignment = SND_PCM_HW_PTR_ALIGNMENT_NO;
+                       else if (strcmp(str, "roundup") == 0)
+                               rec->hw_ptr_alignment = SND_PCM_HW_PTR_ALIGNMENT_ROUNDUP;
+                       else if (strcmp(str, "rounddown") == 0)
+                               rec->hw_ptr_alignment = SND_PCM_HW_PTR_ALIGNMENT_ROUNDDOWN;
+                       else if (strcmp(str, "auto") == 0)
+                               rec->hw_ptr_alignment = SND_PCM_HW_PTR_ALIGNMENT_AUTO;
+                       else {
+                               SNDERR("The field hw_ptr_alignment is invalid : %s", str);
+                               return -EINVAL;
+                       }
+
                        continue;
                }
                if (strcmp(id, "ipc_gid") == 0) {
@@ -1452,13 +1955,20 @@ int snd_pcm_direct_parse_open_conf(snd_config_t *conf, struct snd_pcm_direct_ope
                                continue;
                        }
                        if (isdigit(*group) == 0) {
-                               struct group *grp = getgrnam(group);
-                               if (grp == NULL) {
+                               long clen = sysconf(_SC_GETGR_R_SIZE_MAX);
+                               size_t len = (clen == -1) ? 1024 : (size_t)clen;
+                               struct group grp, *pgrp;
+                               char *buffer = (char *)malloc(len);
+                               if (buffer == NULL)
+                                       return -ENOMEM;
+                               int st = getgrnam_r(group, &grp, buffer, len, &pgrp);
+                               if (st != 0 || !pgrp) {
                                        SNDERR("The field ipc_gid must be a valid group (create group %s)", group);
-                                       free(group);
+                                       free(buffer);
                                        return -EINVAL;
                                }
-                               rec->ipc_gid = grp->gr_gid;
+                               rec->ipc_gid = pgrp->gr_gid;
+                               free(buffer);
                        } else {
                                rec->ipc_gid = strtol(group, &endp, 10);
                        }
@@ -1488,25 +1998,63 @@ int snd_pcm_direct_parse_open_conf(snd_config_t *conf, struct snd_pcm_direct_ope
                        rec->slowptr = err;
                        continue;
                }
-               if (strcmp(id, "variable_buffer_size") == 0) {
+               if (strcmp(id, "max_periods") == 0) {
+                       long val;
+                       err = snd_config_get_integer(n, &val);
+                       if (err < 0)
+                               return err;
+                       rec->max_periods = val;
+                       continue;
+               }
+               if (strcmp(id, "var_periodsize") == 0) {
+                       err = snd_config_get_bool(n);
+                       if (err < 0)
+                               return err;
+                       rec->var_periodsize = err;
+                       continue;
+               }
+               if (strcmp(id, "direct_memory_access") == 0) {
                        err = snd_config_get_bool(n);
                        if (err < 0)
                                return err;
-                       rec->variable_buffer_size = err;
+                       rec->direct_memory_access = err;
                        continue;
                }
                SNDERR("Unknown field %s", id);
                return -EINVAL;
        }
-       if (! rec->slave) {
+       if (!rec->slave) {
                SNDERR("slave is not defined");
                return -EINVAL;
        }
-       if (ipc_key_add_uid)
-               rec->ipc_key += getuid();
        if (!rec->ipc_key) {
                SNDERR("Unique IPC key is not defined");
                return -EINVAL;
        }
+       if (ipc_key_add_uid)
+               rec->ipc_key += getuid();
+       err = snd_pcm_direct_get_slave_ipc_offset(root, conf, stream);
+       if (err < 0)
+               return err;
+       rec->ipc_key += err;
+
        return 0;
 }
+
+void snd_pcm_direct_reset_slave_ptr(snd_pcm_t *pcm, snd_pcm_direct_t *dmix)
+{
+
+       if (dmix->hw_ptr_alignment == SND_PCM_HW_PTR_ALIGNMENT_ROUNDUP ||
+               (dmix->hw_ptr_alignment == SND_PCM_HW_PTR_ALIGNMENT_AUTO &&
+               pcm->buffer_size <= pcm->period_size * 2))
+               dmix->slave_appl_ptr =
+                       ((dmix->slave_appl_ptr + dmix->slave_period_size - 1) /
+                       dmix->slave_period_size) * dmix->slave_period_size;
+       else if (dmix->hw_ptr_alignment == SND_PCM_HW_PTR_ALIGNMENT_ROUNDDOWN ||
+               (dmix->hw_ptr_alignment == SND_PCM_HW_PTR_ALIGNMENT_AUTO &&
+               (dmix->slave_period_size * SEC_TO_MS) /
+               pcm->rate < LOW_LATENCY_PERIOD_TIME))
+               dmix->slave_appl_ptr = dmix->slave_hw_ptr =
+                       ((dmix->slave_hw_ptr / dmix->slave_period_size) *
+                       dmix->slave_period_size);
+}