Merge tag 'sound-fix-4.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai...
[sfrench/cifs-2.6.git] / sound / core / pcm_native.c
index faa2e2be6f2ea52bad0a2be30368b6386dcba11f..22995cb3bd447ee0744e142b9a6500b706d85853 100644 (file)
 #include <sound/minors.h>
 #include <linux/uio.h>
 
+#include "pcm_local.h"
+
+#ifdef CONFIG_SND_DEBUG
+#define CREATE_TRACE_POINTS
+#include "pcm_param_trace.h"
+#else
+#define trace_hw_mask_param_enabled()          0
+#define trace_hw_interval_param_enabled()      0
+#define trace_hw_mask_param(substream, type, index, prev, curr)
+#define trace_hw_interval_param(substream, type, index, prev, curr)
+#endif
+
 /*
  *  Compatibility
  */
@@ -181,20 +193,6 @@ void snd_pcm_stream_unlock_irqrestore(struct snd_pcm_substream *substream,
 }
 EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock_irqrestore);
 
-static inline mm_segment_t snd_enter_user(void)
-{
-       mm_segment_t fs = get_fs();
-       set_fs(get_ds());
-       return fs;
-}
-
-static inline void snd_leave_user(mm_segment_t fs)
-{
-       set_fs(fs);
-}
-
-
-
 int snd_pcm_info(struct snd_pcm_substream *substream, struct snd_pcm_info *info)
 {
        struct snd_pcm_runtime *runtime;
@@ -214,11 +212,7 @@ int snd_pcm_info(struct snd_pcm_substream *substream, struct snd_pcm_info *info)
        info->subdevices_avail = pstr->substream_count - pstr->substream_opened;
        strlcpy(info->subname, substream->name, sizeof(info->subname));
        runtime = substream->runtime;
-       /* AB: FIXME!!! This is definitely nonsense */
-       if (runtime) {
-               info->sync = runtime->sync;
-               substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_INFO, info);
-       }
+
        return 0;
 }
 
@@ -244,10 +238,8 @@ static bool hw_support_mmap(struct snd_pcm_substream *substream)
 {
        if (!(substream->runtime->hw.info & SNDRV_PCM_INFO_MMAP))
                return false;
-       /* check architectures that return -EINVAL from dma_mmap_coherent() */
-       /* FIXME: this should be some global flag */
-#if defined(CONFIG_C6X) || defined(CONFIG_FRV) || defined(CONFIG_MN10300) ||\
-       defined(CONFIG_PARISC) || defined(CONFIG_XTENSA)
+       /* architecture supports dma_mmap_coherent()? */
+#if defined(CONFIG_ARCH_NO_COHERENT_DMA_MMAP) || !defined(CONFIG_HAS_DMA)
        if (!substream->ops->mmap &&
            substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV)
                return false;
@@ -255,205 +247,268 @@ static bool hw_support_mmap(struct snd_pcm_substream *substream)
        return true;
 }
 
-#undef RULES_DEBUG
-
-#ifdef RULES_DEBUG
-#define HW_PARAM(v) [SNDRV_PCM_HW_PARAM_##v] = #v
-static const char * const snd_pcm_hw_param_names[] = {
-       HW_PARAM(ACCESS),
-       HW_PARAM(FORMAT),
-       HW_PARAM(SUBFORMAT),
-       HW_PARAM(SAMPLE_BITS),
-       HW_PARAM(FRAME_BITS),
-       HW_PARAM(CHANNELS),
-       HW_PARAM(RATE),
-       HW_PARAM(PERIOD_TIME),
-       HW_PARAM(PERIOD_SIZE),
-       HW_PARAM(PERIOD_BYTES),
-       HW_PARAM(PERIODS),
-       HW_PARAM(BUFFER_TIME),
-       HW_PARAM(BUFFER_SIZE),
-       HW_PARAM(BUFFER_BYTES),
-       HW_PARAM(TICK_TIME),
-};
-#endif
-
-int snd_pcm_hw_refine(struct snd_pcm_substream *substream, 
-                     struct snd_pcm_hw_params *params)
+static int constrain_mask_params(struct snd_pcm_substream *substream,
+                                struct snd_pcm_hw_params *params)
 {
+       struct snd_pcm_hw_constraints *constrs =
+                                       &substream->runtime->hw_constraints;
+       struct snd_mask *m;
        unsigned int k;
-       struct snd_pcm_hardware *hw;
-       struct snd_interval *i = NULL;
-       struct snd_mask *m = NULL;
-       struct snd_pcm_hw_constraints *constrs = &substream->runtime->hw_constraints;
-       unsigned int rstamps[constrs->rules_num];
-       unsigned int vstamps[SNDRV_PCM_HW_PARAM_LAST_INTERVAL + 1];
-       unsigned int stamp = 2;
-       int changed, again;
-
-       params->info = 0;
-       params->fifo_size = 0;
-       if (params->rmask & (1 << SNDRV_PCM_HW_PARAM_SAMPLE_BITS))
-               params->msbits = 0;
-       if (params->rmask & (1 << SNDRV_PCM_HW_PARAM_RATE)) {
-               params->rate_num = 0;
-               params->rate_den = 0;
-       }
+       struct snd_mask old_mask;
+       int changed;
 
        for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++) {
                m = hw_param_mask(params, k);
                if (snd_mask_empty(m))
                        return -EINVAL;
+
+               /* This parameter is not requested to change by a caller. */
                if (!(params->rmask & (1 << k)))
                        continue;
-#ifdef RULES_DEBUG
-               pr_debug("%s = ", snd_pcm_hw_param_names[k]);
-               pr_cont("%04x%04x%04x%04x -> ", m->bits[3], m->bits[2], m->bits[1], m->bits[0]);
-#endif
+
+               if (trace_hw_mask_param_enabled())
+                       old_mask = *m;
+
                changed = snd_mask_refine(m, constrs_mask(constrs, k));
-#ifdef RULES_DEBUG
-               pr_cont("%04x%04x%04x%04x\n", m->bits[3], m->bits[2], m->bits[1], m->bits[0]);
-#endif
-               if (changed)
-                       params->cmask |= 1 << k;
                if (changed < 0)
                        return changed;
+               if (changed == 0)
+                       continue;
+
+               /* Set corresponding flag so that the caller gets it. */
+               trace_hw_mask_param(substream, k, 0, &old_mask, m);
+               params->cmask |= 1 << k;
        }
 
+       return 0;
+}
+
+static int constrain_interval_params(struct snd_pcm_substream *substream,
+                                    struct snd_pcm_hw_params *params)
+{
+       struct snd_pcm_hw_constraints *constrs =
+                                       &substream->runtime->hw_constraints;
+       struct snd_interval *i;
+       unsigned int k;
+       struct snd_interval old_interval;
+       int changed;
+
        for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) {
                i = hw_param_interval(params, k);
                if (snd_interval_empty(i))
                        return -EINVAL;
+
+               /* This parameter is not requested to change by a caller. */
                if (!(params->rmask & (1 << k)))
                        continue;
-#ifdef RULES_DEBUG
-               pr_debug("%s = ", snd_pcm_hw_param_names[k]);
-               if (i->empty)
-                       pr_cont("empty");
-               else
-                       pr_cont("%c%u %u%c",
-                              i->openmin ? '(' : '[', i->min,
-                              i->max, i->openmax ? ')' : ']');
-               pr_cont(" -> ");
-#endif
+
+               if (trace_hw_interval_param_enabled())
+                       old_interval = *i;
+
                changed = snd_interval_refine(i, constrs_interval(constrs, k));
-#ifdef RULES_DEBUG
-               if (i->empty)
-                       pr_cont("empty\n");
-               else 
-                       pr_cont("%c%u %u%c\n",
-                              i->openmin ? '(' : '[', i->min,
-                              i->max, i->openmax ? ')' : ']');
-#endif
-               if (changed)
-                       params->cmask |= 1 << k;
                if (changed < 0)
                        return changed;
+               if (changed == 0)
+                       continue;
+
+               /* Set corresponding flag so that the caller gets it. */
+               trace_hw_interval_param(substream, k, 0, &old_interval, i);
+               params->cmask |= 1 << k;
        }
 
+       return 0;
+}
+
+static int constrain_params_by_rules(struct snd_pcm_substream *substream,
+                                    struct snd_pcm_hw_params *params)
+{
+       struct snd_pcm_hw_constraints *constrs =
+                                       &substream->runtime->hw_constraints;
+       unsigned int k;
+       unsigned int rstamps[constrs->rules_num];
+       unsigned int vstamps[SNDRV_PCM_HW_PARAM_LAST_INTERVAL + 1];
+       unsigned int stamp;
+       struct snd_pcm_hw_rule *r;
+       unsigned int d;
+       struct snd_mask old_mask;
+       struct snd_interval old_interval;
+       bool again;
+       int changed;
+
+       /*
+        * Each application of rule has own sequence number.
+        *
+        * Each member of 'rstamps' array represents the sequence number of
+        * recent application of corresponding rule.
+        */
        for (k = 0; k < constrs->rules_num; k++)
                rstamps[k] = 0;
-       for (k = 0; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) 
+
+       /*
+        * Each member of 'vstamps' array represents the sequence number of
+        * recent application of rule in which corresponding parameters were
+        * changed.
+        *
+        * In initial state, elements corresponding to parameters requested by
+        * a caller is 1. For unrequested parameters, corresponding members
+        * have 0 so that the parameters are never changed anymore.
+        */
+       for (k = 0; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++)
                vstamps[k] = (params->rmask & (1 << k)) ? 1 : 0;
-       do {
-               again = 0;
-               for (k = 0; k < constrs->rules_num; k++) {
-                       struct snd_pcm_hw_rule *r = &constrs->rules[k];
-                       unsigned int d;
-                       int doit = 0;
-                       if (r->cond && !(r->cond & params->flags))
-                               continue;
-                       for (d = 0; r->deps[d] >= 0; d++) {
-                               if (vstamps[r->deps[d]] > rstamps[k]) {
-                                       doit = 1;
-                                       break;
-                               }
-                       }
-                       if (!doit)
-                               continue;
-#ifdef RULES_DEBUG
-                       pr_debug("Rule %d [%p]: ", k, r->func);
-                       if (r->var >= 0) {
-                               pr_cont("%s = ", snd_pcm_hw_param_names[r->var]);
-                               if (hw_is_mask(r->var)) {
-                                       m = hw_param_mask(params, r->var);
-                                       pr_cont("%x", *m->bits);
-                               } else {
-                                       i = hw_param_interval(params, r->var);
-                                       if (i->empty)
-                                               pr_cont("empty");
-                                       else
-                                               pr_cont("%c%u %u%c",
-                                                      i->openmin ? '(' : '[', i->min,
-                                                      i->max, i->openmax ? ')' : ']');
-                               }
-                       }
-#endif
-                       changed = r->func(params, r);
-#ifdef RULES_DEBUG
-                       if (r->var >= 0) {
-                               pr_cont(" -> ");
-                               if (hw_is_mask(r->var))
-                                       pr_cont("%x", *m->bits);
-                               else {
-                                       if (i->empty)
-                                               pr_cont("empty");
-                                       else
-                                               pr_cont("%c%u %u%c",
-                                                      i->openmin ? '(' : '[', i->min,
-                                                      i->max, i->openmax ? ')' : ']');
-                               }
+
+       /* Due to the above design, actual sequence number starts at 2. */
+       stamp = 2;
+retry:
+       /* Apply all rules in order. */
+       again = false;
+       for (k = 0; k < constrs->rules_num; k++) {
+               r = &constrs->rules[k];
+
+               /*
+                * Check condition bits of this rule. When the rule has
+                * some condition bits, parameter without the bits is
+                * never processed. SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP
+                * is an example of the condition bits.
+                */
+               if (r->cond && !(r->cond & params->flags))
+                       continue;
+
+               /*
+                * The 'deps' array includes maximum three dependencies
+                * to SNDRV_PCM_HW_PARAM_XXXs for this rule. The fourth
+                * member of this array is a sentinel and should be
+                * negative value.
+                *
+                * This rule should be processed in this time when dependent
+                * parameters were changed at former applications of the other
+                * rules.
+                */
+               for (d = 0; r->deps[d] >= 0; d++) {
+                       if (vstamps[r->deps[d]] > rstamps[k])
+                               break;
+               }
+               if (r->deps[d] < 0)
+                       continue;
+
+               if (trace_hw_mask_param_enabled()) {
+                       if (hw_is_mask(r->var))
+                               old_mask = *hw_param_mask(params, r->var);
+               }
+               if (trace_hw_interval_param_enabled()) {
+                       if (hw_is_interval(r->var))
+                               old_interval = *hw_param_interval(params, r->var);
+               }
+
+               changed = r->func(params, r);
+               if (changed < 0)
+                       return changed;
+
+               /*
+                * When the parameter is changed, notify it to the caller
+                * by corresponding returned bit, then preparing for next
+                * iteration.
+                */
+               if (changed && r->var >= 0) {
+                       if (hw_is_mask(r->var)) {
+                               trace_hw_mask_param(substream, r->var,
+                                       k + 1, &old_mask,
+                                       hw_param_mask(params, r->var));
                        }
-                       pr_cont("\n");
-#endif
-                       rstamps[k] = stamp;
-                       if (changed && r->var >= 0) {
-                               params->cmask |= (1 << r->var);
-                               vstamps[r->var] = stamp;
-                               again = 1;
+                       if (hw_is_interval(r->var)) {
+                               trace_hw_interval_param(substream, r->var,
+                                       k + 1, &old_interval,
+                                       hw_param_interval(params, r->var));
                        }
-                       if (changed < 0)
-                               return changed;
-                       stamp++;
+
+                       params->cmask |= (1 << r->var);
+                       vstamps[r->var] = stamp;
+                       again = true;
                }
-       } while (again);
+
+               rstamps[k] = stamp++;
+       }
+
+       /* Iterate to evaluate all rules till no parameters are changed. */
+       if (again)
+               goto retry;
+
+       return 0;
+}
+
+static int fixup_unreferenced_params(struct snd_pcm_substream *substream,
+                                    struct snd_pcm_hw_params *params)
+{
+       const struct snd_interval *i;
+       const struct snd_mask *m;
+       int err;
+
        if (!params->msbits) {
-               i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS);
+               i = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS);
                if (snd_interval_single(i))
                        params->msbits = snd_interval_value(i);
        }
 
        if (!params->rate_den) {
-               i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+               i = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE);
                if (snd_interval_single(i)) {
                        params->rate_num = snd_interval_value(i);
                        params->rate_den = 1;
                }
        }
 
-       hw = &substream->runtime->hw;
+       if (!params->fifo_size) {
+               m = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT);
+               i = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+               if (snd_mask_single(m) && snd_interval_single(i)) {
+                       err = substream->ops->ioctl(substream,
+                                       SNDRV_PCM_IOCTL1_FIFO_SIZE, params);
+                       if (err < 0)
+                               return err;
+               }
+       }
+
        if (!params->info) {
-               params->info = hw->info & ~(SNDRV_PCM_INFO_FIFO_IN_FRAMES |
-                                           SNDRV_PCM_INFO_DRAIN_TRIGGER);
+               params->info = substream->runtime->hw.info;
+               params->info &= ~(SNDRV_PCM_INFO_FIFO_IN_FRAMES |
+                                 SNDRV_PCM_INFO_DRAIN_TRIGGER);
                if (!hw_support_mmap(substream))
                        params->info &= ~(SNDRV_PCM_INFO_MMAP |
                                          SNDRV_PCM_INFO_MMAP_VALID);
        }
-       if (!params->fifo_size) {
-               m = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
-               i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
-               if (snd_mask_min(m) == snd_mask_max(m) &&
-                    snd_interval_min(i) == snd_interval_max(i)) {
-                       changed = substream->ops->ioctl(substream,
-                                       SNDRV_PCM_IOCTL1_FIFO_SIZE, params);
-                       if (changed < 0)
-                               return changed;
-               }
+
+       return 0;
+}
+
+int snd_pcm_hw_refine(struct snd_pcm_substream *substream,
+                     struct snd_pcm_hw_params *params)
+{
+       int err;
+
+       params->info = 0;
+       params->fifo_size = 0;
+       if (params->rmask & (1 << SNDRV_PCM_HW_PARAM_SAMPLE_BITS))
+               params->msbits = 0;
+       if (params->rmask & (1 << SNDRV_PCM_HW_PARAM_RATE)) {
+               params->rate_num = 0;
+               params->rate_den = 0;
        }
+
+       err = constrain_mask_params(substream, params);
+       if (err < 0)
+               return err;
+
+       err = constrain_interval_params(substream, params);
+       if (err < 0)
+               return err;
+
+       err = constrain_params_by_rules(substream, params);
+       if (err < 0)
+               return err;
+
        params->rmask = 0;
+
        return 0;
 }
-
 EXPORT_SYMBOL(snd_pcm_hw_refine);
 
 static int snd_pcm_hw_refine_user(struct snd_pcm_substream *substream,
@@ -467,11 +522,16 @@ static int snd_pcm_hw_refine_user(struct snd_pcm_substream *substream,
                return PTR_ERR(params);
 
        err = snd_pcm_hw_refine(substream, params);
-       if (copy_to_user(_params, params, sizeof(*params))) {
-               if (!err)
-                       err = -EFAULT;
-       }
+       if (err < 0)
+               goto end;
+
+       err = fixup_unreferenced_params(substream, params);
+       if (err < 0)
+               goto end;
 
+       if (copy_to_user(_params, params, sizeof(*params)))
+               err = -EFAULT;
+end:
        kfree(params);
        return err;
 }
@@ -509,6 +569,70 @@ static inline void snd_pcm_timer_notify(struct snd_pcm_substream *substream,
 #endif
 }
 
+/**
+ * snd_pcm_hw_param_choose - choose a configuration defined by @params
+ * @pcm: PCM instance
+ * @params: the hw_params instance
+ *
+ * Choose one configuration from configuration space defined by @params.
+ * The configuration chosen is that obtained fixing in this order:
+ * first access, first format, first subformat, min channels,
+ * min rate, min period time, max buffer size, min tick time
+ *
+ * Return: Zero if successful, or a negative error code on failure.
+ */
+static int snd_pcm_hw_params_choose(struct snd_pcm_substream *pcm,
+                                   struct snd_pcm_hw_params *params)
+{
+       static const int vars[] = {
+               SNDRV_PCM_HW_PARAM_ACCESS,
+               SNDRV_PCM_HW_PARAM_FORMAT,
+               SNDRV_PCM_HW_PARAM_SUBFORMAT,
+               SNDRV_PCM_HW_PARAM_CHANNELS,
+               SNDRV_PCM_HW_PARAM_RATE,
+               SNDRV_PCM_HW_PARAM_PERIOD_TIME,
+               SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
+               SNDRV_PCM_HW_PARAM_TICK_TIME,
+               -1
+       };
+       const int *v;
+       struct snd_mask old_mask;
+       struct snd_interval old_interval;
+       int changed;
+
+       for (v = vars; *v != -1; v++) {
+               /* Keep old parameter to trace. */
+               if (trace_hw_mask_param_enabled()) {
+                       if (hw_is_mask(*v))
+                               old_mask = *hw_param_mask(params, *v);
+               }
+               if (trace_hw_interval_param_enabled()) {
+                       if (hw_is_interval(*v))
+                               old_interval = *hw_param_interval(params, *v);
+               }
+               if (*v != SNDRV_PCM_HW_PARAM_BUFFER_SIZE)
+                       changed = snd_pcm_hw_param_first(pcm, params, *v, NULL);
+               else
+                       changed = snd_pcm_hw_param_last(pcm, params, *v, NULL);
+               if (snd_BUG_ON(changed < 0))
+                       return changed;
+               if (changed == 0)
+                       continue;
+
+               /* Trace the changed parameter. */
+               if (hw_is_mask(*v)) {
+                       trace_hw_mask_param(pcm, *v, 0, &old_mask,
+                                           hw_param_mask(params, *v));
+               }
+               if (hw_is_interval(*v)) {
+                       trace_hw_interval_param(pcm, *v, 0, &old_interval,
+                                               hw_param_interval(params, *v));
+               }
+       }
+
+       return 0;
+}
+
 static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
                             struct snd_pcm_hw_params *params)
 {
@@ -546,6 +670,10 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
        if (err < 0)
                goto _error;
 
+       err = fixup_unreferenced_params(substream, params);
+       if (err < 0)
+               goto _error;
+
        if (substream->ops->hw_params != NULL) {
                err = substream->ops->hw_params(substream, params);
                if (err < 0)
@@ -621,11 +749,12 @@ static int snd_pcm_hw_params_user(struct snd_pcm_substream *substream,
                return PTR_ERR(params);
 
        err = snd_pcm_hw_params(substream, params);
-       if (copy_to_user(_params, params, sizeof(*params))) {
-               if (!err)
-                       err = -EFAULT;
-       }
+       if (err < 0)
+               goto end;
 
+       if (copy_to_user(_params, params, sizeof(*params)))
+               err = -EFAULT;
+end:
        kfree(params);
        return err;
 }
@@ -1081,6 +1210,7 @@ static const struct action_ops snd_pcm_action_start = {
  * @substream: the PCM substream instance
  *
  * Return: Zero if successful, or a negative error code.
+ * The stream lock must be acquired before calling this function.
  */
 int snd_pcm_start(struct snd_pcm_substream *substream)
 {
@@ -1088,6 +1218,13 @@ int snd_pcm_start(struct snd_pcm_substream *substream)
                              SNDRV_PCM_STATE_RUNNING);
 }
 
+/* take the stream lock and start the streams */
+static int snd_pcm_start_lock_irq(struct snd_pcm_substream *substream)
+{
+       return snd_pcm_action_lock_irq(&snd_pcm_action_start, substream,
+                                      SNDRV_PCM_STATE_RUNNING);
+}
+
 /*
  * stop callbacks
  */
@@ -1139,7 +1276,6 @@ int snd_pcm_stop(struct snd_pcm_substream *substream, snd_pcm_state_t state)
 {
        return snd_pcm_action(&snd_pcm_action_stop, substream, state);
 }
-
 EXPORT_SYMBOL(snd_pcm_stop);
 
 /**
@@ -1314,7 +1450,6 @@ int snd_pcm_suspend(struct snd_pcm_substream *substream)
        snd_pcm_stream_unlock_irqrestore(substream, flags);
        return err;
 }
-
 EXPORT_SYMBOL(snd_pcm_suspend);
 
 /**
@@ -1346,7 +1481,6 @@ int snd_pcm_suspend_all(struct snd_pcm *pcm)
        }
        return 0;
 }
-
 EXPORT_SYMBOL(snd_pcm_suspend_all);
 
 /* resume */
@@ -1397,14 +1531,7 @@ static const struct action_ops snd_pcm_action_resume = {
 
 static int snd_pcm_resume(struct snd_pcm_substream *substream)
 {
-       struct snd_card *card = substream->pcm->card;
-       int res;
-
-       snd_power_lock(card);
-       if ((res = snd_power_wait(card, SNDRV_CTL_POWER_D0)) >= 0)
-               res = snd_pcm_action_lock_irq(&snd_pcm_action_resume, substream, 0);
-       snd_power_unlock(card);
-       return res;
+       return snd_pcm_action_lock_irq(&snd_pcm_action_resume, substream, 0);
 }
 
 #else
@@ -1423,17 +1550,9 @@ static int snd_pcm_resume(struct snd_pcm_substream *substream)
  */
 static int snd_pcm_xrun(struct snd_pcm_substream *substream)
 {
-       struct snd_card *card = substream->pcm->card;
        struct snd_pcm_runtime *runtime = substream->runtime;
        int result;
 
-       snd_power_lock(card);
-       if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
-               result = snd_power_wait(card, SNDRV_CTL_POWER_D0);
-               if (result < 0)
-                       goto _unlock;
-       }
-
        snd_pcm_stream_lock_irq(substream);
        switch (runtime->status->state) {
        case SNDRV_PCM_STATE_XRUN:
@@ -1446,8 +1565,6 @@ static int snd_pcm_xrun(struct snd_pcm_substream *substream)
                result = -EBADFD;
        }
        snd_pcm_stream_unlock_irq(substream);
- _unlock:
-       snd_power_unlock(card);
        return result;
 }
 
@@ -1551,8 +1668,6 @@ static const struct action_ops snd_pcm_action_prepare = {
 static int snd_pcm_prepare(struct snd_pcm_substream *substream,
                           struct file *file)
 {
-       int res;
-       struct snd_card *card = substream->pcm->card;
        int f_flags;
 
        if (file)
@@ -1560,12 +1675,19 @@ static int snd_pcm_prepare(struct snd_pcm_substream *substream,
        else
                f_flags = substream->f_flags;
 
-       snd_power_lock(card);
-       if ((res = snd_power_wait(card, SNDRV_CTL_POWER_D0)) >= 0)
-               res = snd_pcm_action_nonatomic(&snd_pcm_action_prepare,
-                                              substream, f_flags);
-       snd_power_unlock(card);
-       return res;
+       snd_pcm_stream_lock_irq(substream);
+       switch (substream->runtime->status->state) {
+       case SNDRV_PCM_STATE_PAUSED:
+               snd_pcm_pause(substream, 0);
+               /* fallthru */
+       case SNDRV_PCM_STATE_SUSPENDED:
+               snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
+               break;
+       }
+       snd_pcm_stream_unlock_irq(substream);
+
+       return snd_pcm_action_nonatomic(&snd_pcm_action_prepare,
+                                       substream, f_flags);
 }
 
 /*
@@ -1662,15 +1784,6 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream,
        if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
                return -EBADFD;
 
-       snd_power_lock(card);
-       if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
-               result = snd_power_wait(card, SNDRV_CTL_POWER_D0);
-               if (result < 0) {
-                       snd_power_unlock(card);
-                       return result;
-               }
-       }
-
        if (file) {
                if (file->f_flags & O_NONBLOCK)
                        nonblock = 1;
@@ -1753,7 +1866,6 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream,
  unlock:
        snd_pcm_stream_unlock_irq(substream);
        up_read(&snd_pcm_link_rwsem);
-       snd_power_unlock(card);
 
        return result;
 }
@@ -1773,8 +1885,7 @@ static int snd_pcm_drop(struct snd_pcm_substream *substream)
        runtime = substream->runtime;
 
        if (runtime->status->state == SNDRV_PCM_STATE_OPEN ||
-           runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED ||
-           runtime->status->state == SNDRV_PCM_STATE_SUSPENDED)
+           runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED)
                return -EBADFD;
 
        snd_pcm_stream_lock_irq(substream);
@@ -1940,7 +2051,8 @@ static int snd_pcm_hw_rule_format(struct snd_pcm_hw_params *params,
                                  struct snd_pcm_hw_rule *rule)
 {
        unsigned int k;
-       struct snd_interval *i = hw_param_interval(params, rule->deps[0]);
+       const struct snd_interval *i =
+                               hw_param_interval_c(params, rule->deps[0]);
        struct snd_mask m;
        struct snd_mask *mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
        snd_mask_any(&m);
@@ -1986,8 +2098,10 @@ static int snd_pcm_hw_rule_sample_bits(struct snd_pcm_hw_params *params,
 #error "Change this table"
 #endif
 
-static unsigned int rates[] = { 5512, 8000, 11025, 16000, 22050, 32000, 44100,
-                                 48000, 64000, 88200, 96000, 176400, 192000 };
+static const unsigned int rates[] = {
+       5512, 8000, 11025, 16000, 22050, 32000, 44100,
+       48000, 64000, 88200, 96000, 176400, 192000
+};
 
 const struct snd_pcm_hw_constraint_list snd_pcm_known_rates = {
        .count = ARRAY_SIZE(rates),
@@ -2250,7 +2364,6 @@ void snd_pcm_release_substream(struct snd_pcm_substream *substream)
        }
        snd_pcm_detach_substream(substream);
 }
-
 EXPORT_SYMBOL(snd_pcm_release_substream);
 
 int snd_pcm_open_substream(struct snd_pcm *pcm, int stream,
@@ -2292,7 +2405,6 @@ int snd_pcm_open_substream(struct snd_pcm *pcm, int stream,
        snd_pcm_release_substream(substream);
        return err;
 }
-
 EXPORT_SYMBOL(snd_pcm_open_substream);
 
 static int snd_pcm_open_file(struct file *file,
@@ -2428,50 +2540,84 @@ static int snd_pcm_release(struct inode *inode, struct file *file)
        return 0;
 }
 
-static snd_pcm_sframes_t snd_pcm_playback_rewind(struct snd_pcm_substream *substream,
-                                                snd_pcm_uframes_t frames)
+/* check and update PCM state; return 0 or a negative error
+ * call this inside PCM lock
+ */
+static int do_pcm_hwsync(struct snd_pcm_substream *substream)
 {
-       struct snd_pcm_runtime *runtime = substream->runtime;
-       snd_pcm_sframes_t appl_ptr;
-       snd_pcm_sframes_t ret;
-       snd_pcm_sframes_t hw_avail;
-
-       if (frames == 0)
-               return 0;
-
-       snd_pcm_stream_lock_irq(substream);
-       switch (runtime->status->state) {
-       case SNDRV_PCM_STATE_PREPARED:
-               break;
+       switch (substream->runtime->status->state) {
        case SNDRV_PCM_STATE_DRAINING:
-       case SNDRV_PCM_STATE_RUNNING:
-               if (snd_pcm_update_hw_ptr(substream) >= 0)
-                       break;
+               if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+                       return -EBADFD;
                /* Fall through */
-       case SNDRV_PCM_STATE_XRUN:
-               ret = -EPIPE;
-               goto __end;
+       case SNDRV_PCM_STATE_RUNNING:
+               return snd_pcm_update_hw_ptr(substream);
+       case SNDRV_PCM_STATE_PREPARED:
+       case SNDRV_PCM_STATE_PAUSED:
+               return 0;
        case SNDRV_PCM_STATE_SUSPENDED:
-               ret = -ESTRPIPE;
-               goto __end;
+               return -ESTRPIPE;
+       case SNDRV_PCM_STATE_XRUN:
+               return -EPIPE;
        default:
-               ret = -EBADFD;
-               goto __end;
+               return -EBADFD;
        }
+}
 
-       hw_avail = snd_pcm_playback_hw_avail(runtime);
-       if (hw_avail <= 0) {
-               ret = 0;
-               goto __end;
-       }
-       if (frames > (snd_pcm_uframes_t)hw_avail)
-               frames = hw_avail;
+/* increase the appl_ptr; returns the processed frames or a negative error */
+static snd_pcm_sframes_t forward_appl_ptr(struct snd_pcm_substream *substream,
+                                         snd_pcm_uframes_t frames,
+                                          snd_pcm_sframes_t avail)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       snd_pcm_sframes_t appl_ptr;
+       int ret;
+
+       if (avail <= 0)
+               return 0;
+       if (frames > (snd_pcm_uframes_t)avail)
+               frames = avail;
+       appl_ptr = runtime->control->appl_ptr + frames;
+       if (appl_ptr >= (snd_pcm_sframes_t)runtime->boundary)
+               appl_ptr -= runtime->boundary;
+       ret = pcm_lib_apply_appl_ptr(substream, appl_ptr);
+       return ret < 0 ? ret : frames;
+}
+
+/* decrease the appl_ptr; returns the processed frames or a negative error */
+static snd_pcm_sframes_t rewind_appl_ptr(struct snd_pcm_substream *substream,
+                                        snd_pcm_uframes_t frames,
+                                        snd_pcm_sframes_t avail)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       snd_pcm_sframes_t appl_ptr;
+       int ret;
+
+       if (avail <= 0)
+               return 0;
+       if (frames > (snd_pcm_uframes_t)avail)
+               frames = avail;
        appl_ptr = runtime->control->appl_ptr - frames;
        if (appl_ptr < 0)
                appl_ptr += runtime->boundary;
-       runtime->control->appl_ptr = appl_ptr;
-       ret = frames;
- __end:
+       ret = pcm_lib_apply_appl_ptr(substream, appl_ptr);
+       return ret < 0 ? ret : frames;
+}
+
+static snd_pcm_sframes_t snd_pcm_playback_rewind(struct snd_pcm_substream *substream,
+                                                snd_pcm_uframes_t frames)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       snd_pcm_sframes_t ret;
+
+       if (frames == 0)
+               return 0;
+
+       snd_pcm_stream_lock_irq(substream);
+       ret = do_pcm_hwsync(substream);
+       if (!ret)
+               ret = rewind_appl_ptr(substream, frames,
+                                     snd_pcm_playback_hw_avail(runtime));
        snd_pcm_stream_unlock_irq(substream);
        return ret;
 }
@@ -2480,46 +2626,16 @@ static snd_pcm_sframes_t snd_pcm_capture_rewind(struct snd_pcm_substream *substr
                                                snd_pcm_uframes_t frames)
 {
        struct snd_pcm_runtime *runtime = substream->runtime;
-       snd_pcm_sframes_t appl_ptr;
        snd_pcm_sframes_t ret;
-       snd_pcm_sframes_t hw_avail;
 
        if (frames == 0)
                return 0;
 
        snd_pcm_stream_lock_irq(substream);
-       switch (runtime->status->state) {
-       case SNDRV_PCM_STATE_PREPARED:
-       case SNDRV_PCM_STATE_DRAINING:
-               break;
-       case SNDRV_PCM_STATE_RUNNING:
-               if (snd_pcm_update_hw_ptr(substream) >= 0)
-                       break;
-               /* Fall through */
-       case SNDRV_PCM_STATE_XRUN:
-               ret = -EPIPE;
-               goto __end;
-       case SNDRV_PCM_STATE_SUSPENDED:
-               ret = -ESTRPIPE;
-               goto __end;
-       default:
-               ret = -EBADFD;
-               goto __end;
-       }
-
-       hw_avail = snd_pcm_capture_hw_avail(runtime);
-       if (hw_avail <= 0) {
-               ret = 0;
-               goto __end;
-       }
-       if (frames > (snd_pcm_uframes_t)hw_avail)
-               frames = hw_avail;
-       appl_ptr = runtime->control->appl_ptr - frames;
-       if (appl_ptr < 0)
-               appl_ptr += runtime->boundary;
-       runtime->control->appl_ptr = appl_ptr;
-       ret = frames;
- __end:
+       ret = do_pcm_hwsync(substream);
+       if (!ret)
+               ret = rewind_appl_ptr(substream, frames,
+                                     snd_pcm_capture_hw_avail(runtime));
        snd_pcm_stream_unlock_irq(substream);
        return ret;
 }
@@ -2528,47 +2644,16 @@ static snd_pcm_sframes_t snd_pcm_playback_forward(struct snd_pcm_substream *subs
                                                  snd_pcm_uframes_t frames)
 {
        struct snd_pcm_runtime *runtime = substream->runtime;
-       snd_pcm_sframes_t appl_ptr;
        snd_pcm_sframes_t ret;
-       snd_pcm_sframes_t avail;
 
        if (frames == 0)
                return 0;
 
        snd_pcm_stream_lock_irq(substream);
-       switch (runtime->status->state) {
-       case SNDRV_PCM_STATE_PREPARED:
-       case SNDRV_PCM_STATE_PAUSED:
-               break;
-       case SNDRV_PCM_STATE_DRAINING:
-       case SNDRV_PCM_STATE_RUNNING:
-               if (snd_pcm_update_hw_ptr(substream) >= 0)
-                       break;
-               /* Fall through */
-       case SNDRV_PCM_STATE_XRUN:
-               ret = -EPIPE;
-               goto __end;
-       case SNDRV_PCM_STATE_SUSPENDED:
-               ret = -ESTRPIPE;
-               goto __end;
-       default:
-               ret = -EBADFD;
-               goto __end;
-       }
-
-       avail = snd_pcm_playback_avail(runtime);
-       if (avail <= 0) {
-               ret = 0;
-               goto __end;
-       }
-       if (frames > (snd_pcm_uframes_t)avail)
-               frames = avail;
-       appl_ptr = runtime->control->appl_ptr + frames;
-       if (appl_ptr >= (snd_pcm_sframes_t)runtime->boundary)
-               appl_ptr -= runtime->boundary;
-       runtime->control->appl_ptr = appl_ptr;
-       ret = frames;
- __end:
+       ret = do_pcm_hwsync(substream);
+       if (!ret)
+               ret = forward_appl_ptr(substream, frames,
+                                      snd_pcm_playback_avail(runtime));
        snd_pcm_stream_unlock_irq(substream);
        return ret;
 }
@@ -2577,123 +2662,47 @@ static snd_pcm_sframes_t snd_pcm_capture_forward(struct snd_pcm_substream *subst
                                                 snd_pcm_uframes_t frames)
 {
        struct snd_pcm_runtime *runtime = substream->runtime;
-       snd_pcm_sframes_t appl_ptr;
        snd_pcm_sframes_t ret;
-       snd_pcm_sframes_t avail;
 
        if (frames == 0)
                return 0;
 
        snd_pcm_stream_lock_irq(substream);
-       switch (runtime->status->state) {
-       case SNDRV_PCM_STATE_PREPARED:
-       case SNDRV_PCM_STATE_DRAINING:
-       case SNDRV_PCM_STATE_PAUSED:
-               break;
-       case SNDRV_PCM_STATE_RUNNING:
-               if (snd_pcm_update_hw_ptr(substream) >= 0)
-                       break;
-               /* Fall through */
-       case SNDRV_PCM_STATE_XRUN:
-               ret = -EPIPE;
-               goto __end;
-       case SNDRV_PCM_STATE_SUSPENDED:
-               ret = -ESTRPIPE;
-               goto __end;
-       default:
-               ret = -EBADFD;
-               goto __end;
-       }
-
-       avail = snd_pcm_capture_avail(runtime);
-       if (avail <= 0) {
-               ret = 0;
-               goto __end;
-       }
-       if (frames > (snd_pcm_uframes_t)avail)
-               frames = avail;
-       appl_ptr = runtime->control->appl_ptr + frames;
-       if (appl_ptr >= (snd_pcm_sframes_t)runtime->boundary)
-               appl_ptr -= runtime->boundary;
-       runtime->control->appl_ptr = appl_ptr;
-       ret = frames;
- __end:
+       ret = do_pcm_hwsync(substream);
+       if (!ret)
+               ret = forward_appl_ptr(substream, frames,
+                                      snd_pcm_capture_avail(runtime));
        snd_pcm_stream_unlock_irq(substream);
        return ret;
 }
 
 static int snd_pcm_hwsync(struct snd_pcm_substream *substream)
 {
-       struct snd_pcm_runtime *runtime = substream->runtime;
        int err;
 
        snd_pcm_stream_lock_irq(substream);
-       switch (runtime->status->state) {
-       case SNDRV_PCM_STATE_DRAINING:
-               if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
-                       goto __badfd;
-               /* Fall through */
-       case SNDRV_PCM_STATE_RUNNING:
-               if ((err = snd_pcm_update_hw_ptr(substream)) < 0)
-                       break;
-               /* Fall through */
-       case SNDRV_PCM_STATE_PREPARED:
-               err = 0;
-               break;
-       case SNDRV_PCM_STATE_SUSPENDED:
-               err = -ESTRPIPE;
-               break;
-       case SNDRV_PCM_STATE_XRUN:
-               err = -EPIPE;
-               break;
-       default:
-             __badfd:
-               err = -EBADFD;
-               break;
-       }
+       err = do_pcm_hwsync(substream);
        snd_pcm_stream_unlock_irq(substream);
        return err;
 }
                
-static int snd_pcm_delay(struct snd_pcm_substream *substream,
-                        snd_pcm_sframes_t __user *res)
+static snd_pcm_sframes_t snd_pcm_delay(struct snd_pcm_substream *substream)
 {
        struct snd_pcm_runtime *runtime = substream->runtime;
        int err;
        snd_pcm_sframes_t n = 0;
 
        snd_pcm_stream_lock_irq(substream);
-       switch (runtime->status->state) {
-       case SNDRV_PCM_STATE_DRAINING:
-               if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
-                       goto __badfd;
-               /* Fall through */
-       case SNDRV_PCM_STATE_RUNNING:
-               if ((err = snd_pcm_update_hw_ptr(substream)) < 0)
-                       break;
-               /* Fall through */
-       case SNDRV_PCM_STATE_PREPARED:
-       case SNDRV_PCM_STATE_SUSPENDED:
-               err = 0;
+       err = do_pcm_hwsync(substream);
+       if (!err) {
                if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
                        n = snd_pcm_playback_hw_avail(runtime);
                else
                        n = snd_pcm_capture_avail(runtime);
                n += runtime->delay;
-               break;
-       case SNDRV_PCM_STATE_XRUN:
-               err = -EPIPE;
-               break;
-       default:
-             __badfd:
-               err = -EBADFD;
-               break;
        }
        snd_pcm_stream_unlock_irq(substream);
-       if (!err)
-               if (put_user(n, res))
-                       err = -EFAULT;
-       return err;
+       return err < 0 ? err : n;
 }
                
 static int snd_pcm_sync_ptr(struct snd_pcm_substream *substream,
@@ -2718,10 +2727,16 @@ static int snd_pcm_sync_ptr(struct snd_pcm_substream *substream,
                        return err;
        }
        snd_pcm_stream_lock_irq(substream);
-       if (!(sync_ptr.flags & SNDRV_PCM_SYNC_PTR_APPL))
-               control->appl_ptr = sync_ptr.c.control.appl_ptr;
-       else
+       if (!(sync_ptr.flags & SNDRV_PCM_SYNC_PTR_APPL)) {
+               err = pcm_lib_apply_appl_ptr(substream,
+                                            sync_ptr.c.control.appl_ptr);
+               if (err < 0) {
+                       snd_pcm_stream_unlock_irq(substream);
+                       return err;
+               }
+       } else {
                sync_ptr.c.control.appl_ptr = control->appl_ptr;
+       }
        if (!(sync_ptr.flags & SNDRV_PCM_SYNC_PTR_AVAIL_MIN))
                control->avail_min = sync_ptr.c.control.avail_min;
        else
@@ -2749,10 +2764,12 @@ static int snd_pcm_tstamp(struct snd_pcm_substream *substream, int __user *_arg)
        return 0;
 }
                
-static int snd_pcm_common_ioctl1(struct file *file,
+static int snd_pcm_common_ioctl(struct file *file,
                                 struct snd_pcm_substream *substream,
                                 unsigned int cmd, void __user *arg)
 {
+       struct snd_pcm_file *pcm_file = file->private_data;
+
        switch (cmd) {
        case SNDRV_PCM_IOCTL_PVERSION:
                return put_user(SNDRV_PCM_VERSION, (int __user *)arg) ? -EFAULT : 0;
@@ -2762,6 +2779,11 @@ static int snd_pcm_common_ioctl1(struct file *file,
                return 0;
        case SNDRV_PCM_IOCTL_TTSTAMP:
                return snd_pcm_tstamp(substream, arg);
+       case SNDRV_PCM_IOCTL_USER_PVERSION:
+               if (get_user(pcm_file->user_pversion,
+                            (unsigned int __user *)arg))
+                       return -EFAULT;
+               return 0;
        case SNDRV_PCM_IOCTL_HW_REFINE:
                return snd_pcm_hw_refine_user(substream, arg);
        case SNDRV_PCM_IOCTL_HW_PARAMS:
@@ -2781,7 +2803,7 @@ static int snd_pcm_common_ioctl1(struct file *file,
        case SNDRV_PCM_IOCTL_RESET:
                return snd_pcm_reset(substream);
        case SNDRV_PCM_IOCTL_START:
-               return snd_pcm_action_lock_irq(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
+               return snd_pcm_start_lock_irq(substream);
        case SNDRV_PCM_IOCTL_LINK:
                return snd_pcm_link(substream, (int)(unsigned long) arg);
        case SNDRV_PCM_IOCTL_UNLINK:
@@ -2793,7 +2815,16 @@ static int snd_pcm_common_ioctl1(struct file *file,
        case SNDRV_PCM_IOCTL_HWSYNC:
                return snd_pcm_hwsync(substream);
        case SNDRV_PCM_IOCTL_DELAY:
-               return snd_pcm_delay(substream, arg);
+       {
+               snd_pcm_sframes_t delay = snd_pcm_delay(substream);
+               snd_pcm_sframes_t __user *res = arg;
+
+               if (delay < 0)
+                       return delay;
+               if (put_user(delay, res))
+                       return -EFAULT;
+               return 0;
+       }
        case SNDRV_PCM_IOCTL_SYNC_PTR:
                return snd_pcm_sync_ptr(substream, arg);
 #ifdef CONFIG_SND_SUPPORT_OLD_API
@@ -2807,23 +2838,34 @@ static int snd_pcm_common_ioctl1(struct file *file,
        case SNDRV_PCM_IOCTL_DROP:
                return snd_pcm_drop(substream);
        case SNDRV_PCM_IOCTL_PAUSE:
-       {
-               int res;
-               snd_pcm_stream_lock_irq(substream);
-               res = snd_pcm_pause(substream, (int)(unsigned long)arg);
-               snd_pcm_stream_unlock_irq(substream);
-               return res;
-       }
+               return snd_pcm_action_lock_irq(&snd_pcm_action_pause,
+                                              substream,
+                                              (int)(unsigned long)arg);
        }
        pcm_dbg(substream->pcm, "unknown ioctl = 0x%x\n", cmd);
        return -ENOTTY;
 }
 
+static int snd_pcm_common_ioctl1(struct file *file,
+                                struct snd_pcm_substream *substream,
+                                unsigned int cmd, void __user *arg)
+{
+       struct snd_card *card = substream->pcm->card;
+       int res;
+
+       snd_power_lock(card);
+       res = snd_power_wait(card, SNDRV_CTL_POWER_D0);
+       if (res >= 0)
+               res = snd_pcm_common_ioctl(file, substream, cmd, arg);
+       snd_power_unlock(card);
+       return res;
+}
+
 static int snd_pcm_playback_ioctl1(struct file *file,
                                   struct snd_pcm_substream *substream,
                                   unsigned int cmd, void __user *arg)
 {
-       if (snd_BUG_ON(!substream))
+       if (PCM_RUNTIME_CHECK(substream))
                return -ENXIO;
        if (snd_BUG_ON(substream->stream != SNDRV_PCM_STREAM_PLAYBACK))
                return -EINVAL;
@@ -2903,7 +2945,7 @@ static int snd_pcm_capture_ioctl1(struct file *file,
                                  struct snd_pcm_substream *substream,
                                  unsigned int cmd, void __user *arg)
 {
-       if (snd_BUG_ON(!substream))
+       if (PCM_RUNTIME_CHECK(substream))
                return -ENXIO;
        if (snd_BUG_ON(substream->stream != SNDRV_PCM_STREAM_CAPTURE))
                return -EINVAL;
@@ -3007,30 +3049,55 @@ static long snd_pcm_capture_ioctl(struct file *file, unsigned int cmd,
                                      (void __user *)arg);
 }
 
+/**
+ * snd_pcm_kernel_ioctl - Execute PCM ioctl in the kernel-space
+ * @substream: PCM substream
+ * @cmd: IOCTL cmd
+ * @arg: IOCTL argument
+ *
+ * The function is provided primarily for OSS layer and USB gadget drivers,
+ * and it allows only the limited set of ioctls (hw_params, sw_params,
+ * prepare, start, drain, drop, forward).
+ */
 int snd_pcm_kernel_ioctl(struct snd_pcm_substream *substream,
                         unsigned int cmd, void *arg)
 {
-       mm_segment_t fs;
-       int result;
+       snd_pcm_uframes_t *frames = arg;
+       snd_pcm_sframes_t result;
        
-       fs = snd_enter_user();
-       switch (substream->stream) {
-       case SNDRV_PCM_STREAM_PLAYBACK:
-               result = snd_pcm_playback_ioctl1(NULL, substream, cmd,
-                                                (void __user *)arg);
-               break;
-       case SNDRV_PCM_STREAM_CAPTURE:
-               result = snd_pcm_capture_ioctl1(NULL, substream, cmd,
-                                               (void __user *)arg);
-               break;
+       switch (cmd) {
+       case SNDRV_PCM_IOCTL_FORWARD:
+       {
+               /* provided only for OSS; capture-only and no value returned */
+               if (substream->stream != SNDRV_PCM_STREAM_CAPTURE)
+                       return -EINVAL;
+               result = snd_pcm_capture_forward(substream, *frames);
+               return result < 0 ? result : 0;
+       }
+       case SNDRV_PCM_IOCTL_HW_PARAMS:
+               return snd_pcm_hw_params(substream, arg);
+       case SNDRV_PCM_IOCTL_SW_PARAMS:
+               return snd_pcm_sw_params(substream, arg);
+       case SNDRV_PCM_IOCTL_PREPARE:
+               return snd_pcm_prepare(substream, NULL);
+       case SNDRV_PCM_IOCTL_START:
+               return snd_pcm_start_lock_irq(substream);
+       case SNDRV_PCM_IOCTL_DRAIN:
+               return snd_pcm_drain(substream, NULL);
+       case SNDRV_PCM_IOCTL_DROP:
+               return snd_pcm_drop(substream);
+       case SNDRV_PCM_IOCTL_DELAY:
+       {
+               result = snd_pcm_delay(substream);
+               if (result < 0)
+                       return result;
+               *frames = result;
+               return 0;
+       }
        default:
-               result = -EINVAL;
-               break;
+               return -EINVAL;
        }
-       snd_leave_user(fs);
-       return result;
 }
-
 EXPORT_SYMBOL(snd_pcm_kernel_ioctl);
 
 static ssize_t snd_pcm_read(struct file *file, char __user *buf, size_t count,
@@ -3314,10 +3381,41 @@ static int snd_pcm_mmap_control(struct snd_pcm_substream *substream, struct file
        area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
        return 0;
 }
+
+static bool pcm_status_mmap_allowed(struct snd_pcm_file *pcm_file)
+{
+       if (pcm_file->no_compat_mmap)
+               return false;
+       /* See pcm_control_mmap_allowed() below.
+        * Since older alsa-lib requires both status and control mmaps to be
+        * coupled, we have to disable the status mmap for old alsa-lib, too.
+        */
+       if (pcm_file->user_pversion < SNDRV_PROTOCOL_VERSION(2, 0, 14) &&
+           (pcm_file->substream->runtime->hw.info & SNDRV_PCM_INFO_SYNC_APPLPTR))
+               return false;
+       return true;
+}
+
+static bool pcm_control_mmap_allowed(struct snd_pcm_file *pcm_file)
+{
+       if (pcm_file->no_compat_mmap)
+               return false;
+       /* Disallow the control mmap when SYNC_APPLPTR flag is set;
+        * it enforces the user-space to fall back to snd_pcm_sync_ptr(),
+        * thus it effectively assures the manual update of appl_ptr.
+        */
+       if (pcm_file->substream->runtime->hw.info & SNDRV_PCM_INFO_SYNC_APPLPTR)
+               return false;
+       return true;
+}
+
 #else /* ! coherent mmap */
 /*
  * don't support mmap for status and control records.
  */
+#define pcm_status_mmap_allowed(pcm_file)      false
+#define pcm_control_mmap_allowed(pcm_file)     false
+
 static int snd_pcm_mmap_status(struct snd_pcm_substream *substream, struct file *file,
                               struct vm_area_struct *area)
 {
@@ -3402,7 +3500,7 @@ int snd_pcm_lib_default_mmap(struct snd_pcm_substream *substream,
        }
 #endif /* CONFIG_GENERIC_ALLOCATOR */
 #ifndef CONFIG_X86 /* for avoiding warnings arch/x86/mm/pat.c */
-       if (!substream->ops->page &&
+       if (IS_ENABLED(CONFIG_HAS_DMA) && !substream->ops->page &&
            substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV)
                return dma_mmap_coherent(substream->dma_buffer.dev.dev,
                                         area,
@@ -3437,7 +3535,6 @@ int snd_pcm_lib_mmap_iomem(struct snd_pcm_substream *substream,
        area->vm_page_prot = pgprot_noncached(area->vm_page_prot);
        return vm_iomap_memory(area, runtime->dma_addr, runtime->dma_bytes);
 }
-
 EXPORT_SYMBOL(snd_pcm_lib_mmap_iomem);
 #endif /* SNDRV_PCM_INFO_MMAP */
 
@@ -3486,7 +3583,6 @@ int snd_pcm_mmap_data(struct snd_pcm_substream *substream, struct file *file,
                atomic_inc(&substream->mmap_count);
        return err;
 }
-
 EXPORT_SYMBOL(snd_pcm_mmap_data);
 
 static int snd_pcm_mmap(struct file *file, struct vm_area_struct *area)
@@ -3503,11 +3599,11 @@ static int snd_pcm_mmap(struct file *file, struct vm_area_struct *area)
        offset = area->vm_pgoff << PAGE_SHIFT;
        switch (offset) {
        case SNDRV_PCM_MMAP_OFFSET_STATUS:
-               if (pcm_file->no_compat_mmap)
+               if (!pcm_status_mmap_allowed(pcm_file))
                        return -ENXIO;
                return snd_pcm_mmap_status(substream, file, area);
        case SNDRV_PCM_MMAP_OFFSET_CONTROL:
-               if (pcm_file->no_compat_mmap)
+               if (!pcm_control_mmap_allowed(pcm_file))
                        return -ENXIO;
                return snd_pcm_mmap_control(substream, file, area);
        default:
@@ -3603,12 +3699,17 @@ static int snd_pcm_hw_refine_old_user(struct snd_pcm_substream *substream,
        }
        snd_pcm_hw_convert_from_old_params(params, oparams);
        err = snd_pcm_hw_refine(substream, params);
-       snd_pcm_hw_convert_to_old_params(oparams, params);
-       if (copy_to_user(_oparams, oparams, sizeof(*oparams))) {
-               if (!err)
-                       err = -EFAULT;
-       }
+       if (err < 0)
+               goto out_old;
 
+       err = fixup_unreferenced_params(substream, params);
+       if (err < 0)
+               goto out_old;
+
+       snd_pcm_hw_convert_to_old_params(oparams, params);
+       if (copy_to_user(_oparams, oparams, sizeof(*oparams)))
+               err = -EFAULT;
+out_old:
        kfree(oparams);
 out:
        kfree(params);
@@ -3631,14 +3732,16 @@ static int snd_pcm_hw_params_old_user(struct snd_pcm_substream *substream,
                err = PTR_ERR(oparams);
                goto out;
        }
+
        snd_pcm_hw_convert_from_old_params(params, oparams);
        err = snd_pcm_hw_params(substream, params);
-       snd_pcm_hw_convert_to_old_params(oparams, params);
-       if (copy_to_user(_oparams, oparams, sizeof(*oparams))) {
-               if (!err)
-                       err = -EFAULT;
-       }
+       if (err < 0)
+               goto out_old;
 
+       snd_pcm_hw_convert_to_old_params(oparams, params);
+       if (copy_to_user(_oparams, oparams, sizeof(*oparams)))
+               err = -EFAULT;
+out_old:
        kfree(oparams);
 out:
        kfree(params);