|
@@ -37,6 +37,18 @@
|
|
|
#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;
|
|
|
}
|
|
|
|
|
@@ -255,205 +249,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 +524,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 +571,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 +672,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 +751,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 +1212,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 +1220,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 +1278,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 +1452,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 +1483,6 @@ int snd_pcm_suspend_all(struct snd_pcm *pcm)
|
|
|
}
|
|
|
return 0;
|
|
|
}
|
|
|
-
|
|
|
EXPORT_SYMBOL(snd_pcm_suspend_all);
|
|
|
|
|
|
/* resume */
|
|
@@ -1397,14 +1533,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 +1552,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 +1567,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 +1670,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 +1677,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 +1786,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 +1868,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 +1887,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 +2053,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 +2100,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 +2366,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 +2407,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 +2542,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 +2628,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 +2646,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 +2664,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 +2729,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 +2766,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 +2781,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 +2805,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 +2817,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 +2840,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 +2947,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 +3051,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 +3383,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)
|
|
|
{
|
|
@@ -3437,7 +3537,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 +3585,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 +3601,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 +3701,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 +3734,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);
|