aboutsummaryrefslogtreecommitdiff
path: root/sound/core/pcm_native.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /sound/core/pcm_native.c
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
Diffstat (limited to 'sound/core/pcm_native.c')
-rw-r--r--sound/core/pcm_native.c3364
1 files changed, 3364 insertions, 0 deletions
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c
new file mode 100644
index 00000000000..cad9bbde998
--- /dev/null
+++ b/sound/core/pcm_native.c
@@ -0,0 +1,3364 @@
+/*
+ * Digital Audio (PCM) abstract layer
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/mm.h>
+#include <linux/smp_lock.h>
+#include <linux/file.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/uio.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/timer.h>
+#include <sound/minors.h>
+#include <asm/io.h>
+
+/*
+ * Compatibility
+ */
+
+struct sndrv_pcm_hw_params_old {
+ unsigned int flags;
+ unsigned int masks[SNDRV_PCM_HW_PARAM_SUBFORMAT -
+ SNDRV_PCM_HW_PARAM_ACCESS + 1];
+ struct sndrv_interval intervals[SNDRV_PCM_HW_PARAM_TICK_TIME -
+ SNDRV_PCM_HW_PARAM_SAMPLE_BITS + 1];
+ unsigned int rmask;
+ unsigned int cmask;
+ unsigned int info;
+ unsigned int msbits;
+ unsigned int rate_num;
+ unsigned int rate_den;
+ sndrv_pcm_uframes_t fifo_size;
+ unsigned char reserved[64];
+};
+
+#define SNDRV_PCM_IOCTL_HW_REFINE_OLD _IOWR('A', 0x10, struct sndrv_pcm_hw_params_old)
+#define SNDRV_PCM_IOCTL_HW_PARAMS_OLD _IOWR('A', 0x11, struct sndrv_pcm_hw_params_old)
+
+static int snd_pcm_hw_refine_old_user(snd_pcm_substream_t * substream, struct sndrv_pcm_hw_params_old __user * _oparams);
+static int snd_pcm_hw_params_old_user(snd_pcm_substream_t * substream, struct sndrv_pcm_hw_params_old __user * _oparams);
+
+/*
+ *
+ */
+
+DEFINE_RWLOCK(snd_pcm_link_rwlock);
+static DECLARE_RWSEM(snd_pcm_link_rwsem);
+
+
+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(snd_pcm_substream_t * substream, snd_pcm_info_t *info)
+{
+ snd_pcm_runtime_t * runtime;
+ snd_pcm_t *pcm = substream->pcm;
+ snd_pcm_str_t *pstr = substream->pstr;
+
+ snd_assert(substream != NULL, return -ENXIO);
+ memset(info, 0, sizeof(*info));
+ info->card = pcm->card->number;
+ info->device = pcm->device;
+ info->stream = substream->stream;
+ info->subdevice = substream->number;
+ strlcpy(info->id, pcm->id, sizeof(info->id));
+ strlcpy(info->name, pcm->name, sizeof(info->name));
+ info->dev_class = pcm->dev_class;
+ info->dev_subclass = pcm->dev_subclass;
+ info->subdevices_count = pstr->substream_count;
+ 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;
+}
+
+int snd_pcm_info_user(snd_pcm_substream_t * substream, snd_pcm_info_t __user * _info)
+{
+ snd_pcm_info_t *info;
+ int err;
+
+ info = kmalloc(sizeof(*info), GFP_KERNEL);
+ if (! info)
+ return -ENOMEM;
+ err = snd_pcm_info(substream, info);
+ if (err >= 0) {
+ if (copy_to_user(_info, info, sizeof(*info)))
+ err = -EFAULT;
+ }
+ kfree(info);
+ return err;
+}
+
+#undef RULES_DEBUG
+
+#ifdef RULES_DEBUG
+#define HW_PARAM(v) [SNDRV_PCM_HW_PARAM_##v] = #v
+char *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(snd_pcm_substream_t *substream,
+ snd_pcm_hw_params_t *params)
+{
+ unsigned int k;
+ snd_pcm_hardware_t *hw;
+ snd_interval_t *i = NULL;
+ snd_mask_t *m = NULL;
+ snd_pcm_hw_constraints_t *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;
+ }
+
+ 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;
+ if (!(params->rmask & (1 << k)))
+ continue;
+#ifdef RULES_DEBUG
+ printk("%s = ", snd_pcm_hw_param_names[k]);
+ printk("%04x%04x%04x%04x -> ", m->bits[3], m->bits[2], m->bits[1], m->bits[0]);
+#endif
+ changed = snd_mask_refine(m, constrs_mask(constrs, k));
+#ifdef RULES_DEBUG
+ printk("%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;
+ }
+
+ 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;
+ if (!(params->rmask & (1 << k)))
+ continue;
+#ifdef RULES_DEBUG
+ printk("%s = ", snd_pcm_hw_param_names[k]);
+ if (i->empty)
+ printk("empty");
+ else
+ printk("%c%u %u%c",
+ i->openmin ? '(' : '[', i->min,
+ i->max, i->openmax ? ')' : ']');
+ printk(" -> ");
+#endif
+ changed = snd_interval_refine(i, constrs_interval(constrs, k));
+#ifdef RULES_DEBUG
+ if (i->empty)
+ printk("empty\n");
+ else
+ printk("%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;
+ }
+
+ for (k = 0; k < constrs->rules_num; k++)
+ rstamps[k] = 0;
+ 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++) {
+ snd_pcm_hw_rule_t *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
+ printk("Rule %d [%p]: ", k, r->func);
+ if (r->var >= 0) {
+ printk("%s = ", snd_pcm_hw_param_names[r->var]);
+ if (hw_is_mask(r->var)) {
+ m = hw_param_mask(params, r->var);
+ printk("%x", *m->bits);
+ } else {
+ i = hw_param_interval(params, r->var);
+ if (i->empty)
+ printk("empty");
+ else
+ printk("%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) {
+ printk(" -> ");
+ if (hw_is_mask(r->var))
+ printk("%x", *m->bits);
+ else {
+ if (i->empty)
+ printk("empty");
+ else
+ printk("%c%u %u%c",
+ i->openmin ? '(' : '[', i->min,
+ i->max, i->openmax ? ')' : ']');
+ }
+ }
+ printk("\n");
+#endif
+ rstamps[k] = stamp;
+ if (changed && r->var >= 0) {
+ params->cmask |= (1 << r->var);
+ vstamps[r->var] = stamp;
+ again = 1;
+ }
+ if (changed < 0)
+ return changed;
+ stamp++;
+ }
+ } while (again);
+ if (!params->msbits) {
+ i = hw_param_interval(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);
+ if (snd_interval_single(i)) {
+ params->rate_num = snd_interval_value(i);
+ params->rate_den = 1;
+ }
+ }
+
+ hw = &substream->runtime->hw;
+ if (!params->info)
+ params->info = hw->info;
+ if (!params->fifo_size)
+ params->fifo_size = hw->fifo_size;
+ params->rmask = 0;
+ return 0;
+}
+
+static int snd_pcm_hw_refine_user(snd_pcm_substream_t * substream, snd_pcm_hw_params_t __user * _params)
+{
+ snd_pcm_hw_params_t *params;
+ int err;
+
+ params = kmalloc(sizeof(*params), GFP_KERNEL);
+ if (!params) {
+ err = -ENOMEM;
+ goto out;
+ }
+ if (copy_from_user(params, _params, sizeof(*params))) {
+ err = -EFAULT;
+ goto out;
+ }
+ err = snd_pcm_hw_refine(substream, params);
+ if (copy_to_user(_params, params, sizeof(*params))) {
+ if (!err)
+ err = -EFAULT;
+ }
+out:
+ kfree(params);
+ return err;
+}
+
+int snd_pcm_hw_params(snd_pcm_substream_t *substream,
+ snd_pcm_hw_params_t *params)
+{
+ snd_pcm_runtime_t *runtime;
+ int err;
+ unsigned int bits;
+ snd_pcm_uframes_t frames;
+
+ snd_assert(substream != NULL, return -ENXIO);
+ runtime = substream->runtime;
+ snd_assert(runtime != NULL, return -ENXIO);
+ snd_pcm_stream_lock_irq(substream);
+ switch (runtime->status->state) {
+ case SNDRV_PCM_STATE_OPEN:
+ case SNDRV_PCM_STATE_SETUP:
+ case SNDRV_PCM_STATE_PREPARED:
+ break;
+ default:
+ snd_pcm_stream_unlock_irq(substream);
+ return -EBADFD;
+ }
+ snd_pcm_stream_unlock_irq(substream);
+#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
+ if (!substream->oss.oss)
+#endif
+ if (atomic_read(&runtime->mmap_count))
+ return -EBADFD;
+
+ params->rmask = ~0U;
+ err = snd_pcm_hw_refine(substream, params);
+ if (err < 0)
+ goto _error;
+
+ err = snd_pcm_hw_params_choose(substream, params);
+ if (err < 0)
+ goto _error;
+
+ if (substream->ops->hw_params != NULL) {
+ err = substream->ops->hw_params(substream, params);
+ if (err < 0)
+ goto _error;
+ }
+
+ runtime->access = params_access(params);
+ runtime->format = params_format(params);
+ runtime->subformat = params_subformat(params);
+ runtime->channels = params_channels(params);
+ runtime->rate = params_rate(params);
+ runtime->period_size = params_period_size(params);
+ runtime->periods = params_periods(params);
+ runtime->buffer_size = params_buffer_size(params);
+ runtime->tick_time = params_tick_time(params);
+ runtime->info = params->info;
+ runtime->rate_num = params->rate_num;
+ runtime->rate_den = params->rate_den;
+
+ bits = snd_pcm_format_physical_width(runtime->format);
+ runtime->sample_bits = bits;
+ bits *= runtime->channels;
+ runtime->frame_bits = bits;
+ frames = 1;
+ while (bits % 8 != 0) {
+ bits *= 2;
+ frames *= 2;
+ }
+ runtime->byte_align = bits / 8;
+ runtime->min_align = frames;
+
+ /* Default sw params */
+ runtime->tstamp_mode = SNDRV_PCM_TSTAMP_NONE;
+ runtime->period_step = 1;
+ runtime->sleep_min = 0;
+ runtime->control->avail_min = runtime->period_size;
+ runtime->xfer_align = runtime->period_size;
+ runtime->start_threshold = 1;
+ runtime->stop_threshold = runtime->buffer_size;
+ runtime->silence_threshold = 0;
+ runtime->silence_size = 0;
+ runtime->boundary = runtime->buffer_size;
+ while (runtime->boundary * 2 <= LONG_MAX - runtime->buffer_size)
+ runtime->boundary *= 2;
+
+ snd_pcm_timer_resolution_change(substream);
+ runtime->status->state = SNDRV_PCM_STATE_SETUP;
+ return 0;
+ _error:
+ /* hardware might be unuseable from this time,
+ so we force application to retry to set
+ the correct hardware parameter settings */
+ runtime->status->state = SNDRV_PCM_STATE_OPEN;
+ if (substream->ops->hw_free != NULL)
+ substream->ops->hw_free(substream);
+ return err;
+}
+
+static int snd_pcm_hw_params_user(snd_pcm_substream_t * substream, snd_pcm_hw_params_t __user * _params)
+{
+ snd_pcm_hw_params_t *params;
+ int err;
+
+ params = kmalloc(sizeof(*params), GFP_KERNEL);
+ if (!params) {
+ err = -ENOMEM;
+ goto out;
+ }
+ if (copy_from_user(params, _params, sizeof(*params))) {
+ err = -EFAULT;
+ goto out;
+ }
+ err = snd_pcm_hw_params(substream, params);
+ if (copy_to_user(_params, params, sizeof(*params))) {
+ if (!err)
+ err = -EFAULT;
+ }
+out:
+ kfree(params);
+ return err;
+}
+
+static int snd_pcm_hw_free(snd_pcm_substream_t * substream)
+{
+ snd_pcm_runtime_t *runtime;
+ int result = 0;
+
+ snd_assert(substream != NULL, return -ENXIO);
+ runtime = substream->runtime;
+ snd_assert(runtime != NULL, return -ENXIO);
+ snd_pcm_stream_lock_irq(substream);
+ switch (runtime->status->state) {
+ case SNDRV_PCM_STATE_SETUP:
+ case SNDRV_PCM_STATE_PREPARED:
+ break;
+ default:
+ snd_pcm_stream_unlock_irq(substream);
+ return -EBADFD;
+ }
+ snd_pcm_stream_unlock_irq(substream);
+ if (atomic_read(&runtime->mmap_count))
+ return -EBADFD;
+ if (substream->ops->hw_free)
+ result = substream->ops->hw_free(substream);
+ runtime->status->state = SNDRV_PCM_STATE_OPEN;
+ return result;
+}
+
+static int snd_pcm_sw_params(snd_pcm_substream_t * substream, snd_pcm_sw_params_t *params)
+{
+ snd_pcm_runtime_t *runtime;
+
+ snd_assert(substream != NULL, return -ENXIO);
+ runtime = substream->runtime;
+ snd_assert(runtime != NULL, return -ENXIO);
+ snd_pcm_stream_lock_irq(substream);
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+ snd_pcm_stream_unlock_irq(substream);
+ return -EBADFD;
+ }
+ snd_pcm_stream_unlock_irq(substream);
+
+ if (params->tstamp_mode > SNDRV_PCM_TSTAMP_LAST)
+ return -EINVAL;
+ if (params->avail_min == 0)
+ return -EINVAL;
+ if (params->xfer_align == 0 ||
+ params->xfer_align % runtime->min_align != 0)
+ return -EINVAL;
+ if (params->silence_size >= runtime->boundary) {
+ if (params->silence_threshold != 0)
+ return -EINVAL;
+ } else {
+ if (params->silence_size > params->silence_threshold)
+ return -EINVAL;
+ if (params->silence_threshold > runtime->buffer_size)
+ return -EINVAL;
+ }
+ snd_pcm_stream_lock_irq(substream);
+ runtime->tstamp_mode = params->tstamp_mode;
+ runtime->sleep_min = params->sleep_min;
+ runtime->period_step = params->period_step;
+ runtime->control->avail_min = params->avail_min;
+ runtime->start_threshold = params->start_threshold;
+ runtime->stop_threshold = params->stop_threshold;
+ runtime->silence_threshold = params->silence_threshold;
+ runtime->silence_size = params->silence_size;
+ runtime->xfer_align = params->xfer_align;
+ params->boundary = runtime->boundary;
+ if (snd_pcm_running(substream)) {
+ if (runtime->sleep_min)
+ snd_pcm_tick_prepare(substream);
+ else
+ snd_pcm_tick_set(substream, 0);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+ runtime->silence_size > 0)
+ snd_pcm_playback_silence(substream, ULONG_MAX);
+ wake_up(&runtime->sleep);
+ }
+ snd_pcm_stream_unlock_irq(substream);
+ return 0;
+}
+
+static int snd_pcm_sw_params_user(snd_pcm_substream_t * substream, snd_pcm_sw_params_t __user * _params)
+{
+ snd_pcm_sw_params_t params;
+ int err;
+ if (copy_from_user(&params, _params, sizeof(params)))
+ return -EFAULT;
+ err = snd_pcm_sw_params(substream, &params);
+ if (copy_to_user(_params, &params, sizeof(params)))
+ return -EFAULT;
+ return err;
+}
+
+int snd_pcm_status(snd_pcm_substream_t *substream,
+ snd_pcm_status_t *status)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+
+ snd_pcm_stream_lock_irq(substream);
+ status->state = runtime->status->state;
+ status->suspended_state = runtime->status->suspended_state;
+ if (status->state == SNDRV_PCM_STATE_OPEN)
+ goto _end;
+ status->trigger_tstamp = runtime->trigger_tstamp;
+ if (snd_pcm_running(substream)) {
+ snd_pcm_update_hw_ptr(substream);
+ if (runtime->tstamp_mode & SNDRV_PCM_TSTAMP_MMAP)
+ status->tstamp = runtime->status->tstamp;
+ else
+ snd_timestamp_now(&status->tstamp, runtime->tstamp_timespec);
+ } else
+ snd_timestamp_now(&status->tstamp, runtime->tstamp_timespec);
+ status->appl_ptr = runtime->control->appl_ptr;
+ status->hw_ptr = runtime->status->hw_ptr;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ status->avail = snd_pcm_playback_avail(runtime);
+ if (runtime->status->state == SNDRV_PCM_STATE_RUNNING ||
+ runtime->status->state == SNDRV_PCM_STATE_DRAINING)
+ status->delay = runtime->buffer_size - status->avail;
+ else
+ status->delay = 0;
+ } else {
+ status->avail = snd_pcm_capture_avail(runtime);
+ if (runtime->status->state == SNDRV_PCM_STATE_RUNNING)
+ status->delay = status->avail;
+ else
+ status->delay = 0;
+ }
+ status->avail_max = runtime->avail_max;
+ status->overrange = runtime->overrange;
+ runtime->avail_max = 0;
+ runtime->overrange = 0;
+ _end:
+ snd_pcm_stream_unlock_irq(substream);
+ return 0;
+}
+
+static int snd_pcm_status_user(snd_pcm_substream_t * substream, snd_pcm_status_t __user * _status)
+{
+ snd_pcm_status_t status;
+ snd_pcm_runtime_t *runtime;
+ int res;
+
+ snd_assert(substream != NULL, return -ENXIO);
+ runtime = substream->runtime;
+ memset(&status, 0, sizeof(status));
+ res = snd_pcm_status(substream, &status);
+ if (res < 0)
+ return res;
+ if (copy_to_user(_status, &status, sizeof(status)))
+ return -EFAULT;
+ return 0;
+}
+
+static int snd_pcm_channel_info(snd_pcm_substream_t * substream, snd_pcm_channel_info_t * info)
+{
+ snd_pcm_runtime_t *runtime;
+ unsigned int channel;
+
+ snd_assert(substream != NULL, return -ENXIO);
+ channel = info->channel;
+ runtime = substream->runtime;
+ snd_pcm_stream_lock_irq(substream);
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+ snd_pcm_stream_unlock_irq(substream);
+ return -EBADFD;
+ }
+ snd_pcm_stream_unlock_irq(substream);
+ if (channel >= runtime->channels)
+ return -EINVAL;
+ memset(info, 0, sizeof(*info));
+ info->channel = channel;
+ return substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_CHANNEL_INFO, info);
+}
+
+static int snd_pcm_channel_info_user(snd_pcm_substream_t * substream, snd_pcm_channel_info_t __user * _info)
+{
+ snd_pcm_channel_info_t info;
+ int res;
+
+ if (copy_from_user(&info, _info, sizeof(info)))
+ return -EFAULT;
+ res = snd_pcm_channel_info(substream, &info);
+ if (res < 0)
+ return res;
+ if (copy_to_user(_info, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+}
+
+static void snd_pcm_trigger_tstamp(snd_pcm_substream_t *substream)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (runtime->trigger_master == NULL)
+ return;
+ if (runtime->trigger_master == substream) {
+ snd_timestamp_now(&runtime->trigger_tstamp, runtime->tstamp_timespec);
+ } else {
+ snd_pcm_trigger_tstamp(runtime->trigger_master);
+ runtime->trigger_tstamp = runtime->trigger_master->runtime->trigger_tstamp;
+ }
+ runtime->trigger_master = NULL;
+}
+
+struct action_ops {
+ int (*pre_action)(snd_pcm_substream_t *substream, int state);
+ int (*do_action)(snd_pcm_substream_t *substream, int state);
+ void (*undo_action)(snd_pcm_substream_t *substream, int state);
+ void (*post_action)(snd_pcm_substream_t *substream, int state);
+};
+
+/*
+ * this functions is core for handling of linked stream
+ * Note: the stream state might be changed also on failure
+ * Note2: call with calling stream lock + link lock
+ */
+static int snd_pcm_action_group(struct action_ops *ops,
+ snd_pcm_substream_t *substream,
+ int state, int do_lock)
+{
+ struct list_head *pos;
+ snd_pcm_substream_t *s = NULL;
+ snd_pcm_substream_t *s1;
+ int res = 0;
+
+ snd_pcm_group_for_each(pos, substream) {
+ s = snd_pcm_group_substream_entry(pos);
+ if (do_lock && s != substream)
+ spin_lock(&s->self_group.lock);
+ res = ops->pre_action(s, state);
+ if (res < 0)
+ goto _unlock;
+ }
+ snd_pcm_group_for_each(pos, substream) {
+ s = snd_pcm_group_substream_entry(pos);
+ res = ops->do_action(s, state);
+ if (res < 0) {
+ if (ops->undo_action) {
+ snd_pcm_group_for_each(pos, substream) {
+ s1 = snd_pcm_group_substream_entry(pos);
+ if (s1 == s) /* failed stream */
+ break;
+ ops->undo_action(s1, state);
+ }
+ }
+ s = NULL; /* unlock all */
+ goto _unlock;
+ }
+ }
+ snd_pcm_group_for_each(pos, substream) {
+ s = snd_pcm_group_substream_entry(pos);
+ ops->post_action(s, state);
+ }
+ _unlock:
+ if (do_lock) {
+ /* unlock streams */
+ snd_pcm_group_for_each(pos, substream) {
+ s1 = snd_pcm_group_substream_entry(pos);
+ if (s1 != substream)
+ spin_unlock(&s1->self_group.lock);
+ if (s1 == s) /* end */
+ break;
+ }
+ }
+ return res;
+}
+
+/*
+ * Note: call with stream lock
+ */
+static int snd_pcm_action_single(struct action_ops *ops,
+ snd_pcm_substream_t *substream,
+ int state)
+{
+ int res;
+
+ res = ops->pre_action(substream, state);
+ if (res < 0)
+ return res;
+ res = ops->do_action(substream, state);
+ if (res == 0)
+ ops->post_action(substream, state);
+ else if (ops->undo_action)
+ ops->undo_action(substream, state);
+ return res;
+}
+
+/*
+ * Note: call with stream lock
+ */
+static int snd_pcm_action(struct action_ops *ops,
+ snd_pcm_substream_t *substream,
+ int state)
+{
+ int res;
+
+ if (snd_pcm_stream_linked(substream)) {
+ if (!spin_trylock(&substream->group->lock)) {
+ spin_unlock(&substream->self_group.lock);
+ spin_lock(&substream->group->lock);
+ spin_lock(&substream->self_group.lock);
+ }
+ res = snd_pcm_action_group(ops, substream, state, 1);
+ spin_unlock(&substream->group->lock);
+ } else {
+ res = snd_pcm_action_single(ops, substream, state);
+ }
+ return res;
+}
+
+/*
+ * Note: don't use any locks before
+ */
+static int snd_pcm_action_lock_irq(struct action_ops *ops,
+ snd_pcm_substream_t *substream,
+ int state)
+{
+ int res;
+
+ read_lock_irq(&snd_pcm_link_rwlock);
+ if (snd_pcm_stream_linked(substream)) {
+ spin_lock(&substream->group->lock);
+ spin_lock(&substream->self_group.lock);
+ res = snd_pcm_action_group(ops, substream, state, 1);
+ spin_unlock(&substream->self_group.lock);
+ spin_unlock(&substream->group->lock);
+ } else {
+ spin_lock(&substream->self_group.lock);
+ res = snd_pcm_action_single(ops, substream, state);
+ spin_unlock(&substream->self_group.lock);
+ }
+ read_unlock_irq(&snd_pcm_link_rwlock);
+ return res;
+}
+
+/*
+ */
+static int snd_pcm_action_nonatomic(struct action_ops *ops,
+ snd_pcm_substream_t *substream,
+ int state)
+{
+ int res;
+
+ down_read(&snd_pcm_link_rwsem);
+ if (snd_pcm_stream_linked(substream))
+ res = snd_pcm_action_group(ops, substream, state, 0);
+ else
+ res = snd_pcm_action_single(ops, substream, state);
+ up_read(&snd_pcm_link_rwsem);
+ return res;
+}
+
+/*
+ * start callbacks
+ */
+static int snd_pcm_pre_start(snd_pcm_substream_t *substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (runtime->status->state != SNDRV_PCM_STATE_PREPARED)
+ return -EBADFD;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+ !snd_pcm_playback_data(substream))
+ return -EPIPE;
+ runtime->trigger_master = substream;
+ return 0;
+}
+
+static int snd_pcm_do_start(snd_pcm_substream_t *substream, int state)
+{
+ if (substream->runtime->trigger_master != substream)
+ return 0;
+ return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START);
+}
+
+static void snd_pcm_undo_start(snd_pcm_substream_t *substream, int state)
+{
+ if (substream->runtime->trigger_master == substream)
+ substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP);
+}
+
+static void snd_pcm_post_start(snd_pcm_substream_t *substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_trigger_tstamp(substream);
+ runtime->status->state = state;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+ runtime->silence_size > 0)
+ snd_pcm_playback_silence(substream, ULONG_MAX);
+ if (runtime->sleep_min)
+ snd_pcm_tick_prepare(substream);
+ if (substream->timer)
+ snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MSTART, &runtime->trigger_tstamp);
+}
+
+static struct action_ops snd_pcm_action_start = {
+ .pre_action = snd_pcm_pre_start,
+ .do_action = snd_pcm_do_start,
+ .undo_action = snd_pcm_undo_start,
+ .post_action = snd_pcm_post_start
+};
+
+/**
+ * snd_pcm_start
+ *
+ * Start all linked streams.
+ */
+int snd_pcm_start(snd_pcm_substream_t *substream)
+{
+ return snd_pcm_action(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
+}
+
+/*
+ * stop callbacks
+ */
+static int snd_pcm_pre_stop(snd_pcm_substream_t *substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+ return -EBADFD;
+ runtime->trigger_master = substream;
+ return 0;
+}
+
+static int snd_pcm_do_stop(snd_pcm_substream_t *substream, int state)
+{
+ if (substream->runtime->trigger_master == substream &&
+ snd_pcm_running(substream))
+ substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP);
+ return 0; /* unconditonally stop all substreams */
+}
+
+static void snd_pcm_post_stop(snd_pcm_substream_t *substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (runtime->status->state != state) {
+ snd_pcm_trigger_tstamp(substream);
+ if (substream->timer)
+ snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MSTOP, &runtime->trigger_tstamp);
+ runtime->status->state = state;
+ snd_pcm_tick_set(substream, 0);
+ }
+ wake_up(&runtime->sleep);
+}
+
+static struct action_ops snd_pcm_action_stop = {
+ .pre_action = snd_pcm_pre_stop,
+ .do_action = snd_pcm_do_stop,
+ .post_action = snd_pcm_post_stop
+};
+
+/**
+ * snd_pcm_stop
+ *
+ * Try to stop all running streams in the substream group.
+ * The state of each stream is changed to the given value after that unconditionally.
+ */
+int snd_pcm_stop(snd_pcm_substream_t *substream, int state)
+{
+ return snd_pcm_action(&snd_pcm_action_stop, substream, state);
+}
+
+/**
+ * snd_pcm_drain_done
+ *
+ * Stop the DMA only when the given stream is playback.
+ * The state is changed to SETUP.
+ * Unlike snd_pcm_stop(), this affects only the given stream.
+ */
+int snd_pcm_drain_done(snd_pcm_substream_t *substream)
+{
+ return snd_pcm_action_single(&snd_pcm_action_stop, substream, SNDRV_PCM_STATE_SETUP);
+}
+
+/*
+ * pause callbacks
+ */
+static int snd_pcm_pre_pause(snd_pcm_substream_t *substream, int push)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (!(runtime->info & SNDRV_PCM_INFO_PAUSE))
+ return -ENOSYS;
+ if (push) {
+ if (runtime->status->state != SNDRV_PCM_STATE_RUNNING)
+ return -EBADFD;
+ } else if (runtime->status->state != SNDRV_PCM_STATE_PAUSED)
+ return -EBADFD;
+ runtime->trigger_master = substream;
+ return 0;
+}
+
+static int snd_pcm_do_pause(snd_pcm_substream_t *substream, int push)
+{
+ if (substream->runtime->trigger_master != substream)
+ return 0;
+ return substream->ops->trigger(substream,
+ push ? SNDRV_PCM_TRIGGER_PAUSE_PUSH :
+ SNDRV_PCM_TRIGGER_PAUSE_RELEASE);
+}
+
+static void snd_pcm_undo_pause(snd_pcm_substream_t *substream, int push)
+{
+ if (substream->runtime->trigger_master == substream)
+ substream->ops->trigger(substream,
+ push ? SNDRV_PCM_TRIGGER_PAUSE_RELEASE :
+ SNDRV_PCM_TRIGGER_PAUSE_PUSH);
+}
+
+static void snd_pcm_post_pause(snd_pcm_substream_t *substream, int push)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_trigger_tstamp(substream);
+ if (push) {
+ runtime->status->state = SNDRV_PCM_STATE_PAUSED;
+ if (substream->timer)
+ snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MPAUSE, &runtime->trigger_tstamp);
+ snd_pcm_tick_set(substream, 0);
+ wake_up(&runtime->sleep);
+ } else {
+ runtime->status->state = SNDRV_PCM_STATE_RUNNING;
+ if (runtime->sleep_min)
+ snd_pcm_tick_prepare(substream);
+ if (substream->timer)
+ snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MCONTINUE, &runtime->trigger_tstamp);
+ }
+}
+
+static struct action_ops snd_pcm_action_pause = {
+ .pre_action = snd_pcm_pre_pause,
+ .do_action = snd_pcm_do_pause,
+ .undo_action = snd_pcm_undo_pause,
+ .post_action = snd_pcm_post_pause
+};
+
+/*
+ * Push/release the pause for all linked streams.
+ */
+static int snd_pcm_pause(snd_pcm_substream_t *substream, int push)
+{
+ return snd_pcm_action(&snd_pcm_action_pause, substream, push);
+}
+
+#ifdef CONFIG_PM
+/* suspend */
+
+static int snd_pcm_pre_suspend(snd_pcm_substream_t *substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED)
+ return -EBUSY;
+ runtime->trigger_master = substream;
+ return 0;
+}
+
+static int snd_pcm_do_suspend(snd_pcm_substream_t *substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (runtime->trigger_master != substream)
+ return 0;
+ if (! snd_pcm_running(substream))
+ return 0;
+ substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_SUSPEND);
+ return 0; /* suspend unconditionally */
+}
+
+static void snd_pcm_post_suspend(snd_pcm_substream_t *substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_trigger_tstamp(substream);
+ if (substream->timer)
+ snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MPAUSE, &runtime->trigger_tstamp);
+ runtime->status->suspended_state = runtime->status->state;
+ runtime->status->state = SNDRV_PCM_STATE_SUSPENDED;
+ snd_pcm_tick_set(substream, 0);
+ wake_up(&runtime->sleep);
+}
+
+static struct action_ops snd_pcm_action_suspend = {
+ .pre_action = snd_pcm_pre_suspend,
+ .do_action = snd_pcm_do_suspend,
+ .post_action = snd_pcm_post_suspend
+};
+
+/**
+ * snd_pcm_suspend
+ *
+ * Trigger SUSPEND to all linked streams.
+ * After this call, all streams are changed to SUSPENDED state.
+ */
+int snd_pcm_suspend(snd_pcm_substream_t *substream)
+{
+ int err;
+ unsigned long flags;
+
+ snd_pcm_stream_lock_irqsave(substream, flags);
+ err = snd_pcm_action(&snd_pcm_action_suspend, substream, 0);
+ snd_pcm_stream_unlock_irqrestore(substream, flags);
+ return err;
+}
+
+/**
+ * snd_pcm_suspend_all
+ *
+ * Trigger SUSPEND to all substreams in the given pcm.
+ * After this call, all streams are changed to SUSPENDED state.
+ */
+int snd_pcm_suspend_all(snd_pcm_t *pcm)
+{
+ snd_pcm_substream_t *substream;
+ int stream, err = 0;
+
+ for (stream = 0; stream < 2; stream++) {
+ for (substream = pcm->streams[stream].substream; substream; substream = substream->next) {
+ /* FIXME: the open/close code should lock this as well */
+ if (substream->runtime == NULL)
+ continue;
+ err = snd_pcm_suspend(substream);
+ if (err < 0 && err != -EBUSY)
+ return err;
+ }
+ }
+ return 0;
+}
+
+/* resume */
+
+static int snd_pcm_pre_resume(snd_pcm_substream_t *substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (!(runtime->info & SNDRV_PCM_INFO_RESUME))
+ return -ENOSYS;
+ runtime->trigger_master = substream;
+ return 0;
+}
+
+static int snd_pcm_do_resume(snd_pcm_substream_t *substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (runtime->trigger_master != substream)
+ return 0;
+ /* DMA not running previously? */
+ if (runtime->status->suspended_state != SNDRV_PCM_STATE_RUNNING &&
+ (runtime->status->suspended_state != SNDRV_PCM_STATE_DRAINING ||
+ substream->stream != SNDRV_PCM_STREAM_PLAYBACK))
+ return 0;
+ return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_RESUME);
+}
+
+static void snd_pcm_undo_resume(snd_pcm_substream_t *substream, int state)
+{
+ if (substream->runtime->trigger_master == substream &&
+ snd_pcm_running(substream))
+ substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_SUSPEND);
+}
+
+static void snd_pcm_post_resume(snd_pcm_substream_t *substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_trigger_tstamp(substream);
+ if (substream->timer)
+ snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MCONTINUE, &runtime->trigger_tstamp);