Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Linux/sound/core/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 116 kB image not shown  

Quelle  pcm_native.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *  Digital Audio (PCM) abstract layer
 *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
 */


#include <linux/compat.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/file.h>
#include <linux/slab.h>
#include <linux/sched/signal.h>
#include <linux/time.h>
#include <linux/pm_qos.h>
#include <linux/io.h>
#include <linux/dma-mapping.h>
#include <linux/vmalloc.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 <linux/uio.h>
#include <linux/delay.h>
#include <linux/bitops.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
 */


struct snd_pcm_hw_params_old {
 unsigned int flags;
 unsigned int masks[SNDRV_PCM_HW_PARAM_SUBFORMAT -
      SNDRV_PCM_HW_PARAM_ACCESS + 1];
 struct snd_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;
 snd_pcm_uframes_t fifo_size;
 unsigned char reserved[64];
};

#ifdef CONFIG_SND_SUPPORT_OLD_API
#define SNDRV_PCM_IOCTL_HW_REFINE_OLD _IOWR('A', 0x10, struct snd_pcm_hw_params_old)
#define SNDRV_PCM_IOCTL_HW_PARAMS_OLD _IOWR('A', 0x11, struct snd_pcm_hw_params_old)

static int snd_pcm_hw_refine_old_user(struct snd_pcm_substream *substream,
          struct snd_pcm_hw_params_old __user * _oparams);
static int snd_pcm_hw_params_old_user(struct snd_pcm_substream *substream,
          struct snd_pcm_hw_params_old __user * _oparams);
#endif
static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream);

/*
 *
 */


static DECLARE_RWSEM(snd_pcm_link_rwsem);

void snd_pcm_group_init(struct snd_pcm_group *group)
{
 spin_lock_init(&group->lock);
 mutex_init(&group->mutex);
 INIT_LIST_HEAD(&group->substreams);
 refcount_set(&group->refs, 1);
}

/* define group lock helpers */
#define DEFINE_PCM_GROUP_LOCK(action, bh_lock, bh_unlock, mutex_action) \
static void snd_pcm_group_ ## action(struct snd_pcm_group *group, bool nonatomic) \
{ \
 if (nonatomic) { \
  mutex_ ## mutex_action(&group->mutex); \
 } else { \
  if (IS_ENABLED(CONFIG_PREEMPT_RT) && bh_lock)   \
   local_bh_disable();   \
  spin_ ## action(&group->lock);   \
  if (IS_ENABLED(CONFIG_PREEMPT_RT) && bh_unlock) \
   local_bh_enable();                      \
 }       \
}

DEFINE_PCM_GROUP_LOCK(lock, falsefalse, lock);
DEFINE_PCM_GROUP_LOCK(unlock, falsefalse, unlock);
DEFINE_PCM_GROUP_LOCK(lock_irq, truefalse, lock);
DEFINE_PCM_GROUP_LOCK(unlock_irq, falsetrue, unlock);

/**
 * snd_pcm_stream_lock - Lock the PCM stream
 * @substream: PCM substream
 *
 * This locks the PCM stream's spinlock or mutex depending on the nonatomic
 * flag of the given substream.  This also takes the global link rw lock
 * (or rw sem), too, for avoiding the race with linked streams.
 */

void snd_pcm_stream_lock(struct snd_pcm_substream *substream)
{
 snd_pcm_group_lock(&substream->self_group, substream->pcm->nonatomic);
}
EXPORT_SYMBOL_GPL(snd_pcm_stream_lock);

/**
 * snd_pcm_stream_unlock - Unlock the PCM stream
 * @substream: PCM substream
 *
 * This unlocks the PCM stream that has been locked via snd_pcm_stream_lock().
 */

void snd_pcm_stream_unlock(struct snd_pcm_substream *substream)
{
 snd_pcm_group_unlock(&substream->self_group, substream->pcm->nonatomic);
}
EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock);

/**
 * snd_pcm_stream_lock_irq - Lock the PCM stream
 * @substream: PCM substream
 *
 * This locks the PCM stream like snd_pcm_stream_lock() and disables the local
 * IRQ (only when nonatomic is false).  In nonatomic case, this is identical
 * as snd_pcm_stream_lock().
 */

void snd_pcm_stream_lock_irq(struct snd_pcm_substream *substream)
{
 snd_pcm_group_lock_irq(&substream->self_group,
          substream->pcm->nonatomic);
}
EXPORT_SYMBOL_GPL(snd_pcm_stream_lock_irq);

static void snd_pcm_stream_lock_nested(struct snd_pcm_substream *substream)
{
 struct snd_pcm_group *group = &substream->self_group;

 if (substream->pcm->nonatomic)
  mutex_lock_nested(&group->mutex, SINGLE_DEPTH_NESTING);
 else
  spin_lock_nested(&group->lock, SINGLE_DEPTH_NESTING);
}

/**
 * snd_pcm_stream_unlock_irq - Unlock the PCM stream
 * @substream: PCM substream
 *
 * This is a counter-part of snd_pcm_stream_lock_irq().
 */

void snd_pcm_stream_unlock_irq(struct snd_pcm_substream *substream)
{
 snd_pcm_group_unlock_irq(&substream->self_group,
     substream->pcm->nonatomic);
}
EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock_irq);

unsigned long _snd_pcm_stream_lock_irqsave(struct snd_pcm_substream *substream)
{
 unsigned long flags = 0;
 if (substream->pcm->nonatomic)
  mutex_lock(&substream->self_group.mutex);
 else
  spin_lock_irqsave(&substream->self_group.lock, flags);
 return flags;
}
EXPORT_SYMBOL_GPL(_snd_pcm_stream_lock_irqsave);

unsigned long _snd_pcm_stream_lock_irqsave_nested(struct snd_pcm_substream *substream)
{
 unsigned long flags = 0;
 if (substream->pcm->nonatomic)
  mutex_lock_nested(&substream->self_group.mutex,
      SINGLE_DEPTH_NESTING);
 else
  spin_lock_irqsave_nested(&substream->self_group.lock, flags,
      SINGLE_DEPTH_NESTING);
 return flags;
}
EXPORT_SYMBOL_GPL(_snd_pcm_stream_lock_irqsave_nested);

/**
 * snd_pcm_stream_unlock_irqrestore - Unlock the PCM stream
 * @substream: PCM substream
 * @flags: irq flags
 *
 * This is a counter-part of snd_pcm_stream_lock_irqsave().
 */

void snd_pcm_stream_unlock_irqrestore(struct snd_pcm_substream *substream,
          unsigned long flags)
{
 if (substream->pcm->nonatomic)
  mutex_unlock(&substream->self_group.mutex);
 else
  spin_unlock_irqrestore(&substream->self_group.lock, flags);
}
EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock_irqrestore);

/* Run PCM ioctl ops */
static int snd_pcm_ops_ioctl(struct snd_pcm_substream *substream,
        unsigned cmd, void *arg)
{
 if (substream->ops->ioctl)
  return substream->ops->ioctl(substream, cmd, arg);
 else
  return snd_pcm_lib_ioctl(substream, cmd, arg);
}

int snd_pcm_info(struct snd_pcm_substream *substream, struct snd_pcm_info *info)
{
 struct snd_pcm *pcm = substream->pcm;
 struct snd_pcm_str *pstr = substream->pstr;

 memset(info, 0, sizeof(*info));
 info->card = pcm->card->number;
 info->device = pcm->device;
 info->stream = substream->stream;
 info->subdevice = substream->number;
 strscpy(info->id, pcm->id, sizeof(info->id));
 strscpy(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;
 strscpy(info->subname, substream->name, sizeof(info->subname));

 return 0;
}

int snd_pcm_info_user(struct snd_pcm_substream *substream,
        struct snd_pcm_info __user * _info)
{
 struct snd_pcm_info *info __free(kfree) = NULL;
 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;
 }
 return err;
}

/* macro for simplified cast */
#define PARAM_MASK_BIT(b) (1U << (__force int)(b))

static bool hw_support_mmap(struct snd_pcm_substream *substream)
{
 struct snd_dma_buffer *dmabuf;

 if (!(substream->runtime->hw.info & SNDRV_PCM_INFO_MMAP))
  return false;

 if (substream->ops->mmap || substream->ops->page)
  return true;

 dmabuf = snd_pcm_get_dma_buf(substream);
 if (!dmabuf)
  dmabuf = &substream->dma_buffer;
 switch (dmabuf->dev.type) {
 case SNDRV_DMA_TYPE_UNKNOWN:
  /* we can't know the device, so just assume that the driver does
 * everything right
 */

  return true;
 case SNDRV_DMA_TYPE_CONTINUOUS:
 case SNDRV_DMA_TYPE_VMALLOC:
  return true;
 default:
  return dma_can_mmap(dmabuf->dev.dev);
 }
}

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_mask old_mask __maybe_unused;
 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 & PARAM_MASK_BIT(k)))
   continue;

  if (trace_hw_mask_param_enabled())
   old_mask = *m;

  changed = snd_mask_refine(m, constrs_mask(constrs, 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 |= PARAM_MASK_BIT(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 __maybe_unused;
 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 & PARAM_MASK_BIT(k)))
   continue;

  if (trace_hw_interval_param_enabled())
   old_interval = *i;

  changed = snd_interval_refine(i, constrs_interval(constrs, 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 |= PARAM_MASK_BIT(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 __free(kfree) = NULL;
 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 __maybe_unused;
 struct snd_interval old_interval __maybe_unused;
 bool again;
 int changed, err = 0;

 /*
 * Each application of rule has own sequence number.
 *
 * Each member of 'rstamps' array represents the sequence number of
 * recent application of corresponding rule.
 */

 rstamps = kcalloc(constrs->rules_num, sizeof(unsigned int), GFP_KERNEL);
 if (!rstamps)
  return -ENOMEM;

 /*
 * 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 & PARAM_MASK_BIT(k)) ? 1 : 0;

 /* 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 four dependencies
 * to SNDRV_PCM_HW_PARAM_XXXs for this rule. The fifth
 * 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));
   }
   if (hw_is_interval(r->var)) {
    trace_hw_interval_param(substream, r->var,
     k + 1, &old_interval,
     hw_param_interval(params, r->var));
   }

   params->cmask |= PARAM_MASK_BIT(r->var);
   vstamps[r->var] = stamp;
   again = true;
  }

  rstamps[k] = stamp++;
 }

 /* Iterate to evaluate all rules till no parameters are changed. */
 if (again)
  goto retry;

 return err;
}

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;
 struct snd_mask *m_rw;
 int err;

 if (!params->msbits) {
  i = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS);
  if (snd_interval_single(i))
   params->msbits = snd_interval_value(i);
  m = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT);
  if (snd_mask_single(m)) {
   snd_pcm_format_t format = (__force snd_pcm_format_t)snd_mask_min(m);
   params->msbits = snd_pcm_format_width(format);
  }
 }

 if (params->msbits) {
  m = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT);
  if (snd_mask_single(m)) {
   snd_pcm_format_t format = (__force snd_pcm_format_t)snd_mask_min(m);

   if (snd_pcm_format_linear(format) &&
       snd_pcm_format_width(format) != params->msbits) {
    m_rw = hw_param_mask(params, SNDRV_PCM_HW_PARAM_SUBFORMAT);
    snd_mask_reset(m_rw,
            (__force unsigned)SNDRV_PCM_SUBFORMAT_MSBITS_MAX);
    if (snd_mask_empty(m_rw))
     return -EINVAL;
   }
  }
 }

 if (!params->rate_den) {
  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;
  }
 }

 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 = snd_pcm_ops_ioctl(substream,
      SNDRV_PCM_IOCTL1_FIFO_SIZE,
      params);
   if (err < 0)
    return err;
  }
 }

 if (!params->info) {
  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);
 }

 err = snd_pcm_ops_ioctl(substream,
    SNDRV_PCM_IOCTL1_SYNC_ID,
    params);
 if (err < 0)
  return err;

 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 & PARAM_MASK_BIT(SNDRV_PCM_HW_PARAM_SAMPLE_BITS))
  params->msbits = 0;
 if (params->rmask & PARAM_MASK_BIT(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,
      struct snd_pcm_hw_params __user * _params)
{
 struct snd_pcm_hw_params *params __free(kfree) = NULL;
 int err;

 params = memdup_user(_params, sizeof(*params));
 if (IS_ERR(params))
  return PTR_ERR(params);

 err = snd_pcm_hw_refine(substream, params);
 if (err < 0)
  return err;

 err = fixup_unreferenced_params(substream, params);
 if (err < 0)
  return err;

 if (copy_to_user(_params, params, sizeof(*params)))
  return -EFAULT;
 return 0;
}

static int period_to_usecs(struct snd_pcm_runtime *runtime)
{
 int usecs;

 if (! runtime->rate)
  return -1; /* invalid */

 /* take 75% of period time as the deadline */
 usecs = (750000 / runtime->rate) * runtime->period_size;
 usecs += ((750000 % runtime->rate) * runtime->period_size) /
  runtime->rate;

 return usecs;
}

static void snd_pcm_set_state(struct snd_pcm_substream *substream,
         snd_pcm_state_t state)
{
 guard(pcm_stream_lock_irq)(substream);
 if (substream->runtime->state != SNDRV_PCM_STATE_DISCONNECTED)
  __snd_pcm_set_state(substream->runtime, state);
}

static inline void snd_pcm_timer_notify(struct snd_pcm_substream *substream,
     int event)
{
#ifdef CONFIG_SND_PCM_TIMER
 if (substream->timer)
  snd_timer_notify(substream->timer, event,
     &substream->runtime->trigger_tstamp);
#endif
}

void snd_pcm_sync_stop(struct snd_pcm_substream *substream, bool sync_irq)
{
 if (substream->runtime && substream->runtime->stop_operating) {
  substream->runtime->stop_operating = false;
  if (substream->ops && substream->ops->sync_stop)
   substream->ops->sync_stop(substream);
  else if (sync_irq && substream->pcm->card->sync_irq > 0)
   synchronize_irq(substream->pcm->card->sync_irq);
 }
}

/**
 * snd_pcm_hw_params_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 __maybe_unused;
 struct snd_interval old_interval __maybe_unused;
 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 (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;
}

/* acquire buffer_mutex; if it's in r/w operation, return -EBUSY, otherwise
 * block the further r/w operations
 */

static int snd_pcm_buffer_access_lock(struct snd_pcm_runtime *runtime)
{
 if (!atomic_dec_unless_positive(&runtime->buffer_accessing))
  return -EBUSY;
 mutex_lock(&runtime->buffer_mutex);
 return 0; /* keep buffer_mutex, unlocked by below */
}

/* release buffer_mutex and clear r/w access flag */
static void snd_pcm_buffer_access_unlock(struct snd_pcm_runtime *runtime)
{
 mutex_unlock(&runtime->buffer_mutex);
 atomic_inc(&runtime->buffer_accessing);
}

/* fill the PCM buffer with the current silence format; called from pcm_oss.c */
void snd_pcm_runtime_buffer_set_silence(struct snd_pcm_runtime *runtime)
{
 snd_pcm_buffer_access_lock(runtime);
 if (runtime->dma_area)
  snd_pcm_format_set_silence(runtime->format, runtime->dma_area,
        bytes_to_samples(runtime, runtime->dma_bytes));
 snd_pcm_buffer_access_unlock(runtime);
}
EXPORT_SYMBOL_GPL(snd_pcm_runtime_buffer_set_silence);

#if IS_ENABLED(CONFIG_SND_PCM_OSS)
#define is_oss_stream(substream) ((substream)->oss.oss)
#else
#define is_oss_stream(substream) false
#endif

static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
        struct snd_pcm_hw_params *params)
{
 struct snd_pcm_runtime *runtime;
 int err, usecs;
 unsigned int bits;
 snd_pcm_uframes_t frames;

 if (PCM_RUNTIME_CHECK(substream))
  return -ENXIO;
 runtime = substream->runtime;
 err = snd_pcm_buffer_access_lock(runtime);
 if (err < 0)
  return err;
 scoped_guard(pcm_stream_lock_irq, substream) {
  switch (runtime->state) {
  case SNDRV_PCM_STATE_OPEN:
  case SNDRV_PCM_STATE_SETUP:
  case SNDRV_PCM_STATE_PREPARED:
   if (!is_oss_stream(substream) &&
       atomic_read(&substream->mmap_count))
    err = -EBADFD;
   break;
  default:
   err = -EBADFD;
   break;
  }
 }
 if (err)
  goto unlock;

 snd_pcm_sync_stop(substream, true);

 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;

 err = fixup_unreferenced_params(substream, params);
 if (err < 0)
  goto _error;

 if (substream->managed_buffer_alloc) {
  err = snd_pcm_lib_malloc_pages(substream,
            params_buffer_bytes(params));
  if (err < 0)
   goto _error;
  runtime->buffer_changed = err > 0;
 }

 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->info = params->info;
 runtime->rate_num = params->rate_num;
 runtime->rate_den = params->rate_den;
 runtime->no_period_wakeup =
   (params->info & SNDRV_PCM_INFO_NO_PERIOD_WAKEUP) &&
   (params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP);

 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->control->avail_min = 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;

 /* clear the buffer for avoiding possible kernel info leaks */
 if (runtime->dma_area && !substream->ops->copy) {
  size_t size = runtime->dma_bytes;

  if (runtime->info & SNDRV_PCM_INFO_MMAP)
   size = PAGE_ALIGN(size);
  memset(runtime->dma_area, 0, size);
 }

 snd_pcm_timer_resolution_change(substream);
 snd_pcm_set_state(substream, SNDRV_PCM_STATE_SETUP);

 if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req))
  cpu_latency_qos_remove_request(&substream->latency_pm_qos_req);
 usecs = period_to_usecs(runtime);
 if (usecs >= 0)
  cpu_latency_qos_add_request(&substream->latency_pm_qos_req,
         usecs);
 err = 0;
 _error:
 if (err) {
  /* hardware might be unusable from this time,
 * so we force application to retry to set
 * the correct hardware parameter settings
 */

  snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN);
  if (substream->ops->hw_free != NULL)
   substream->ops->hw_free(substream);
  if (substream->managed_buffer_alloc)
   snd_pcm_lib_free_pages(substream);
 }
 unlock:
 snd_pcm_buffer_access_unlock(runtime);
 return err;
}

static int snd_pcm_hw_params_user(struct snd_pcm_substream *substream,
      struct snd_pcm_hw_params __user * _params)
{
 struct snd_pcm_hw_params *params __free(kfree) = NULL;
 int err;

 params = memdup_user(_params, sizeof(*params));
 if (IS_ERR(params))
  return PTR_ERR(params);

 err = snd_pcm_hw_params(substream, params);
 if (err < 0)
  return err;

 if (copy_to_user(_params, params, sizeof(*params)))
  return -EFAULT;
 return err;
}

static int do_hw_free(struct snd_pcm_substream *substream)
{
 int result = 0;

 snd_pcm_sync_stop(substream, true);
 if (substream->ops->hw_free)
  result = substream->ops->hw_free(substream);
 if (substream->managed_buffer_alloc)
  snd_pcm_lib_free_pages(substream);
 return result;
}

static int snd_pcm_hw_free(struct snd_pcm_substream *substream)
{
 struct snd_pcm_runtime *runtime;
 int result = 0;

 if (PCM_RUNTIME_CHECK(substream))
  return -ENXIO;
 runtime = substream->runtime;
 result = snd_pcm_buffer_access_lock(runtime);
 if (result < 0)
  return result;
 scoped_guard(pcm_stream_lock_irq, substream) {
  switch (runtime->state) {
  case SNDRV_PCM_STATE_SETUP:
  case SNDRV_PCM_STATE_PREPARED:
   if (atomic_read(&substream->mmap_count))
    result = -EBADFD;
   break;
  default:
   result = -EBADFD;
   break;
  }
 }
 if (result)
  goto unlock;
 result = do_hw_free(substream);
 snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN);
 cpu_latency_qos_remove_request(&substream->latency_pm_qos_req);
 unlock:
 snd_pcm_buffer_access_unlock(runtime);
 return result;
}

static int snd_pcm_sw_params(struct snd_pcm_substream *substream,
        struct snd_pcm_sw_params *params)
{
 struct snd_pcm_runtime *runtime;
 int err;

 if (PCM_RUNTIME_CHECK(substream))
  return -ENXIO;
 runtime = substream->runtime;
 scoped_guard(pcm_stream_lock_irq, substream) {
  if (runtime->state == SNDRV_PCM_STATE_OPEN)
   return -EBADFD;
 }

 if (params->tstamp_mode < 0 ||
     params->tstamp_mode > SNDRV_PCM_TSTAMP_LAST)
  return -EINVAL;
 if (params->proto >= SNDRV_PROTOCOL_VERSION(2, 0, 12) &&
     params->tstamp_type > SNDRV_PCM_TSTAMP_TYPE_LAST)
  return -EINVAL;
 if (params->avail_min == 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;
 }
 err = 0;
 scoped_guard(pcm_stream_lock_irq, substream) {
  runtime->tstamp_mode = params->tstamp_mode;
  if (params->proto >= SNDRV_PROTOCOL_VERSION(2, 0, 12))
   runtime->tstamp_type = params->tstamp_type;
  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;
  params->boundary = runtime->boundary;
  if (snd_pcm_running(substream)) {
   if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
       runtime->silence_size > 0)
    snd_pcm_playback_silence(substream, ULONG_MAX);
   err = snd_pcm_update_state(substream, runtime);
  }
 }
 return err;
}

static int snd_pcm_sw_params_user(struct snd_pcm_substream *substream,
      struct snd_pcm_sw_params __user * _params)
{
 struct snd_pcm_sw_params params;
 int err;
 if (copy_from_user(¶ms, _params, sizeof(params)))
  return -EFAULT;
 err = snd_pcm_sw_params(substream, ¶ms);
 if (copy_to_user(_params, ¶ms, sizeof(params)))
  return -EFAULT;
 return err;
}

static inline snd_pcm_uframes_t
snd_pcm_calc_delay(struct snd_pcm_substream *substream)
{
 snd_pcm_uframes_t delay;

 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
  delay = snd_pcm_playback_hw_avail(substream->runtime);
 else
  delay = snd_pcm_capture_avail(substream->runtime);
 return delay + substream->runtime->delay;
}

int snd_pcm_status64(struct snd_pcm_substream *substream,
       struct snd_pcm_status64 *status)
{
 struct snd_pcm_runtime *runtime = substream->runtime;

 guard(pcm_stream_lock_irq)(substream);

 snd_pcm_unpack_audio_tstamp_config(status->audio_tstamp_data,
     &runtime->audio_tstamp_config);

 /* backwards compatible behavior */
 if (runtime->audio_tstamp_config.type_requested ==
  SNDRV_PCM_AUDIO_TSTAMP_TYPE_COMPAT) {
  if (runtime->hw.info & SNDRV_PCM_INFO_HAS_WALL_CLOCK)
   runtime->audio_tstamp_config.type_requested =
    SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK;
  else
   runtime->audio_tstamp_config.type_requested =
    SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT;
  runtime->audio_tstamp_report.valid = 0;
 } else
  runtime->audio_tstamp_report.valid = 1;

 status->state = runtime->state;
 status->suspended_state = runtime->suspended_state;
 if (status->state == SNDRV_PCM_STATE_OPEN)
  return 0;
 status->trigger_tstamp_sec = runtime->trigger_tstamp.tv_sec;
 status->trigger_tstamp_nsec = runtime->trigger_tstamp.tv_nsec;
 if (snd_pcm_running(substream)) {
  snd_pcm_update_hw_ptr(substream);
  if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) {
   status->tstamp_sec = runtime->status->tstamp.tv_sec;
   status->tstamp_nsec =
    runtime->status->tstamp.tv_nsec;
   status->driver_tstamp_sec =
    runtime->driver_tstamp.tv_sec;
   status->driver_tstamp_nsec =
    runtime->driver_tstamp.tv_nsec;
   status->audio_tstamp_sec =
    runtime->status->audio_tstamp.tv_sec;
   status->audio_tstamp_nsec =
    runtime->status->audio_tstamp.tv_nsec;
   if (runtime->audio_tstamp_report.valid == 1)
    /* backwards compatibility, no report provided in COMPAT mode */
    snd_pcm_pack_audio_tstamp_report(&status->audio_tstamp_data,
        &status->audio_tstamp_accuracy,
        &runtime->audio_tstamp_report);

   goto _tstamp_end;
  }
 } else {
  /* get tstamp only in fallback mode and only if enabled */
  if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) {
   struct timespec64 tstamp;

   snd_pcm_gettime(runtime, &tstamp);
   status->tstamp_sec = tstamp.tv_sec;
   status->tstamp_nsec = tstamp.tv_nsec;
  }
 }
 _tstamp_end:
 status->appl_ptr = runtime->control->appl_ptr;
 status->hw_ptr = runtime->status->hw_ptr;
 status->avail = snd_pcm_avail(substream);
 status->delay = snd_pcm_running(substream) ?
  snd_pcm_calc_delay(substream) : 0;
 status->avail_max = runtime->avail_max;
 status->overrange = runtime->overrange;
 runtime->avail_max = 0;
 runtime->overrange = 0;
 return 0;
}

static int snd_pcm_status_user64(struct snd_pcm_substream *substream,
     struct snd_pcm_status64 __user * _status,
     bool ext)
{
 struct snd_pcm_status64 status;
 int res;

 memset(&status, 0, sizeof(status));
 /*
 * with extension, parameters are read/write,
 * get audio_tstamp_data from user,
 * ignore rest of status structure
 */

 if (ext && get_user(status.audio_tstamp_data,
    (u32 __user *)(&_status->audio_tstamp_data)))
  return -EFAULT;
 res = snd_pcm_status64(substream, &status);
 if (res < 0)
  return res;
 if (copy_to_user(_status, &status, sizeof(status)))
  return -EFAULT;
 return 0;
}

static int snd_pcm_status_user32(struct snd_pcm_substream *substream,
     struct snd_pcm_status32 __user * _status,
     bool ext)
{
 struct snd_pcm_status64 status64;
 struct snd_pcm_status32 status32;
 int res;

 memset(&status64, 0, sizeof(status64));
 memset(&status32, 0, sizeof(status32));
 /*
 * with extension, parameters are read/write,
 * get audio_tstamp_data from user,
 * ignore rest of status structure
 */

 if (ext && get_user(status64.audio_tstamp_data,
       (u32 __user *)(&_status->audio_tstamp_data)))
  return -EFAULT;
 res = snd_pcm_status64(substream, &status64);
 if (res < 0)
  return res;

 status32 = (struct snd_pcm_status32) {
  .state = status64.state,
  .trigger_tstamp_sec = status64.trigger_tstamp_sec,
  .trigger_tstamp_nsec = status64.trigger_tstamp_nsec,
  .tstamp_sec = status64.tstamp_sec,
  .tstamp_nsec = status64.tstamp_nsec,
  .appl_ptr = status64.appl_ptr,
  .hw_ptr = status64.hw_ptr,
  .delay = status64.delay,
  .avail = status64.avail,
  .avail_max = status64.avail_max,
  .overrange = status64.overrange,
  .suspended_state = status64.suspended_state,
  .audio_tstamp_data = status64.audio_tstamp_data,
  .audio_tstamp_sec = status64.audio_tstamp_sec,
  .audio_tstamp_nsec = status64.audio_tstamp_nsec,
  .driver_tstamp_sec = status64.audio_tstamp_sec,
  .driver_tstamp_nsec = status64.audio_tstamp_nsec,
  .audio_tstamp_accuracy = status64.audio_tstamp_accuracy,
 };

 if (copy_to_user(_status, &status32, sizeof(status32)))
  return -EFAULT;

 return 0;
}

static int snd_pcm_channel_info(struct snd_pcm_substream *substream,
    struct snd_pcm_channel_info * info)
{
 struct snd_pcm_runtime *runtime;
 unsigned int channel;
 
 channel = info->channel;
 runtime = substream->runtime;
 scoped_guard(pcm_stream_lock_irq, substream) {
  if (runtime->state == SNDRV_PCM_STATE_OPEN)
   return -EBADFD;
 }
 if (channel >= runtime->channels)
  return -EINVAL;
 memset(info, 0, sizeof(*info));
 info->channel = channel;
 return snd_pcm_ops_ioctl(substream, SNDRV_PCM_IOCTL1_CHANNEL_INFO, info);
}

static int snd_pcm_channel_info_user(struct snd_pcm_substream *substream,
         struct snd_pcm_channel_info __user * _info)
{
 struct snd_pcm_channel_info 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(struct snd_pcm_substream *substream)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 if (runtime->trigger_master == NULL)
  return;
 if (runtime->trigger_master == substream) {
  if (!runtime->trigger_tstamp_latched)
   snd_pcm_gettime(runtime, &runtime->trigger_tstamp);
 } else {
  snd_pcm_trigger_tstamp(runtime->trigger_master);
  runtime->trigger_tstamp = runtime->trigger_master->runtime->trigger_tstamp;
 }
 runtime->trigger_master = NULL;
}

#define ACTION_ARG_IGNORE (__force snd_pcm_state_t)0

struct action_ops {
 int (*pre_action)(struct snd_pcm_substream *substream,
     snd_pcm_state_t state);
 int (*do_action)(struct snd_pcm_substream *substream,
    snd_pcm_state_t state);
 void (*undo_action)(struct snd_pcm_substream *substream,
       snd_pcm_state_t state);
 void (*post_action)(struct snd_pcm_substream *substream,
       snd_pcm_state_t 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(const struct action_ops *ops,
    struct snd_pcm_substream *substream,
    snd_pcm_state_t state,
    bool stream_lock)
{
 struct snd_pcm_substream *s = NULL;
 struct snd_pcm_substream *s1;
 int res = 0, depth = 1;

 snd_pcm_group_for_each_entry(s, substream) {
  if (s != substream) {
   if (!stream_lock)
    mutex_lock_nested(&s->runtime->buffer_mutex, depth);
   else if (s->pcm->nonatomic)
    mutex_lock_nested(&s->self_group.mutex, depth);
   else
    spin_lock_nested(&s->self_group.lock, depth);
   depth++;
  }
  res = ops->pre_action(s, state);
  if (res < 0)
   goto _unlock;
 }
 snd_pcm_group_for_each_entry(s, substream) {
  res = ops->do_action(s, state);
  if (res < 0) {
   if (ops->undo_action) {
    snd_pcm_group_for_each_entry(s1, substream) {
     if (s1 == s) /* failed stream */
      break;
     ops->undo_action(s1, state);
    }
   }
   s = NULL; /* unlock all */
   goto _unlock;
  }
 }
 snd_pcm_group_for_each_entry(s, substream) {
  ops->post_action(s, state);
 }
 _unlock:
 /* unlock streams */
 snd_pcm_group_for_each_entry(s1, substream) {
  if (s1 != substream) {
   if (!stream_lock)
    mutex_unlock(&s1->runtime->buffer_mutex);
   else if (s1->pcm->nonatomic)
    mutex_unlock(&s1->self_group.mutex);
   else
    spin_unlock(&s1->self_group.lock);
  }
  if (s1 == s) /* end */
   break;
 }
 return res;
}

/*
 *  Note: call with stream lock
 */

static int snd_pcm_action_single(const struct action_ops *ops,
     struct snd_pcm_substream *substream,
     snd_pcm_state_t 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;
}

static void snd_pcm_group_assign(struct snd_pcm_substream *substream,
     struct snd_pcm_group *new_group)
{
 substream->group = new_group;
 list_move(&substream->link_list, &new_group->substreams);
}

/*
 * Unref and unlock the group, but keep the stream lock;
 * when the group becomes empty and no longer referred, destroy itself
 */

static void snd_pcm_group_unref(struct snd_pcm_group *group,
    struct snd_pcm_substream *substream)
{
 bool do_free;

 if (!group)
  return;
 do_free = refcount_dec_and_test(&group->refs);
 snd_pcm_group_unlock(group, substream->pcm->nonatomic);
 if (do_free)
  kfree(group);
}

/*
 * Lock the group inside a stream lock and reference it;
 * return the locked group object, or NULL if not linked
 */

static struct snd_pcm_group *
snd_pcm_stream_group_ref(struct snd_pcm_substream *substream)
{
 bool nonatomic = substream->pcm->nonatomic;
 struct snd_pcm_group *group;
 bool trylock;

 for (;;) {
  if (!snd_pcm_stream_linked(substream))
   return NULL;
  group = substream->group;
  /* block freeing the group object */
  refcount_inc(&group->refs);

  trylock = nonatomic ? mutex_trylock(&group->mutex) :
   spin_trylock(&group->lock);
  if (trylock)
   break/* OK */

  /* re-lock for avoiding ABBA deadlock */
  snd_pcm_stream_unlock(substream);
  snd_pcm_group_lock(group, nonatomic);
  snd_pcm_stream_lock(substream);

  /* check the group again; the above opens a small race window */
  if (substream->group == group)
   break/* OK */
  /* group changed, try again */
  snd_pcm_group_unref(group, substream);
 }
 return group;
}

/*
 *  Note: call with stream lock
 */

static int snd_pcm_action(const struct action_ops *ops,
     struct snd_pcm_substream *substream,
     snd_pcm_state_t state)
{
 struct snd_pcm_group *group;
 int res;

 group = snd_pcm_stream_group_ref(substream);
 if (group)
  res = snd_pcm_action_group(ops, substream, state, true);
 else
  res = snd_pcm_action_single(ops, substream, state);
 snd_pcm_group_unref(group, substream);
 return res;
}

/*
 *  Note: don't use any locks before
 */

static int snd_pcm_action_lock_irq(const struct action_ops *ops,
       struct snd_pcm_substream *substream,
       snd_pcm_state_t state)
{
 guard(pcm_stream_lock_irq)(substream);
 return snd_pcm_action(ops, substream, state);
}

/*
 */

static int snd_pcm_action_nonatomic(const struct action_ops *ops,
        struct snd_pcm_substream *substream,
        snd_pcm_state_t state)
{
 int res;

 /* Guarantee the group members won't change during non-atomic action */
 guard(rwsem_read)(&snd_pcm_link_rwsem);
 res = snd_pcm_buffer_access_lock(substream->runtime);
 if (res < 0)
  return res;
 if (snd_pcm_stream_linked(substream))
  res = snd_pcm_action_group(ops, substream, state, false);
 else
  res = snd_pcm_action_single(ops, substream, state);
 snd_pcm_buffer_access_unlock(substream->runtime);
 return res;
}

/*
 * start callbacks
 */

static int snd_pcm_pre_start(struct snd_pcm_substream *substream,
        snd_pcm_state_t state)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 if (runtime->state != SNDRV_PCM_STATE_PREPARED)
  return -EBADFD;
 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
     !snd_pcm_playback_data(substream))
  return -EPIPE;
 runtime->trigger_tstamp_latched = false;
 runtime->trigger_master = substream;
 return 0;
}

static int snd_pcm_do_start(struct snd_pcm_substream *substream,
       snd_pcm_state_t state)
{
 int err;

 if (substream->runtime->trigger_master != substream)
  return 0;
 err = substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START);
 /* XRUN happened during the start */
 if (err == -EPIPE)
  __snd_pcm_set_state(substream->runtime, SNDRV_PCM_STATE_XRUN);
 return err;
}

static void snd_pcm_undo_start(struct snd_pcm_substream *substream,
          snd_pcm_state_t state)
{
 if (substream->runtime->trigger_master == substream) {
  substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP);
  substream->runtime->stop_operating = true;
 }
}

static void snd_pcm_post_start(struct snd_pcm_substream *substream,
          snd_pcm_state_t state)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 snd_pcm_trigger_tstamp(substream);
 runtime->hw_ptr_jiffies = jiffies;
 runtime->hw_ptr_buffer_jiffies = (runtime->buffer_size * HZ) / 
           runtime->rate;
 __snd_pcm_set_state(runtime, state);
 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
     runtime->silence_size > 0)
  snd_pcm_playback_silence(substream, ULONG_MAX);
 snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSTART);
}

static const 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
 * @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)
{
 return snd_pcm_action(&snd_pcm_action_start, 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
 */

static int snd_pcm_pre_stop(struct snd_pcm_substream *substream,
       snd_pcm_state_t state)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 if (runtime->state == SNDRV_PCM_STATE_OPEN)
  return -EBADFD;
 runtime->trigger_master = substream;
 return 0;
}

static int snd_pcm_do_stop(struct snd_pcm_substream *substream,
      snd_pcm_state_t state)
{
 if (substream->runtime->trigger_master == substream &&
     snd_pcm_running(substream)) {
  substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP);
  substream->runtime->stop_operating = true;
 }
 return 0; /* unconditionally stop all substreams */
}

static void snd_pcm_post_stop(struct snd_pcm_substream *substream,
         snd_pcm_state_t state)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 if (runtime->state != state) {
  snd_pcm_trigger_tstamp(substream);
  __snd_pcm_set_state(runtime, state);
  snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSTOP);
 }
 wake_up(&runtime->sleep);
 wake_up(&runtime->tsleep);
}

static const 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
 * @substream: the PCM substream instance
 * @state: PCM state after stopping the stream
 *
 * The state of each stream is then changed to the given state unconditionally.
 *
 * Return: Zero if successful, or a negative error code.
 */

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);

/**
 * snd_pcm_drain_done - stop the DMA only when the given stream is playback
 * @substream: the PCM substream
 *
 * After stopping, the state is changed to SETUP.
 * Unlike snd_pcm_stop(), this affects only the given stream.
 *
 * Return: Zero if successful, or a negative error code.
 */

int snd_pcm_drain_done(struct snd_pcm_substream *substream)
{
 return snd_pcm_action_single(&snd_pcm_action_stop, substream,
         SNDRV_PCM_STATE_SETUP);
}

/**
 * snd_pcm_stop_xrun - stop the running streams as XRUN
 * @substream: the PCM substream instance
 *
 * This stops the given running substream (and all linked substreams) as XRUN.
 * Unlike snd_pcm_stop(), this function takes the substream lock by itself.
 *
 * Return: Zero if successful, or a negative error code.
 */

int snd_pcm_stop_xrun(struct snd_pcm_substream *substream)
{
 guard(pcm_stream_lock_irqsave)(substream);
 if (substream->runtime && snd_pcm_running(substream))
  __snd_pcm_xrun(substream);
 return 0;
}
EXPORT_SYMBOL_GPL(snd_pcm_stop_xrun);

/*
 * pause callbacks: pass boolean (to start pause or resume) as state argument
 */

#define pause_pushed(state) (__force bool)(state)

static int snd_pcm_pre_pause(struct snd_pcm_substream *substream,
        snd_pcm_state_t state)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 if (!(runtime->info & SNDRV_PCM_INFO_PAUSE))
  return -ENOSYS;
 if (pause_pushed(state)) {
  if (runtime->state != SNDRV_PCM_STATE_RUNNING)
   return -EBADFD;
 } else if (runtime->state != SNDRV_PCM_STATE_PAUSED)
  return -EBADFD;
 runtime->trigger_master = substream;
 return 0;
}

static int snd_pcm_do_pause(struct snd_pcm_substream *substream,
       snd_pcm_state_t state)
{
 if (substream->runtime->trigger_master != substream)
  return 0;
 /* The jiffies check in snd_pcm_update_hw_ptr*() is done by
 * a delta between the current jiffies, this gives a large enough
 * delta, effectively to skip the check once.
 */

 substream->runtime->hw_ptr_jiffies = jiffies - HZ * 1000;
 return substream->ops->trigger(substream,
           pause_pushed(state) ?
           SNDRV_PCM_TRIGGER_PAUSE_PUSH :
           SNDRV_PCM_TRIGGER_PAUSE_RELEASE);
}

static void snd_pcm_undo_pause(struct snd_pcm_substream *substream,
          snd_pcm_state_t state)
{
 if (substream->runtime->trigger_master == substream)
  substream->ops->trigger(substream,
     pause_pushed(state) ?
     SNDRV_PCM_TRIGGER_PAUSE_RELEASE :
     SNDRV_PCM_TRIGGER_PAUSE_PUSH);
}

static void snd_pcm_post_pause(struct snd_pcm_substream *substream,
          snd_pcm_state_t state)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 snd_pcm_trigger_tstamp(substream);
 if (pause_pushed(state)) {
  __snd_pcm_set_state(runtime, SNDRV_PCM_STATE_PAUSED);
  snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MPAUSE);
  wake_up(&runtime->sleep);
  wake_up(&runtime->tsleep);
 } else {
  __snd_pcm_set_state(runtime, SNDRV_PCM_STATE_RUNNING);
  snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MCONTINUE);
 }
}

static const 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(struct snd_pcm_substream *substream, bool push)
{
 return snd_pcm_action(&snd_pcm_action_pause, substream,
         (__force snd_pcm_state_t)push);
}

static int snd_pcm_pause_lock_irq(struct snd_pcm_substream *substream,
      bool push)
{
 return snd_pcm_action_lock_irq(&snd_pcm_action_pause, substream,
           (__force snd_pcm_state_t)push);
}

#ifdef CONFIG_PM
/* suspend callback: state argument ignored */

static int snd_pcm_pre_suspend(struct snd_pcm_substream *substream,
          snd_pcm_state_t state)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 switch (runtime->state) {
 case SNDRV_PCM_STATE_SUSPENDED:
  return -EBUSY;
 /* unresumable PCM state; return -EBUSY for skipping suspend */
 case SNDRV_PCM_STATE_OPEN:
 case SNDRV_PCM_STATE_SETUP:
 case SNDRV_PCM_STATE_DISCONNECTED:
  return -EBUSY;
 }
 runtime->trigger_master = substream;
 return 0;
}

static int snd_pcm_do_suspend(struct snd_pcm_substream *substream,
         snd_pcm_state_t state)
{
 struct snd_pcm_runtime *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);
 runtime->stop_operating = true;
 return 0; /* suspend unconditionally */
}

static void snd_pcm_post_suspend(struct snd_pcm_substream *substream,
     snd_pcm_state_t state)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 snd_pcm_trigger_tstamp(substream);
 runtime->suspended_state = runtime->state;
 runtime->status->suspended_state = runtime->suspended_state;
 __snd_pcm_set_state(runtime, SNDRV_PCM_STATE_SUSPENDED);
 snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSUSPEND);
 wake_up(&runtime->sleep);
 wake_up(&runtime->tsleep);
}

static const 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
 * @substream: the PCM substream
 *
 * After this call, all streams are changed to SUSPENDED state.
 *
 * Return: Zero if successful, or a negative error code.
 */

static int snd_pcm_suspend(struct snd_pcm_substream *substream)
{
 guard(pcm_stream_lock_irqsave)(substream);
 return snd_pcm_action(&snd_pcm_action_suspend, substream,
         ACTION_ARG_IGNORE);
}

/**
 * snd_pcm_suspend_all - trigger SUSPEND to all substreams in the given pcm
 * @pcm: the PCM instance
 *
 * After this call, all streams are changed to SUSPENDED state.
 *
 * Return: Zero if successful (or @pcm is %NULL), or a negative error code.
 */

int snd_pcm_suspend_all(struct snd_pcm *pcm)
{
 struct snd_pcm_substream *substream;
 int stream, err = 0;

 if (! pcm)
  return 0;

 for_each_pcm_substream(pcm, stream, substream) {
  /* FIXME: the open/close code should lock this as well */
  if (!substream->runtime)
   continue;

  /*
 * Skip BE dai link PCM's that are internal and may
 * not have their substream ops set.
 */

  if (!substream->ops)
   continue;

  err = snd_pcm_suspend(substream);
  if (err < 0 && err != -EBUSY)
   return err;
 }

 for_each_pcm_substream(pcm, stream, substream)
  snd_pcm_sync_stop(substream, false);

 return 0;
}
EXPORT_SYMBOL(snd_pcm_suspend_all);

/* resume callbacks: state argument ignored */

static int snd_pcm_pre_resume(struct snd_pcm_substream *substream,
         snd_pcm_state_t state)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 if (runtime->state != SNDRV_PCM_STATE_SUSPENDED)
  return -EBADFD;
 if (!(runtime->info & SNDRV_PCM_INFO_RESUME))
  return -ENOSYS;
 runtime->trigger_master = substream;
 return 0;
}

static int snd_pcm_do_resume(struct snd_pcm_substream *substream,
        snd_pcm_state_t state)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 if (runtime->trigger_master != substream)
  return 0;
 /* DMA not running previously? */
 if (runtime->suspended_state != SNDRV_PCM_STATE_RUNNING &&
     (runtime->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(struct snd_pcm_substream *substream,
    snd_pcm_state_t 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(struct snd_pcm_substream *substream,
    snd_pcm_state_t state)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 snd_pcm_trigger_tstamp(substream);
 __snd_pcm_set_state(runtime, runtime->suspended_state);
 snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MRESUME);
}

static const struct action_ops snd_pcm_action_resume = {
 .pre_action = snd_pcm_pre_resume,
 .do_action = snd_pcm_do_resume,
 .undo_action = snd_pcm_undo_resume,
 .post_action = snd_pcm_post_resume
};

static int snd_pcm_resume(struct snd_pcm_substream *substream)
{
 return snd_pcm_action_lock_irq(&snd_pcm_action_resume, substream,
           ACTION_ARG_IGNORE);
}

#else

static int snd_pcm_resume(struct snd_pcm_substream *substream)
{
 return -ENOSYS;
}

#endif /* CONFIG_PM */

/*
 * xrun ioctl
 *
 * Change the RUNNING stream(s) to XRUN state.
 */

static int snd_pcm_xrun(struct snd_pcm_substream *substream)
{
 struct snd_pcm_runtime *runtime = substream->runtime;

 guard(pcm_stream_lock_irq)(substream);
 switch (runtime->state) {
 case SNDRV_PCM_STATE_XRUN:
  return 0; /* already there */
 case SNDRV_PCM_STATE_RUNNING:
  __snd_pcm_xrun(substream);
  return 0;
 default:
  return -EBADFD;
 }
}

/*
 * reset ioctl
 */

/* reset callbacks:  state argument ignored */
static int snd_pcm_pre_reset(struct snd_pcm_substream *substream,
        snd_pcm_state_t state)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 switch (runtime->state) {
 case SNDRV_PCM_STATE_RUNNING:
 case SNDRV_PCM_STATE_PREPARED:
 case SNDRV_PCM_STATE_PAUSED:
 case SNDRV_PCM_STATE_SUSPENDED:
  return 0;
 default:
  return -EBADFD;
 }
}

static int snd_pcm_do_reset(struct snd_pcm_substream *substream,
       snd_pcm_state_t state)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 int err = snd_pcm_ops_ioctl(substream, SNDRV_PCM_IOCTL1_RESET, NULL);
 if (err < 0)
  return err;
 guard(pcm_stream_lock_irq)(substream);
 runtime->hw_ptr_base = 0;
 runtime->hw_ptr_interrupt = runtime->status->hw_ptr -
  runtime->status->hw_ptr % runtime->period_size;
 runtime->silence_start = runtime->status->hw_ptr;
 runtime->silence_filled = 0;
 return 0;
}

static void snd_pcm_post_reset(struct snd_pcm_substream *substream,
          snd_pcm_state_t state)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 guard(pcm_stream_lock_irq)(substream);
 runtime->control->appl_ptr = runtime->status->hw_ptr;
 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
     runtime->silence_size > 0)
  snd_pcm_playback_silence(substream, ULONG_MAX);
}

static const struct action_ops snd_pcm_action_reset = {
 .pre_action = snd_pcm_pre_reset,
 .do_action = snd_pcm_do_reset,
 .post_action = snd_pcm_post_reset
};

static int snd_pcm_reset(struct snd_pcm_substream *substream)
{
 return snd_pcm_action_nonatomic(&snd_pcm_action_reset, substream,
     ACTION_ARG_IGNORE);
}

/*
 * prepare ioctl
 */

/* pass f_flags as state argument */
static int snd_pcm_pre_prepare(struct snd_pcm_substream *substream,
          snd_pcm_state_t state)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 int f_flags = (__force int)state;

 if (runtime->state == SNDRV_PCM_STATE_OPEN ||
     runtime->state == SNDRV_PCM_STATE_DISCONNECTED)
  return -EBADFD;
 if (snd_pcm_running(substream))
  return -EBUSY;
 substream->f_flags = f_flags;
 return 0;
}

static int snd_pcm_do_prepare(struct snd_pcm_substream *substream,
         snd_pcm_state_t state)
{
 int err;
 snd_pcm_sync_stop(substream, true);
 err = substream->ops->prepare(substream);
 if (err < 0)
  return err;
 return snd_pcm_do_reset(substream, state);
}

static void snd_pcm_post_prepare(struct snd_pcm_substream *substream,
     snd_pcm_state_t state)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 runtime->control->appl_ptr = runtime->status->hw_ptr;
 snd_pcm_set_state(substream, SNDRV_PCM_STATE_PREPARED);
}

static const struct action_ops snd_pcm_action_prepare = {
 .pre_action = snd_pcm_pre_prepare,
 .do_action = snd_pcm_do_prepare,
 .post_action = snd_pcm_post_prepare
};

/**
 * snd_pcm_prepare - prepare the PCM substream to be triggerable
 * @substream: the PCM substream instance
 * @file: file to refer f_flags
 *
 * Return: Zero if successful, or a negative error code.
 */

static int snd_pcm_prepare(struct snd_pcm_substream *substream,
      struct file *file)
{
 int f_flags;

 if (file)
  f_flags = file->f_flags;
 else
  f_flags = substream->f_flags;

 scoped_guard(pcm_stream_lock_irq, substream) {
  switch (substream->runtime->state) {
  case SNDRV_PCM_STATE_PAUSED:
   snd_pcm_pause(substream, false);
   fallthrough;
  case SNDRV_PCM_STATE_SUSPENDED:
   snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
   break;
  }
 }

 return snd_pcm_action_nonatomic(&snd_pcm_action_prepare,
     substream,
     (__force snd_pcm_state_t)f_flags);
}

/*
 * drain ioctl
 */


/* drain init callbacks: state argument ignored */
static int snd_pcm_pre_drain_init(struct snd_pcm_substream *substream,
      snd_pcm_state_t state)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 switch (runtime->state) {
 case SNDRV_PCM_STATE_OPEN:
 case SNDRV_PCM_STATE_DISCONNECTED:
 case SNDRV_PCM_STATE_SUSPENDED:
  return -EBADFD;
 }
 runtime->trigger_master = substream;
 return 0;
}

static int snd_pcm_do_drain_init(struct snd_pcm_substream *substream,
     snd_pcm_state_t state)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
  switch (runtime->state) {
  case SNDRV_PCM_STATE_PREPARED:
   /* start playback stream if possible */
   if (! snd_pcm_playback_empty(substream)) {
    snd_pcm_do_start(substream, SNDRV_PCM_STATE_DRAINING);
    snd_pcm_post_start(substream, SNDRV_PCM_STATE_DRAINING);
   } else {
    __snd_pcm_set_state(runtime, SNDRV_PCM_STATE_SETUP);
   }
   break;
  case SNDRV_PCM_STATE_RUNNING:
   __snd_pcm_set_state(runtime, SNDRV_PCM_STATE_DRAINING);
   break;
  case SNDRV_PCM_STATE_XRUN:
   __snd_pcm_set_state(runtime, SNDRV_PCM_STATE_SETUP);
   break;
  default:
   break;
  }
 } else {
  /* stop running stream */
  if (runtime->state == SNDRV_PCM_STATE_RUNNING) {
   snd_pcm_state_t new_state;

   new_state = snd_pcm_capture_avail(runtime) > 0 ?
    SNDRV_PCM_STATE_DRAINING : SNDRV_PCM_STATE_SETUP;
   snd_pcm_do_stop(substream, new_state);
   snd_pcm_post_stop(substream, new_state);
  }
 }

 if (runtime->state == SNDRV_PCM_STATE_DRAINING &&
     runtime->trigger_master == substream &&
     (runtime->hw.info & SNDRV_PCM_INFO_DRAIN_TRIGGER))
  return substream->ops->trigger(substream,
            SNDRV_PCM_TRIGGER_DRAIN);

 return 0;
}

static void snd_pcm_post_drain_init(struct snd_pcm_substream *substream,
        snd_pcm_state_t state)
{
}

static const struct action_ops snd_pcm_action_drain_init = {
 .pre_action = snd_pcm_pre_drain_init,
 .do_action = snd_pcm_do_drain_init,
 .post_action = snd_pcm_post_drain_init
};

/*
 * Drain the stream(s).
 * When the substream is linked, sync until the draining of all playback streams
 * is finished.
 * After this call, all streams are supposed to be either SETUP or DRAINING
 * (capture only) state.
 */

static int snd_pcm_drain(struct snd_pcm_substream *substream,
    struct file *file)
{
 struct snd_card *card;
 struct snd_pcm_runtime *runtime;
 struct snd_pcm_substream *s;
 struct snd_pcm_group *group;
 wait_queue_entry_t wait;
 int result = 0;
 int nonblock = 0;

 card = substream->pcm->card;
 runtime = substream->runtime;

 if (runtime->state == SNDRV_PCM_STATE_OPEN)
  return -EBADFD;

 if (file) {
  if (file->f_flags & O_NONBLOCK)
   nonblock = 1;
 } else if (substream->f_flags & O_NONBLOCK)
  nonblock = 1;

 snd_pcm_stream_lock_irq(substream);
 /* resume pause */
 if (runtime->state == SNDRV_PCM_STATE_PAUSED)
  snd_pcm_pause(substream, false);

 /* pre-start/stop - all running streams are changed to DRAINING state */
 result = snd_pcm_action(&snd_pcm_action_drain_init, substream,
    ACTION_ARG_IGNORE);
 if (result < 0)
  goto unlock;
 /* in non-blocking, we don't wait in ioctl but let caller poll */
 if (nonblock) {
  result = -EAGAIN;
  goto unlock;
 }

 for (;;) {
  long tout;
  struct snd_pcm_runtime *to_check;
  if (signal_pending(current)) {
   result = -ERESTARTSYS;
   break;
  }
  /* find a substream to drain */
  to_check = NULL;
  group = snd_pcm_stream_group_ref(substream);
  snd_pcm_group_for_each_entry(s, substream) {
   if (s->stream != SNDRV_PCM_STREAM_PLAYBACK)
    continue;
   runtime = s->runtime;
   if (runtime->state == SNDRV_PCM_STATE_DRAINING) {
    to_check = runtime;
    break;
   }
  }
  snd_pcm_group_unref(group, substream);
  if (!to_check)
   break/* all drained */
  init_waitqueue_entry(&wait, current);
  set_current_state(TASK_INTERRUPTIBLE);
  add_wait_queue(&to_check->sleep, &wait);
  snd_pcm_stream_unlock_irq(substream);
  if (runtime->no_period_wakeup)
   tout = MAX_SCHEDULE_TIMEOUT;
  else {
   tout = 100;
   if (runtime->rate) {
    long t = runtime->buffer_size * 1100 / runtime->rate;
    tout = max(t, tout);
   }
   tout = msecs_to_jiffies(tout);
  }
  tout = schedule_timeout(tout);

  snd_pcm_stream_lock_irq(substream);
  group = snd_pcm_stream_group_ref(substream);
  snd_pcm_group_for_each_entry(s, substream) {
   if (s->runtime == to_check) {
    remove_wait_queue(&to_check->sleep, &wait);
    break;
   }
  }
  snd_pcm_group_unref(group, substream);

  if (card->shutdown) {
   result = -ENODEV;
   break;
  }
  if (tout == 0) {
   if (substream->runtime->state == SNDRV_PCM_STATE_SUSPENDED)
    result = -ESTRPIPE;
   else {
    dev_dbg(substream->pcm->card->dev,
     "playback drain timeout (DMA or IRQ trouble?)\n");
    snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
    result = -EIO;
   }
   break;
  }
 }

 unlock:
 snd_pcm_stream_unlock_irq(substream);

 return result;
}

/*
 * drop ioctl
 *
 * Immediately put all linked substreams into SETUP state.
 */

static int snd_pcm_drop(struct snd_pcm_substream *substream)
{
 struct snd_pcm_runtime *runtime;
 int result = 0;
 
 if (PCM_RUNTIME_CHECK(substream))
  return -ENXIO;
 runtime = substream->runtime;

 if (runtime->state == SNDRV_PCM_STATE_OPEN ||
     runtime->state == SNDRV_PCM_STATE_DISCONNECTED)
  return -EBADFD;

 guard(pcm_stream_lock_irq)(substream);
 /* resume pause */
 if (runtime->state == SNDRV_PCM_STATE_PAUSED)
  snd_pcm_pause(substream, false);

 snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
 /* runtime->control->appl_ptr = runtime->status->hw_ptr; */

 return result;
}


static bool is_pcm_file(struct file *file)
{
 struct inode *inode = file_inode(file);
 struct snd_pcm *pcm;
 unsigned int minor;

 if (!S_ISCHR(inode->i_mode) || imajor(inode) != snd_major)
  return false;
 minor = iminor(inode);
 pcm = snd_lookup_minor_data(minor, SNDRV_DEVICE_TYPE_PCM_PLAYBACK);
 if (!pcm)
  pcm = snd_lookup_minor_data(minor, SNDRV_DEVICE_TYPE_PCM_CAPTURE);
 if (!pcm)
  return false;
 snd_card_unref(pcm->card);
 return true;
}

/*
 * PCM link handling
 */

static int snd_pcm_link(struct snd_pcm_substream *substream, int fd)
{
 struct snd_pcm_file *pcm_file;
 struct snd_pcm_substream *substream1;
 struct snd_pcm_group *group __free(kfree) = NULL;
 struct snd_pcm_group *target_group;
 bool nonatomic = substream->pcm->nonatomic;
 CLASS(fd, f)(fd);

 if (fd_empty(f))
  return -EBADFD;
 if (!is_pcm_file(fd_file(f)))
  return -EBADFD;

 pcm_file = fd_file(f)->private_data;
 substream1 = pcm_file->substream;

 if (substream == substream1)
  return -EINVAL;

 group = kzalloc(sizeof(*group), GFP_KERNEL);
 if (!group)
  return -ENOMEM;
 snd_pcm_group_init(group);

 guard(rwsem_write)(&snd_pcm_link_rwsem);
 if (substream->runtime->state == SNDRV_PCM_STATE_OPEN ||
     substream->runtime->state != substream1->runtime->state ||
     substream->pcm->nonatomic != substream1->pcm->nonatomic)
  return -EBADFD;
 if (snd_pcm_stream_linked(substream1))
  return -EALREADY;

 scoped_guard(pcm_stream_lock_irq, substream) {
  if (!snd_pcm_stream_linked(substream)) {
   snd_pcm_group_assign(substream, group);
   group = NULL; /* assigned, don't free this one below */
  }
  target_group = substream->group;
 }

 snd_pcm_group_lock_irq(target_group, nonatomic);
 snd_pcm_stream_lock_nested(substream1);
 snd_pcm_group_assign(substream1, target_group);
 refcount_inc(&target_group->refs);
 snd_pcm_stream_unlock(substream1);
 snd_pcm_group_unlock_irq(target_group, nonatomic);
 return 0;
}

static void relink_to_local(struct snd_pcm_substream *substream)
{
 snd_pcm_stream_lock_nested(substream);
 snd_pcm_group_assign(substream, &substream->self_group);
 snd_pcm_stream_unlock(substream);
}

static int snd_pcm_unlink(struct snd_pcm_substream *substream)
{
 struct snd_pcm_group *group;
 bool nonatomic = substream->pcm->nonatomic;
 bool do_free = false;

 guard(rwsem_write)(&snd_pcm_link_rwsem);

 if (!snd_pcm_stream_linked(substream))
  return -EALREADY;

 group = substream->group;
 snd_pcm_group_lock_irq(group, nonatomic);

 relink_to_local(substream);
 refcount_dec(&group->refs);

 /* detach the last stream, too */
 if (list_is_singular(&group->substreams)) {
  relink_to_local(list_first_entry(&group->substreams,
       struct snd_pcm_substream,
       link_list));
  do_free = refcount_dec_and_test(&group->refs);
 }

 snd_pcm_group_unlock_irq(group, nonatomic);
 if (do_free)
  kfree(group);
 return 0;
}

/*
 * hw configurator
 */

static int snd_pcm_hw_rule_mul(struct snd_pcm_hw_params *params,
          struct snd_pcm_hw_rule *rule)
{
 struct snd_interval t;
 snd_interval_mul(hw_param_interval_c(params, rule->deps[0]),
       hw_param_interval_c(params, rule->deps[1]), &t);
 return snd_interval_refine(hw_param_interval(params, rule->var), &t);
}

static int snd_pcm_hw_rule_div(struct snd_pcm_hw_params *params,
          struct snd_pcm_hw_rule *rule)
{
 struct snd_interval t;
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=96 H=93 G=94

¤ Dauer der Verarbeitung: 0.23 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.