diff options
Diffstat (limited to 'sound/core/pcm_lib.c')
| -rw-r--r-- | sound/core/pcm_lib.c | 526 | 
1 files changed, 460 insertions, 66 deletions
diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index a1707cca9c6..9acc77eae48 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -23,8 +23,10 @@  #include <linux/slab.h>  #include <linux/time.h>  #include <linux/math64.h> +#include <linux/export.h>  #include <sound/core.h>  #include <sound/control.h> +#include <sound/tlv.h>  #include <sound/info.h>  #include <sound/pcm.h>  #include <sound/pcm_params.h> @@ -128,7 +130,8 @@ void snd_pcm_playback_silence(struct snd_pcm_substream *substream, snd_pcm_ufram  	}  } -static void pcm_debug_name(struct snd_pcm_substream *substream, +#ifdef CONFIG_SND_DEBUG +void snd_pcm_debug_name(struct snd_pcm_substream *substream,  			   char *name, size_t len)  {  	snprintf(name, len, "pcmC%dD%d%c:%d", @@ -137,6 +140,8 @@ static void pcm_debug_name(struct snd_pcm_substream *substream,  		 substream->stream ? 'c' : 'p',  		 substream->number);  } +EXPORT_SYMBOL(snd_pcm_debug_name); +#endif  #define XRUN_DEBUG_BASIC	(1<<0)  #define XRUN_DEBUG_STACK	(1<<1)	/* dump also stack */ @@ -168,8 +173,8 @@ static void xrun(struct snd_pcm_substream *substream)  	snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);  	if (xrun_debug(substream, XRUN_DEBUG_BASIC)) {  		char name[16]; -		pcm_debug_name(substream, name, sizeof(name)); -		snd_printd(KERN_DEBUG "XRUN: %s\n", name); +		snd_pcm_debug_name(substream, name, sizeof(name)); +		pcm_warn(substream->pcm, "XRUN: %s\n", name);  		dump_stack_on_xrun(substream);  	}  } @@ -179,9 +184,7 @@ static void xrun(struct snd_pcm_substream *substream)  	do {								\  		if (xrun_debug(substream, XRUN_DEBUG_BASIC)) {		\  			xrun_log_show(substream);			\ -			if (printk_ratelimit()) {			\ -				snd_printd("PCM: " fmt, ##args);	\ -			}						\ +			pr_err_ratelimited("ALSA: PCM: " fmt, ##args);	\  			dump_stack_on_xrun(substream);			\  		}							\  	} while (0) @@ -189,6 +192,7 @@ static void xrun(struct snd_pcm_substream *substream)  #define XRUN_LOG_CNT	10  struct hwptr_log_entry { +	unsigned int in_interrupt;  	unsigned long jiffies;  	snd_pcm_uframes_t pos;  	snd_pcm_uframes_t period_size; @@ -204,7 +208,7 @@ struct snd_pcm_hwptr_log {  };  static void xrun_log(struct snd_pcm_substream *substream, -		     snd_pcm_uframes_t pos) +		     snd_pcm_uframes_t pos, int in_interrupt)  {  	struct snd_pcm_runtime *runtime = substream->runtime;  	struct snd_pcm_hwptr_log *log = runtime->hwptr_log; @@ -220,10 +224,11 @@ static void xrun_log(struct snd_pcm_substream *substream,  			return;  	}  	entry = &log->entries[log->idx]; +	entry->in_interrupt = in_interrupt;  	entry->jiffies = jiffies;  	entry->pos = pos;  	entry->period_size = runtime->period_size; -	entry->buffer_size = runtime->buffer_size;; +	entry->buffer_size = runtime->buffer_size;  	entry->old_hw_ptr = runtime->status->hw_ptr;  	entry->hw_ptr_base = runtime->hw_ptr_base;  	log->idx = (log->idx + 1) % XRUN_LOG_CNT; @@ -241,14 +246,16 @@ static void xrun_log_show(struct snd_pcm_substream *substream)  		return;  	if (xrun_debug(substream, XRUN_DEBUG_LOGONCE) && log->hit)  		return; -	pcm_debug_name(substream, name, sizeof(name)); +	snd_pcm_debug_name(substream, name, sizeof(name));  	for (cnt = 0, idx = log->idx; cnt < XRUN_LOG_CNT; cnt++) {  		entry = &log->entries[idx];  		if (entry->period_size == 0)  			break; -		snd_printd("hwptr log: %s: j=%lu, pos=%ld/%ld/%ld, " +		pr_info("hwptr log: %s: %sj=%lu, pos=%ld/%ld/%ld, "  			   "hwptr=%ld/%ld\n", -			   name, entry->jiffies, (unsigned long)entry->pos, +			   name, entry->in_interrupt ? "[Q] " : "", +			   entry->jiffies, +			   (unsigned long)entry->pos,  			   (unsigned long)entry->period_size,  			   (unsigned long)entry->buffer_size,  			   (unsigned long)entry->old_hw_ptr, @@ -262,7 +269,7 @@ static void xrun_log_show(struct snd_pcm_substream *substream)  #else /* ! CONFIG_SND_PCM_XRUN_DEBUG */  #define hw_ptr_error(substream, fmt, args...) do { } while (0) -#define xrun_log(substream, pos)	do { } while (0) +#define xrun_log(substream, pos, in_interrupt)	do { } while (0)  #define xrun_log_show(substream)	do { } while (0)  #endif @@ -305,9 +312,29 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,  	snd_pcm_uframes_t old_hw_ptr, new_hw_ptr, hw_base;  	snd_pcm_sframes_t hdelta, delta;  	unsigned long jdelta; +	unsigned long curr_jiffies; +	struct timespec curr_tstamp; +	struct timespec audio_tstamp; +	int crossed_boundary = 0;  	old_hw_ptr = runtime->status->hw_ptr; + +	/* +	 * group pointer, time and jiffies reads to allow for more +	 * accurate correlations/corrections. +	 * The values are stored at the end of this routine after +	 * corrections for hw_ptr position +	 */  	pos = substream->ops->pointer(substream); +	curr_jiffies = jiffies; +	if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) { +		snd_pcm_gettime(runtime, (struct timespec *)&curr_tstamp); + +		if ((runtime->hw.info & SNDRV_PCM_INFO_HAS_WALL_CLOCK) && +			(substream->ops->wall_clock)) +			substream->ops->wall_clock(substream, &audio_tstamp); +	} +  	if (pos == SNDRV_PCM_POS_XRUN) {  		xrun(substream);  		return -EPIPE; @@ -315,18 +342,18 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,  	if (pos >= runtime->buffer_size) {  		if (printk_ratelimit()) {  			char name[16]; -			pcm_debug_name(substream, name, sizeof(name)); +			snd_pcm_debug_name(substream, name, sizeof(name));  			xrun_log_show(substream); -			snd_printd(KERN_ERR  "BUG: %s, pos = %ld, " -				   "buffer size = %ld, period size = %ld\n", -				   name, pos, runtime->buffer_size, -				   runtime->period_size); +			pcm_err(substream->pcm, +				"XRUN: %s, pos = %ld, buffer size = %ld, period size = %ld\n", +				name, pos, runtime->buffer_size, +				runtime->period_size);  		}  		pos = 0;  	}  	pos -= pos % runtime->min_align;  	if (xrun_debug(substream, XRUN_DEBUG_LOG)) -		xrun_log(substream, pos); +		xrun_log(substream, pos, in_interrupt);  	hw_base = runtime->hw_ptr_base;  	new_hw_ptr = hw_base + pos;  	if (in_interrupt) { @@ -335,11 +362,13 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,  		delta = runtime->hw_ptr_interrupt + runtime->period_size;  		if (delta > new_hw_ptr) {  			/* check for double acknowledged interrupts */ -			hdelta = jiffies - runtime->hw_ptr_jiffies; +			hdelta = curr_jiffies - runtime->hw_ptr_jiffies;  			if (hdelta > runtime->hw_ptr_buffer_jiffies/2) {  				hw_base += runtime->buffer_size; -				if (hw_base >= runtime->boundary) +				if (hw_base >= runtime->boundary) {  					hw_base = 0; +					crossed_boundary++; +				}  				new_hw_ptr = hw_base + pos;  				goto __delta;  			} @@ -349,8 +378,10 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,  	/* pointer crosses the end of the ring buffer */  	if (new_hw_ptr < old_hw_ptr) {  		hw_base += runtime->buffer_size; -		if (hw_base >= runtime->boundary) +		if (hw_base >= runtime->boundary) {  			hw_base = 0; +			crossed_boundary++; +		}  		new_hw_ptr = hw_base + pos;  	}        __delta: @@ -360,9 +391,9 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,  	if (xrun_debug(substream, in_interrupt ?  			XRUN_DEBUG_PERIODUPDATE : XRUN_DEBUG_HWPTRUPDATE)) {  		char name[16]; -		pcm_debug_name(substream, name, sizeof(name)); -		snd_printd("%s_update: %s: pos=%u/%u/%u, " -			   "hwptr=%ld/%ld/%ld/%ld\n", +		snd_pcm_debug_name(substream, name, sizeof(name)); +		pcm_dbg(substream->pcm, +			"%s_update: %s: pos=%u/%u/%u, hwptr=%ld/%ld/%ld/%ld\n",  			   in_interrupt ? "period" : "hwptr",  			   name,  			   (unsigned int)pos, @@ -373,6 +404,31 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,  			   (unsigned long)new_hw_ptr,  			   (unsigned long)runtime->hw_ptr_base);  	} + +	if (runtime->no_period_wakeup) { +		snd_pcm_sframes_t xrun_threshold; +		/* +		 * Without regular period interrupts, we have to check +		 * the elapsed time to detect xruns. +		 */ +		jdelta = curr_jiffies - runtime->hw_ptr_jiffies; +		if (jdelta < runtime->hw_ptr_buffer_jiffies / 2) +			goto no_delta_check; +		hdelta = jdelta - delta * HZ / runtime->rate; +		xrun_threshold = runtime->hw_ptr_buffer_jiffies / 2 + 1; +		while (hdelta > xrun_threshold) { +			delta += runtime->buffer_size; +			hw_base += runtime->buffer_size; +			if (hw_base >= runtime->boundary) { +				hw_base = 0; +				crossed_boundary++; +			} +			new_hw_ptr = hw_base + pos; +			hdelta -= runtime->hw_ptr_buffer_jiffies; +		} +		goto no_delta_check; +	} +  	/* something must be really wrong */  	if (delta >= runtime->buffer_size + runtime->period_size) {  		hw_ptr_error(substream, @@ -399,7 +455,7 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,  	if (hdelta < runtime->delay)  		goto no_jiffies_check;  	hdelta -= runtime->delay; -	jdelta = jiffies - runtime->hw_ptr_jiffies; +	jdelta = curr_jiffies - runtime->hw_ptr_jiffies;  	if (((hdelta * HZ) / runtime->rate) > jdelta + HZ/100) {  		delta = jdelta /  			(((runtime->period_size * HZ) / runtime->rate) @@ -411,8 +467,10 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,  		/* the delta value is small or zero in most cases */  		while (delta > 0) {  			new_hw_ptr += runtime->period_size; -			if (new_hw_ptr >= runtime->boundary) +			if (new_hw_ptr >= runtime->boundary) {  				new_hw_ptr -= runtime->boundary; +				crossed_boundary--; +			}  			delta--;  		}  		/* align hw_base to buffer_size */ @@ -442,6 +500,7 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,  			     (long)old_hw_ptr);  	} + no_delta_check:  	if (runtime->status->hw_ptr == new_hw_ptr)  		return 0; @@ -460,9 +519,35 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,  	}  	runtime->hw_ptr_base = hw_base;  	runtime->status->hw_ptr = new_hw_ptr; -	runtime->hw_ptr_jiffies = jiffies; -	if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) -		snd_pcm_gettime(runtime, (struct timespec *)&runtime->status->tstamp); +	runtime->hw_ptr_jiffies = curr_jiffies; +	if (crossed_boundary) { +		snd_BUG_ON(crossed_boundary != 1); +		runtime->hw_ptr_wrap += runtime->boundary; +	} +	if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) { +		runtime->status->tstamp = curr_tstamp; + +		if (!(runtime->hw.info & SNDRV_PCM_INFO_HAS_WALL_CLOCK)) { +			/* +			 * no wall clock available, provide audio timestamp +			 * derived from pointer position+delay +			 */ +			u64 audio_frames, audio_nsecs; + +			if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) +				audio_frames = runtime->hw_ptr_wrap +					+ runtime->status->hw_ptr +					- runtime->delay; +			else +				audio_frames = runtime->hw_ptr_wrap +					+ runtime->status->hw_ptr +					+ runtime->delay; +			audio_nsecs = div_u64(audio_frames * 1000000000LL, +					runtime->rate); +			audio_tstamp = ns_to_timespec(audio_nsecs); +		} +		runtime->status->audio_tstamp = audio_tstamp; +	}  	return snd_pcm_update_state(substream, runtime);  } @@ -481,7 +566,8 @@ int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream)   *   * Sets the given PCM operators to the pcm instance.   */ -void snd_pcm_set_ops(struct snd_pcm *pcm, int direction, struct snd_pcm_ops *ops) +void snd_pcm_set_ops(struct snd_pcm *pcm, int direction, +		     const struct snd_pcm_ops *ops)  {  	struct snd_pcm_str *stream = &pcm->streams[direction];  	struct snd_pcm_substream *substream; @@ -579,7 +665,8 @@ static inline unsigned int muldiv32(unsigned int a, unsigned int b,   * The interval is changed to the range satisfying both intervals.   * The interval status (min, max, integer, etc.) are evaluated.   * - * Returns non-zero if the value is changed, zero if not changed. + * Return: Positive if the value is changed, zero if it's not changed, or a + * negative error code.   */  int snd_interval_refine(struct snd_interval *i, const struct snd_interval *v)  { @@ -778,7 +865,8 @@ void snd_interval_mulkdiv(const struct snd_interval *a, unsigned int k,   * @nump: pointer to store the resultant numerator   * @denp: pointer to store the resultant denominator   * - * Returns non-zero if the value is changed, zero if not changed. + * Return: Positive if the value is changed, zero if it's not changed, or a + * negative error code.   */  int snd_interval_ratnum(struct snd_interval *i,  			unsigned int rats_count, struct snd_ratnum *rats, @@ -896,7 +984,8 @@ EXPORT_SYMBOL(snd_interval_ratnum);   * @nump: pointer to store the resultant numerator   * @denp: pointer to store the resultant denominator   * - * Returns non-zero if the value is changed, zero if not changed. + * Return: Positive if the value is changed, zero if it's not changed, or a + * negative error code.   */  static int snd_interval_ratden(struct snd_interval *i,  			       unsigned int rats_count, struct snd_ratden *rats, @@ -995,9 +1084,11 @@ static int snd_interval_ratden(struct snd_interval *i,   * When mask is non-zero, only the elements corresponding to bit 1 are   * evaluated.   * - * Returns non-zero if the value is changed, zero if not changed. + * Return: Positive if the value is changed, zero if it's not changed, or a + * negative error code.   */ -int snd_interval_list(struct snd_interval *i, unsigned int count, unsigned int *list, unsigned int mask) +int snd_interval_list(struct snd_interval *i, unsigned int count, +		      const unsigned int *list, unsigned int mask)  {          unsigned int k;  	struct snd_interval list_range; @@ -1054,7 +1145,7 @@ static int snd_interval_step(struct snd_interval *i, unsigned int min, unsigned   * @private: the private data pointer passed to function   * @dep: the dependent variables   * - * Returns zero if successful, or a negative error code on failure. + * Return: Zero if successful, or a negative error code on failure.   */  int snd_pcm_hw_rule_add(struct snd_pcm_runtime *runtime, unsigned int cond,  			int var, @@ -1070,8 +1161,10 @@ int snd_pcm_hw_rule_add(struct snd_pcm_runtime *runtime, unsigned int cond,  		struct snd_pcm_hw_rule *new;  		unsigned int new_rules = constrs->rules_all + 16;  		new = kcalloc(new_rules, sizeof(*c), GFP_KERNEL); -		if (!new) +		if (!new) { +			va_end(args);  			return -ENOMEM; +		}  		if (constrs->rules) {  			memcpy(new, constrs->rules,  			       constrs->rules_num * sizeof(*c)); @@ -1087,8 +1180,10 @@ int snd_pcm_hw_rule_add(struct snd_pcm_runtime *runtime, unsigned int cond,  	c->private = private;  	k = 0;  	while (1) { -		if (snd_BUG_ON(k >= ARRAY_SIZE(c->deps))) +		if (snd_BUG_ON(k >= ARRAY_SIZE(c->deps))) { +			va_end(args);  			return -EINVAL; +		}  		c->deps[k++] = dep;  		if (dep < 0)  			break; @@ -1097,7 +1192,7 @@ int snd_pcm_hw_rule_add(struct snd_pcm_runtime *runtime, unsigned int cond,  	constrs->rules_num++;  	va_end(args);  	return 0; -}				     +}  EXPORT_SYMBOL(snd_pcm_hw_rule_add); @@ -1108,6 +1203,8 @@ EXPORT_SYMBOL(snd_pcm_hw_rule_add);   * @mask: the bitmap mask   *   * Apply the constraint of the given bitmap mask to a 32-bit mask parameter. + * + * Return: Zero if successful, or a negative error code on failure.   */  int snd_pcm_hw_constraint_mask(struct snd_pcm_runtime *runtime, snd_pcm_hw_param_t var,  			       u_int32_t mask) @@ -1128,6 +1225,8 @@ int snd_pcm_hw_constraint_mask(struct snd_pcm_runtime *runtime, snd_pcm_hw_param   * @mask: the 64bit bitmap mask   *   * Apply the constraint of the given bitmap mask to a 64-bit mask parameter. + * + * Return: Zero if successful, or a negative error code on failure.   */  int snd_pcm_hw_constraint_mask64(struct snd_pcm_runtime *runtime, snd_pcm_hw_param_t var,  				 u_int64_t mask) @@ -1141,6 +1240,7 @@ int snd_pcm_hw_constraint_mask64(struct snd_pcm_runtime *runtime, snd_pcm_hw_par  		return -EINVAL;  	return 0;  } +EXPORT_SYMBOL(snd_pcm_hw_constraint_mask64);  /**   * snd_pcm_hw_constraint_integer - apply an integer constraint to an interval @@ -1148,6 +1248,9 @@ int snd_pcm_hw_constraint_mask64(struct snd_pcm_runtime *runtime, snd_pcm_hw_par   * @var: hw_params variable to apply the integer constraint   *   * Apply the constraint of integer to an interval parameter. + * + * Return: Positive if the value is changed, zero if it's not changed, or a + * negative error code.   */  int snd_pcm_hw_constraint_integer(struct snd_pcm_runtime *runtime, snd_pcm_hw_param_t var)  { @@ -1165,6 +1268,9 @@ EXPORT_SYMBOL(snd_pcm_hw_constraint_integer);   * @max: the maximal value   *    * Apply the min/max range constraint to an interval parameter. + * + * Return: Positive if the value is changed, zero if it's not changed, or a + * negative error code.   */  int snd_pcm_hw_constraint_minmax(struct snd_pcm_runtime *runtime, snd_pcm_hw_param_t var,  				 unsigned int min, unsigned int max) @@ -1196,14 +1302,16 @@ static int snd_pcm_hw_rule_list(struct snd_pcm_hw_params *params,   * @l: list   *    * Apply the list of constraints to an interval parameter. + * + * Return: Zero if successful, or a negative error code on failure.   */  int snd_pcm_hw_constraint_list(struct snd_pcm_runtime *runtime,  			       unsigned int cond,  			       snd_pcm_hw_param_t var, -			       struct snd_pcm_hw_constraint_list *l) +			       const struct snd_pcm_hw_constraint_list *l)  {  	return snd_pcm_hw_rule_add(runtime, cond, var, -				   snd_pcm_hw_rule_list, l, +				   snd_pcm_hw_rule_list, (void *)l,  				   var, -1);  } @@ -1230,6 +1338,8 @@ static int snd_pcm_hw_rule_ratnums(struct snd_pcm_hw_params *params,   * @cond: condition bits   * @var: hw_params variable to apply the ratnums constraint   * @r: struct snd_ratnums constriants + * + * Return: Zero if successful, or a negative error code on failure.   */  int snd_pcm_hw_constraint_ratnums(struct snd_pcm_runtime *runtime,   				  unsigned int cond, @@ -1263,6 +1373,8 @@ static int snd_pcm_hw_rule_ratdens(struct snd_pcm_hw_params *params,   * @cond: condition bits   * @var: hw_params variable to apply the ratdens constraint   * @r: struct snd_ratdens constriants + * + * Return: Zero if successful, or a negative error code on failure.   */  int snd_pcm_hw_constraint_ratdens(struct snd_pcm_runtime *runtime,   				  unsigned int cond, @@ -1294,6 +1406,8 @@ static int snd_pcm_hw_rule_msbits(struct snd_pcm_hw_params *params,   * @cond: condition bits   * @width: sample bits width   * @msbits: msbits width + * + * Return: Zero if successful, or a negative error code on failure.   */  int snd_pcm_hw_constraint_msbits(struct snd_pcm_runtime *runtime,   				 unsigned int cond, @@ -1322,6 +1436,8 @@ static int snd_pcm_hw_rule_step(struct snd_pcm_hw_params *params,   * @cond: condition bits   * @var: hw_params variable to apply the step constraint   * @step: step size + * + * Return: Zero if successful, or a negative error code on failure.   */  int snd_pcm_hw_constraint_step(struct snd_pcm_runtime *runtime,  			       unsigned int cond, @@ -1352,6 +1468,8 @@ static int snd_pcm_hw_rule_pow2(struct snd_pcm_hw_params *params, struct snd_pcm   * @runtime: PCM runtime instance   * @cond: condition bits   * @var: hw_params variable to apply the power-of-2 constraint + * + * Return: Zero if successful, or a negative error code on failure.   */  int snd_pcm_hw_constraint_pow2(struct snd_pcm_runtime *runtime,  			       unsigned int cond, @@ -1364,6 +1482,34 @@ int snd_pcm_hw_constraint_pow2(struct snd_pcm_runtime *runtime,  EXPORT_SYMBOL(snd_pcm_hw_constraint_pow2); +static int snd_pcm_hw_rule_noresample_func(struct snd_pcm_hw_params *params, +					   struct snd_pcm_hw_rule *rule) +{ +	unsigned int base_rate = (unsigned int)(uintptr_t)rule->private; +	struct snd_interval *rate; + +	rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); +	return snd_interval_list(rate, 1, &base_rate, 0); +} + +/** + * snd_pcm_hw_rule_noresample - add a rule to allow disabling hw resampling + * @runtime: PCM runtime instance + * @base_rate: the rate at which the hardware does not resample + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_hw_rule_noresample(struct snd_pcm_runtime *runtime, +			       unsigned int base_rate) +{ +	return snd_pcm_hw_rule_add(runtime, SNDRV_PCM_HW_PARAMS_NORESAMPLE, +				   SNDRV_PCM_HW_PARAM_RATE, +				   snd_pcm_hw_rule_noresample_func, +				   (void *)(uintptr_t)base_rate, +				   SNDRV_PCM_HW_PARAM_RATE, -1); +} +EXPORT_SYMBOL(snd_pcm_hw_rule_noresample); +  static void _snd_pcm_hw_param_any(struct snd_pcm_hw_params *params,  				  snd_pcm_hw_param_t var)  { @@ -1401,8 +1547,8 @@ EXPORT_SYMBOL(_snd_pcm_hw_params_any);   * @var: parameter to retrieve   * @dir: pointer to the direction (-1,0,1) or %NULL   * - * Return the value for field @var if it's fixed in configuration space - * defined by @params. Return -%EINVAL otherwise. + * Return: The value for field @var if it's fixed in configuration space + * defined by @params. -%EINVAL otherwise.   */  int snd_pcm_hw_param_value(const struct snd_pcm_hw_params *params,  			   snd_pcm_hw_param_t var, int *dir) @@ -1473,7 +1619,8 @@ static int _snd_pcm_hw_param_first(struct snd_pcm_hw_params *params,   *   * Inside configuration space defined by @params remove from @var all   * values > minimum. Reduce configuration space accordingly. - * Return the minimum. + * + * Return: The minimum, or a negative error code on failure.   */  int snd_pcm_hw_param_first(struct snd_pcm_substream *pcm,   			   struct snd_pcm_hw_params *params,  @@ -1519,7 +1666,8 @@ static int _snd_pcm_hw_param_last(struct snd_pcm_hw_params *params,   *   * Inside configuration space defined by @params remove from @var all   * values < maximum. Reduce configuration space accordingly. - * Return the maximum. + * + * Return: The maximum, or a negative error code on failure.   */  int snd_pcm_hw_param_last(struct snd_pcm_substream *pcm,   			  struct snd_pcm_hw_params *params, @@ -1547,6 +1695,8 @@ EXPORT_SYMBOL(snd_pcm_hw_param_last);   * 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.   */  int snd_pcm_hw_params_choose(struct snd_pcm_substream *pcm,  			     struct snd_pcm_hw_params *params) @@ -1584,8 +1734,10 @@ static int snd_pcm_lib_ioctl_reset(struct snd_pcm_substream *substream,  	if (snd_pcm_running(substream) &&  	    snd_pcm_update_hw_ptr(substream) >= 0)  		runtime->status->hw_ptr %= runtime->buffer_size; -	else +	else {  		runtime->status->hw_ptr = 0; +		runtime->hw_ptr_wrap = 0; +	}  	snd_pcm_stream_unlock_irqrestore(substream, flags);  	return 0;  } @@ -1651,7 +1803,7 @@ static int snd_pcm_lib_ioctl_fifo_size(struct snd_pcm_substream *substream,   * Processes the generic ioctl commands for PCM.   * Can be passed as the ioctl callback for PCM ops.   * - * Returns zero if successful, or a negative error code on failure. + * Return: Zero if successful, or a negative error code on failure.   */  int snd_pcm_lib_ioctl(struct snd_pcm_substream *substream,  		      unsigned int cmd, void *arg) @@ -1724,19 +1876,48 @@ static int wait_for_avail(struct snd_pcm_substream *substream,  	wait_queue_t wait;  	int err = 0;  	snd_pcm_uframes_t avail = 0; -	long tout; +	long wait_time, tout;  	init_waitqueue_entry(&wait, current); +	set_current_state(TASK_INTERRUPTIBLE);  	add_wait_queue(&runtime->tsleep, &wait); + +	if (runtime->no_period_wakeup) +		wait_time = MAX_SCHEDULE_TIMEOUT; +	else { +		wait_time = 10; +		if (runtime->rate) { +			long t = runtime->period_size * 2 / runtime->rate; +			wait_time = max(t, wait_time); +		} +		wait_time = msecs_to_jiffies(wait_time * 1000); +	} +  	for (;;) {  		if (signal_pending(current)) {  			err = -ERESTARTSYS;  			break;  		} -		set_current_state(TASK_INTERRUPTIBLE); + +		/* +		 * We need to check if space became available already +		 * (and thus the wakeup happened already) first to close +		 * the race of space already having become available. +		 * This check must happen after been added to the waitqueue +		 * and having current state be INTERRUPTIBLE. +		 */ +		if (is_playback) +			avail = snd_pcm_playback_avail(runtime); +		else +			avail = snd_pcm_capture_avail(runtime); +		if (avail >= runtime->twake) +			break;  		snd_pcm_stream_unlock_irq(substream); -		tout = schedule_timeout(msecs_to_jiffies(10000)); + +		tout = schedule_timeout(wait_time); +  		snd_pcm_stream_lock_irq(substream); +		set_current_state(TASK_INTERRUPTIBLE);  		switch (runtime->status->state) {  		case SNDRV_PCM_STATE_SUSPENDED:  			err = -ESTRPIPE; @@ -1755,21 +1936,19 @@ static int wait_for_avail(struct snd_pcm_substream *substream,  		case SNDRV_PCM_STATE_DISCONNECTED:  			err = -EBADFD;  			goto _endloop; +		case SNDRV_PCM_STATE_PAUSED: +			continue;  		}  		if (!tout) { -			snd_printd("%s write error (DMA or IRQ trouble?)\n", -				   is_playback ? "playback" : "capture"); +			pcm_dbg(substream->pcm, +				"%s write error (DMA or IRQ trouble?)\n", +				is_playback ? "playback" : "capture");  			err = -EIO;  			break;  		} -		if (is_playback) -			avail = snd_pcm_playback_avail(runtime); -		else -			avail = snd_pcm_capture_avail(runtime); -		if (avail >= runtime->twake) -			break;  	}   _endloop: +	set_current_state(TASK_RUNNING);  	remove_wait_queue(&runtime->tsleep, &wait);  	*availp = avail;  	return err; @@ -1807,6 +1986,7 @@ static snd_pcm_sframes_t snd_pcm_lib_write1(struct snd_pcm_substream *substream,  	struct snd_pcm_runtime *runtime = substream->runtime;  	snd_pcm_uframes_t xfer = 0;  	snd_pcm_uframes_t offset = 0; +	snd_pcm_uframes_t avail;  	int err = 0;  	if (size == 0) @@ -1830,13 +2010,12 @@ static snd_pcm_sframes_t snd_pcm_lib_write1(struct snd_pcm_substream *substream,  	}  	runtime->twake = runtime->control->avail_min ? : 1; +	if (runtime->status->state == SNDRV_PCM_STATE_RUNNING) +		snd_pcm_update_hw_ptr(substream); +	avail = snd_pcm_playback_avail(runtime);  	while (size > 0) {  		snd_pcm_uframes_t frames, appl_ptr, appl_ofs; -		snd_pcm_uframes_t avail;  		snd_pcm_uframes_t cont; -		if (runtime->status->state == SNDRV_PCM_STATE_RUNNING) -			snd_pcm_update_hw_ptr(substream); -		avail = snd_pcm_playback_avail(runtime);  		if (!avail) {  			if (nonblock) {  				err = -EAGAIN; @@ -1884,6 +2063,7 @@ static snd_pcm_sframes_t snd_pcm_lib_write1(struct snd_pcm_substream *substream,  		offset += frames;  		size -= frames;  		xfer += frames; +		avail -= frames;  		if (runtime->status->state == SNDRV_PCM_STATE_PREPARED &&  		    snd_pcm_playback_hw_avail(runtime) >= (snd_pcm_sframes_t)runtime->start_threshold) {  			err = snd_pcm_start(substream); @@ -2024,6 +2204,7 @@ static snd_pcm_sframes_t snd_pcm_lib_read1(struct snd_pcm_substream *substream,  	struct snd_pcm_runtime *runtime = substream->runtime;  	snd_pcm_uframes_t xfer = 0;  	snd_pcm_uframes_t offset = 0; +	snd_pcm_uframes_t avail;  	int err = 0;  	if (size == 0) @@ -2054,13 +2235,12 @@ static snd_pcm_sframes_t snd_pcm_lib_read1(struct snd_pcm_substream *substream,  	}  	runtime->twake = runtime->control->avail_min ? : 1; +	if (runtime->status->state == SNDRV_PCM_STATE_RUNNING) +		snd_pcm_update_hw_ptr(substream); +	avail = snd_pcm_capture_avail(runtime);  	while (size > 0) {  		snd_pcm_uframes_t frames, appl_ptr, appl_ofs; -		snd_pcm_uframes_t avail;  		snd_pcm_uframes_t cont; -		if (runtime->status->state == SNDRV_PCM_STATE_RUNNING) -			snd_pcm_update_hw_ptr(substream); -		avail = snd_pcm_capture_avail(runtime);  		if (!avail) {  			if (runtime->status->state ==  			    SNDRV_PCM_STATE_DRAINING) { @@ -2115,6 +2295,7 @@ static snd_pcm_sframes_t snd_pcm_lib_read1(struct snd_pcm_substream *substream,  		offset += frames;  		size -= frames;  		xfer += frames; +		avail -= frames;  	}   _end_unlock:  	runtime->twake = 0; @@ -2200,3 +2381,216 @@ snd_pcm_sframes_t snd_pcm_lib_readv(struct snd_pcm_substream *substream,  }  EXPORT_SYMBOL(snd_pcm_lib_readv); + +/* + * standard channel mapping helpers + */ + +/* default channel maps for multi-channel playbacks, up to 8 channels */ +const struct snd_pcm_chmap_elem snd_pcm_std_chmaps[] = { +	{ .channels = 1, +	  .map = { SNDRV_CHMAP_MONO } }, +	{ .channels = 2, +	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } }, +	{ .channels = 4, +	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, +		   SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, +	{ .channels = 6, +	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, +		   SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, +		   SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE } }, +	{ .channels = 8, +	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, +		   SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, +		   SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, +		   SNDRV_CHMAP_SL, SNDRV_CHMAP_SR } }, +	{ } +}; +EXPORT_SYMBOL_GPL(snd_pcm_std_chmaps); + +/* alternative channel maps with CLFE <-> surround swapped for 6/8 channels */ +const struct snd_pcm_chmap_elem snd_pcm_alt_chmaps[] = { +	{ .channels = 1, +	  .map = { SNDRV_CHMAP_MONO } }, +	{ .channels = 2, +	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } }, +	{ .channels = 4, +	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, +		   SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, +	{ .channels = 6, +	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, +		   SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, +		   SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, +	{ .channels = 8, +	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, +		   SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, +		   SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, +		   SNDRV_CHMAP_SL, SNDRV_CHMAP_SR } }, +	{ } +}; +EXPORT_SYMBOL_GPL(snd_pcm_alt_chmaps); + +static bool valid_chmap_channels(const struct snd_pcm_chmap *info, int ch) +{ +	if (ch > info->max_channels) +		return false; +	return !info->channel_mask || (info->channel_mask & (1U << ch)); +} + +static int pcm_chmap_ctl_info(struct snd_kcontrol *kcontrol, +			      struct snd_ctl_elem_info *uinfo) +{ +	struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); + +	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; +	uinfo->count = 0; +	uinfo->count = info->max_channels; +	uinfo->value.integer.min = 0; +	uinfo->value.integer.max = SNDRV_CHMAP_LAST; +	return 0; +} + +/* get callback for channel map ctl element + * stores the channel position firstly matching with the current channels + */ +static int pcm_chmap_ctl_get(struct snd_kcontrol *kcontrol, +			     struct snd_ctl_elem_value *ucontrol) +{ +	struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); +	unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); +	struct snd_pcm_substream *substream; +	const struct snd_pcm_chmap_elem *map; + +	if (snd_BUG_ON(!info->chmap)) +		return -EINVAL; +	substream = snd_pcm_chmap_substream(info, idx); +	if (!substream) +		return -ENODEV; +	memset(ucontrol->value.integer.value, 0, +	       sizeof(ucontrol->value.integer.value)); +	if (!substream->runtime) +		return 0; /* no channels set */ +	for (map = info->chmap; map->channels; map++) { +		int i; +		if (map->channels == substream->runtime->channels && +		    valid_chmap_channels(info, map->channels)) { +			for (i = 0; i < map->channels; i++) +				ucontrol->value.integer.value[i] = map->map[i]; +			return 0; +		} +	} +	return -EINVAL; +} + +/* tlv callback for channel map ctl element + * expands the pre-defined channel maps in a form of TLV + */ +static int pcm_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag, +			     unsigned int size, unsigned int __user *tlv) +{ +	struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); +	const struct snd_pcm_chmap_elem *map; +	unsigned int __user *dst; +	int c, count = 0; + +	if (snd_BUG_ON(!info->chmap)) +		return -EINVAL; +	if (size < 8) +		return -ENOMEM; +	if (put_user(SNDRV_CTL_TLVT_CONTAINER, tlv)) +		return -EFAULT; +	size -= 8; +	dst = tlv + 2; +	for (map = info->chmap; map->channels; map++) { +		int chs_bytes = map->channels * 4; +		if (!valid_chmap_channels(info, map->channels)) +			continue; +		if (size < 8) +			return -ENOMEM; +		if (put_user(SNDRV_CTL_TLVT_CHMAP_FIXED, dst) || +		    put_user(chs_bytes, dst + 1)) +			return -EFAULT; +		dst += 2; +		size -= 8; +		count += 8; +		if (size < chs_bytes) +			return -ENOMEM; +		size -= chs_bytes; +		count += chs_bytes; +		for (c = 0; c < map->channels; c++) { +			if (put_user(map->map[c], dst)) +				return -EFAULT; +			dst++; +		} +	} +	if (put_user(count, tlv + 1)) +		return -EFAULT; +	return 0; +} + +static void pcm_chmap_ctl_private_free(struct snd_kcontrol *kcontrol) +{ +	struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); +	info->pcm->streams[info->stream].chmap_kctl = NULL; +	kfree(info); +} + +/** + * snd_pcm_add_chmap_ctls - create channel-mapping control elements + * @pcm: the assigned PCM instance + * @stream: stream direction + * @chmap: channel map elements (for query) + * @max_channels: the max number of channels for the stream + * @private_value: the value passed to each kcontrol's private_value field + * @info_ret: store struct snd_pcm_chmap instance if non-NULL + * + * Create channel-mapping control elements assigned to the given PCM stream(s). + * Return: Zero if successful, or a negative error value. + */ +int snd_pcm_add_chmap_ctls(struct snd_pcm *pcm, int stream, +			   const struct snd_pcm_chmap_elem *chmap, +			   int max_channels, +			   unsigned long private_value, +			   struct snd_pcm_chmap **info_ret) +{ +	struct snd_pcm_chmap *info; +	struct snd_kcontrol_new knew = { +		.iface = SNDRV_CTL_ELEM_IFACE_PCM, +		.access = SNDRV_CTL_ELEM_ACCESS_READ | +			SNDRV_CTL_ELEM_ACCESS_TLV_READ | +			SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, +		.info = pcm_chmap_ctl_info, +		.get = pcm_chmap_ctl_get, +		.tlv.c = pcm_chmap_ctl_tlv, +	}; +	int err; + +	info = kzalloc(sizeof(*info), GFP_KERNEL); +	if (!info) +		return -ENOMEM; +	info->pcm = pcm; +	info->stream = stream; +	info->chmap = chmap; +	info->max_channels = max_channels; +	if (stream == SNDRV_PCM_STREAM_PLAYBACK) +		knew.name = "Playback Channel Map"; +	else +		knew.name = "Capture Channel Map"; +	knew.device = pcm->device; +	knew.count = pcm->streams[stream].substream_count; +	knew.private_value = private_value; +	info->kctl = snd_ctl_new1(&knew, info); +	if (!info->kctl) { +		kfree(info); +		return -ENOMEM; +	} +	info->kctl->private_free = pcm_chmap_ctl_private_free; +	err = snd_ctl_add(pcm->card, info->kctl); +	if (err < 0) +		return err; +	pcm->streams[stream].chmap_kctl = info->kctl; +	if (info_ret) +		*info_ret = info; +	return 0; +} +EXPORT_SYMBOL_GPL(snd_pcm_add_chmap_ctls);  | 
