fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / sound / core / oss / pcm_oss.c
index 0691aca..e0ef240 100644 (file)
@@ -45,7 +45,7 @@
 
 #define OSS_ALSAEMULVER                _SIOR ('M', 249, int)
 
-static int dsp_map[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 0};
+static int dsp_map[SNDRV_CARDS];
 static int adsp_map[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 1};
 static int nonblock_open = 1;
 
@@ -78,6 +78,487 @@ static inline void snd_leave_user(mm_segment_t fs)
        set_fs(fs);
 }
 
+/*
+ * helper functions to process hw_params
+ */
+static int snd_interval_refine_min(struct snd_interval *i, unsigned int min, int openmin)
+{
+       int changed = 0;
+       if (i->min < min) {
+               i->min = min;
+               i->openmin = openmin;
+               changed = 1;
+       } else if (i->min == min && !i->openmin && openmin) {
+               i->openmin = 1;
+               changed = 1;
+       }
+       if (i->integer) {
+               if (i->openmin) {
+                       i->min++;
+                       i->openmin = 0;
+               }
+       }
+       if (snd_interval_checkempty(i)) {
+               snd_interval_none(i);
+               return -EINVAL;
+       }
+       return changed;
+}
+
+static int snd_interval_refine_max(struct snd_interval *i, unsigned int max, int openmax)
+{
+       int changed = 0;
+       if (i->max > max) {
+               i->max = max;
+               i->openmax = openmax;
+               changed = 1;
+       } else if (i->max == max && !i->openmax && openmax) {
+               i->openmax = 1;
+               changed = 1;
+       }
+       if (i->integer) {
+               if (i->openmax) {
+                       i->max--;
+                       i->openmax = 0;
+               }
+       }
+       if (snd_interval_checkempty(i)) {
+               snd_interval_none(i);
+               return -EINVAL;
+       }
+       return changed;
+}
+
+static int snd_interval_refine_set(struct snd_interval *i, unsigned int val)
+{
+       struct snd_interval t;
+       t.empty = 0;
+       t.min = t.max = val;
+       t.openmin = t.openmax = 0;
+       t.integer = 1;
+       return snd_interval_refine(i, &t);
+}
+
+/**
+ * snd_pcm_hw_param_value_min
+ * @params: the hw_params instance
+ * @var: parameter to retrieve
+ * @dir: pointer to the direction (-1,0,1) or NULL
+ *
+ * Return the minimum value for field PAR.
+ */
+static unsigned int
+snd_pcm_hw_param_value_min(const struct snd_pcm_hw_params *params,
+                          snd_pcm_hw_param_t var, int *dir)
+{
+       if (hw_is_mask(var)) {
+               if (dir)
+                       *dir = 0;
+               return snd_mask_min(hw_param_mask_c(params, var));
+       }
+       if (hw_is_interval(var)) {
+               const struct snd_interval *i = hw_param_interval_c(params, var);
+               if (dir)
+                       *dir = i->openmin;
+               return snd_interval_min(i);
+       }
+       return -EINVAL;
+}
+
+/**
+ * snd_pcm_hw_param_value_max
+ * @params: the hw_params instance
+ * @var: parameter to retrieve
+ * @dir: pointer to the direction (-1,0,1) or NULL
+ *
+ * Return the maximum value for field PAR.
+ */
+static unsigned int
+snd_pcm_hw_param_value_max(const struct snd_pcm_hw_params *params,
+                          snd_pcm_hw_param_t var, int *dir)
+{
+       if (hw_is_mask(var)) {
+               if (dir)
+                       *dir = 0;
+               return snd_mask_max(hw_param_mask_c(params, var));
+       }
+       if (hw_is_interval(var)) {
+               const struct snd_interval *i = hw_param_interval_c(params, var);
+               if (dir)
+                       *dir = - (int) i->openmax;
+               return snd_interval_max(i);
+       }
+       return -EINVAL;
+}
+
+static int _snd_pcm_hw_param_mask(struct snd_pcm_hw_params *params,
+                                 snd_pcm_hw_param_t var,
+                                 const struct snd_mask *val)
+{
+       int changed;
+       changed = snd_mask_refine(hw_param_mask(params, var), val);
+       if (changed) {
+               params->cmask |= 1 << var;
+               params->rmask |= 1 << var;
+       }
+       return changed;
+}
+
+static int snd_pcm_hw_param_mask(struct snd_pcm_substream *pcm,
+                                struct snd_pcm_hw_params *params,
+                                snd_pcm_hw_param_t var,
+                                const struct snd_mask *val)
+{
+       int changed = _snd_pcm_hw_param_mask(params, var, val);
+       if (changed < 0)
+               return changed;
+       if (params->rmask) {
+               int err = snd_pcm_hw_refine(pcm, params);
+               if (err < 0)
+                       return err;
+       }
+       return 0;
+}
+
+static int _snd_pcm_hw_param_min(struct snd_pcm_hw_params *params,
+                                snd_pcm_hw_param_t var, unsigned int val,
+                                int dir)
+{
+       int changed;
+       int open = 0;
+       if (dir) {
+               if (dir > 0) {
+                       open = 1;
+               } else if (dir < 0) {
+                       if (val > 0) {
+                               open = 1;
+                               val--;
+                       }
+               }
+       }
+       if (hw_is_mask(var))
+               changed = snd_mask_refine_min(hw_param_mask(params, var),
+                                             val + !!open);
+       else if (hw_is_interval(var))
+               changed = snd_interval_refine_min(hw_param_interval(params, var),
+                                                 val, open);
+       else
+               return -EINVAL;
+       if (changed) {
+               params->cmask |= 1 << var;
+               params->rmask |= 1 << var;
+       }
+       return changed;
+}
+
+/**
+ * snd_pcm_hw_param_min
+ * @pcm: PCM instance
+ * @params: the hw_params instance
+ * @var: parameter to retrieve
+ * @val: minimal value
+ * @dir: pointer to the direction (-1,0,1) or NULL
+ *
+ * Inside configuration space defined by PARAMS remove from PAR all 
+ * values < VAL. Reduce configuration space accordingly.
+ * Return new minimum or -EINVAL if the configuration space is empty
+ */
+static int snd_pcm_hw_param_min(struct snd_pcm_substream *pcm,
+                               struct snd_pcm_hw_params *params,
+                               snd_pcm_hw_param_t var, unsigned int val,
+                               int *dir)
+{
+       int changed = _snd_pcm_hw_param_min(params, var, val, dir ? *dir : 0);
+       if (changed < 0)
+               return changed;
+       if (params->rmask) {
+               int err = snd_pcm_hw_refine(pcm, params);
+               if (err < 0)
+                       return err;
+       }
+       return snd_pcm_hw_param_value_min(params, var, dir);
+}
+
+static int _snd_pcm_hw_param_max(struct snd_pcm_hw_params *params,
+                                snd_pcm_hw_param_t var, unsigned int val,
+                                int dir)
+{
+       int changed;
+       int open = 0;
+       if (dir) {
+               if (dir < 0) {
+                       open = 1;
+               } else if (dir > 0) {
+                       open = 1;
+                       val++;
+               }
+       }
+       if (hw_is_mask(var)) {
+               if (val == 0 && open) {
+                       snd_mask_none(hw_param_mask(params, var));
+                       changed = -EINVAL;
+               } else
+                       changed = snd_mask_refine_max(hw_param_mask(params, var),
+                                                     val - !!open);
+       } else if (hw_is_interval(var))
+               changed = snd_interval_refine_max(hw_param_interval(params, var),
+                                                 val, open);
+       else
+               return -EINVAL;
+       if (changed) {
+               params->cmask |= 1 << var;
+               params->rmask |= 1 << var;
+       }
+       return changed;
+}
+
+/**
+ * snd_pcm_hw_param_max
+ * @pcm: PCM instance
+ * @params: the hw_params instance
+ * @var: parameter to retrieve
+ * @val: maximal value
+ * @dir: pointer to the direction (-1,0,1) or NULL
+ *
+ * Inside configuration space defined by PARAMS remove from PAR all 
+ *  values >= VAL + 1. Reduce configuration space accordingly.
+ *  Return new maximum or -EINVAL if the configuration space is empty
+ */
+static int snd_pcm_hw_param_max(struct snd_pcm_substream *pcm,
+                               struct snd_pcm_hw_params *params,
+                               snd_pcm_hw_param_t var, unsigned int val,
+                               int *dir)
+{
+       int changed = _snd_pcm_hw_param_max(params, var, val, dir ? *dir : 0);
+       if (changed < 0)
+               return changed;
+       if (params->rmask) {
+               int err = snd_pcm_hw_refine(pcm, params);
+               if (err < 0)
+                       return err;
+       }
+       return snd_pcm_hw_param_value_max(params, var, dir);
+}
+
+static int boundary_sub(int a, int adir,
+                       int b, int bdir,
+                       int *c, int *cdir)
+{
+       adir = adir < 0 ? -1 : (adir > 0 ? 1 : 0);
+       bdir = bdir < 0 ? -1 : (bdir > 0 ? 1 : 0);
+       *c = a - b;
+       *cdir = adir - bdir;
+       if (*cdir == -2) {
+               (*c)--;
+       } else if (*cdir == 2) {
+               (*c)++;
+       }
+       return 0;
+}
+
+static int boundary_lt(unsigned int a, int adir,
+                      unsigned int b, int bdir)
+{
+       if (adir < 0) {
+               a--;
+               adir = 1;
+       } else if (adir > 0)
+               adir = 1;
+       if (bdir < 0) {
+               b--;
+               bdir = 1;
+       } else if (bdir > 0)
+               bdir = 1;
+       return a < b || (a == b && adir < bdir);
+}
+
+/* Return 1 if min is nearer to best than max */
+static int boundary_nearer(int min, int mindir,
+                          int best, int bestdir,
+                          int max, int maxdir)
+{
+       int dmin, dmindir;
+       int dmax, dmaxdir;
+       boundary_sub(best, bestdir, min, mindir, &dmin, &dmindir);
+       boundary_sub(max, maxdir, best, bestdir, &dmax, &dmaxdir);
+       return boundary_lt(dmin, dmindir, dmax, dmaxdir);
+}
+
+/**
+ * snd_pcm_hw_param_near
+ * @pcm: PCM instance
+ * @params: the hw_params instance
+ * @var: parameter to retrieve
+ * @best: value to set
+ * @dir: pointer to the direction (-1,0,1) or NULL
+ *
+ * Inside configuration space defined by PARAMS set PAR to the available value
+ * nearest to VAL. Reduce configuration space accordingly.
+ * This function cannot be called for SNDRV_PCM_HW_PARAM_ACCESS,
+ * SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_HW_PARAM_SUBFORMAT.
+ * Return the value found.
+  */
+static int snd_pcm_hw_param_near(struct snd_pcm_substream *pcm,
+                                struct snd_pcm_hw_params *params,
+                                snd_pcm_hw_param_t var, unsigned int best,
+                                int *dir)
+{
+       struct snd_pcm_hw_params *save = NULL;
+       int v;
+       unsigned int saved_min;
+       int last = 0;
+       int min, max;
+       int mindir, maxdir;
+       int valdir = dir ? *dir : 0;
+       /* FIXME */
+       if (best > INT_MAX)
+               best = INT_MAX;
+       min = max = best;
+       mindir = maxdir = valdir;
+       if (maxdir > 0)
+               maxdir = 0;
+       else if (maxdir == 0)
+               maxdir = -1;
+       else {
+               maxdir = 1;
+               max--;
+       }
+       save = kmalloc(sizeof(*save), GFP_KERNEL);
+       if (save == NULL)
+               return -ENOMEM;
+       *save = *params;
+       saved_min = min;
+       min = snd_pcm_hw_param_min(pcm, params, var, min, &mindir);
+       if (min >= 0) {
+               struct snd_pcm_hw_params *params1;
+               if (max < 0)
+                       goto _end;
+               if ((unsigned int)min == saved_min && mindir == valdir)
+                       goto _end;
+               params1 = kmalloc(sizeof(*params1), GFP_KERNEL);
+               if (params1 == NULL) {
+                       kfree(save);
+                       return -ENOMEM;
+               }
+               *params1 = *save;
+               max = snd_pcm_hw_param_max(pcm, params1, var, max, &maxdir);
+               if (max < 0) {
+                       kfree(params1);
+                       goto _end;
+               }
+               if (boundary_nearer(max, maxdir, best, valdir, min, mindir)) {
+                       *params = *params1;
+                       last = 1;
+               }
+               kfree(params1);
+       } else {
+               *params = *save;
+               max = snd_pcm_hw_param_max(pcm, params, var, max, &maxdir);
+               snd_assert(max >= 0, return -EINVAL);
+               last = 1;
+       }
+ _end:
+       kfree(save);
+       if (last)
+               v = snd_pcm_hw_param_last(pcm, params, var, dir);
+       else
+               v = snd_pcm_hw_param_first(pcm, params, var, dir);
+       snd_assert(v >= 0, return -EINVAL);
+       return v;
+}
+
+static int _snd_pcm_hw_param_set(struct snd_pcm_hw_params *params,
+                                snd_pcm_hw_param_t var, unsigned int val,
+                                int dir)
+{
+       int changed;
+       if (hw_is_mask(var)) {
+               struct snd_mask *m = hw_param_mask(params, var);
+               if (val == 0 && dir < 0) {
+                       changed = -EINVAL;
+                       snd_mask_none(m);
+               } else {
+                       if (dir > 0)
+                               val++;
+                       else if (dir < 0)
+                               val--;
+                       changed = snd_mask_refine_set(hw_param_mask(params, var), val);
+               }
+       } else if (hw_is_interval(var)) {
+               struct snd_interval *i = hw_param_interval(params, var);
+               if (val == 0 && dir < 0) {
+                       changed = -EINVAL;
+                       snd_interval_none(i);
+               } else if (dir == 0)
+                       changed = snd_interval_refine_set(i, val);
+               else {
+                       struct snd_interval t;
+                       t.openmin = 1;
+                       t.openmax = 1;
+                       t.empty = 0;
+                       t.integer = 0;
+                       if (dir < 0) {
+                               t.min = val - 1;
+                               t.max = val;
+                       } else {
+                               t.min = val;
+                               t.max = val+1;
+                       }
+                       changed = snd_interval_refine(i, &t);
+               }
+       } else
+               return -EINVAL;
+       if (changed) {
+               params->cmask |= 1 << var;
+               params->rmask |= 1 << var;
+       }
+       return changed;
+}
+
+/**
+ * snd_pcm_hw_param_set
+ * @pcm: PCM instance
+ * @params: the hw_params instance
+ * @var: parameter to retrieve
+ * @val: value to set
+ * @dir: pointer to the direction (-1,0,1) or NULL
+ *
+ * Inside configuration space defined by PARAMS remove from PAR all 
+ * values != VAL. Reduce configuration space accordingly.
+ *  Return VAL or -EINVAL if the configuration space is empty
+ */
+static int snd_pcm_hw_param_set(struct snd_pcm_substream *pcm,
+                               struct snd_pcm_hw_params *params,
+                               snd_pcm_hw_param_t var, unsigned int val,
+                               int dir)
+{
+       int changed = _snd_pcm_hw_param_set(params, var, val, dir);
+       if (changed < 0)
+               return changed;
+       if (params->rmask) {
+               int err = snd_pcm_hw_refine(pcm, params);
+               if (err < 0)
+                       return err;
+       }
+       return snd_pcm_hw_param_value(params, var, NULL);
+}
+
+static int _snd_pcm_hw_param_setinteger(struct snd_pcm_hw_params *params,
+                                       snd_pcm_hw_param_t var)
+{
+       int changed;
+       changed = snd_interval_setinteger(hw_param_interval(params, var));
+       if (changed) {
+               params->cmask |= 1 << var;
+               params->rmask |= 1 << var;
+       }
+       return changed;
+}
+       
+/*
+ * plugin
+ */
+
 #ifdef CONFIG_SND_PCM_OSS_PLUGINS
 static int snd_pcm_oss_plugin_clear(struct snd_pcm_substream *substream)
 {
@@ -203,7 +684,7 @@ static int snd_pcm_oss_period_size(struct snd_pcm_substream *substream,
        oss_buffer_size = snd_pcm_plug_client_size(substream,
                                                   snd_pcm_hw_param_value_max(slave_params, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, NULL)) * oss_frame_size;
        oss_buffer_size = 1 << ld2(oss_buffer_size);
-       if (atomic_read(&runtime->mmap_count)) {
+       if (atomic_read(&substream->mmap_count)) {
                if (oss_buffer_size > runtime->oss.mmap_bytes)
                        oss_buffer_size = runtime->oss.mmap_bytes;
        }
@@ -329,6 +810,8 @@ static int snd_pcm_oss_change_params(struct snd_pcm_substream *substream)
        struct snd_mask sformat_mask;
        struct snd_mask mask;
 
+       if (mutex_lock_interruptible(&runtime->oss.params_lock))
+               return -EINTR;
        sw_params = kmalloc(sizeof(*sw_params), GFP_KERNEL);
        params = kmalloc(sizeof(*params), GFP_KERNEL);
        sparams = kmalloc(sizeof(*sparams), GFP_KERNEL);
@@ -338,7 +821,7 @@ static int snd_pcm_oss_change_params(struct snd_pcm_substream *substream)
                goto failure;
        }
 
-       if (atomic_read(&runtime->mmap_count))
+       if (atomic_read(&substream->mmap_count))
                direct = 1;
        else
                direct = substream->oss.setup.direct;
@@ -347,7 +830,7 @@ static int snd_pcm_oss_change_params(struct snd_pcm_substream *substream)
        _snd_pcm_hw_param_setinteger(sparams, SNDRV_PCM_HW_PARAM_PERIODS);
        _snd_pcm_hw_param_min(sparams, SNDRV_PCM_HW_PARAM_PERIODS, 2, 0);
        snd_mask_none(&mask);
-       if (atomic_read(&runtime->mmap_count))
+       if (atomic_read(&substream->mmap_count))
                snd_mask_set(&mask, SNDRV_PCM_ACCESS_MMAP_INTERLEAVED);
        else {
                snd_mask_set(&mask, SNDRV_PCM_ACCESS_RW_INTERLEAVED);
@@ -466,7 +949,8 @@ static int snd_pcm_oss_change_params(struct snd_pcm_substream *substream)
        } else {
                sw_params->start_threshold = runtime->boundary;
        }
-       if (atomic_read(&runtime->mmap_count) || substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+       if (atomic_read(&substream->mmap_count) ||
+           substream->stream == SNDRV_PCM_STREAM_CAPTURE)
                sw_params->stop_threshold = runtime->boundary;
        else
                sw_params->stop_threshold = runtime->buffer_size;
@@ -476,7 +960,7 @@ static int snd_pcm_oss_change_params(struct snd_pcm_substream *substream)
        sw_params->avail_min = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
                1 : runtime->period_size;
        sw_params->xfer_align = 1;
-       if (atomic_read(&runtime->mmap_count) ||
+       if (atomic_read(&substream->mmap_count) ||
            substream->oss.setup.nosilence) {
                sw_params->silence_threshold = 0;
                sw_params->silence_size = 0;
@@ -538,6 +1022,7 @@ failure:
        kfree(sw_params);
        kfree(params);
        kfree(sparams);
+       mutex_unlock(&runtime->oss.params_lock);
        return err;
 }
 
@@ -820,19 +1305,22 @@ static ssize_t snd_pcm_oss_write1(struct snd_pcm_substream *substream, const cha
        ssize_t tmp;
        struct snd_pcm_runtime *runtime = substream->runtime;
 
-       if (atomic_read(&runtime->mmap_count))
+       if (atomic_read(&substream->mmap_count))
                return -ENXIO;
 
        if ((tmp = snd_pcm_oss_make_ready(substream)) < 0)
                return tmp;
+       mutex_lock(&runtime->oss.params_lock);
        while (bytes > 0) {
                if (bytes < runtime->oss.period_bytes || runtime->oss.buffer_used > 0) {
                        tmp = bytes;
                        if (tmp + runtime->oss.buffer_used > runtime->oss.period_bytes)
                                tmp = runtime->oss.period_bytes - runtime->oss.buffer_used;
                        if (tmp > 0) {
-                               if (copy_from_user(runtime->oss.buffer + runtime->oss.buffer_used, buf, tmp))
-                                       return xfer > 0 ? (snd_pcm_sframes_t)xfer : -EFAULT;
+                               if (copy_from_user(runtime->oss.buffer + runtime->oss.buffer_used, buf, tmp)) {
+                                       tmp = -EFAULT;
+                                       goto err;
+                               }
                        }
                        runtime->oss.buffer_used += tmp;
                        buf += tmp;
@@ -843,32 +1331,39 @@ static ssize_t snd_pcm_oss_write1(struct snd_pcm_substream *substream, const cha
                                tmp = snd_pcm_oss_write2(substream, runtime->oss.buffer + runtime->oss.period_ptr, 
                                                         runtime->oss.buffer_used - runtime->oss.period_ptr, 1);
                                if (tmp <= 0)
-                                       return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp;
+                                       goto err;
                                runtime->oss.bytes += tmp;
                                runtime->oss.period_ptr += tmp;
                                runtime->oss.period_ptr %= runtime->oss.period_bytes;
                                if (runtime->oss.period_ptr == 0 ||
                                    runtime->oss.period_ptr == runtime->oss.buffer_used)
                                        runtime->oss.buffer_used = 0;
-                               else if ((substream->ffile->f_flags & O_NONBLOCK) != 0)
-                                       return xfer > 0 ? xfer : -EAGAIN;
+                               else if ((substream->f_flags & O_NONBLOCK) != 0) {
+                                       tmp = -EAGAIN;
+                                       goto err;
+                               }
                        }
                } else {
                        tmp = snd_pcm_oss_write2(substream,
                                                 (const char __force *)buf,
                                                 runtime->oss.period_bytes, 0);
                        if (tmp <= 0)
-                               return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp;
+                               goto err;
                        runtime->oss.bytes += tmp;
                        buf += tmp;
                        bytes -= tmp;
                        xfer += tmp;
-                       if ((substream->ffile->f_flags & O_NONBLOCK) != 0 &&
+                       if ((substream->f_flags & O_NONBLOCK) != 0 &&
                            tmp != runtime->oss.period_bytes)
                                break;
                }
        }
+       mutex_unlock(&runtime->oss.params_lock);
        return xfer;
+
+ err:
+       mutex_unlock(&runtime->oss.params_lock);
+       return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp;
 }
 
 static ssize_t snd_pcm_oss_read2(struct snd_pcm_substream *substream, char *buf, size_t bytes, int in_kernel)
@@ -910,17 +1405,18 @@ static ssize_t snd_pcm_oss_read1(struct snd_pcm_substream *substream, char __use
        ssize_t tmp;
        struct snd_pcm_runtime *runtime = substream->runtime;
 
-       if (atomic_read(&runtime->mmap_count))
+       if (atomic_read(&substream->mmap_count))
                return -ENXIO;
 
        if ((tmp = snd_pcm_oss_make_ready(substream)) < 0)
                return tmp;
+       mutex_lock(&runtime->oss.params_lock);
        while (bytes > 0) {
                if (bytes < runtime->oss.period_bytes || runtime->oss.buffer_used > 0) {
                        if (runtime->oss.buffer_used == 0) {
                                tmp = snd_pcm_oss_read2(substream, runtime->oss.buffer, runtime->oss.period_bytes, 1);
                                if (tmp <= 0)
-                                       return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp;
+                                       goto err;
                                runtime->oss.bytes += tmp;
                                runtime->oss.period_ptr = tmp;
                                runtime->oss.buffer_used = tmp;
@@ -928,8 +1424,10 @@ static ssize_t snd_pcm_oss_read1(struct snd_pcm_substream *substream, char __use
                        tmp = bytes;
                        if ((size_t) tmp > runtime->oss.buffer_used)
                                tmp = runtime->oss.buffer_used;
-                       if (copy_to_user(buf, runtime->oss.buffer + (runtime->oss.period_ptr - runtime->oss.buffer_used), tmp))
-                               return xfer > 0 ? (snd_pcm_sframes_t)xfer : -EFAULT;
+                       if (copy_to_user(buf, runtime->oss.buffer + (runtime->oss.period_ptr - runtime->oss.buffer_used), tmp)) {
+                               tmp = -EFAULT;
+                               goto err;
+                       }
                        buf += tmp;
                        bytes -= tmp;
                        xfer += tmp;
@@ -938,14 +1436,19 @@ static ssize_t snd_pcm_oss_read1(struct snd_pcm_substream *substream, char __use
                        tmp = snd_pcm_oss_read2(substream, (char __force *)buf,
                                                runtime->oss.period_bytes, 0);
                        if (tmp <= 0)
-                               return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp;
+                               goto err;
                        runtime->oss.bytes += tmp;
                        buf += tmp;
                        bytes -= tmp;
                        xfer += tmp;
                }
        }
+       mutex_unlock(&runtime->oss.params_lock);
        return xfer;
+
+ err:
+       mutex_unlock(&runtime->oss.params_lock);
+       return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp;
 }
 
 static int snd_pcm_oss_reset(struct snd_pcm_oss_file *pcm_oss_file)
@@ -987,6 +1490,13 @@ static int snd_pcm_oss_sync1(struct snd_pcm_substream *substream, size_t size)
        ssize_t result = 0;
        long res;
        wait_queue_t wait;
+       static char printed_comm[16];
+
+       if (strncmp(printed_comm, current->comm, 16)) {
+               printk(KERN_DEBUG "application %s uses obsolete OSS audio interface\n",
+                      current->comm);
+               memcpy(printed_comm, current->comm, 16);
+       }
 
        runtime = substream->runtime;
        init_waitqueue_entry(&wait, current);
@@ -1040,12 +1550,13 @@ static int snd_pcm_oss_sync(struct snd_pcm_oss_file *pcm_oss_file)
        substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
        if (substream != NULL) {
                runtime = substream->runtime;
-               if (atomic_read(&runtime->mmap_count))
+               if (atomic_read(&substream->mmap_count))
                        goto __direct;
                if ((err = snd_pcm_oss_make_ready(substream)) < 0)
                        return err;
                format = snd_pcm_oss_format_from(runtime->oss.format);
                width = snd_pcm_format_physical_width(format);
+               mutex_lock(&runtime->oss.params_lock);
                if (runtime->oss.buffer_used > 0) {
 #ifdef OSS_DEBUG
                        printk("sync: buffer_used\n");
@@ -1055,8 +1566,10 @@ static int snd_pcm_oss_sync(struct snd_pcm_oss_file *pcm_oss_file)
                                                   runtime->oss.buffer + runtime->oss.buffer_used,
                                                   size);
                        err = snd_pcm_oss_sync1(substream, runtime->oss.period_bytes);
-                       if (err < 0)
+                       if (err < 0) {
+                               mutex_unlock(&runtime->oss.params_lock);
                                return err;
+                       }
                } else if (runtime->oss.period_ptr > 0) {
 #ifdef OSS_DEBUG
                        printk("sync: period_ptr\n");
@@ -1066,8 +1579,10 @@ static int snd_pcm_oss_sync(struct snd_pcm_oss_file *pcm_oss_file)
                                                   runtime->oss.buffer,
                                                   size * 8 / width);
                        err = snd_pcm_oss_sync1(substream, size);
-                       if (err < 0)
+                       if (err < 0) {
+                               mutex_unlock(&runtime->oss.params_lock);
                                return err;
+                       }
                }
                /*
                 * The ALSA's period might be a bit large than OSS one.
@@ -1097,14 +1612,15 @@ static int snd_pcm_oss_sync(struct snd_pcm_oss_file *pcm_oss_file)
                                snd_pcm_lib_writev(substream, buffers, size);
                        }
                }
+               mutex_unlock(&runtime->oss.params_lock);
                /*
                 * finish sync: drain the buffer
                 */
              __direct:
-               saved_f_flags = substream->ffile->f_flags;
-               substream->ffile->f_flags &= ~O_NONBLOCK;
+               saved_f_flags = substream->f_flags;
+               substream->f_flags &= ~O_NONBLOCK;
                err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL);
-               substream->ffile->f_flags = saved_f_flags;
+               substream->f_flags = saved_f_flags;
                if (err < 0)
                        return err;
                runtime->oss.prepare = 1;
@@ -1209,7 +1725,7 @@ static int snd_pcm_oss_get_formats(struct snd_pcm_oss_file *pcm_oss_file)
 
        if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0)
                return err;
-       if (atomic_read(&substream->runtime->mmap_count))
+       if (atomic_read(&substream->mmap_count))
                direct = 1;
        else
                direct = substream->oss.setup.direct;
@@ -1419,7 +1935,7 @@ static int snd_pcm_oss_set_trigger(struct snd_pcm_oss_file *pcm_oss_file, int tr
                if (trigger & PCM_ENABLE_OUTPUT) {
                        if (runtime->oss.trigger)
                                goto _skip1;
-                       if (atomic_read(&psubstream->runtime->mmap_count))
+                       if (atomic_read(&psubstream->mmap_count))
                                snd_pcm_oss_simulate_fill(psubstream, runtime->hw_ptr_interrupt);
                        runtime->oss.trigger = 1;
                        runtime->start_threshold = 1;
@@ -1537,7 +2053,7 @@ static int snd_pcm_oss_get_ptr(struct snd_pcm_oss_file *pcm_oss_file, int stream
        if (err < 0)
                return err;
        info.ptr = snd_pcm_oss_bytes(substream, runtime->status->hw_ptr % runtime->buffer_size);
-       if (atomic_read(&runtime->mmap_count)) {
+       if (atomic_read(&substream->mmap_count)) {
                snd_pcm_sframes_t n;
                n = (delay = runtime->hw_ptr_interrupt) - runtime->oss.prev_hw_ptr_interrupt;
                if (n < 0)
@@ -1683,13 +2199,14 @@ static void snd_pcm_oss_init_substream(struct snd_pcm_substream *substream,
        substream->oss.oss = 1;
        substream->oss.setup = *setup;
        if (setup->nonblock)
-               substream->ffile->f_flags |= O_NONBLOCK;
+               substream->f_flags |= O_NONBLOCK;
        else if (setup->block)
-               substream->ffile->f_flags &= ~O_NONBLOCK;
+               substream->f_flags &= ~O_NONBLOCK;
        runtime = substream->runtime;
        runtime->oss.params = 1;
        runtime->oss.trigger = 1;
        runtime->oss.rate = 8000;
+       mutex_init(&runtime->oss.params_lock);
        switch (SNDRV_MINOR_OSS_DEVICE(minor)) {
        case SNDRV_MINOR_OSS_PCM_8:
                runtime->oss.format = AFMT_U8;
@@ -1742,6 +2259,7 @@ static int snd_pcm_oss_open_file(struct file *file,
            (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX))
                f_mode = FMODE_WRITE;
 
+       file->f_flags &= ~O_APPEND;
        for (idx = 0; idx < 2; idx++) {
                if (setup[idx].disable)
                        continue;
@@ -1796,13 +2314,6 @@ static int snd_pcm_oss_open(struct inode *inode, struct file *file)
        struct snd_pcm_oss_setup setup[2];
        int nonblock;
        wait_queue_t wait;
-       static char printed_comm[16];
-
-       if (strncmp(printed_comm, current->comm, 16)) {
-               printk(KERN_DEBUG "application %s uses obsolete OSS audio interface\n",
-                      current->comm);
-               memcpy(printed_comm, current->comm, 16);
-       }
 
        pcm = snd_lookup_oss_minor_data(iminor(inode),
                                        SNDRV_OSS_DEVICE_TYPE_PCM);
@@ -1883,7 +2394,8 @@ static int snd_pcm_oss_release(struct inode *inode, struct file *file)
                substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE];
        snd_assert(substream != NULL, return -ENXIO);
        pcm = substream->pcm;
-       snd_pcm_oss_sync(pcm_oss_file);
+       if (!pcm->card->shutdown)
+               snd_pcm_oss_sync(pcm_oss_file);
        mutex_lock(&pcm->open_mutex);
        snd_pcm_oss_release_file(pcm_oss_file);
        mutex_unlock(&pcm->open_mutex);
@@ -2068,6 +2580,7 @@ static ssize_t snd_pcm_oss_read(struct file *file, char __user *buf, size_t coun
        substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE];
        if (substream == NULL)
                return -ENXIO;
+       substream->f_flags = file->f_flags & O_NONBLOCK;
 #ifndef OSS_DEBUG
        return snd_pcm_oss_read1(substream, buf, count);
 #else
@@ -2089,6 +2602,7 @@ static ssize_t snd_pcm_oss_write(struct file *file, const char __user *buf, size
        substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
        if (substream == NULL)
                return -ENXIO;
+       substream->f_flags = file->f_flags & O_NONBLOCK;
        result = snd_pcm_oss_write1(substream, buf, count);
 #ifdef OSS_DEBUG
        printk("pcm_oss: write %li bytes (wrote %li bytes)\n", (long)count, (long)result);
@@ -2099,7 +2613,7 @@ static ssize_t snd_pcm_oss_write(struct file *file, const char __user *buf, size
 static int snd_pcm_oss_playback_ready(struct snd_pcm_substream *substream)
 {
        struct snd_pcm_runtime *runtime = substream->runtime;
-       if (atomic_read(&runtime->mmap_count))
+       if (atomic_read(&substream->mmap_count))
                return runtime->oss.prev_hw_ptr_interrupt != runtime->hw_ptr_interrupt;
        else
                return snd_pcm_playback_avail(runtime) >= runtime->oss.period_frames;
@@ -2108,7 +2622,7 @@ static int snd_pcm_oss_playback_ready(struct snd_pcm_substream *substream)
 static int snd_pcm_oss_capture_ready(struct snd_pcm_substream *substream)
 {
        struct snd_pcm_runtime *runtime = substream->runtime;
-       if (atomic_read(&runtime->mmap_count))
+       if (atomic_read(&substream->mmap_count))
                return runtime->oss.prev_hw_ptr_interrupt != runtime->hw_ptr_interrupt;
        else
                return snd_pcm_capture_avail(runtime) >= runtime->oss.period_frames;
@@ -2351,9 +2865,7 @@ static void snd_pcm_oss_proc_init(struct snd_pcm *pcm)
                if ((entry = snd_info_create_card_entry(pcm->card, "oss", pstr->proc_root)) != NULL) {
                        entry->content = SNDRV_INFO_CONTENT_TEXT;
                        entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
-                       entry->c.text.read_size = 8192;
                        entry->c.text.read = snd_pcm_oss_proc_read;
-                       entry->c.text.write_size = 8192;
                        entry->c.text.write = snd_pcm_oss_proc_write;
                        entry->private_data = pstr;
                        if (snd_info_register(entry) < 0) {
@@ -2370,11 +2882,9 @@ static void snd_pcm_oss_proc_done(struct snd_pcm *pcm)
        int stream;
        for (stream = 0; stream < 2; ++stream) {
                struct snd_pcm_str *pstr = &pcm->streams[stream];
-               if (pstr->oss.proc_entry) {
-                       snd_info_unregister(pstr->oss.proc_entry);
-                       pstr->oss.proc_entry = NULL;
-                       snd_pcm_oss_proc_free_setup_list(pstr);
-               }
+               snd_info_free_entry(pstr->oss.proc_entry);
+               pstr->oss.proc_entry = NULL;
+               snd_pcm_oss_proc_free_setup_list(pstr);
        }
 }
 #else /* !CONFIG_SND_VERBOSE_PROCFS */
@@ -2455,25 +2965,23 @@ static int snd_pcm_oss_disconnect_minor(struct snd_pcm *pcm)
                        snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_PCM,
                                                  pcm->card, 1);
                }
-       }
-       return 0;
-}
-
-static int snd_pcm_oss_unregister_minor(struct snd_pcm *pcm)
-{
-       snd_pcm_oss_disconnect_minor(pcm);
-       if (pcm->oss.reg) {
                if (dsp_map[pcm->card->number] == (int)pcm->device) {
 #ifdef SNDRV_OSS_INFO_DEV_AUDIO
                        snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_AUDIO, pcm->card->number);
 #endif
                }
                pcm->oss.reg = 0;
-               snd_pcm_oss_proc_done(pcm);
        }
        return 0;
 }
 
+static int snd_pcm_oss_unregister_minor(struct snd_pcm *pcm)
+{
+       snd_pcm_oss_disconnect_minor(pcm);
+       snd_pcm_oss_proc_done(pcm);
+       return 0;
+}
+
 static struct snd_pcm_notify snd_pcm_oss_notify =
 {
        .n_register =   snd_pcm_oss_register_minor,