2019-05-27 13:55:05 +07:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Digital Audio (PCM) abstract layer
|
2007-10-15 14:50:19 +07:00
|
|
|
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
2005-04-17 05:20:36 +07:00
|
|
|
* Abramo Bagnara <abramo@alsa-project.org>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/slab.h>
|
2017-02-03 01:15:33 +07:00
|
|
|
#include <linux/sched/signal.h>
|
2005-04-17 05:20:36 +07:00
|
|
|
#include <linux/time.h>
|
2009-06-05 22:40:04 +07:00
|
|
|
#include <linux/math64.h>
|
2011-09-22 20:34:58 +07:00
|
|
|
#include <linux/export.h>
|
2005-04-17 05:20:36 +07:00
|
|
|
#include <sound/core.h>
|
|
|
|
#include <sound/control.h>
|
2012-07-27 23:27:00 +07:00
|
|
|
#include <sound/tlv.h>
|
2005-04-17 05:20:36 +07:00
|
|
|
#include <sound/info.h>
|
|
|
|
#include <sound/pcm.h>
|
|
|
|
#include <sound/pcm_params.h>
|
|
|
|
#include <sound/timer.h>
|
|
|
|
|
2017-05-26 07:30:46 +07:00
|
|
|
#include "pcm_local.h"
|
|
|
|
|
ALSA: pcm: Replace PCM hwptr tracking with tracepoints
ALSA PCM core has a mechanism tracking the PCM hwptr updates for
analyzing XRUNs. But its log is limited (up to 10) and its log output
is a kernel message, which is hard to handle.
In this patch, the hwptr logging is moved to the tracing
infrastructure instead of its own. Not only the hwptr updates but
also XRUN and hwptr errors are recorded on the trace log, so that user
can see such events at the exact timing.
The new "snd_pcm" entry will appear in the tracing events:
# ls -F /sys/kernel/debug/tracing/events/snd_pcm
enable filter hw_ptr_error/ hwptr/ xrun/
The hwptr is for the regular hwptr update events. An event trace
looks like:
aplay-26187 [004] d..3 4012.834761: hwptr: pcmC0D0p/sub0: POS: pos=488, old=0, base=0, period=1024, buf=16384
"POS" shows the hwptr update by the explicit position update call and
"IRQ" means the hwptr update by the interrupt,
i.e. snd_pcm_period_elapsed() call. The "pos" is the passed
ring-buffer offset by the caller, "old" is the previous hwptr, "base"
is the hwptr base position, "period" and "buf" are period- and
buffer-size of the target PCM substream.
(Note that the hwptr position displayed here isn't the ring-buffer
offset. It increments up to the PCM position boundary.)
The XRUN event appears similarly, but without "pos" field.
The hwptr error events appear with the PCM identifier and its reason
string, such as "Lost interrupt?".
The XRUN and hwptr error reports on kernel message are still left, can
be turned on/off via xrun_debug proc like before. But the bit 3, 4, 5
and 6 bits of xrun_debug proc are dropped by this patch. Also, along
with the change, the message strings have been reformatted to be a bit
more consistent.
Last but not least, the hwptr reporting is enabled only when
CONFIG_SND_PCM_XRUN_DEBUG is set.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2014-11-04 18:45:59 +07:00
|
|
|
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
|
|
|
|
#define CREATE_TRACE_POINTS
|
|
|
|
#include "pcm_trace.h"
|
|
|
|
#else
|
|
|
|
#define trace_hwptr(substream, pos, in_interrupt)
|
|
|
|
#define trace_xrun(substream)
|
|
|
|
#define trace_hw_ptr_error(substream, reason)
|
ALSA: pcm: add 'applptr' event of tracepoint
In design of ALSA PCM core, status and control data for runtime of ALSA
PCM substream are shared between kernel/user spaces by page frame
mapping with read-only attribute. Both of hardware-side and
application-side position on PCM buffer are maintained as a part of
the status data. In a view of ALSA PCM application, these two positions
can be updated by executing ioctl(2) with some commands.
There's an event of tracepoint for hardware-side position; 'hwptr'.
On the other hand, no events for application-side position. This commit
adds a new event for this purpose; 'applptr'. When the application-side
position is changed in kernel space, this event is probed with useful
information for developers.
I note that the event is not probed for all of ALSA PCM applications, When
applications are written by read/write programming scenario, the event is
surely probed. The applications execute ioctl(2) with
SNDRV_PCM_IOCTL_[READ|WRITE][N/I]_FRAMES to read/write any PCM frame, then
ALSA PCM core updates the application-side position in kernel land.
However, when applications are written by mmap programming scenario, if
maintaining the application side position in kernel space accurately,
applications should voluntarily execute ioctl(2) with
SNDRV_PCM_IOCTL_SYNC_PTR to commit the number of handled PCM frames. If
not voluntarily, the application-side position is not changed, thus the
added event is not probed.
There's a loophole, using architectures to which ALSA PCM core judges
non cache coherent. In this case, the status and control data is not mapped
into processe's VMA for any applications. Userland library, alsa-lib, is
programmed for this case. It executes ioctl(2) with
SNDRV_PCM_IOCTL_SYNC_PTR command every time to requiring the status and
control data.
ARM is such an architecture. Below is an example with serial sound interface
(ssi) on i.mx6 quad core SoC. I use v4.1 kernel released by fsl-community
with patches from VIA Tech. Inc. for VAB820, and my backport patches for
relevant features for this patchset. I use Ubuntu 17.04 from
ports.ubuntu.com as user land for armhf architecture.
$ aplay -v -M -D hw:imx6vab820sgtl5,0 /dev/urandom -f S16_LE -r 48000 --period-size=128 --buffer-size=256
Playing raw data '/dev/urandom' : Signed 16 bit Little Endian, Rate 48000 Hz, Mono
Hardware PCM card 0 'imx6-vab820-sgtl5000' device 0 subdevice 0
Its setup is:
stream : PLAYBACK
access : MMAP_INTERLEAVED
format : S16_LE
subformat : STD
channels : 1
rate : 48000
exact rate : 48000 (48000/1)
msbits : 16
buffer_size : 256
period_size : 128
period_time : 2666
tstamp_mode : NONE
tstamp_type : MONOTONIC
period_step : 1
avail_min : 128
period_event : 0
start_threshold : 256
stop_threshold : 256
silence_threshold: 0
silence_size : 0
boundary : 1073741824
appl_ptr : 0
hw_ptr : 0
mmap_area[0] = 0x76f98000,0,16 (16)
$ trace-cmd record -e snd_pcm:hwptr -e snd_pcm:applptr
$ trace-cmd report
...
60.208495: applptr: pcmC0D0p/sub0: prev=1792, curr=1792, avail=0, period=128, buf=256
60.208633: applptr: pcmC0D0p/sub0: prev=1792, curr=1792, avail=0, period=128, buf=256
60.210022: hwptr: pcmC0D0p/sub0: IRQ: pos=128, old=1536, base=1536, period=128, buf=256
60.210202: applptr: pcmC0D0p/sub0: prev=1792, curr=1792, avail=128, period=128, buf=256
60.210344: hwptr: pcmC0D0p/sub0: POS: pos=128, old=1664, base=1536, period=128, buf=256
60.210348: applptr: pcmC0D0p/sub0: prev=1792, curr=1792, avail=128, period=128, buf=256
60.210486: applptr: pcmC0D0p/sub0: prev=1792, curr=1792, avail=128, period=128, buf=256
60.210626: applptr: pcmC0D0p/sub0: prev=1792, curr=1920, avail=0, period=128, buf=256
60.211002: applptr: pcmC0D0p/sub0: prev=1920, curr=1920, avail=0, period=128, buf=256
60.211142: hwptr: pcmC0D0p/sub0: POS: pos=128, old=1664, base=1536, period=128, buf=256
60.211146: applptr: pcmC0D0p/sub0: prev=1920, curr=1920, avail=0, period=128, buf=256
60.211287: applptr: pcmC0D0p/sub0: prev=1920, curr=1920, avail=0, period=128, buf=256
60.212690: hwptr: pcmC0D0p/sub0: IRQ: pos=0, old=1664, base=1536, period=128, buf=256
60.212866: applptr: pcmC0D0p/sub0: prev=1920, curr=1920, avail=128, period=128, buf=256
60.212999: hwptr: pcmC0D0p/sub0: POS: pos=0, old=1792, base=1792, period=128, buf=256
60.213003: applptr: pcmC0D0p/sub0: prev=1920, curr=1920, avail=128, period=128, buf=256
60.213135: applptr: pcmC0D0p/sub0: prev=1920, curr=1920, avail=128, period=128, buf=256
60.213276: applptr: pcmC0D0p/sub0: prev=1920, curr=2048, avail=0, period=128, buf=256
60.213654: applptr: pcmC0D0p/sub0: prev=2048, curr=2048, avail=0, period=128, buf=256
60.213796: hwptr: pcmC0D0p/sub0: POS: pos=0, old=1792, base=1792, period=128, buf=256
60.213800: applptr: pcmC0D0p/sub0: prev=2048, curr=2048, avail=0, period=128, buf=256
60.213937: applptr: pcmC0D0p/sub0: prev=2048, curr=2048, avail=0, period=128, buf=256
60.215356: hwptr: pcmC0D0p/sub0: IRQ: pos=128, old=1792, base=1792, period=128, buf=256
60.215542: applptr: pcmC0D0p/sub0: prev=2048, curr=2048, avail=128, period=128, buf=256
60.215679: hwptr: pcmC0D0p/sub0: POS: pos=128, old=1920, base=1792, period=128, buf=256
60.215683: applptr: pcmC0D0p/sub0: prev=2048, curr=2048, avail=128, period=128, buf=256
60.215813: applptr: pcmC0D0p/sub0: prev=2048, curr=2048, avail=128, period=128, buf=256
60.215947: applptr: pcmC0D0p/sub0: prev=2048, curr=2176, avail=0, period=128, buf=256
...
We can surely see 'applptr' event is probed even if the application run
for mmap programming scenario ('-M' option and 'hw' plugin). Below is a
result of strace:
02:44:15.886382 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.887203 poll([{fd=4, events=POLLOUT|POLLERR|POLLNVAL}], 1, -1) = 1 ([{fd=4, revents=POLLOUT}])
02:44:15.887471 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.887637 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.887805 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.887969 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.888132 read(3, "..."..., 256) = 256
02:44:15.889040 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.889221 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.889431 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.889606 poll([{fd=4, events=POLLOUT|POLLERR|POLLNVAL}], 1, -1) = 1 ([{fd=4, revents=POLLOUT}])
02:44:15.889833 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.889998 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.890164 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.891048 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.891228 read(3, "..."..., 256) = 256
02:44:15.891497 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.891661 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.891829 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.891991 poll([{fd=4, events=POLLOUT|POLLERR|POLLNVAL}], 1, -1) = 1 ([{fd=4, revents=POLLOUT}])
02:44:15.893007 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
We can see 7 calls of ioctl(2) with SNDRV_PCM_IOCTL_SYNC_PTR per loop with
call of poll(2). 128 PCM frames are transferred per loop of one poll(2),
because the PCM substream is configured with S16_LE format and 1 channel
(2 byte * 1 * 128 = 256 bytes). This equals to the size of period of PCM
buffer. Comparing to the probed data, one of the 7 calls of ioctl(2) is
actually used to commit the number of copied PCM frames to kernel space.
The other calls are just used to check runtime status of PCM substream;
e.g. XRUN.
The tracepoint event is useful to investigate this case. I note that below
modules are related to the above sample.
* snd-soc-dummy.ko
* snd-soc-imx-sgtl5000.ko
* snd-soc-fsl-ssi.ko
* snd-soc-imx-pcm-dma.ko
* snd-soc-sgtl5000.ko
My additional note is lock acquisition. The event is probed under acquiring
PCM stream lock. This means that calculation in the event is free from
any hardware events.
Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2017-06-12 07:41:45 +07:00
|
|
|
#define trace_applptr(substream, prev, curr)
|
ALSA: pcm: Replace PCM hwptr tracking with tracepoints
ALSA PCM core has a mechanism tracking the PCM hwptr updates for
analyzing XRUNs. But its log is limited (up to 10) and its log output
is a kernel message, which is hard to handle.
In this patch, the hwptr logging is moved to the tracing
infrastructure instead of its own. Not only the hwptr updates but
also XRUN and hwptr errors are recorded on the trace log, so that user
can see such events at the exact timing.
The new "snd_pcm" entry will appear in the tracing events:
# ls -F /sys/kernel/debug/tracing/events/snd_pcm
enable filter hw_ptr_error/ hwptr/ xrun/
The hwptr is for the regular hwptr update events. An event trace
looks like:
aplay-26187 [004] d..3 4012.834761: hwptr: pcmC0D0p/sub0: POS: pos=488, old=0, base=0, period=1024, buf=16384
"POS" shows the hwptr update by the explicit position update call and
"IRQ" means the hwptr update by the interrupt,
i.e. snd_pcm_period_elapsed() call. The "pos" is the passed
ring-buffer offset by the caller, "old" is the previous hwptr, "base"
is the hwptr base position, "period" and "buf" are period- and
buffer-size of the target PCM substream.
(Note that the hwptr position displayed here isn't the ring-buffer
offset. It increments up to the PCM position boundary.)
The XRUN event appears similarly, but without "pos" field.
The hwptr error events appear with the PCM identifier and its reason
string, such as "Lost interrupt?".
The XRUN and hwptr error reports on kernel message are still left, can
be turned on/off via xrun_debug proc like before. But the bit 3, 4, 5
and 6 bits of xrun_debug proc are dropped by this patch. Also, along
with the change, the message strings have been reformatted to be a bit
more consistent.
Last but not least, the hwptr reporting is enabled only when
CONFIG_SND_PCM_XRUN_DEBUG is set.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2014-11-04 18:45:59 +07:00
|
|
|
#endif
|
|
|
|
|
2017-05-24 23:18:15 +07:00
|
|
|
static int fill_silence_frames(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_uframes_t off, snd_pcm_uframes_t frames);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* fill ring buffer with silence
|
|
|
|
* runtime->silence_start: starting pointer to silence area
|
|
|
|
* runtime->silence_filled: size filled with silence
|
|
|
|
* runtime->silence_threshold: threshold from application
|
|
|
|
* runtime->silence_size: maximal size from application
|
|
|
|
*
|
|
|
|
* when runtime->silence_size >= runtime->boundary - fill processed area with silence immediately
|
|
|
|
*/
|
2005-11-17 19:59:38 +07:00
|
|
|
void snd_pcm_playback_silence(struct snd_pcm_substream *substream, snd_pcm_uframes_t new_hw_ptr)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:59:38 +07:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-04-17 05:20:36 +07:00
|
|
|
snd_pcm_uframes_t frames, ofs, transfer;
|
ALSA: pcm: Introduce copy_user, copy_kernel and fill_silence ops
For supporting the explicit in-kernel copy of PCM buffer data, and
also for further code refactoring, three new PCM ops, copy_user,
copy_kernel and fill_silence, are introduced. The old copy and
silence ops will be deprecated and removed later once when all callers
are converted.
The copy_kernel ops is the new one, and it's supposed to transfer the
PCM data from the given kernel buffer to the hardware ring-buffer (or
vice-versa depending on the stream direction), while the copy_user ops
is equivalent with the former copy ops, to transfer the data from the
user-space buffer.
The major difference of the new copy_* and fill_silence ops from the
previous ops is that the new ops take bytes instead of frames for size
and position arguments. It has two merits: first, it allows the
callback implementation often simpler (just call directly memcpy() &
co), and second, it may unify the implementations of both interleaved
and non-interleaved cases, as we'll see in the later patch.
As of this stage, copy_kernel ops isn't referred yet, but only
copy_user is used.
Reviewed-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Acked-by: Mark Brown <broonie@kernel.org>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2017-05-11 01:02:35 +07:00
|
|
|
int err;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (runtime->silence_size < runtime->boundary) {
|
|
|
|
snd_pcm_sframes_t noise_dist, n;
|
2017-06-17 03:29:55 +07:00
|
|
|
snd_pcm_uframes_t appl_ptr = READ_ONCE(runtime->control->appl_ptr);
|
|
|
|
if (runtime->silence_start != appl_ptr) {
|
|
|
|
n = appl_ptr - runtime->silence_start;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (n < 0)
|
|
|
|
n += runtime->boundary;
|
|
|
|
if ((snd_pcm_uframes_t)n < runtime->silence_filled)
|
|
|
|
runtime->silence_filled -= n;
|
|
|
|
else
|
|
|
|
runtime->silence_filled = 0;
|
2017-06-17 03:29:55 +07:00
|
|
|
runtime->silence_start = appl_ptr;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2005-12-07 21:28:07 +07:00
|
|
|
if (runtime->silence_filled >= runtime->buffer_size)
|
2005-04-17 05:20:36 +07:00
|
|
|
return;
|
|
|
|
noise_dist = snd_pcm_playback_hw_avail(runtime) + runtime->silence_filled;
|
|
|
|
if (noise_dist >= (snd_pcm_sframes_t) runtime->silence_threshold)
|
|
|
|
return;
|
|
|
|
frames = runtime->silence_threshold - noise_dist;
|
|
|
|
if (frames > runtime->silence_size)
|
|
|
|
frames = runtime->silence_size;
|
|
|
|
} else {
|
|
|
|
if (new_hw_ptr == ULONG_MAX) { /* initialization */
|
|
|
|
snd_pcm_sframes_t avail = snd_pcm_playback_hw_avail(runtime);
|
2010-07-19 21:37:39 +07:00
|
|
|
if (avail > runtime->buffer_size)
|
|
|
|
avail = runtime->buffer_size;
|
2005-04-17 05:20:36 +07:00
|
|
|
runtime->silence_filled = avail > 0 ? avail : 0;
|
|
|
|
runtime->silence_start = (runtime->status->hw_ptr +
|
|
|
|
runtime->silence_filled) %
|
|
|
|
runtime->boundary;
|
|
|
|
} else {
|
|
|
|
ofs = runtime->status->hw_ptr;
|
|
|
|
frames = new_hw_ptr - ofs;
|
|
|
|
if ((snd_pcm_sframes_t)frames < 0)
|
|
|
|
frames += runtime->boundary;
|
|
|
|
runtime->silence_filled -= frames;
|
|
|
|
if ((snd_pcm_sframes_t)runtime->silence_filled < 0) {
|
|
|
|
runtime->silence_filled = 0;
|
2006-10-23 21:26:57 +07:00
|
|
|
runtime->silence_start = new_hw_ptr;
|
2005-04-17 05:20:36 +07:00
|
|
|
} else {
|
2006-10-23 21:26:57 +07:00
|
|
|
runtime->silence_start = ofs;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
frames = runtime->buffer_size - runtime->silence_filled;
|
|
|
|
}
|
2008-08-08 22:09:09 +07:00
|
|
|
if (snd_BUG_ON(frames > runtime->buffer_size))
|
|
|
|
return;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (frames == 0)
|
|
|
|
return;
|
2006-10-23 21:26:57 +07:00
|
|
|
ofs = runtime->silence_start % runtime->buffer_size;
|
2005-04-17 05:20:36 +07:00
|
|
|
while (frames > 0) {
|
|
|
|
transfer = ofs + frames > runtime->buffer_size ? runtime->buffer_size - ofs : frames;
|
2017-05-24 23:18:15 +07:00
|
|
|
err = fill_silence_frames(substream, ofs, transfer);
|
|
|
|
snd_BUG_ON(err < 0);
|
2005-04-17 05:20:36 +07:00
|
|
|
runtime->silence_filled += transfer;
|
|
|
|
frames -= transfer;
|
|
|
|
ofs = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-07-23 07:36:25 +07:00
|
|
|
#ifdef CONFIG_SND_DEBUG
|
|
|
|
void snd_pcm_debug_name(struct snd_pcm_substream *substream,
|
2009-06-08 20:58:48 +07:00
|
|
|
char *name, size_t len)
|
|
|
|
{
|
|
|
|
snprintf(name, len, "pcmC%dD%d%c:%d",
|
|
|
|
substream->pcm->card->number,
|
|
|
|
substream->pcm->device,
|
|
|
|
substream->stream ? 'c' : 'p',
|
|
|
|
substream->number);
|
|
|
|
}
|
2011-07-23 07:36:25 +07:00
|
|
|
EXPORT_SYMBOL(snd_pcm_debug_name);
|
|
|
|
#endif
|
2009-06-08 20:58:48 +07:00
|
|
|
|
2009-12-17 23:34:39 +07:00
|
|
|
#define XRUN_DEBUG_BASIC (1<<0)
|
|
|
|
#define XRUN_DEBUG_STACK (1<<1) /* dump also stack */
|
|
|
|
#define XRUN_DEBUG_JIFFIESCHECK (1<<2) /* do jiffies check */
|
|
|
|
|
2009-03-03 23:00:15 +07:00
|
|
|
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
|
2009-12-20 17:47:57 +07:00
|
|
|
|
2009-12-17 23:34:39 +07:00
|
|
|
#define xrun_debug(substream, mask) \
|
|
|
|
((substream)->pstr->xrun_debug & (mask))
|
2010-03-26 21:07:25 +07:00
|
|
|
#else
|
|
|
|
#define xrun_debug(substream, mask) 0
|
|
|
|
#endif
|
2009-03-03 23:00:15 +07:00
|
|
|
|
2009-12-17 23:34:39 +07:00
|
|
|
#define dump_stack_on_xrun(substream) do { \
|
|
|
|
if (xrun_debug(substream, XRUN_DEBUG_STACK)) \
|
|
|
|
dump_stack(); \
|
2009-03-03 23:00:15 +07:00
|
|
|
} while (0)
|
|
|
|
|
2018-07-04 19:46:27 +07:00
|
|
|
/* call with stream lock held */
|
|
|
|
void __snd_pcm_xrun(struct snd_pcm_substream *substream)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2009-05-28 16:31:20 +07:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
|
|
|
ALSA: pcm: Replace PCM hwptr tracking with tracepoints
ALSA PCM core has a mechanism tracking the PCM hwptr updates for
analyzing XRUNs. But its log is limited (up to 10) and its log output
is a kernel message, which is hard to handle.
In this patch, the hwptr logging is moved to the tracing
infrastructure instead of its own. Not only the hwptr updates but
also XRUN and hwptr errors are recorded on the trace log, so that user
can see such events at the exact timing.
The new "snd_pcm" entry will appear in the tracing events:
# ls -F /sys/kernel/debug/tracing/events/snd_pcm
enable filter hw_ptr_error/ hwptr/ xrun/
The hwptr is for the regular hwptr update events. An event trace
looks like:
aplay-26187 [004] d..3 4012.834761: hwptr: pcmC0D0p/sub0: POS: pos=488, old=0, base=0, period=1024, buf=16384
"POS" shows the hwptr update by the explicit position update call and
"IRQ" means the hwptr update by the interrupt,
i.e. snd_pcm_period_elapsed() call. The "pos" is the passed
ring-buffer offset by the caller, "old" is the previous hwptr, "base"
is the hwptr base position, "period" and "buf" are period- and
buffer-size of the target PCM substream.
(Note that the hwptr position displayed here isn't the ring-buffer
offset. It increments up to the PCM position boundary.)
The XRUN event appears similarly, but without "pos" field.
The hwptr error events appear with the PCM identifier and its reason
string, such as "Lost interrupt?".
The XRUN and hwptr error reports on kernel message are still left, can
be turned on/off via xrun_debug proc like before. But the bit 3, 4, 5
and 6 bits of xrun_debug proc are dropped by this patch. Also, along
with the change, the message strings have been reformatted to be a bit
more consistent.
Last but not least, the hwptr reporting is enabled only when
CONFIG_SND_PCM_XRUN_DEBUG is set.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2014-11-04 18:45:59 +07:00
|
|
|
trace_xrun(substream);
|
2009-05-28 16:31:20 +07:00
|
|
|
if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE)
|
|
|
|
snd_pcm_gettime(runtime, (struct timespec *)&runtime->status->tstamp);
|
2005-04-17 05:20:36 +07:00
|
|
|
snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
|
2009-12-17 23:34:39 +07:00
|
|
|
if (xrun_debug(substream, XRUN_DEBUG_BASIC)) {
|
2009-06-08 20:58:48 +07:00
|
|
|
char name[16];
|
2011-07-23 07:36:25 +07:00
|
|
|
snd_pcm_debug_name(substream, name, sizeof(name));
|
2014-02-05 00:19:48 +07:00
|
|
|
pcm_warn(substream->pcm, "XRUN: %s\n", name);
|
2009-03-03 23:00:15 +07:00
|
|
|
dump_stack_on_xrun(substream);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-03-26 21:07:25 +07:00
|
|
|
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
|
ALSA: pcm: Replace PCM hwptr tracking with tracepoints
ALSA PCM core has a mechanism tracking the PCM hwptr updates for
analyzing XRUNs. But its log is limited (up to 10) and its log output
is a kernel message, which is hard to handle.
In this patch, the hwptr logging is moved to the tracing
infrastructure instead of its own. Not only the hwptr updates but
also XRUN and hwptr errors are recorded on the trace log, so that user
can see such events at the exact timing.
The new "snd_pcm" entry will appear in the tracing events:
# ls -F /sys/kernel/debug/tracing/events/snd_pcm
enable filter hw_ptr_error/ hwptr/ xrun/
The hwptr is for the regular hwptr update events. An event trace
looks like:
aplay-26187 [004] d..3 4012.834761: hwptr: pcmC0D0p/sub0: POS: pos=488, old=0, base=0, period=1024, buf=16384
"POS" shows the hwptr update by the explicit position update call and
"IRQ" means the hwptr update by the interrupt,
i.e. snd_pcm_period_elapsed() call. The "pos" is the passed
ring-buffer offset by the caller, "old" is the previous hwptr, "base"
is the hwptr base position, "period" and "buf" are period- and
buffer-size of the target PCM substream.
(Note that the hwptr position displayed here isn't the ring-buffer
offset. It increments up to the PCM position boundary.)
The XRUN event appears similarly, but without "pos" field.
The hwptr error events appear with the PCM identifier and its reason
string, such as "Lost interrupt?".
The XRUN and hwptr error reports on kernel message are still left, can
be turned on/off via xrun_debug proc like before. But the bit 3, 4, 5
and 6 bits of xrun_debug proc are dropped by this patch. Also, along
with the change, the message strings have been reformatted to be a bit
more consistent.
Last but not least, the hwptr reporting is enabled only when
CONFIG_SND_PCM_XRUN_DEBUG is set.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2014-11-04 18:45:59 +07:00
|
|
|
#define hw_ptr_error(substream, in_interrupt, reason, fmt, args...) \
|
2009-12-20 17:47:57 +07:00
|
|
|
do { \
|
ALSA: pcm: Replace PCM hwptr tracking with tracepoints
ALSA PCM core has a mechanism tracking the PCM hwptr updates for
analyzing XRUNs. But its log is limited (up to 10) and its log output
is a kernel message, which is hard to handle.
In this patch, the hwptr logging is moved to the tracing
infrastructure instead of its own. Not only the hwptr updates but
also XRUN and hwptr errors are recorded on the trace log, so that user
can see such events at the exact timing.
The new "snd_pcm" entry will appear in the tracing events:
# ls -F /sys/kernel/debug/tracing/events/snd_pcm
enable filter hw_ptr_error/ hwptr/ xrun/
The hwptr is for the regular hwptr update events. An event trace
looks like:
aplay-26187 [004] d..3 4012.834761: hwptr: pcmC0D0p/sub0: POS: pos=488, old=0, base=0, period=1024, buf=16384
"POS" shows the hwptr update by the explicit position update call and
"IRQ" means the hwptr update by the interrupt,
i.e. snd_pcm_period_elapsed() call. The "pos" is the passed
ring-buffer offset by the caller, "old" is the previous hwptr, "base"
is the hwptr base position, "period" and "buf" are period- and
buffer-size of the target PCM substream.
(Note that the hwptr position displayed here isn't the ring-buffer
offset. It increments up to the PCM position boundary.)
The XRUN event appears similarly, but without "pos" field.
The hwptr error events appear with the PCM identifier and its reason
string, such as "Lost interrupt?".
The XRUN and hwptr error reports on kernel message are still left, can
be turned on/off via xrun_debug proc like before. But the bit 3, 4, 5
and 6 bits of xrun_debug proc are dropped by this patch. Also, along
with the change, the message strings have been reformatted to be a bit
more consistent.
Last but not least, the hwptr reporting is enabled only when
CONFIG_SND_PCM_XRUN_DEBUG is set.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2014-11-04 18:45:59 +07:00
|
|
|
trace_hw_ptr_error(substream, reason); \
|
2009-12-20 17:47:57 +07:00
|
|
|
if (xrun_debug(substream, XRUN_DEBUG_BASIC)) { \
|
ALSA: pcm: Replace PCM hwptr tracking with tracepoints
ALSA PCM core has a mechanism tracking the PCM hwptr updates for
analyzing XRUNs. But its log is limited (up to 10) and its log output
is a kernel message, which is hard to handle.
In this patch, the hwptr logging is moved to the tracing
infrastructure instead of its own. Not only the hwptr updates but
also XRUN and hwptr errors are recorded on the trace log, so that user
can see such events at the exact timing.
The new "snd_pcm" entry will appear in the tracing events:
# ls -F /sys/kernel/debug/tracing/events/snd_pcm
enable filter hw_ptr_error/ hwptr/ xrun/
The hwptr is for the regular hwptr update events. An event trace
looks like:
aplay-26187 [004] d..3 4012.834761: hwptr: pcmC0D0p/sub0: POS: pos=488, old=0, base=0, period=1024, buf=16384
"POS" shows the hwptr update by the explicit position update call and
"IRQ" means the hwptr update by the interrupt,
i.e. snd_pcm_period_elapsed() call. The "pos" is the passed
ring-buffer offset by the caller, "old" is the previous hwptr, "base"
is the hwptr base position, "period" and "buf" are period- and
buffer-size of the target PCM substream.
(Note that the hwptr position displayed here isn't the ring-buffer
offset. It increments up to the PCM position boundary.)
The XRUN event appears similarly, but without "pos" field.
The hwptr error events appear with the PCM identifier and its reason
string, such as "Lost interrupt?".
The XRUN and hwptr error reports on kernel message are still left, can
be turned on/off via xrun_debug proc like before. But the bit 3, 4, 5
and 6 bits of xrun_debug proc are dropped by this patch. Also, along
with the change, the message strings have been reformatted to be a bit
more consistent.
Last but not least, the hwptr reporting is enabled only when
CONFIG_SND_PCM_XRUN_DEBUG is set.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2014-11-04 18:45:59 +07:00
|
|
|
pr_err_ratelimited("ALSA: PCM: [%c] " reason ": " fmt, \
|
|
|
|
(in_interrupt) ? 'Q' : 'P', ##args); \
|
2009-12-20 17:47:57 +07:00
|
|
|
dump_stack_on_xrun(substream); \
|
|
|
|
} \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
#else /* ! CONFIG_SND_PCM_XRUN_DEBUG */
|
|
|
|
|
|
|
|
#define hw_ptr_error(substream, fmt, args...) do { } while (0)
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
2010-01-07 21:36:31 +07:00
|
|
|
int snd_pcm_update_state(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_runtime *runtime)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
snd_pcm_uframes_t avail;
|
|
|
|
|
2018-04-11 22:56:52 +07:00
|
|
|
avail = snd_pcm_avail(substream);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (avail > runtime->avail_max)
|
|
|
|
runtime->avail_max = avail;
|
2009-08-20 21:40:16 +07:00
|
|
|
if (runtime->status->state == SNDRV_PCM_STATE_DRAINING) {
|
|
|
|
if (avail >= runtime->buffer_size) {
|
2005-04-17 05:20:36 +07:00
|
|
|
snd_pcm_drain_done(substream);
|
2009-08-20 21:40:16 +07:00
|
|
|
return -EPIPE;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (avail >= runtime->stop_threshold) {
|
2018-07-04 19:46:27 +07:00
|
|
|
__snd_pcm_xrun(substream);
|
2009-08-20 21:40:16 +07:00
|
|
|
return -EPIPE;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2010-06-27 05:13:20 +07:00
|
|
|
if (runtime->twake) {
|
|
|
|
if (avail >= runtime->twake)
|
|
|
|
wake_up(&runtime->tsleep);
|
|
|
|
} else if (avail >= runtime->control->avail_min)
|
|
|
|
wake_up(&runtime->sleep);
|
2005-04-17 05:20:36 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-02-14 04:14:06 +07:00
|
|
|
static void update_audio_tstamp(struct snd_pcm_substream *substream,
|
|
|
|
struct timespec *curr_tstamp,
|
|
|
|
struct timespec *audio_tstamp)
|
|
|
|
{
|
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
|
|
u64 audio_frames, audio_nsecs;
|
|
|
|
struct timespec driver_tstamp;
|
|
|
|
|
|
|
|
if (runtime->tstamp_mode != SNDRV_PCM_TSTAMP_ENABLE)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!(substream->ops->get_time_info) ||
|
|
|
|
(runtime->audio_tstamp_report.actual_type ==
|
|
|
|
SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT)) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
* provide audio timestamp derived from pointer position
|
|
|
|
* add delay only if requested
|
|
|
|
*/
|
|
|
|
|
|
|
|
audio_frames = runtime->hw_ptr_wrap + runtime->status->hw_ptr;
|
|
|
|
|
|
|
|
if (runtime->audio_tstamp_config.report_delay) {
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
|
|
audio_frames -= runtime->delay;
|
|
|
|
else
|
|
|
|
audio_frames += runtime->delay;
|
|
|
|
}
|
|
|
|
audio_nsecs = div_u64(audio_frames * 1000000000LL,
|
|
|
|
runtime->rate);
|
|
|
|
*audio_tstamp = ns_to_timespec(audio_nsecs);
|
|
|
|
}
|
2017-11-21 15:29:28 +07:00
|
|
|
if (!timespec_equal(&runtime->status->audio_tstamp, audio_tstamp)) {
|
|
|
|
runtime->status->audio_tstamp = *audio_tstamp;
|
|
|
|
runtime->status->tstamp = *curr_tstamp;
|
|
|
|
}
|
2015-02-14 04:14:06 +07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* re-take a driver timestamp to let apps detect if the reference tstamp
|
|
|
|
* read by low-level hardware was provided with a delay
|
|
|
|
*/
|
|
|
|
snd_pcm_gettime(substream->runtime, (struct timespec *)&driver_tstamp);
|
|
|
|
runtime->driver_tstamp = driver_tstamp;
|
|
|
|
}
|
|
|
|
|
2010-01-05 23:19:34 +07:00
|
|
|
static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,
|
|
|
|
unsigned int in_interrupt)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:59:38 +07:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-04-17 05:20:36 +07:00
|
|
|
snd_pcm_uframes_t pos;
|
2010-01-05 23:19:34 +07:00
|
|
|
snd_pcm_uframes_t old_hw_ptr, new_hw_ptr, hw_base;
|
2009-04-10 17:28:58 +07:00
|
|
|
snd_pcm_sframes_t hdelta, delta;
|
|
|
|
unsigned long jdelta;
|
2012-05-23 02:54:02 +07:00
|
|
|
unsigned long curr_jiffies;
|
|
|
|
struct timespec curr_tstamp;
|
2012-10-23 04:42:15 +07:00
|
|
|
struct timespec audio_tstamp;
|
2012-10-23 04:42:14 +07:00
|
|
|
int crossed_boundary = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2009-04-10 17:28:58 +07:00
|
|
|
old_hw_ptr = runtime->status->hw_ptr;
|
2012-05-23 02:54:02 +07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
*/
|
2010-01-05 23:19:34 +07:00
|
|
|
pos = substream->ops->pointer(substream);
|
2012-05-23 02:54:02 +07:00
|
|
|
curr_jiffies = jiffies;
|
2012-10-23 04:42:15 +07:00
|
|
|
if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) {
|
2015-02-14 04:14:06 +07:00
|
|
|
if ((substream->ops->get_time_info) &&
|
|
|
|
(runtime->audio_tstamp_config.type_requested != SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT)) {
|
|
|
|
substream->ops->get_time_info(substream, &curr_tstamp,
|
|
|
|
&audio_tstamp,
|
|
|
|
&runtime->audio_tstamp_config,
|
|
|
|
&runtime->audio_tstamp_report);
|
|
|
|
|
|
|
|
/* re-test in case tstamp type is not supported in hardware and was demoted to DEFAULT */
|
|
|
|
if (runtime->audio_tstamp_report.actual_type == SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT)
|
|
|
|
snd_pcm_gettime(runtime, (struct timespec *)&curr_tstamp);
|
|
|
|
} else
|
|
|
|
snd_pcm_gettime(runtime, (struct timespec *)&curr_tstamp);
|
2012-10-23 04:42:15 +07:00
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
if (pos == SNDRV_PCM_POS_XRUN) {
|
2018-07-04 19:46:27 +07:00
|
|
|
__snd_pcm_xrun(substream);
|
2005-04-17 05:20:36 +07:00
|
|
|
return -EPIPE;
|
|
|
|
}
|
2010-01-05 23:19:34 +07:00
|
|
|
if (pos >= runtime->buffer_size) {
|
2014-02-05 00:19:48 +07:00
|
|
|
if (printk_ratelimit()) {
|
2010-01-05 23:19:34 +07:00
|
|
|
char name[16];
|
2011-07-23 07:36:25 +07:00
|
|
|
snd_pcm_debug_name(substream, name, sizeof(name));
|
2014-02-05 00:19:48 +07:00
|
|
|
pcm_err(substream->pcm,
|
2016-03-11 02:56:20 +07:00
|
|
|
"invalid position: %s, pos = %ld, buffer size = %ld, period size = %ld\n",
|
2014-02-05 00:19:48 +07:00
|
|
|
name, pos, runtime->buffer_size,
|
|
|
|
runtime->period_size);
|
2010-01-05 23:19:34 +07:00
|
|
|
}
|
|
|
|
pos = 0;
|
2009-07-23 16:04:13 +07:00
|
|
|
}
|
2010-01-05 23:19:34 +07:00
|
|
|
pos -= pos % runtime->min_align;
|
ALSA: pcm: Replace PCM hwptr tracking with tracepoints
ALSA PCM core has a mechanism tracking the PCM hwptr updates for
analyzing XRUNs. But its log is limited (up to 10) and its log output
is a kernel message, which is hard to handle.
In this patch, the hwptr logging is moved to the tracing
infrastructure instead of its own. Not only the hwptr updates but
also XRUN and hwptr errors are recorded on the trace log, so that user
can see such events at the exact timing.
The new "snd_pcm" entry will appear in the tracing events:
# ls -F /sys/kernel/debug/tracing/events/snd_pcm
enable filter hw_ptr_error/ hwptr/ xrun/
The hwptr is for the regular hwptr update events. An event trace
looks like:
aplay-26187 [004] d..3 4012.834761: hwptr: pcmC0D0p/sub0: POS: pos=488, old=0, base=0, period=1024, buf=16384
"POS" shows the hwptr update by the explicit position update call and
"IRQ" means the hwptr update by the interrupt,
i.e. snd_pcm_period_elapsed() call. The "pos" is the passed
ring-buffer offset by the caller, "old" is the previous hwptr, "base"
is the hwptr base position, "period" and "buf" are period- and
buffer-size of the target PCM substream.
(Note that the hwptr position displayed here isn't the ring-buffer
offset. It increments up to the PCM position boundary.)
The XRUN event appears similarly, but without "pos" field.
The hwptr error events appear with the PCM identifier and its reason
string, such as "Lost interrupt?".
The XRUN and hwptr error reports on kernel message are still left, can
be turned on/off via xrun_debug proc like before. But the bit 3, 4, 5
and 6 bits of xrun_debug proc are dropped by this patch. Also, along
with the change, the message strings have been reformatted to be a bit
more consistent.
Last but not least, the hwptr reporting is enabled only when
CONFIG_SND_PCM_XRUN_DEBUG is set.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2014-11-04 18:45:59 +07:00
|
|
|
trace_hwptr(substream, pos, in_interrupt);
|
2009-03-03 23:00:15 +07:00
|
|
|
hw_base = runtime->hw_ptr_base;
|
|
|
|
new_hw_ptr = hw_base + pos;
|
2010-01-05 23:19:34 +07:00
|
|
|
if (in_interrupt) {
|
|
|
|
/* we know that one period was processed */
|
|
|
|
/* delta = "expected next hw_ptr" for in_interrupt != 0 */
|
2010-01-26 23:08:24 +07:00
|
|
|
delta = runtime->hw_ptr_interrupt + runtime->period_size;
|
2010-01-05 23:19:34 +07:00
|
|
|
if (delta > new_hw_ptr) {
|
2010-08-18 19:16:54 +07:00
|
|
|
/* check for double acknowledged interrupts */
|
2012-05-23 02:54:02 +07:00
|
|
|
hdelta = curr_jiffies - runtime->hw_ptr_jiffies;
|
2015-05-13 21:39:03 +07:00
|
|
|
if (hdelta > runtime->hw_ptr_buffer_jiffies/2 + 1) {
|
2010-08-18 19:16:54 +07:00
|
|
|
hw_base += runtime->buffer_size;
|
2012-10-23 04:42:14 +07:00
|
|
|
if (hw_base >= runtime->boundary) {
|
2010-08-18 19:16:54 +07:00
|
|
|
hw_base = 0;
|
2012-10-23 04:42:14 +07:00
|
|
|
crossed_boundary++;
|
|
|
|
}
|
2010-08-18 19:16:54 +07:00
|
|
|
new_hw_ptr = hw_base + pos;
|
|
|
|
goto __delta;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
}
|
2010-01-05 23:19:34 +07:00
|
|
|
/* new_hw_ptr might be lower than old_hw_ptr in case when */
|
|
|
|
/* pointer crosses the end of the ring buffer */
|
|
|
|
if (new_hw_ptr < old_hw_ptr) {
|
|
|
|
hw_base += runtime->buffer_size;
|
2012-10-23 04:42:14 +07:00
|
|
|
if (hw_base >= runtime->boundary) {
|
2010-01-05 23:19:34 +07:00
|
|
|
hw_base = 0;
|
2012-10-23 04:42:14 +07:00
|
|
|
crossed_boundary++;
|
|
|
|
}
|
2010-01-05 23:19:34 +07:00
|
|
|
new_hw_ptr = hw_base + pos;
|
|
|
|
}
|
|
|
|
__delta:
|
2010-05-25 14:01:46 +07:00
|
|
|
delta = new_hw_ptr - old_hw_ptr;
|
|
|
|
if (delta < 0)
|
|
|
|
delta += runtime->boundary;
|
2010-11-15 16:46:23 +07:00
|
|
|
|
2010-11-18 15:43:52 +07:00
|
|
|
if (runtime->no_period_wakeup) {
|
2011-04-01 16:58:25 +07:00
|
|
|
snd_pcm_sframes_t xrun_threshold;
|
2010-11-18 15:43:52 +07:00
|
|
|
/*
|
|
|
|
* Without regular period interrupts, we have to check
|
|
|
|
* the elapsed time to detect xruns.
|
|
|
|
*/
|
2012-05-23 02:54:02 +07:00
|
|
|
jdelta = curr_jiffies - runtime->hw_ptr_jiffies;
|
2010-11-18 15:53:07 +07:00
|
|
|
if (jdelta < runtime->hw_ptr_buffer_jiffies / 2)
|
|
|
|
goto no_delta_check;
|
2010-11-18 15:43:52 +07:00
|
|
|
hdelta = jdelta - delta * HZ / runtime->rate;
|
2011-04-01 16:58:25 +07:00
|
|
|
xrun_threshold = runtime->hw_ptr_buffer_jiffies / 2 + 1;
|
|
|
|
while (hdelta > xrun_threshold) {
|
2010-11-18 15:43:52 +07:00
|
|
|
delta += runtime->buffer_size;
|
|
|
|
hw_base += runtime->buffer_size;
|
2012-10-23 04:42:14 +07:00
|
|
|
if (hw_base >= runtime->boundary) {
|
2010-11-18 15:43:52 +07:00
|
|
|
hw_base = 0;
|
2012-10-23 04:42:14 +07:00
|
|
|
crossed_boundary++;
|
|
|
|
}
|
2010-11-18 15:43:52 +07:00
|
|
|
new_hw_ptr = hw_base + pos;
|
|
|
|
hdelta -= runtime->hw_ptr_buffer_jiffies;
|
|
|
|
}
|
2010-11-15 16:46:23 +07:00
|
|
|
goto no_delta_check;
|
2010-11-18 15:43:52 +07:00
|
|
|
}
|
2010-11-15 16:46:23 +07:00
|
|
|
|
2010-01-05 23:19:34 +07:00
|
|
|
/* something must be really wrong */
|
2010-01-08 14:43:01 +07:00
|
|
|
if (delta >= runtime->buffer_size + runtime->period_size) {
|
ALSA: pcm: Replace PCM hwptr tracking with tracepoints
ALSA PCM core has a mechanism tracking the PCM hwptr updates for
analyzing XRUNs. But its log is limited (up to 10) and its log output
is a kernel message, which is hard to handle.
In this patch, the hwptr logging is moved to the tracing
infrastructure instead of its own. Not only the hwptr updates but
also XRUN and hwptr errors are recorded on the trace log, so that user
can see such events at the exact timing.
The new "snd_pcm" entry will appear in the tracing events:
# ls -F /sys/kernel/debug/tracing/events/snd_pcm
enable filter hw_ptr_error/ hwptr/ xrun/
The hwptr is for the regular hwptr update events. An event trace
looks like:
aplay-26187 [004] d..3 4012.834761: hwptr: pcmC0D0p/sub0: POS: pos=488, old=0, base=0, period=1024, buf=16384
"POS" shows the hwptr update by the explicit position update call and
"IRQ" means the hwptr update by the interrupt,
i.e. snd_pcm_period_elapsed() call. The "pos" is the passed
ring-buffer offset by the caller, "old" is the previous hwptr, "base"
is the hwptr base position, "period" and "buf" are period- and
buffer-size of the target PCM substream.
(Note that the hwptr position displayed here isn't the ring-buffer
offset. It increments up to the PCM position boundary.)
The XRUN event appears similarly, but without "pos" field.
The hwptr error events appear with the PCM identifier and its reason
string, such as "Lost interrupt?".
The XRUN and hwptr error reports on kernel message are still left, can
be turned on/off via xrun_debug proc like before. But the bit 3, 4, 5
and 6 bits of xrun_debug proc are dropped by this patch. Also, along
with the change, the message strings have been reformatted to be a bit
more consistent.
Last but not least, the hwptr reporting is enabled only when
CONFIG_SND_PCM_XRUN_DEBUG is set.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2014-11-04 18:45:59 +07:00
|
|
|
hw_ptr_error(substream, in_interrupt, "Unexpected hw_ptr",
|
|
|
|
"(stream=%i, pos=%ld, new_hw_ptr=%ld, old_hw_ptr=%ld)\n",
|
|
|
|
substream->stream, (long)pos,
|
|
|
|
(long)new_hw_ptr, (long)old_hw_ptr);
|
2010-01-05 23:19:34 +07:00
|
|
|
return 0;
|
|
|
|
}
|
2009-05-27 15:53:33 +07:00
|
|
|
|
|
|
|
/* Do jiffies check only in xrun_debug mode */
|
2009-12-17 23:34:39 +07:00
|
|
|
if (!xrun_debug(substream, XRUN_DEBUG_JIFFIESCHECK))
|
2009-05-27 15:53:33 +07:00
|
|
|
goto no_jiffies_check;
|
|
|
|
|
2009-04-28 17:07:08 +07:00
|
|
|
/* Skip the jiffies check for hardwares with BATCH flag.
|
|
|
|
* Such hardware usually just increases the position at each IRQ,
|
|
|
|
* thus it can't give any strange position.
|
|
|
|
*/
|
|
|
|
if (runtime->hw.info & SNDRV_PCM_INFO_BATCH)
|
|
|
|
goto no_jiffies_check;
|
2010-01-05 23:19:34 +07:00
|
|
|
hdelta = delta;
|
2009-05-28 17:31:56 +07:00
|
|
|
if (hdelta < runtime->delay)
|
|
|
|
goto no_jiffies_check;
|
|
|
|
hdelta -= runtime->delay;
|
2012-05-23 02:54:02 +07:00
|
|
|
jdelta = curr_jiffies - runtime->hw_ptr_jiffies;
|
2009-04-10 17:28:58 +07:00
|
|
|
if (((hdelta * HZ) / runtime->rate) > jdelta + HZ/100) {
|
|
|
|
delta = jdelta /
|
|
|
|
(((runtime->period_size * HZ) / runtime->rate)
|
|
|
|
+ HZ/100);
|
2010-01-05 23:19:34 +07:00
|
|
|
/* move new_hw_ptr according jiffies not pos variable */
|
|
|
|
new_hw_ptr = old_hw_ptr;
|
2010-01-13 14:12:31 +07:00
|
|
|
hw_base = delta;
|
2010-01-05 23:19:34 +07:00
|
|
|
/* use loop to avoid checks for delta overflows */
|
|
|
|
/* the delta value is small or zero in most cases */
|
|
|
|
while (delta > 0) {
|
|
|
|
new_hw_ptr += runtime->period_size;
|
2012-10-23 04:42:14 +07:00
|
|
|
if (new_hw_ptr >= runtime->boundary) {
|
2010-01-05 23:19:34 +07:00
|
|
|
new_hw_ptr -= runtime->boundary;
|
2012-10-23 04:42:14 +07:00
|
|
|
crossed_boundary--;
|
|
|
|
}
|
2010-01-05 23:19:34 +07:00
|
|
|
delta--;
|
|
|
|
}
|
|
|
|
/* align hw_base to buffer_size */
|
ALSA: pcm: Replace PCM hwptr tracking with tracepoints
ALSA PCM core has a mechanism tracking the PCM hwptr updates for
analyzing XRUNs. But its log is limited (up to 10) and its log output
is a kernel message, which is hard to handle.
In this patch, the hwptr logging is moved to the tracing
infrastructure instead of its own. Not only the hwptr updates but
also XRUN and hwptr errors are recorded on the trace log, so that user
can see such events at the exact timing.
The new "snd_pcm" entry will appear in the tracing events:
# ls -F /sys/kernel/debug/tracing/events/snd_pcm
enable filter hw_ptr_error/ hwptr/ xrun/
The hwptr is for the regular hwptr update events. An event trace
looks like:
aplay-26187 [004] d..3 4012.834761: hwptr: pcmC0D0p/sub0: POS: pos=488, old=0, base=0, period=1024, buf=16384
"POS" shows the hwptr update by the explicit position update call and
"IRQ" means the hwptr update by the interrupt,
i.e. snd_pcm_period_elapsed() call. The "pos" is the passed
ring-buffer offset by the caller, "old" is the previous hwptr, "base"
is the hwptr base position, "period" and "buf" are period- and
buffer-size of the target PCM substream.
(Note that the hwptr position displayed here isn't the ring-buffer
offset. It increments up to the PCM position boundary.)
The XRUN event appears similarly, but without "pos" field.
The hwptr error events appear with the PCM identifier and its reason
string, such as "Lost interrupt?".
The XRUN and hwptr error reports on kernel message are still left, can
be turned on/off via xrun_debug proc like before. But the bit 3, 4, 5
and 6 bits of xrun_debug proc are dropped by this patch. Also, along
with the change, the message strings have been reformatted to be a bit
more consistent.
Last but not least, the hwptr reporting is enabled only when
CONFIG_SND_PCM_XRUN_DEBUG is set.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2014-11-04 18:45:59 +07:00
|
|
|
hw_ptr_error(substream, in_interrupt, "hw_ptr skipping",
|
|
|
|
"(pos=%ld, delta=%ld, period=%ld, jdelta=%lu/%lu/%lu, hw_ptr=%ld/%ld)\n",
|
2009-04-10 17:28:58 +07:00
|
|
|
(long)pos, (long)hdelta,
|
|
|
|
(long)runtime->period_size, jdelta,
|
2010-01-13 14:12:31 +07:00
|
|
|
((hdelta * HZ) / runtime->rate), hw_base,
|
2010-01-05 23:19:34 +07:00
|
|
|
(unsigned long)old_hw_ptr,
|
|
|
|
(unsigned long)new_hw_ptr);
|
2010-01-13 14:12:31 +07:00
|
|
|
/* reset values to proper state */
|
|
|
|
delta = 0;
|
|
|
|
hw_base = new_hw_ptr - (new_hw_ptr % runtime->buffer_size);
|
2009-04-10 17:28:58 +07:00
|
|
|
}
|
2009-04-28 17:07:08 +07:00
|
|
|
no_jiffies_check:
|
2009-04-10 17:28:58 +07:00
|
|
|
if (delta > runtime->period_size + runtime->period_size / 2) {
|
ALSA: pcm: Replace PCM hwptr tracking with tracepoints
ALSA PCM core has a mechanism tracking the PCM hwptr updates for
analyzing XRUNs. But its log is limited (up to 10) and its log output
is a kernel message, which is hard to handle.
In this patch, the hwptr logging is moved to the tracing
infrastructure instead of its own. Not only the hwptr updates but
also XRUN and hwptr errors are recorded on the trace log, so that user
can see such events at the exact timing.
The new "snd_pcm" entry will appear in the tracing events:
# ls -F /sys/kernel/debug/tracing/events/snd_pcm
enable filter hw_ptr_error/ hwptr/ xrun/
The hwptr is for the regular hwptr update events. An event trace
looks like:
aplay-26187 [004] d..3 4012.834761: hwptr: pcmC0D0p/sub0: POS: pos=488, old=0, base=0, period=1024, buf=16384
"POS" shows the hwptr update by the explicit position update call and
"IRQ" means the hwptr update by the interrupt,
i.e. snd_pcm_period_elapsed() call. The "pos" is the passed
ring-buffer offset by the caller, "old" is the previous hwptr, "base"
is the hwptr base position, "period" and "buf" are period- and
buffer-size of the target PCM substream.
(Note that the hwptr position displayed here isn't the ring-buffer
offset. It increments up to the PCM position boundary.)
The XRUN event appears similarly, but without "pos" field.
The hwptr error events appear with the PCM identifier and its reason
string, such as "Lost interrupt?".
The XRUN and hwptr error reports on kernel message are still left, can
be turned on/off via xrun_debug proc like before. But the bit 3, 4, 5
and 6 bits of xrun_debug proc are dropped by this patch. Also, along
with the change, the message strings have been reformatted to be a bit
more consistent.
Last but not least, the hwptr reporting is enabled only when
CONFIG_SND_PCM_XRUN_DEBUG is set.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2014-11-04 18:45:59 +07:00
|
|
|
hw_ptr_error(substream, in_interrupt,
|
|
|
|
"Lost interrupts?",
|
|
|
|
"(stream=%i, delta=%ld, new_hw_ptr=%ld, old_hw_ptr=%ld)\n",
|
2009-03-03 23:00:15 +07:00
|
|
|
substream->stream, (long)delta,
|
2010-01-05 23:19:34 +07:00
|
|
|
(long)new_hw_ptr,
|
|
|
|
(long)old_hw_ptr);
|
2009-03-03 23:00:15 +07:00
|
|
|
}
|
2010-01-05 23:19:34 +07:00
|
|
|
|
2010-11-15 16:46:23 +07:00
|
|
|
no_delta_check:
|
2015-02-14 04:14:06 +07:00
|
|
|
if (runtime->status->hw_ptr == new_hw_ptr) {
|
|
|
|
update_audio_tstamp(substream, &curr_tstamp, &audio_tstamp);
|
2010-01-05 23:19:34 +07:00
|
|
|
return 0;
|
2015-02-14 04:14:06 +07:00
|
|
|
}
|
2009-06-07 17:09:17 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
|
|
|
|
runtime->silence_size > 0)
|
|
|
|
snd_pcm_playback_silence(substream, new_hw_ptr);
|
|
|
|
|
2010-01-26 23:08:24 +07:00
|
|
|
if (in_interrupt) {
|
2010-05-21 14:15:59 +07:00
|
|
|
delta = new_hw_ptr - runtime->hw_ptr_interrupt;
|
|
|
|
if (delta < 0)
|
|
|
|
delta += runtime->boundary;
|
|
|
|
delta -= (snd_pcm_uframes_t)delta % runtime->period_size;
|
|
|
|
runtime->hw_ptr_interrupt += delta;
|
|
|
|
if (runtime->hw_ptr_interrupt >= runtime->boundary)
|
|
|
|
runtime->hw_ptr_interrupt -= runtime->boundary;
|
2010-01-26 23:08:24 +07:00
|
|
|
}
|
2009-03-03 23:00:15 +07:00
|
|
|
runtime->hw_ptr_base = hw_base;
|
2005-04-17 05:20:36 +07:00
|
|
|
runtime->status->hw_ptr = new_hw_ptr;
|
2012-05-23 02:54:02 +07:00
|
|
|
runtime->hw_ptr_jiffies = curr_jiffies;
|
2012-10-23 04:42:14 +07:00
|
|
|
if (crossed_boundary) {
|
|
|
|
snd_BUG_ON(crossed_boundary != 1);
|
|
|
|
runtime->hw_ptr_wrap += runtime->boundary;
|
|
|
|
}
|
2012-10-23 04:42:15 +07:00
|
|
|
|
2015-02-14 04:14:06 +07:00
|
|
|
update_audio_tstamp(substream, &curr_tstamp, &audio_tstamp);
|
2012-10-23 04:42:15 +07:00
|
|
|
|
2010-01-07 21:36:31 +07:00
|
|
|
return snd_pcm_update_state(substream, runtime);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/* CAUTION: call it with irq disabled */
|
2005-11-17 19:59:38 +07:00
|
|
|
int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2010-01-05 23:19:34 +07:00
|
|
|
return snd_pcm_update_hw_ptr0(substream, 0);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* snd_pcm_set_ops - set the PCM operators
|
|
|
|
* @pcm: the pcm instance
|
|
|
|
* @direction: stream direction, SNDRV_PCM_STREAM_XXX
|
|
|
|
* @ops: the operator table
|
|
|
|
*
|
|
|
|
* Sets the given PCM operators to the pcm instance.
|
|
|
|
*/
|
2013-05-24 20:18:10 +07:00
|
|
|
void snd_pcm_set_ops(struct snd_pcm *pcm, int direction,
|
|
|
|
const struct snd_pcm_ops *ops)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:59:38 +07:00
|
|
|
struct snd_pcm_str *stream = &pcm->streams[direction];
|
|
|
|
struct snd_pcm_substream *substream;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
for (substream = stream->substream; substream != NULL; substream = substream->next)
|
|
|
|
substream->ops = ops;
|
|
|
|
}
|
2006-04-28 20:13:40 +07:00
|
|
|
EXPORT_SYMBOL(snd_pcm_set_ops);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* snd_pcm_sync - set the PCM sync id
|
|
|
|
* @substream: the pcm substream
|
|
|
|
*
|
|
|
|
* Sets the PCM sync identifier for the card.
|
|
|
|
*/
|
2005-11-17 19:59:38 +07:00
|
|
|
void snd_pcm_set_sync(struct snd_pcm_substream *substream)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:59:38 +07:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
runtime->sync.id32[0] = substream->pcm->card->number;
|
|
|
|
runtime->sync.id32[1] = -1;
|
|
|
|
runtime->sync.id32[2] = -1;
|
|
|
|
runtime->sync.id32[3] = -1;
|
|
|
|
}
|
2006-04-28 20:13:40 +07:00
|
|
|
EXPORT_SYMBOL(snd_pcm_set_sync);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Standard ioctl routine
|
|
|
|
*/
|
|
|
|
|
|
|
|
static inline unsigned int div32(unsigned int a, unsigned int b,
|
|
|
|
unsigned int *r)
|
|
|
|
{
|
|
|
|
if (b == 0) {
|
|
|
|
*r = 0;
|
|
|
|
return UINT_MAX;
|
|
|
|
}
|
|
|
|
*r = a % b;
|
|
|
|
return a / b;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline unsigned int div_down(unsigned int a, unsigned int b)
|
|
|
|
{
|
|
|
|
if (b == 0)
|
|
|
|
return UINT_MAX;
|
|
|
|
return a / b;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline unsigned int div_up(unsigned int a, unsigned int b)
|
|
|
|
{
|
|
|
|
unsigned int r;
|
|
|
|
unsigned int q;
|
|
|
|
if (b == 0)
|
|
|
|
return UINT_MAX;
|
|
|
|
q = div32(a, b, &r);
|
|
|
|
if (r)
|
|
|
|
++q;
|
|
|
|
return q;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline unsigned int mul(unsigned int a, unsigned int b)
|
|
|
|
{
|
|
|
|
if (a == 0)
|
|
|
|
return 0;
|
|
|
|
if (div_down(UINT_MAX, a) < b)
|
|
|
|
return UINT_MAX;
|
|
|
|
return a * b;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline unsigned int muldiv32(unsigned int a, unsigned int b,
|
|
|
|
unsigned int c, unsigned int *r)
|
|
|
|
{
|
|
|
|
u_int64_t n = (u_int64_t) a * b;
|
|
|
|
if (c == 0) {
|
|
|
|
*r = 0;
|
|
|
|
return UINT_MAX;
|
|
|
|
}
|
2009-06-05 22:40:04 +07:00
|
|
|
n = div_u64_rem(n, c, r);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (n >= UINT_MAX) {
|
|
|
|
*r = 0;
|
|
|
|
return UINT_MAX;
|
|
|
|
}
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* snd_interval_refine - refine the interval value of configurator
|
|
|
|
* @i: the interval value to refine
|
|
|
|
* @v: the interval value to refer to
|
|
|
|
*
|
|
|
|
* Refines the interval value with the reference value.
|
|
|
|
* The interval is changed to the range satisfying both intervals.
|
|
|
|
* The interval status (min, max, integer, etc.) are evaluated.
|
|
|
|
*
|
2013-03-12 04:05:14 +07:00
|
|
|
* Return: Positive if the value is changed, zero if it's not changed, or a
|
|
|
|
* negative error code.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2005-11-17 19:59:38 +07:00
|
|
|
int snd_interval_refine(struct snd_interval *i, const struct snd_interval *v)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
int changed = 0;
|
2008-08-08 22:09:09 +07:00
|
|
|
if (snd_BUG_ON(snd_interval_empty(i)))
|
|
|
|
return -EINVAL;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (i->min < v->min) {
|
|
|
|
i->min = v->min;
|
|
|
|
i->openmin = v->openmin;
|
|
|
|
changed = 1;
|
|
|
|
} else if (i->min == v->min && !i->openmin && v->openmin) {
|
|
|
|
i->openmin = 1;
|
|
|
|
changed = 1;
|
|
|
|
}
|
|
|
|
if (i->max > v->max) {
|
|
|
|
i->max = v->max;
|
|
|
|
i->openmax = v->openmax;
|
|
|
|
changed = 1;
|
|
|
|
} else if (i->max == v->max && !i->openmax && v->openmax) {
|
|
|
|
i->openmax = 1;
|
|
|
|
changed = 1;
|
|
|
|
}
|
|
|
|
if (!i->integer && v->integer) {
|
|
|
|
i->integer = 1;
|
|
|
|
changed = 1;
|
|
|
|
}
|
|
|
|
if (i->integer) {
|
|
|
|
if (i->openmin) {
|
|
|
|
i->min++;
|
|
|
|
i->openmin = 0;
|
|
|
|
}
|
|
|
|
if (i->openmax) {
|
|
|
|
i->max--;
|
|
|
|
i->openmax = 0;
|
|
|
|
}
|
|
|
|
} else if (!i->openmin && !i->openmax && i->min == i->max)
|
|
|
|
i->integer = 1;
|
|
|
|
if (snd_interval_checkempty(i)) {
|
|
|
|
snd_interval_none(i);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
return changed;
|
|
|
|
}
|
2006-04-28 20:13:40 +07:00
|
|
|
EXPORT_SYMBOL(snd_interval_refine);
|
|
|
|
|
2005-11-17 19:59:38 +07:00
|
|
|
static int snd_interval_refine_first(struct snd_interval *i)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2018-07-10 22:28:45 +07:00
|
|
|
const unsigned int last_max = i->max;
|
|
|
|
|
2008-08-08 22:09:09 +07:00
|
|
|
if (snd_BUG_ON(snd_interval_empty(i)))
|
|
|
|
return -EINVAL;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (snd_interval_single(i))
|
|
|
|
return 0;
|
|
|
|
i->max = i->min;
|
2018-07-10 22:28:45 +07:00
|
|
|
if (i->openmin)
|
2005-04-17 05:20:36 +07:00
|
|
|
i->max++;
|
2018-07-10 22:28:45 +07:00
|
|
|
/* only exclude max value if also excluded before refine */
|
|
|
|
i->openmax = (i->openmax && i->max >= last_max);
|
2005-04-17 05:20:36 +07:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2005-11-17 19:59:38 +07:00
|
|
|
static int snd_interval_refine_last(struct snd_interval *i)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2018-07-10 22:28:45 +07:00
|
|
|
const unsigned int last_min = i->min;
|
|
|
|
|
2008-08-08 22:09:09 +07:00
|
|
|
if (snd_BUG_ON(snd_interval_empty(i)))
|
|
|
|
return -EINVAL;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (snd_interval_single(i))
|
|
|
|
return 0;
|
|
|
|
i->min = i->max;
|
2018-07-10 22:28:45 +07:00
|
|
|
if (i->openmax)
|
2005-04-17 05:20:36 +07:00
|
|
|
i->min--;
|
2018-07-10 22:28:45 +07:00
|
|
|
/* only exclude min value if also excluded before refine */
|
|
|
|
i->openmin = (i->openmin && i->min <= last_min);
|
2005-04-17 05:20:36 +07:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2005-11-17 19:59:38 +07:00
|
|
|
void snd_interval_mul(const struct snd_interval *a, const struct snd_interval *b, struct snd_interval *c)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
if (a->empty || b->empty) {
|
|
|
|
snd_interval_none(c);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
c->empty = 0;
|
|
|
|
c->min = mul(a->min, b->min);
|
|
|
|
c->openmin = (a->openmin || b->openmin);
|
|
|
|
c->max = mul(a->max, b->max);
|
|
|
|
c->openmax = (a->openmax || b->openmax);
|
|
|
|
c->integer = (a->integer && b->integer);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* snd_interval_div - refine the interval value with division
|
2005-09-07 18:38:19 +07:00
|
|
|
* @a: dividend
|
|
|
|
* @b: divisor
|
|
|
|
* @c: quotient
|
2005-04-17 05:20:36 +07:00
|
|
|
*
|
|
|
|
* c = a / b
|
|
|
|
*
|
|
|
|
* Returns non-zero if the value is changed, zero if not changed.
|
|
|
|
*/
|
2005-11-17 19:59:38 +07:00
|
|
|
void snd_interval_div(const struct snd_interval *a, const struct snd_interval *b, struct snd_interval *c)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
unsigned int r;
|
|
|
|
if (a->empty || b->empty) {
|
|
|
|
snd_interval_none(c);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
c->empty = 0;
|
|
|
|
c->min = div32(a->min, b->max, &r);
|
|
|
|
c->openmin = (r || a->openmin || b->openmax);
|
|
|
|
if (b->min > 0) {
|
|
|
|
c->max = div32(a->max, b->min, &r);
|
|
|
|
if (r) {
|
|
|
|
c->max++;
|
|
|
|
c->openmax = 1;
|
|
|
|
} else
|
|
|
|
c->openmax = (a->openmax || b->openmin);
|
|
|
|
} else {
|
|
|
|
c->max = UINT_MAX;
|
|
|
|
c->openmax = 0;
|
|
|
|
}
|
|
|
|
c->integer = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* snd_interval_muldivk - refine the interval value
|
2005-09-07 18:38:19 +07:00
|
|
|
* @a: dividend 1
|
|
|
|
* @b: dividend 2
|
|
|
|
* @k: divisor (as integer)
|
|
|
|
* @c: result
|
|
|
|
*
|
2005-04-17 05:20:36 +07:00
|
|
|
* c = a * b / k
|
|
|
|
*
|
|
|
|
* Returns non-zero if the value is changed, zero if not changed.
|
|
|
|
*/
|
2005-11-17 19:59:38 +07:00
|
|
|
void snd_interval_muldivk(const struct snd_interval *a, const struct snd_interval *b,
|
|
|
|
unsigned int k, struct snd_interval *c)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
unsigned int r;
|
|
|
|
if (a->empty || b->empty) {
|
|
|
|
snd_interval_none(c);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
c->empty = 0;
|
|
|
|
c->min = muldiv32(a->min, b->min, k, &r);
|
|
|
|
c->openmin = (r || a->openmin || b->openmin);
|
|
|
|
c->max = muldiv32(a->max, b->max, k, &r);
|
|
|
|
if (r) {
|
|
|
|
c->max++;
|
|
|
|
c->openmax = 1;
|
|
|
|
} else
|
|
|
|
c->openmax = (a->openmax || b->openmax);
|
|
|
|
c->integer = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* snd_interval_mulkdiv - refine the interval value
|
2005-09-07 18:38:19 +07:00
|
|
|
* @a: dividend 1
|
|
|
|
* @k: dividend 2 (as integer)
|
|
|
|
* @b: divisor
|
|
|
|
* @c: result
|
2005-04-17 05:20:36 +07:00
|
|
|
*
|
|
|
|
* c = a * k / b
|
|
|
|
*
|
|
|
|
* Returns non-zero if the value is changed, zero if not changed.
|
|
|
|
*/
|
2005-11-17 19:59:38 +07:00
|
|
|
void snd_interval_mulkdiv(const struct snd_interval *a, unsigned int k,
|
|
|
|
const struct snd_interval *b, struct snd_interval *c)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
unsigned int r;
|
|
|
|
if (a->empty || b->empty) {
|
|
|
|
snd_interval_none(c);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
c->empty = 0;
|
|
|
|
c->min = muldiv32(a->min, k, b->max, &r);
|
|
|
|
c->openmin = (r || a->openmin || b->openmax);
|
|
|
|
if (b->min > 0) {
|
|
|
|
c->max = muldiv32(a->max, k, b->min, &r);
|
|
|
|
if (r) {
|
|
|
|
c->max++;
|
|
|
|
c->openmax = 1;
|
|
|
|
} else
|
|
|
|
c->openmax = (a->openmax || b->openmin);
|
|
|
|
} else {
|
|
|
|
c->max = UINT_MAX;
|
|
|
|
c->openmax = 0;
|
|
|
|
}
|
|
|
|
c->integer = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ---- */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* snd_interval_ratnum - refine the interval value
|
2005-09-07 18:38:19 +07:00
|
|
|
* @i: interval to refine
|
|
|
|
* @rats_count: number of ratnum_t
|
|
|
|
* @rats: ratnum_t array
|
|
|
|
* @nump: pointer to store the resultant numerator
|
|
|
|
* @denp: pointer to store the resultant denominator
|
2005-04-17 05:20:36 +07:00
|
|
|
*
|
2013-03-12 04:05:14 +07:00
|
|
|
* Return: Positive if the value is changed, zero if it's not changed, or a
|
|
|
|
* negative error code.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2005-11-17 19:59:38 +07:00
|
|
|
int snd_interval_ratnum(struct snd_interval *i,
|
2015-10-28 17:37:53 +07:00
|
|
|
unsigned int rats_count, const struct snd_ratnum *rats,
|
2005-11-17 19:59:38 +07:00
|
|
|
unsigned int *nump, unsigned int *denp)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2009-12-21 23:07:08 +07:00
|
|
|
unsigned int best_num, best_den;
|
|
|
|
int best_diff;
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned int k;
|
2005-11-17 19:59:38 +07:00
|
|
|
struct snd_interval t;
|
2005-04-17 05:20:36 +07:00
|
|
|
int err;
|
2009-12-21 23:07:08 +07:00
|
|
|
unsigned int result_num, result_den;
|
|
|
|
int result_diff;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
best_num = best_den = best_diff = 0;
|
|
|
|
for (k = 0; k < rats_count; ++k) {
|
|
|
|
unsigned int num = rats[k].num;
|
|
|
|
unsigned int den;
|
|
|
|
unsigned int q = i->min;
|
|
|
|
int diff;
|
|
|
|
if (q == 0)
|
|
|
|
q = 1;
|
2009-12-20 00:31:04 +07:00
|
|
|
den = div_up(num, q);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (den < rats[k].den_min)
|
|
|
|
continue;
|
|
|
|
if (den > rats[k].den_max)
|
|
|
|
den = rats[k].den_max;
|
|
|
|
else {
|
|
|
|
unsigned int r;
|
|
|
|
r = (den - rats[k].den_min) % rats[k].den_step;
|
|
|
|
if (r != 0)
|
|
|
|
den -= r;
|
|
|
|
}
|
|
|
|
diff = num - q * den;
|
2009-12-21 23:07:08 +07:00
|
|
|
if (diff < 0)
|
|
|
|
diff = -diff;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (best_num == 0 ||
|
|
|
|
diff * best_den < best_diff * den) {
|
|
|
|
best_diff = diff;
|
|
|
|
best_den = den;
|
|
|
|
best_num = num;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (best_den == 0) {
|
|
|
|
i->empty = 1;
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
t.min = div_down(best_num, best_den);
|
|
|
|
t.openmin = !!(best_num % best_den);
|
|
|
|
|
2009-12-21 23:07:08 +07:00
|
|
|
result_num = best_num;
|
|
|
|
result_diff = best_diff;
|
|
|
|
result_den = best_den;
|
2005-04-17 05:20:36 +07:00
|
|
|
best_num = best_den = best_diff = 0;
|
|
|
|
for (k = 0; k < rats_count; ++k) {
|
|
|
|
unsigned int num = rats[k].num;
|
|
|
|
unsigned int den;
|
|
|
|
unsigned int q = i->max;
|
|
|
|
int diff;
|
|
|
|
if (q == 0) {
|
|
|
|
i->empty = 1;
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
2009-12-20 00:31:04 +07:00
|
|
|
den = div_down(num, q);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (den > rats[k].den_max)
|
|
|
|
continue;
|
|
|
|
if (den < rats[k].den_min)
|
|
|
|
den = rats[k].den_min;
|
|
|
|
else {
|
|
|
|
unsigned int r;
|
|
|
|
r = (den - rats[k].den_min) % rats[k].den_step;
|
|
|
|
if (r != 0)
|
|
|
|
den += rats[k].den_step - r;
|
|
|
|
}
|
|
|
|
diff = q * den - num;
|
2009-12-21 23:07:08 +07:00
|
|
|
if (diff < 0)
|
|
|
|
diff = -diff;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (best_num == 0 ||
|
|
|
|
diff * best_den < best_diff * den) {
|
|
|
|
best_diff = diff;
|
|
|
|
best_den = den;
|
|
|
|
best_num = num;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (best_den == 0) {
|
|
|
|
i->empty = 1;
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
t.max = div_up(best_num, best_den);
|
|
|
|
t.openmax = !!(best_num % best_den);
|
|
|
|
t.integer = 0;
|
|
|
|
err = snd_interval_refine(i, &t);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
if (snd_interval_single(i)) {
|
2009-12-21 23:07:08 +07:00
|
|
|
if (best_diff * result_den < result_diff * best_den) {
|
|
|
|
result_num = best_num;
|
|
|
|
result_den = best_den;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
if (nump)
|
2009-12-21 23:07:08 +07:00
|
|
|
*nump = result_num;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (denp)
|
2009-12-21 23:07:08 +07:00
|
|
|
*denp = result_den;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
return err;
|
|
|
|
}
|
2006-04-28 20:13:40 +07:00
|
|
|
EXPORT_SYMBOL(snd_interval_ratnum);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/**
|
|
|
|
* snd_interval_ratden - refine the interval value
|
2005-09-07 18:38:19 +07:00
|
|
|
* @i: interval to refine
|
2005-11-17 19:59:38 +07:00
|
|
|
* @rats_count: number of struct ratden
|
|
|
|
* @rats: struct ratden array
|
2005-09-07 18:38:19 +07:00
|
|
|
* @nump: pointer to store the resultant numerator
|
|
|
|
* @denp: pointer to store the resultant denominator
|
2005-04-17 05:20:36 +07:00
|
|
|
*
|
2013-03-12 04:05:14 +07:00
|
|
|
* Return: Positive if the value is changed, zero if it's not changed, or a
|
|
|
|
* negative error code.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2005-11-17 19:59:38 +07:00
|
|
|
static int snd_interval_ratden(struct snd_interval *i,
|
2015-10-28 17:37:53 +07:00
|
|
|
unsigned int rats_count,
|
|
|
|
const struct snd_ratden *rats,
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned int *nump, unsigned int *denp)
|
|
|
|
{
|
|
|
|
unsigned int best_num, best_diff, best_den;
|
|
|
|
unsigned int k;
|
2005-11-17 19:59:38 +07:00
|
|
|
struct snd_interval t;
|
2005-04-17 05:20:36 +07:00
|
|
|
int err;
|
|
|
|
|
|
|
|
best_num = best_den = best_diff = 0;
|
|
|
|
for (k = 0; k < rats_count; ++k) {
|
|
|
|
unsigned int num;
|
|
|
|
unsigned int den = rats[k].den;
|
|
|
|
unsigned int q = i->min;
|
|
|
|
int diff;
|
|
|
|
num = mul(q, den);
|
|
|
|
if (num > rats[k].num_max)
|
|
|
|
continue;
|
|
|
|
if (num < rats[k].num_min)
|
|
|
|
num = rats[k].num_max;
|
|
|
|
else {
|
|
|
|
unsigned int r;
|
|
|
|
r = (num - rats[k].num_min) % rats[k].num_step;
|
|
|
|
if (r != 0)
|
|
|
|
num += rats[k].num_step - r;
|
|
|
|
}
|
|
|
|
diff = num - q * den;
|
|
|
|
if (best_num == 0 ||
|
|
|
|
diff * best_den < best_diff * den) {
|
|
|
|
best_diff = diff;
|
|
|
|
best_den = den;
|
|
|
|
best_num = num;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (best_den == 0) {
|
|
|
|
i->empty = 1;
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
t.min = div_down(best_num, best_den);
|
|
|
|
t.openmin = !!(best_num % best_den);
|
|
|
|
|
|
|
|
best_num = best_den = best_diff = 0;
|
|
|
|
for (k = 0; k < rats_count; ++k) {
|
|
|
|
unsigned int num;
|
|
|
|
unsigned int den = rats[k].den;
|
|
|
|
unsigned int q = i->max;
|
|
|
|
int diff;
|
|
|
|
num = mul(q, den);
|
|
|
|
if (num < rats[k].num_min)
|
|
|
|
continue;
|
|
|
|
if (num > rats[k].num_max)
|
|
|
|
num = rats[k].num_max;
|
|
|
|
else {
|
|
|
|
unsigned int r;
|
|
|
|
r = (num - rats[k].num_min) % rats[k].num_step;
|
|
|
|
if (r != 0)
|
|
|
|
num -= r;
|
|
|
|
}
|
|
|
|
diff = q * den - num;
|
|
|
|
if (best_num == 0 ||
|
|
|
|
diff * best_den < best_diff * den) {
|
|
|
|
best_diff = diff;
|
|
|
|
best_den = den;
|
|
|
|
best_num = num;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (best_den == 0) {
|
|
|
|
i->empty = 1;
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
t.max = div_up(best_num, best_den);
|
|
|
|
t.openmax = !!(best_num % best_den);
|
|
|
|
t.integer = 0;
|
|
|
|
err = snd_interval_refine(i, &t);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
if (snd_interval_single(i)) {
|
|
|
|
if (nump)
|
|
|
|
*nump = best_num;
|
|
|
|
if (denp)
|
|
|
|
*denp = best_den;
|
|
|
|
}
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* snd_interval_list - refine the interval value from the list
|
|
|
|
* @i: the interval value to refine
|
|
|
|
* @count: the number of elements in the list
|
|
|
|
* @list: the value list
|
|
|
|
* @mask: the bit-mask to evaluate
|
|
|
|
*
|
|
|
|
* Refines the interval value from the list.
|
|
|
|
* When mask is non-zero, only the elements corresponding to bit 1 are
|
|
|
|
* evaluated.
|
|
|
|
*
|
2013-03-12 04:05:14 +07:00
|
|
|
* Return: Positive if the value is changed, zero if it's not changed, or a
|
|
|
|
* negative error code.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2012-03-15 02:48:43 +07:00
|
|
|
int snd_interval_list(struct snd_interval *i, unsigned int count,
|
|
|
|
const unsigned int *list, unsigned int mask)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
unsigned int k;
|
2009-08-25 13:15:41 +07:00
|
|
|
struct snd_interval list_range;
|
2007-02-01 20:53:49 +07:00
|
|
|
|
|
|
|
if (!count) {
|
|
|
|
i->empty = 1;
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
2009-08-25 13:15:41 +07:00
|
|
|
snd_interval_any(&list_range);
|
|
|
|
list_range.min = UINT_MAX;
|
|
|
|
list_range.max = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
for (k = 0; k < count; k++) {
|
|
|
|
if (mask && !(mask & (1 << k)))
|
|
|
|
continue;
|
2009-08-25 13:15:41 +07:00
|
|
|
if (!snd_interval_test(i, list[k]))
|
2005-04-17 05:20:36 +07:00
|
|
|
continue;
|
2009-08-25 13:15:41 +07:00
|
|
|
list_range.min = min(list_range.min, list[k]);
|
|
|
|
list_range.max = max(list_range.max, list[k]);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2009-08-25 13:15:41 +07:00
|
|
|
return snd_interval_refine(i, &list_range);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2006-04-28 20:13:40 +07:00
|
|
|
EXPORT_SYMBOL(snd_interval_list);
|
|
|
|
|
2015-01-28 21:16:06 +07:00
|
|
|
/**
|
|
|
|
* snd_interval_ranges - refine the interval value from the list of ranges
|
|
|
|
* @i: the interval value to refine
|
|
|
|
* @count: the number of elements in the list of ranges
|
|
|
|
* @ranges: the ranges list
|
|
|
|
* @mask: the bit-mask to evaluate
|
|
|
|
*
|
|
|
|
* Refines the interval value from the list of ranges.
|
|
|
|
* When mask is non-zero, only the elements corresponding to bit 1 are
|
|
|
|
* evaluated.
|
|
|
|
*
|
|
|
|
* Return: Positive if the value is changed, zero if it's not changed, or a
|
|
|
|
* negative error code.
|
|
|
|
*/
|
|
|
|
int snd_interval_ranges(struct snd_interval *i, unsigned int count,
|
|
|
|
const struct snd_interval *ranges, unsigned int mask)
|
|
|
|
{
|
|
|
|
unsigned int k;
|
|
|
|
struct snd_interval range_union;
|
|
|
|
struct snd_interval range;
|
|
|
|
|
|
|
|
if (!count) {
|
|
|
|
snd_interval_none(i);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
snd_interval_any(&range_union);
|
|
|
|
range_union.min = UINT_MAX;
|
|
|
|
range_union.max = 0;
|
|
|
|
for (k = 0; k < count; k++) {
|
|
|
|
if (mask && !(mask & (1 << k)))
|
|
|
|
continue;
|
|
|
|
snd_interval_copy(&range, &ranges[k]);
|
|
|
|
if (snd_interval_refine(&range, i) < 0)
|
|
|
|
continue;
|
|
|
|
if (snd_interval_empty(&range))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (range.min < range_union.min) {
|
|
|
|
range_union.min = range.min;
|
|
|
|
range_union.openmin = 1;
|
|
|
|
}
|
|
|
|
if (range.min == range_union.min && !range.openmin)
|
|
|
|
range_union.openmin = 0;
|
|
|
|
if (range.max > range_union.max) {
|
|
|
|
range_union.max = range.max;
|
|
|
|
range_union.openmax = 1;
|
|
|
|
}
|
|
|
|
if (range.max == range_union.max && !range.openmax)
|
|
|
|
range_union.openmax = 0;
|
|
|
|
}
|
|
|
|
return snd_interval_refine(i, &range_union);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(snd_interval_ranges);
|
|
|
|
|
2014-09-08 02:43:07 +07:00
|
|
|
static int snd_interval_step(struct snd_interval *i, unsigned int step)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
unsigned int n;
|
|
|
|
int changed = 0;
|
2014-09-08 02:43:07 +07:00
|
|
|
n = i->min % step;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (n != 0 || i->openmin) {
|
|
|
|
i->min += step - n;
|
2014-09-08 02:43:41 +07:00
|
|
|
i->openmin = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
changed = 1;
|
|
|
|
}
|
2014-09-08 02:43:07 +07:00
|
|
|
n = i->max % step;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (n != 0 || i->openmax) {
|
|
|
|
i->max -= n;
|
2014-09-08 02:43:41 +07:00
|
|
|
i->openmax = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
changed = 1;
|
|
|
|
}
|
|
|
|
if (snd_interval_checkempty(i)) {
|
|
|
|
i->empty = 1;
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
return changed;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Info constraints helpers */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* snd_pcm_hw_rule_add - add the hw-constraint rule
|
|
|
|
* @runtime: the pcm runtime instance
|
|
|
|
* @cond: condition bits
|
|
|
|
* @var: the variable to evaluate
|
|
|
|
* @func: the evaluation function
|
|
|
|
* @private: the private data pointer passed to function
|
|
|
|
* @dep: the dependent variables
|
|
|
|
*
|
2013-03-12 04:05:14 +07:00
|
|
|
* Return: Zero if successful, or a negative error code on failure.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2005-11-17 19:59:38 +07:00
|
|
|
int snd_pcm_hw_rule_add(struct snd_pcm_runtime *runtime, unsigned int cond,
|
2005-04-17 05:20:36 +07:00
|
|
|
int var,
|
|
|
|
snd_pcm_hw_rule_func_t func, void *private,
|
|
|
|
int dep, ...)
|
|
|
|
{
|
2005-11-17 19:59:38 +07:00
|
|
|
struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints;
|
|
|
|
struct snd_pcm_hw_rule *c;
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned int k;
|
|
|
|
va_list args;
|
|
|
|
va_start(args, dep);
|
|
|
|
if (constrs->rules_num >= constrs->rules_all) {
|
2005-11-17 19:59:38 +07:00
|
|
|
struct snd_pcm_hw_rule *new;
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned int new_rules = constrs->rules_all + 16;
|
2018-03-13 17:31:25 +07:00
|
|
|
new = krealloc(constrs->rules, new_rules * sizeof(*c),
|
|
|
|
GFP_KERNEL);
|
2010-12-21 06:03:17 +07:00
|
|
|
if (!new) {
|
|
|
|
va_end(args);
|
2005-04-17 05:20:36 +07:00
|
|
|
return -ENOMEM;
|
2010-12-21 06:03:17 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
constrs->rules = new;
|
|
|
|
constrs->rules_all = new_rules;
|
|
|
|
}
|
|
|
|
c = &constrs->rules[constrs->rules_num];
|
|
|
|
c->cond = cond;
|
|
|
|
c->func = func;
|
|
|
|
c->var = var;
|
|
|
|
c->private = private;
|
|
|
|
k = 0;
|
|
|
|
while (1) {
|
2010-12-21 06:03:17 +07:00
|
|
|
if (snd_BUG_ON(k >= ARRAY_SIZE(c->deps))) {
|
|
|
|
va_end(args);
|
2008-08-08 22:09:09 +07:00
|
|
|
return -EINVAL;
|
2010-12-21 06:03:17 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
c->deps[k++] = dep;
|
|
|
|
if (dep < 0)
|
|
|
|
break;
|
|
|
|
dep = va_arg(args, int);
|
|
|
|
}
|
|
|
|
constrs->rules_num++;
|
|
|
|
va_end(args);
|
|
|
|
return 0;
|
2010-12-21 06:03:17 +07:00
|
|
|
}
|
2006-04-28 20:13:40 +07:00
|
|
|
EXPORT_SYMBOL(snd_pcm_hw_rule_add);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/**
|
2008-10-16 04:38:40 +07:00
|
|
|
* snd_pcm_hw_constraint_mask - apply the given bitmap mask constraint
|
2005-09-07 18:38:19 +07:00
|
|
|
* @runtime: PCM runtime instance
|
|
|
|
* @var: hw_params variable to apply the mask
|
|
|
|
* @mask: the bitmap mask
|
|
|
|
*
|
2008-10-16 04:38:40 +07:00
|
|
|
* Apply the constraint of the given bitmap mask to a 32-bit mask parameter.
|
2013-03-12 04:05:14 +07:00
|
|
|
*
|
|
|
|
* Return: Zero if successful, or a negative error code on failure.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2005-11-17 19:59:38 +07:00
|
|
|
int snd_pcm_hw_constraint_mask(struct snd_pcm_runtime *runtime, snd_pcm_hw_param_t var,
|
2005-04-17 05:20:36 +07:00
|
|
|
u_int32_t mask)
|
|
|
|
{
|
2005-11-17 19:59:38 +07:00
|
|
|
struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints;
|
|
|
|
struct snd_mask *maskp = constrs_mask(constrs, var);
|
2005-04-17 05:20:36 +07:00
|
|
|
*maskp->bits &= mask;
|
|
|
|
memset(maskp->bits + 1, 0, (SNDRV_MASK_MAX-32) / 8); /* clear rest */
|
|
|
|
if (*maskp->bits == 0)
|
|
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2008-10-16 04:38:40 +07:00
|
|
|
* snd_pcm_hw_constraint_mask64 - apply the given bitmap mask constraint
|
2005-09-07 18:38:19 +07:00
|
|
|
* @runtime: PCM runtime instance
|
|
|
|
* @var: hw_params variable to apply the mask
|
|
|
|
* @mask: the 64bit bitmap mask
|
|
|
|
*
|
2008-10-16 04:38:40 +07:00
|
|
|
* Apply the constraint of the given bitmap mask to a 64-bit mask parameter.
|
2013-03-12 04:05:14 +07:00
|
|
|
*
|
|
|
|
* Return: Zero if successful, or a negative error code on failure.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2005-11-17 19:59:38 +07:00
|
|
|
int snd_pcm_hw_constraint_mask64(struct snd_pcm_runtime *runtime, snd_pcm_hw_param_t var,
|
2005-04-17 05:20:36 +07:00
|
|
|
u_int64_t mask)
|
|
|
|
{
|
2005-11-17 19:59:38 +07:00
|
|
|
struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints;
|
|
|
|
struct snd_mask *maskp = constrs_mask(constrs, var);
|
2005-04-17 05:20:36 +07:00
|
|
|
maskp->bits[0] &= (u_int32_t)mask;
|
|
|
|
maskp->bits[1] &= (u_int32_t)(mask >> 32);
|
|
|
|
memset(maskp->bits + 2, 0, (SNDRV_MASK_MAX-64) / 8); /* clear rest */
|
|
|
|
if (! maskp->bits[0] && ! maskp->bits[1])
|
|
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
|
|
}
|
2014-02-20 10:13:52 +07:00
|
|
|
EXPORT_SYMBOL(snd_pcm_hw_constraint_mask64);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
/**
|
2008-10-16 04:38:40 +07:00
|
|
|
* snd_pcm_hw_constraint_integer - apply an integer constraint to an interval
|
2005-09-07 18:38:19 +07:00
|
|
|
* @runtime: PCM runtime instance
|
|
|
|
* @var: hw_params variable to apply the integer constraint
|
|
|
|
*
|
|
|
|
* Apply the constraint of integer to an interval parameter.
|
2013-03-12 04:05:14 +07:00
|
|
|
*
|
|
|
|
* Return: Positive if the value is changed, zero if it's not changed, or a
|
|
|
|
* negative error code.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2005-11-17 19:59:38 +07:00
|
|
|
int snd_pcm_hw_constraint_integer(struct snd_pcm_runtime *runtime, snd_pcm_hw_param_t var)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:59:38 +07:00
|
|
|
struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints;
|
2005-04-17 05:20:36 +07:00
|
|
|
return snd_interval_setinteger(constrs_interval(constrs, var));
|
|
|
|
}
|
2006-04-28 20:13:40 +07:00
|
|
|
EXPORT_SYMBOL(snd_pcm_hw_constraint_integer);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/**
|
2008-10-16 04:38:40 +07:00
|
|
|
* snd_pcm_hw_constraint_minmax - apply a min/max range constraint to an interval
|
2005-09-07 18:38:19 +07:00
|
|
|
* @runtime: PCM runtime instance
|
|
|
|
* @var: hw_params variable to apply the range
|
|
|
|
* @min: the minimal value
|
|
|
|
* @max: the maximal value
|
|
|
|
*
|
|
|
|
* Apply the min/max range constraint to an interval parameter.
|
2013-03-12 04:05:14 +07:00
|
|
|
*
|
|
|
|
* Return: Positive if the value is changed, zero if it's not changed, or a
|
|
|
|
* negative error code.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2005-11-17 19:59:38 +07:00
|
|
|
int snd_pcm_hw_constraint_minmax(struct snd_pcm_runtime *runtime, snd_pcm_hw_param_t var,
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned int min, unsigned int max)
|
|
|
|
{
|
2005-11-17 19:59:38 +07:00
|
|
|
struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints;
|
|
|
|
struct snd_interval t;
|
2005-04-17 05:20:36 +07:00
|
|
|
t.min = min;
|
|
|
|
t.max = max;
|
|
|
|
t.openmin = t.openmax = 0;
|
|
|
|
t.integer = 0;
|
|
|
|
return snd_interval_refine(constrs_interval(constrs, var), &t);
|
|
|
|
}
|
2006-04-28 20:13:40 +07:00
|
|
|
EXPORT_SYMBOL(snd_pcm_hw_constraint_minmax);
|
|
|
|
|
2005-11-17 19:59:38 +07:00
|
|
|
static int snd_pcm_hw_rule_list(struct snd_pcm_hw_params *params,
|
|
|
|
struct snd_pcm_hw_rule *rule)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:59:38 +07:00
|
|
|
struct snd_pcm_hw_constraint_list *list = rule->private;
|
2005-04-17 05:20:36 +07:00
|
|
|
return snd_interval_list(hw_param_interval(params, rule->var), list->count, list->list, list->mask);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2008-10-16 04:38:40 +07:00
|
|
|
* snd_pcm_hw_constraint_list - apply a list of constraints to a parameter
|
2005-09-07 18:38:19 +07:00
|
|
|
* @runtime: PCM runtime instance
|
|
|
|
* @cond: condition bits
|
|
|
|
* @var: hw_params variable to apply the list constraint
|
|
|
|
* @l: list
|
|
|
|
*
|
|
|
|
* Apply the list of constraints to an interval parameter.
|
2013-03-12 04:05:14 +07:00
|
|
|
*
|
|
|
|
* Return: Zero if successful, or a negative error code on failure.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2005-11-17 19:59:38 +07:00
|
|
|
int snd_pcm_hw_constraint_list(struct snd_pcm_runtime *runtime,
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned int cond,
|
|
|
|
snd_pcm_hw_param_t var,
|
2012-07-05 18:15:01 +07:00
|
|
|
const struct snd_pcm_hw_constraint_list *l)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
return snd_pcm_hw_rule_add(runtime, cond, var,
|
2012-07-05 18:15:01 +07:00
|
|
|
snd_pcm_hw_rule_list, (void *)l,
|
2005-04-17 05:20:36 +07:00
|
|
|
var, -1);
|
|
|
|
}
|
2006-04-28 20:13:40 +07:00
|
|
|
EXPORT_SYMBOL(snd_pcm_hw_constraint_list);
|
|
|
|
|
2015-01-28 21:16:06 +07:00
|
|
|
static int snd_pcm_hw_rule_ranges(struct snd_pcm_hw_params *params,
|
|
|
|
struct snd_pcm_hw_rule *rule)
|
|
|
|
{
|
|
|
|
struct snd_pcm_hw_constraint_ranges *r = rule->private;
|
|
|
|
return snd_interval_ranges(hw_param_interval(params, rule->var),
|
|
|
|
r->count, r->ranges, r->mask);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* snd_pcm_hw_constraint_ranges - apply list of range constraints to a parameter
|
|
|
|
* @runtime: PCM runtime instance
|
|
|
|
* @cond: condition bits
|
|
|
|
* @var: hw_params variable to apply the list of range constraints
|
|
|
|
* @r: ranges
|
|
|
|
*
|
|
|
|
* Apply the list of range constraints to an interval parameter.
|
|
|
|
*
|
|
|
|
* Return: Zero if successful, or a negative error code on failure.
|
|
|
|
*/
|
|
|
|
int snd_pcm_hw_constraint_ranges(struct snd_pcm_runtime *runtime,
|
|
|
|
unsigned int cond,
|
|
|
|
snd_pcm_hw_param_t var,
|
|
|
|
const struct snd_pcm_hw_constraint_ranges *r)
|
|
|
|
{
|
|
|
|
return snd_pcm_hw_rule_add(runtime, cond, var,
|
|
|
|
snd_pcm_hw_rule_ranges, (void *)r,
|
|
|
|
var, -1);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(snd_pcm_hw_constraint_ranges);
|
|
|
|
|
2005-11-17 19:59:38 +07:00
|
|
|
static int snd_pcm_hw_rule_ratnums(struct snd_pcm_hw_params *params,
|
|
|
|
struct snd_pcm_hw_rule *rule)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2015-10-28 17:37:53 +07:00
|
|
|
const struct snd_pcm_hw_constraint_ratnums *r = rule->private;
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned int num = 0, den = 0;
|
|
|
|
int err;
|
|
|
|
err = snd_interval_ratnum(hw_param_interval(params, rule->var),
|
|
|
|
r->nrats, r->rats, &num, &den);
|
|
|
|
if (err >= 0 && den && rule->var == SNDRV_PCM_HW_PARAM_RATE) {
|
|
|
|
params->rate_num = num;
|
|
|
|
params->rate_den = den;
|
|
|
|
}
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2008-10-16 04:38:40 +07:00
|
|
|
* snd_pcm_hw_constraint_ratnums - apply ratnums constraint to a parameter
|
2005-09-07 18:38:19 +07:00
|
|
|
* @runtime: PCM runtime instance
|
|
|
|
* @cond: condition bits
|
|
|
|
* @var: hw_params variable to apply the ratnums constraint
|
2005-11-17 19:59:38 +07:00
|
|
|
* @r: struct snd_ratnums constriants
|
2013-03-12 04:05:14 +07:00
|
|
|
*
|
|
|
|
* Return: Zero if successful, or a negative error code on failure.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2005-11-17 19:59:38 +07:00
|
|
|
int snd_pcm_hw_constraint_ratnums(struct snd_pcm_runtime *runtime,
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned int cond,
|
|
|
|
snd_pcm_hw_param_t var,
|
2015-10-28 17:37:53 +07:00
|
|
|
const struct snd_pcm_hw_constraint_ratnums *r)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
return snd_pcm_hw_rule_add(runtime, cond, var,
|
2015-10-28 17:37:53 +07:00
|
|
|
snd_pcm_hw_rule_ratnums, (void *)r,
|
2005-04-17 05:20:36 +07:00
|
|
|
var, -1);
|
|
|
|
}
|
2006-04-28 20:13:40 +07:00
|
|
|
EXPORT_SYMBOL(snd_pcm_hw_constraint_ratnums);
|
|
|
|
|
2005-11-17 19:59:38 +07:00
|
|
|
static int snd_pcm_hw_rule_ratdens(struct snd_pcm_hw_params *params,
|
|
|
|
struct snd_pcm_hw_rule *rule)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2015-10-28 17:37:53 +07:00
|
|
|
const struct snd_pcm_hw_constraint_ratdens *r = rule->private;
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned int num = 0, den = 0;
|
|
|
|
int err = snd_interval_ratden(hw_param_interval(params, rule->var),
|
|
|
|
r->nrats, r->rats, &num, &den);
|
|
|
|
if (err >= 0 && den && rule->var == SNDRV_PCM_HW_PARAM_RATE) {
|
|
|
|
params->rate_num = num;
|
|
|
|
params->rate_den = den;
|
|
|
|
}
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2008-10-16 04:38:40 +07:00
|
|
|
* snd_pcm_hw_constraint_ratdens - apply ratdens constraint to a parameter
|
2005-09-07 18:38:19 +07:00
|
|
|
* @runtime: PCM runtime instance
|
|
|
|
* @cond: condition bits
|
|
|
|
* @var: hw_params variable to apply the ratdens constraint
|
2005-11-17 19:59:38 +07:00
|
|
|
* @r: struct snd_ratdens constriants
|
2013-03-12 04:05:14 +07:00
|
|
|
*
|
|
|
|
* Return: Zero if successful, or a negative error code on failure.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2005-11-17 19:59:38 +07:00
|
|
|
int snd_pcm_hw_constraint_ratdens(struct snd_pcm_runtime *runtime,
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned int cond,
|
|
|
|
snd_pcm_hw_param_t var,
|
2015-10-28 17:37:53 +07:00
|
|
|
const struct snd_pcm_hw_constraint_ratdens *r)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
return snd_pcm_hw_rule_add(runtime, cond, var,
|
2015-10-28 17:37:53 +07:00
|
|
|
snd_pcm_hw_rule_ratdens, (void *)r,
|
2005-04-17 05:20:36 +07:00
|
|
|
var, -1);
|
|
|
|
}
|
2006-04-28 20:13:40 +07:00
|
|
|
EXPORT_SYMBOL(snd_pcm_hw_constraint_ratdens);
|
|
|
|
|
2005-11-17 19:59:38 +07:00
|
|
|
static int snd_pcm_hw_rule_msbits(struct snd_pcm_hw_params *params,
|
|
|
|
struct snd_pcm_hw_rule *rule)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
unsigned int l = (unsigned long) rule->private;
|
|
|
|
int width = l & 0xffff;
|
|
|
|
unsigned int msbits = l >> 16;
|
2017-05-17 06:48:20 +07:00
|
|
|
const struct snd_interval *i =
|
|
|
|
hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS);
|
2014-12-30 00:43:37 +07:00
|
|
|
|
|
|
|
if (!snd_interval_single(i))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if ((snd_interval_value(i) == width) ||
|
|
|
|
(width == 0 && snd_interval_value(i) > msbits))
|
2014-12-30 00:43:36 +07:00
|
|
|
params->msbits = min_not_zero(params->msbits, msbits);
|
2014-12-30 00:43:37 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2008-10-16 04:38:40 +07:00
|
|
|
* snd_pcm_hw_constraint_msbits - add a hw constraint msbits rule
|
2005-09-07 18:38:19 +07:00
|
|
|
* @runtime: PCM runtime instance
|
|
|
|
* @cond: condition bits
|
|
|
|
* @width: sample bits width
|
|
|
|
* @msbits: msbits width
|
2013-03-12 04:05:14 +07:00
|
|
|
*
|
2014-12-30 00:43:37 +07:00
|
|
|
* This constraint will set the number of most significant bits (msbits) if a
|
|
|
|
* sample format with the specified width has been select. If width is set to 0
|
|
|
|
* the msbits will be set for any sample format with a width larger than the
|
|
|
|
* specified msbits.
|
|
|
|
*
|
2013-03-12 04:05:14 +07:00
|
|
|
* Return: Zero if successful, or a negative error code on failure.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2005-11-17 19:59:38 +07:00
|
|
|
int snd_pcm_hw_constraint_msbits(struct snd_pcm_runtime *runtime,
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned int cond,
|
|
|
|
unsigned int width,
|
|
|
|
unsigned int msbits)
|
|
|
|
{
|
|
|
|
unsigned long l = (msbits << 16) | width;
|
|
|
|
return snd_pcm_hw_rule_add(runtime, cond, -1,
|
|
|
|
snd_pcm_hw_rule_msbits,
|
|
|
|
(void*) l,
|
|
|
|
SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);
|
|
|
|
}
|
2006-04-28 20:13:40 +07:00
|
|
|
EXPORT_SYMBOL(snd_pcm_hw_constraint_msbits);
|
|
|
|
|
2005-11-17 19:59:38 +07:00
|
|
|
static int snd_pcm_hw_rule_step(struct snd_pcm_hw_params *params,
|
|
|
|
struct snd_pcm_hw_rule *rule)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
unsigned long step = (unsigned long) rule->private;
|
2014-09-08 02:43:07 +07:00
|
|
|
return snd_interval_step(hw_param_interval(params, rule->var), step);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2008-10-16 04:38:40 +07:00
|
|
|
* snd_pcm_hw_constraint_step - add a hw constraint step rule
|
2005-09-07 18:38:19 +07:00
|
|
|
* @runtime: PCM runtime instance
|
|
|
|
* @cond: condition bits
|
|
|
|
* @var: hw_params variable to apply the step constraint
|
|
|
|
* @step: step size
|
2013-03-12 04:05:14 +07:00
|
|
|
*
|
|
|
|
* Return: Zero if successful, or a negative error code on failure.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2005-11-17 19:59:38 +07:00
|
|
|
int snd_pcm_hw_constraint_step(struct snd_pcm_runtime *runtime,
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned int cond,
|
|
|
|
snd_pcm_hw_param_t var,
|
|
|
|
unsigned long step)
|
|
|
|
{
|
|
|
|
return snd_pcm_hw_rule_add(runtime, cond, var,
|
|
|
|
snd_pcm_hw_rule_step, (void *) step,
|
|
|
|
var, -1);
|
|
|
|
}
|
2006-04-28 20:13:40 +07:00
|
|
|
EXPORT_SYMBOL(snd_pcm_hw_constraint_step);
|
|
|
|
|
2005-11-17 19:59:38 +07:00
|
|
|
static int snd_pcm_hw_rule_pow2(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2007-12-14 18:53:21 +07:00
|
|
|
static unsigned int pow2_sizes[] = {
|
2005-04-17 05:20:36 +07:00
|
|
|
1<<0, 1<<1, 1<<2, 1<<3, 1<<4, 1<<5, 1<<6, 1<<7,
|
|
|
|
1<<8, 1<<9, 1<<10, 1<<11, 1<<12, 1<<13, 1<<14, 1<<15,
|
|
|
|
1<<16, 1<<17, 1<<18, 1<<19, 1<<20, 1<<21, 1<<22, 1<<23,
|
|
|
|
1<<24, 1<<25, 1<<26, 1<<27, 1<<28, 1<<29, 1<<30
|
|
|
|
};
|
|
|
|
return snd_interval_list(hw_param_interval(params, rule->var),
|
|
|
|
ARRAY_SIZE(pow2_sizes), pow2_sizes, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2008-10-16 04:38:40 +07:00
|
|
|
* snd_pcm_hw_constraint_pow2 - add a hw constraint power-of-2 rule
|
2005-09-07 18:38:19 +07:00
|
|
|
* @runtime: PCM runtime instance
|
|
|
|
* @cond: condition bits
|
|
|
|
* @var: hw_params variable to apply the power-of-2 constraint
|
2013-03-12 04:05:14 +07:00
|
|
|
*
|
|
|
|
* Return: Zero if successful, or a negative error code on failure.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2005-11-17 19:59:38 +07:00
|
|
|
int snd_pcm_hw_constraint_pow2(struct snd_pcm_runtime *runtime,
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned int cond,
|
|
|
|
snd_pcm_hw_param_t var)
|
|
|
|
{
|
|
|
|
return snd_pcm_hw_rule_add(runtime, cond, var,
|
|
|
|
snd_pcm_hw_rule_pow2, NULL,
|
|
|
|
var, -1);
|
|
|
|
}
|
2006-04-28 20:13:40 +07:00
|
|
|
EXPORT_SYMBOL(snd_pcm_hw_constraint_pow2);
|
|
|
|
|
2011-09-17 04:03:02 +07:00
|
|
|
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
|
2013-03-12 04:05:14 +07:00
|
|
|
*
|
|
|
|
* Return: Zero if successful, or a negative error code on failure.
|
2011-09-17 04:03:02 +07:00
|
|
|
*/
|
|
|
|
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);
|
|
|
|
|
2005-11-17 19:59:38 +07:00
|
|
|
static void _snd_pcm_hw_param_any(struct snd_pcm_hw_params *params,
|
2005-05-18 23:02:04 +07:00
|
|
|
snd_pcm_hw_param_t var)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
if (hw_is_mask(var)) {
|
|
|
|
snd_mask_any(hw_param_mask(params, var));
|
|
|
|
params->cmask |= 1 << var;
|
|
|
|
params->rmask |= 1 << var;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (hw_is_interval(var)) {
|
|
|
|
snd_interval_any(hw_param_interval(params, var));
|
|
|
|
params->cmask |= 1 << var;
|
|
|
|
params->rmask |= 1 << var;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
snd_BUG();
|
|
|
|
}
|
|
|
|
|
2005-11-17 19:59:38 +07:00
|
|
|
void _snd_pcm_hw_params_any(struct snd_pcm_hw_params *params)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
unsigned int k;
|
|
|
|
memset(params, 0, sizeof(*params));
|
|
|
|
for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++)
|
|
|
|
_snd_pcm_hw_param_any(params, k);
|
|
|
|
for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++)
|
|
|
|
_snd_pcm_hw_param_any(params, k);
|
|
|
|
params->info = ~0U;
|
|
|
|
}
|
2006-04-28 20:13:40 +07:00
|
|
|
EXPORT_SYMBOL(_snd_pcm_hw_params_any);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
/**
|
2008-10-16 04:38:40 +07:00
|
|
|
* snd_pcm_hw_param_value - return @params field @var value
|
2005-09-07 18:38:19 +07:00
|
|
|
* @params: the hw_params instance
|
|
|
|
* @var: parameter to retrieve
|
2008-10-16 04:38:40 +07:00
|
|
|
* @dir: pointer to the direction (-1,0,1) or %NULL
|
2005-04-17 05:20:36 +07:00
|
|
|
*
|
2013-03-12 04:05:14 +07:00
|
|
|
* Return: The value for field @var if it's fixed in configuration space
|
|
|
|
* defined by @params. -%EINVAL otherwise.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2006-04-28 20:13:40 +07:00
|
|
|
int snd_pcm_hw_param_value(const struct snd_pcm_hw_params *params,
|
|
|
|
snd_pcm_hw_param_t var, int *dir)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
if (hw_is_mask(var)) {
|
2005-11-17 19:59:38 +07:00
|
|
|
const struct snd_mask *mask = hw_param_mask_c(params, var);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (!snd_mask_single(mask))
|
|
|
|
return -EINVAL;
|
|
|
|
if (dir)
|
|
|
|
*dir = 0;
|
|
|
|
return snd_mask_value(mask);
|
|
|
|
}
|
|
|
|
if (hw_is_interval(var)) {
|
2005-11-17 19:59:38 +07:00
|
|
|
const struct snd_interval *i = hw_param_interval_c(params, var);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (!snd_interval_single(i))
|
|
|
|
return -EINVAL;
|
|
|
|
if (dir)
|
|
|
|
*dir = i->openmin;
|
|
|
|
return snd_interval_value(i);
|
|
|
|
}
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
2006-04-28 20:13:40 +07:00
|
|
|
EXPORT_SYMBOL(snd_pcm_hw_param_value);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2005-11-17 19:59:38 +07:00
|
|
|
void _snd_pcm_hw_param_setempty(struct snd_pcm_hw_params *params,
|
2005-04-17 05:20:36 +07:00
|
|
|
snd_pcm_hw_param_t var)
|
|
|
|
{
|
|
|
|
if (hw_is_mask(var)) {
|
|
|
|
snd_mask_none(hw_param_mask(params, var));
|
|
|
|
params->cmask |= 1 << var;
|
|
|
|
params->rmask |= 1 << var;
|
|
|
|
} else if (hw_is_interval(var)) {
|
|
|
|
snd_interval_none(hw_param_interval(params, var));
|
|
|
|
params->cmask |= 1 << var;
|
|
|
|
params->rmask |= 1 << var;
|
|
|
|
} else {
|
|
|
|
snd_BUG();
|
|
|
|
}
|
|
|
|
}
|
2006-04-28 20:13:40 +07:00
|
|
|
EXPORT_SYMBOL(_snd_pcm_hw_param_setempty);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2005-11-17 19:59:38 +07:00
|
|
|
static int _snd_pcm_hw_param_first(struct snd_pcm_hw_params *params,
|
2005-05-18 23:02:04 +07:00
|
|
|
snd_pcm_hw_param_t var)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
int changed;
|
|
|
|
if (hw_is_mask(var))
|
|
|
|
changed = snd_mask_refine_first(hw_param_mask(params, var));
|
|
|
|
else if (hw_is_interval(var))
|
|
|
|
changed = snd_interval_refine_first(hw_param_interval(params, var));
|
2006-04-28 20:13:40 +07:00
|
|
|
else
|
2005-04-17 05:20:36 +07:00
|
|
|
return -EINVAL;
|
2018-01-02 17:00:57 +07:00
|
|
|
if (changed > 0) {
|
2005-04-17 05:20:36 +07:00
|
|
|
params->cmask |= 1 << var;
|
|
|
|
params->rmask |= 1 << var;
|
|
|
|
}
|
|
|
|
return changed;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2008-10-16 04:38:40 +07:00
|
|
|
* snd_pcm_hw_param_first - refine config space and return minimum value
|
2005-09-07 18:38:19 +07:00
|
|
|
* @pcm: PCM instance
|
|
|
|
* @params: the hw_params instance
|
|
|
|
* @var: parameter to retrieve
|
2008-10-16 04:38:40 +07:00
|
|
|
* @dir: pointer to the direction (-1,0,1) or %NULL
|
2005-04-17 05:20:36 +07:00
|
|
|
*
|
2008-10-16 04:38:40 +07:00
|
|
|
* Inside configuration space defined by @params remove from @var all
|
2005-04-17 05:20:36 +07:00
|
|
|
* values > minimum. Reduce configuration space accordingly.
|
2013-03-12 04:05:14 +07:00
|
|
|
*
|
|
|
|
* Return: The minimum, or a negative error code on failure.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2006-04-28 20:13:40 +07:00
|
|
|
int snd_pcm_hw_param_first(struct snd_pcm_substream *pcm,
|
|
|
|
struct snd_pcm_hw_params *params,
|
|
|
|
snd_pcm_hw_param_t var, int *dir)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
int changed = _snd_pcm_hw_param_first(params, var);
|
|
|
|
if (changed < 0)
|
|
|
|
return changed;
|
|
|
|
if (params->rmask) {
|
|
|
|
int err = snd_pcm_hw_refine(pcm, params);
|
2018-01-01 15:50:50 +07:00
|
|
|
if (err < 0)
|
2008-08-08 22:09:09 +07:00
|
|
|
return err;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
return snd_pcm_hw_param_value(params, var, dir);
|
|
|
|
}
|
2006-04-28 20:13:40 +07:00
|
|
|
EXPORT_SYMBOL(snd_pcm_hw_param_first);
|
|
|
|
|
2005-11-17 19:59:38 +07:00
|
|
|
static int _snd_pcm_hw_param_last(struct snd_pcm_hw_params *params,
|
2005-05-18 23:02:04 +07:00
|
|
|
snd_pcm_hw_param_t var)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
int changed;
|
|
|
|
if (hw_is_mask(var))
|
|
|
|
changed = snd_mask_refine_last(hw_param_mask(params, var));
|
|
|
|
else if (hw_is_interval(var))
|
|
|
|
changed = snd_interval_refine_last(hw_param_interval(params, var));
|
2006-04-28 20:13:40 +07:00
|
|
|
else
|
2005-04-17 05:20:36 +07:00
|
|
|
return -EINVAL;
|
2018-01-02 17:00:57 +07:00
|
|
|
if (changed > 0) {
|
2005-04-17 05:20:36 +07:00
|
|
|
params->cmask |= 1 << var;
|
|
|
|
params->rmask |= 1 << var;
|
|
|
|
}
|
|
|
|
return changed;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2008-10-16 04:38:40 +07:00
|
|
|
* snd_pcm_hw_param_last - refine config space and return maximum value
|
2005-09-07 18:38:19 +07:00
|
|
|
* @pcm: PCM instance
|
|
|
|
* @params: the hw_params instance
|
|
|
|
* @var: parameter to retrieve
|
2008-10-16 04:38:40 +07:00
|
|
|
* @dir: pointer to the direction (-1,0,1) or %NULL
|
2005-04-17 05:20:36 +07:00
|
|
|
*
|
2008-10-16 04:38:40 +07:00
|
|
|
* Inside configuration space defined by @params remove from @var all
|
2005-04-17 05:20:36 +07:00
|
|
|
* values < maximum. Reduce configuration space accordingly.
|
2013-03-12 04:05:14 +07:00
|
|
|
*
|
|
|
|
* Return: The maximum, or a negative error code on failure.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2006-04-28 20:13:40 +07:00
|
|
|
int snd_pcm_hw_param_last(struct snd_pcm_substream *pcm,
|
|
|
|
struct snd_pcm_hw_params *params,
|
|
|
|
snd_pcm_hw_param_t var, int *dir)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
int changed = _snd_pcm_hw_param_last(params, var);
|
|
|
|
if (changed < 0)
|
|
|
|
return changed;
|
|
|
|
if (params->rmask) {
|
|
|
|
int err = snd_pcm_hw_refine(pcm, params);
|
2018-01-01 15:50:50 +07:00
|
|
|
if (err < 0)
|
2008-08-08 22:09:09 +07:00
|
|
|
return err;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
return snd_pcm_hw_param_value(params, var, dir);
|
|
|
|
}
|
2006-04-28 20:13:40 +07:00
|
|
|
EXPORT_SYMBOL(snd_pcm_hw_param_last);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2005-11-17 19:59:38 +07:00
|
|
|
static int snd_pcm_lib_ioctl_reset(struct snd_pcm_substream *substream,
|
2005-04-17 05:20:36 +07:00
|
|
|
void *arg)
|
|
|
|
{
|
2005-11-17 19:59:38 +07:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned long flags;
|
|
|
|
snd_pcm_stream_lock_irqsave(substream, flags);
|
|
|
|
if (snd_pcm_running(substream) &&
|
|
|
|
snd_pcm_update_hw_ptr(substream) >= 0)
|
|
|
|
runtime->status->hw_ptr %= runtime->buffer_size;
|
2012-10-23 04:42:14 +07:00
|
|
|
else {
|
2005-04-17 05:20:36 +07:00
|
|
|
runtime->status->hw_ptr = 0;
|
2012-10-23 04:42:14 +07:00
|
|
|
runtime->hw_ptr_wrap = 0;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
snd_pcm_stream_unlock_irqrestore(substream, flags);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-11-17 19:59:38 +07:00
|
|
|
static int snd_pcm_lib_ioctl_channel_info(struct snd_pcm_substream *substream,
|
2005-04-17 05:20:36 +07:00
|
|
|
void *arg)
|
|
|
|
{
|
2005-11-17 19:59:38 +07:00
|
|
|
struct snd_pcm_channel_info *info = arg;
|
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-04-17 05:20:36 +07:00
|
|
|
int width;
|
|
|
|
if (!(runtime->info & SNDRV_PCM_INFO_MMAP)) {
|
|
|
|
info->offset = -1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
width = snd_pcm_format_physical_width(runtime->format);
|
|
|
|
if (width < 0)
|
|
|
|
return width;
|
|
|
|
info->offset = 0;
|
|
|
|
switch (runtime->access) {
|
|
|
|
case SNDRV_PCM_ACCESS_MMAP_INTERLEAVED:
|
|
|
|
case SNDRV_PCM_ACCESS_RW_INTERLEAVED:
|
|
|
|
info->first = info->channel * width;
|
|
|
|
info->step = runtime->channels * width;
|
|
|
|
break;
|
|
|
|
case SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED:
|
|
|
|
case SNDRV_PCM_ACCESS_RW_NONINTERLEAVED:
|
|
|
|
{
|
|
|
|
size_t size = runtime->dma_bytes / runtime->channels;
|
|
|
|
info->first = info->channel * size * 8;
|
|
|
|
info->step = width;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
snd_BUG();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-04-27 14:44:40 +07:00
|
|
|
static int snd_pcm_lib_ioctl_fifo_size(struct snd_pcm_substream *substream,
|
|
|
|
void *arg)
|
|
|
|
{
|
|
|
|
struct snd_pcm_hw_params *params = arg;
|
|
|
|
snd_pcm_format_t format;
|
2014-09-22 03:50:57 +07:00
|
|
|
int channels;
|
|
|
|
ssize_t frame_size;
|
2009-04-27 14:44:40 +07:00
|
|
|
|
|
|
|
params->fifo_size = substream->runtime->hw.fifo_size;
|
|
|
|
if (!(substream->runtime->hw.info & SNDRV_PCM_INFO_FIFO_IN_FRAMES)) {
|
|
|
|
format = params_format(params);
|
|
|
|
channels = params_channels(params);
|
2014-09-22 03:50:57 +07:00
|
|
|
frame_size = snd_pcm_format_size(format, channels);
|
|
|
|
if (frame_size > 0)
|
|
|
|
params->fifo_size /= (unsigned)frame_size;
|
2009-04-27 14:44:40 +07:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/**
|
|
|
|
* snd_pcm_lib_ioctl - a generic PCM ioctl callback
|
|
|
|
* @substream: the pcm substream instance
|
|
|
|
* @cmd: ioctl command
|
|
|
|
* @arg: ioctl argument
|
|
|
|
*
|
|
|
|
* Processes the generic ioctl commands for PCM.
|
|
|
|
* Can be passed as the ioctl callback for PCM ops.
|
|
|
|
*
|
2013-03-12 04:05:14 +07:00
|
|
|
* Return: Zero if successful, or a negative error code on failure.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2005-11-17 19:59:38 +07:00
|
|
|
int snd_pcm_lib_ioctl(struct snd_pcm_substream *substream,
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned int cmd, void *arg)
|
|
|
|
{
|
|
|
|
switch (cmd) {
|
|
|
|
case SNDRV_PCM_IOCTL1_RESET:
|
|
|
|
return snd_pcm_lib_ioctl_reset(substream, arg);
|
|
|
|
case SNDRV_PCM_IOCTL1_CHANNEL_INFO:
|
|
|
|
return snd_pcm_lib_ioctl_channel_info(substream, arg);
|
2009-04-27 14:44:40 +07:00
|
|
|
case SNDRV_PCM_IOCTL1_FIFO_SIZE:
|
|
|
|
return snd_pcm_lib_ioctl_fifo_size(substream, arg);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
return -ENXIO;
|
|
|
|
}
|
2006-04-28 20:13:40 +07:00
|
|
|
EXPORT_SYMBOL(snd_pcm_lib_ioctl);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/**
|
|
|
|
* snd_pcm_period_elapsed - update the pcm status for the next period
|
|
|
|
* @substream: the pcm substream instance
|
|
|
|
*
|
|
|
|
* This function is called from the interrupt handler when the
|
|
|
|
* PCM has processed the period size. It will update the current
|
2008-01-09 00:09:57 +07:00
|
|
|
* pointer, wake up sleepers, etc.
|
2005-04-17 05:20:36 +07:00
|
|
|
*
|
|
|
|
* Even if more than one periods have elapsed since the last call, you
|
|
|
|
* have to call this only once.
|
|
|
|
*/
|
2005-11-17 19:59:38 +07:00
|
|
|
void snd_pcm_period_elapsed(struct snd_pcm_substream *substream)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:59:38 +07:00
|
|
|
struct snd_pcm_runtime *runtime;
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned long flags;
|
|
|
|
|
2008-08-08 22:09:09 +07:00
|
|
|
if (PCM_RUNTIME_CHECK(substream))
|
|
|
|
return;
|
2005-04-17 05:20:36 +07:00
|
|
|
runtime = substream->runtime;
|
|
|
|
|
|
|
|
snd_pcm_stream_lock_irqsave(substream, flags);
|
|
|
|
if (!snd_pcm_running(substream) ||
|
2010-01-05 23:19:34 +07:00
|
|
|
snd_pcm_update_hw_ptr0(substream, 1) < 0)
|
2005-04-17 05:20:36 +07:00
|
|
|
goto _end;
|
|
|
|
|
2015-10-16 16:57:46 +07:00
|
|
|
#ifdef CONFIG_SND_PCM_TIMER
|
2005-04-17 05:20:36 +07:00
|
|
|
if (substream->timer_running)
|
|
|
|
snd_timer_interrupt(substream->timer, 1);
|
2015-10-16 16:57:46 +07:00
|
|
|
#endif
|
2005-04-17 05:20:36 +07:00
|
|
|
_end:
|
|
|
|
kill_fasync(&runtime->fasync, SIGIO, POLL_IN);
|
2016-04-14 23:02:37 +07:00
|
|
|
snd_pcm_stream_unlock_irqrestore(substream, flags);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2006-04-28 20:13:40 +07:00
|
|
|
EXPORT_SYMBOL(snd_pcm_period_elapsed);
|
|
|
|
|
2008-01-09 00:08:14 +07:00
|
|
|
/*
|
|
|
|
* Wait until avail_min data becomes available
|
|
|
|
* Returns a negative error code if any error occurs during operation.
|
|
|
|
* The available space is stored on availp. When err = 0 and avail = 0
|
|
|
|
* on the capture stream, it indicates the stream is in DRAINING state.
|
|
|
|
*/
|
2010-06-27 05:13:20 +07:00
|
|
|
static int wait_for_avail(struct snd_pcm_substream *substream,
|
2008-01-09 00:08:14 +07:00
|
|
|
snd_pcm_uframes_t *availp)
|
|
|
|
{
|
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
|
|
int is_playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
2017-06-20 17:06:13 +07:00
|
|
|
wait_queue_entry_t wait;
|
2008-01-09 00:08:14 +07:00
|
|
|
int err = 0;
|
|
|
|
snd_pcm_uframes_t avail = 0;
|
2011-05-26 13:09:38 +07:00
|
|
|
long wait_time, tout;
|
|
|
|
|
2011-09-15 13:49:25 +07:00
|
|
|
init_waitqueue_entry(&wait, current);
|
|
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
|
|
add_wait_queue(&runtime->tsleep, &wait);
|
|
|
|
|
2011-05-26 13:09:38 +07:00
|
|
|
if (runtime->no_period_wakeup)
|
|
|
|
wait_time = MAX_SCHEDULE_TIMEOUT;
|
|
|
|
else {
|
2018-07-06 19:50:36 +07:00
|
|
|
/* use wait time from substream if available */
|
|
|
|
if (substream->wait_time) {
|
|
|
|
wait_time = substream->wait_time;
|
|
|
|
} 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);
|
2011-05-26 13:09:38 +07:00
|
|
|
}
|
|
|
|
}
|
2011-09-15 13:49:25 +07:00
|
|
|
|
2008-01-09 00:08:14 +07:00
|
|
|
for (;;) {
|
|
|
|
if (signal_pending(current)) {
|
|
|
|
err = -ERESTARTSYS;
|
|
|
|
break;
|
|
|
|
}
|
2011-09-15 13:49:25 +07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2018-04-11 22:56:52 +07:00
|
|
|
avail = snd_pcm_avail(substream);
|
2011-09-15 13:49:25 +07:00
|
|
|
if (avail >= runtime->twake)
|
|
|
|
break;
|
2008-01-09 00:08:14 +07:00
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
2011-09-15 13:49:25 +07:00
|
|
|
|
|
|
|
tout = schedule_timeout(wait_time);
|
|
|
|
|
2008-01-09 00:08:14 +07:00
|
|
|
snd_pcm_stream_lock_irq(substream);
|
2011-09-15 13:49:25 +07:00
|
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
2008-01-09 00:08:14 +07:00
|
|
|
switch (runtime->status->state) {
|
|
|
|
case SNDRV_PCM_STATE_SUSPENDED:
|
|
|
|
err = -ESTRPIPE;
|
|
|
|
goto _endloop;
|
|
|
|
case SNDRV_PCM_STATE_XRUN:
|
|
|
|
err = -EPIPE;
|
|
|
|
goto _endloop;
|
|
|
|
case SNDRV_PCM_STATE_DRAINING:
|
|
|
|
if (is_playback)
|
|
|
|
err = -EPIPE;
|
|
|
|
else
|
|
|
|
avail = 0; /* indicate draining */
|
|
|
|
goto _endloop;
|
|
|
|
case SNDRV_PCM_STATE_OPEN:
|
|
|
|
case SNDRV_PCM_STATE_SETUP:
|
|
|
|
case SNDRV_PCM_STATE_DISCONNECTED:
|
|
|
|
err = -EBADFD;
|
|
|
|
goto _endloop;
|
2013-12-17 21:02:24 +07:00
|
|
|
case SNDRV_PCM_STATE_PAUSED:
|
|
|
|
continue;
|
2008-01-09 00:08:14 +07:00
|
|
|
}
|
|
|
|
if (!tout) {
|
2014-02-05 00:19:48 +07:00
|
|
|
pcm_dbg(substream->pcm,
|
|
|
|
"%s write error (DMA or IRQ trouble?)\n",
|
|
|
|
is_playback ? "playback" : "capture");
|
2008-01-09 00:08:14 +07:00
|
|
|
err = -EIO;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_endloop:
|
2011-09-15 13:49:25 +07:00
|
|
|
set_current_state(TASK_RUNNING);
|
2010-01-21 16:32:15 +07:00
|
|
|
remove_wait_queue(&runtime->tsleep, &wait);
|
2008-01-09 00:08:14 +07:00
|
|
|
*availp = avail;
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2017-05-24 23:15:26 +07:00
|
|
|
typedef int (*pcm_transfer_f)(struct snd_pcm_substream *substream,
|
|
|
|
int channel, unsigned long hwoff,
|
|
|
|
void *buf, unsigned long bytes);
|
2017-05-24 22:59:17 +07:00
|
|
|
|
2017-05-24 23:15:26 +07:00
|
|
|
typedef int (*pcm_copy_f)(struct snd_pcm_substream *, snd_pcm_uframes_t, void *,
|
|
|
|
snd_pcm_uframes_t, snd_pcm_uframes_t, pcm_transfer_f);
|
|
|
|
|
|
|
|
/* calculate the target DMA-buffer position to be written/read */
|
|
|
|
static void *get_dma_ptr(struct snd_pcm_runtime *runtime,
|
|
|
|
int channel, unsigned long hwoff)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2017-05-24 23:15:26 +07:00
|
|
|
return runtime->dma_area + hwoff +
|
|
|
|
channel * (runtime->dma_bytes / runtime->channels);
|
|
|
|
}
|
|
|
|
|
2017-05-25 03:36:23 +07:00
|
|
|
/* default copy_user ops for write; used for both interleaved and non- modes */
|
|
|
|
static int default_write_copy(struct snd_pcm_substream *substream,
|
|
|
|
int channel, unsigned long hwoff,
|
|
|
|
void *buf, unsigned long bytes)
|
2017-05-24 23:15:26 +07:00
|
|
|
{
|
|
|
|
if (copy_from_user(get_dma_ptr(substream->runtime, channel, hwoff),
|
2017-05-25 03:36:23 +07:00
|
|
|
(void __user *)buf, bytes))
|
2017-05-24 23:15:26 +07:00
|
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-05-24 23:23:20 +07:00
|
|
|
/* default copy_kernel ops for write */
|
|
|
|
static int default_write_copy_kernel(struct snd_pcm_substream *substream,
|
|
|
|
int channel, unsigned long hwoff,
|
|
|
|
void *buf, unsigned long bytes)
|
|
|
|
{
|
|
|
|
memcpy(get_dma_ptr(substream->runtime, channel, hwoff), buf, bytes);
|
2005-04-17 05:20:36 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-05-24 23:15:26 +07:00
|
|
|
/* fill silence instead of copy data; called as a transfer helper
|
|
|
|
* from __snd_pcm_lib_write() or directly from noninterleaved_copy() when
|
|
|
|
* a NULL buffer is passed
|
|
|
|
*/
|
|
|
|
static int fill_silence(struct snd_pcm_substream *substream, int channel,
|
|
|
|
unsigned long hwoff, void *buf, unsigned long bytes)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:59:38 +07:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2017-05-24 23:15:26 +07:00
|
|
|
if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)
|
2005-04-17 05:20:36 +07:00
|
|
|
return 0;
|
2017-05-24 23:15:26 +07:00
|
|
|
if (substream->ops->fill_silence)
|
|
|
|
return substream->ops->fill_silence(substream, channel,
|
|
|
|
hwoff, bytes);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2017-05-24 23:15:26 +07:00
|
|
|
snd_pcm_format_set_silence(runtime->format,
|
|
|
|
get_dma_ptr(runtime, channel, hwoff),
|
|
|
|
bytes_to_samples(runtime, bytes));
|
2005-04-17 05:20:36 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-05-25 03:36:23 +07:00
|
|
|
/* default copy_user ops for read; used for both interleaved and non- modes */
|
|
|
|
static int default_read_copy(struct snd_pcm_substream *substream,
|
|
|
|
int channel, unsigned long hwoff,
|
|
|
|
void *buf, unsigned long bytes)
|
|
|
|
{
|
|
|
|
if (copy_to_user((void __user *)buf,
|
|
|
|
get_dma_ptr(substream->runtime, channel, hwoff),
|
|
|
|
bytes))
|
|
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2017-05-24 23:23:20 +07:00
|
|
|
/* default copy_kernel ops for read */
|
|
|
|
static int default_read_copy_kernel(struct snd_pcm_substream *substream,
|
|
|
|
int channel, unsigned long hwoff,
|
|
|
|
void *buf, unsigned long bytes)
|
|
|
|
{
|
|
|
|
memcpy(buf, get_dma_ptr(substream->runtime, channel, hwoff), bytes);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-05-24 23:15:26 +07:00
|
|
|
/* call transfer function with the converted pointers and sizes;
|
|
|
|
* for interleaved mode, it's one shot for all samples
|
|
|
|
*/
|
|
|
|
static int interleaved_copy(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_uframes_t hwoff, void *data,
|
|
|
|
snd_pcm_uframes_t off,
|
|
|
|
snd_pcm_uframes_t frames,
|
|
|
|
pcm_transfer_f transfer)
|
|
|
|
{
|
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
|
|
|
|
|
|
/* convert to bytes */
|
|
|
|
hwoff = frames_to_bytes(runtime, hwoff);
|
|
|
|
off = frames_to_bytes(runtime, off);
|
|
|
|
frames = frames_to_bytes(runtime, frames);
|
|
|
|
return transfer(substream, 0, hwoff, data + off, frames);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* call transfer function with the converted pointers and sizes for each
|
|
|
|
* non-interleaved channel; when buffer is NULL, silencing instead of copying
|
|
|
|
*/
|
|
|
|
static int noninterleaved_copy(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_uframes_t hwoff, void *data,
|
|
|
|
snd_pcm_uframes_t off,
|
|
|
|
snd_pcm_uframes_t frames,
|
|
|
|
pcm_transfer_f transfer)
|
2017-05-24 22:59:17 +07:00
|
|
|
{
|
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
|
|
int channels = runtime->channels;
|
2017-05-24 23:15:26 +07:00
|
|
|
void **bufs = data;
|
|
|
|
int c, err;
|
|
|
|
|
|
|
|
/* convert to bytes; note that it's not frames_to_bytes() here.
|
|
|
|
* in non-interleaved mode, we copy for each channel, thus
|
|
|
|
* each copy is n_samples bytes x channels = whole frames.
|
|
|
|
*/
|
|
|
|
off = samples_to_bytes(runtime, off);
|
|
|
|
frames = samples_to_bytes(runtime, frames);
|
|
|
|
hwoff = samples_to_bytes(runtime, hwoff);
|
|
|
|
for (c = 0; c < channels; ++c, ++bufs) {
|
|
|
|
if (!data || !*bufs)
|
|
|
|
err = fill_silence(substream, c, hwoff, NULL, frames);
|
|
|
|
else
|
|
|
|
err = transfer(substream, c, hwoff, *bufs + off,
|
|
|
|
frames);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2017-05-24 22:59:17 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-05-24 23:18:15 +07:00
|
|
|
/* fill silence on the given buffer position;
|
|
|
|
* called from snd_pcm_playback_silence()
|
|
|
|
*/
|
|
|
|
static int fill_silence_frames(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_uframes_t off, snd_pcm_uframes_t frames)
|
|
|
|
{
|
|
|
|
if (substream->runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED ||
|
|
|
|
substream->runtime->access == SNDRV_PCM_ACCESS_MMAP_INTERLEAVED)
|
|
|
|
return interleaved_copy(substream, off, NULL, 0, frames,
|
|
|
|
fill_silence);
|
|
|
|
else
|
|
|
|
return noninterleaved_copy(substream, off, NULL, 0, frames,
|
|
|
|
fill_silence);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2008-08-08 22:09:09 +07:00
|
|
|
/* sanity-check for read/write methods */
|
|
|
|
static int pcm_sanity_check(struct snd_pcm_substream *substream)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:59:38 +07:00
|
|
|
struct snd_pcm_runtime *runtime;
|
2008-08-08 22:09:09 +07:00
|
|
|
if (PCM_RUNTIME_CHECK(substream))
|
|
|
|
return -ENXIO;
|
2005-04-17 05:20:36 +07:00
|
|
|
runtime = substream->runtime;
|
2017-05-24 22:59:17 +07:00
|
|
|
if (snd_BUG_ON(!substream->ops->copy_user && !runtime->dma_area))
|
2008-08-08 22:09:09 +07:00
|
|
|
return -EINVAL;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
|
|
|
|
return -EBADFD;
|
2008-08-08 22:09:09 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-05-24 22:51:30 +07:00
|
|
|
static int pcm_accessible_state(struct snd_pcm_runtime *runtime)
|
2008-08-08 22:09:09 +07:00
|
|
|
{
|
2017-05-24 22:51:30 +07:00
|
|
|
switch (runtime->status->state) {
|
|
|
|
case SNDRV_PCM_STATE_PREPARED:
|
|
|
|
case SNDRV_PCM_STATE_RUNNING:
|
|
|
|
case SNDRV_PCM_STATE_PAUSED:
|
|
|
|
return 0;
|
|
|
|
case SNDRV_PCM_STATE_XRUN:
|
|
|
|
return -EPIPE;
|
|
|
|
case SNDRV_PCM_STATE_SUSPENDED:
|
|
|
|
return -ESTRPIPE;
|
|
|
|
default:
|
|
|
|
return -EBADFD;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2017-06-12 07:41:44 +07:00
|
|
|
/* update to the given appl_ptr and call ack callback if needed;
|
|
|
|
* when an error is returned, take back to the original value
|
|
|
|
*/
|
|
|
|
int pcm_lib_apply_appl_ptr(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_uframes_t appl_ptr)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:59:38 +07:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2017-06-12 07:41:44 +07:00
|
|
|
snd_pcm_uframes_t old_appl_ptr = runtime->control->appl_ptr;
|
|
|
|
int ret;
|
|
|
|
|
2017-06-13 20:57:28 +07:00
|
|
|
if (old_appl_ptr == appl_ptr)
|
|
|
|
return 0;
|
|
|
|
|
2017-06-12 07:41:44 +07:00
|
|
|
runtime->control->appl_ptr = appl_ptr;
|
|
|
|
if (substream->ops->ack) {
|
|
|
|
ret = substream->ops->ack(substream);
|
|
|
|
if (ret < 0) {
|
|
|
|
runtime->control->appl_ptr = old_appl_ptr;
|
|
|
|
return ret;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
}
|
ALSA: pcm: add 'applptr' event of tracepoint
In design of ALSA PCM core, status and control data for runtime of ALSA
PCM substream are shared between kernel/user spaces by page frame
mapping with read-only attribute. Both of hardware-side and
application-side position on PCM buffer are maintained as a part of
the status data. In a view of ALSA PCM application, these two positions
can be updated by executing ioctl(2) with some commands.
There's an event of tracepoint for hardware-side position; 'hwptr'.
On the other hand, no events for application-side position. This commit
adds a new event for this purpose; 'applptr'. When the application-side
position is changed in kernel space, this event is probed with useful
information for developers.
I note that the event is not probed for all of ALSA PCM applications, When
applications are written by read/write programming scenario, the event is
surely probed. The applications execute ioctl(2) with
SNDRV_PCM_IOCTL_[READ|WRITE][N/I]_FRAMES to read/write any PCM frame, then
ALSA PCM core updates the application-side position in kernel land.
However, when applications are written by mmap programming scenario, if
maintaining the application side position in kernel space accurately,
applications should voluntarily execute ioctl(2) with
SNDRV_PCM_IOCTL_SYNC_PTR to commit the number of handled PCM frames. If
not voluntarily, the application-side position is not changed, thus the
added event is not probed.
There's a loophole, using architectures to which ALSA PCM core judges
non cache coherent. In this case, the status and control data is not mapped
into processe's VMA for any applications. Userland library, alsa-lib, is
programmed for this case. It executes ioctl(2) with
SNDRV_PCM_IOCTL_SYNC_PTR command every time to requiring the status and
control data.
ARM is such an architecture. Below is an example with serial sound interface
(ssi) on i.mx6 quad core SoC. I use v4.1 kernel released by fsl-community
with patches from VIA Tech. Inc. for VAB820, and my backport patches for
relevant features for this patchset. I use Ubuntu 17.04 from
ports.ubuntu.com as user land for armhf architecture.
$ aplay -v -M -D hw:imx6vab820sgtl5,0 /dev/urandom -f S16_LE -r 48000 --period-size=128 --buffer-size=256
Playing raw data '/dev/urandom' : Signed 16 bit Little Endian, Rate 48000 Hz, Mono
Hardware PCM card 0 'imx6-vab820-sgtl5000' device 0 subdevice 0
Its setup is:
stream : PLAYBACK
access : MMAP_INTERLEAVED
format : S16_LE
subformat : STD
channels : 1
rate : 48000
exact rate : 48000 (48000/1)
msbits : 16
buffer_size : 256
period_size : 128
period_time : 2666
tstamp_mode : NONE
tstamp_type : MONOTONIC
period_step : 1
avail_min : 128
period_event : 0
start_threshold : 256
stop_threshold : 256
silence_threshold: 0
silence_size : 0
boundary : 1073741824
appl_ptr : 0
hw_ptr : 0
mmap_area[0] = 0x76f98000,0,16 (16)
$ trace-cmd record -e snd_pcm:hwptr -e snd_pcm:applptr
$ trace-cmd report
...
60.208495: applptr: pcmC0D0p/sub0: prev=1792, curr=1792, avail=0, period=128, buf=256
60.208633: applptr: pcmC0D0p/sub0: prev=1792, curr=1792, avail=0, period=128, buf=256
60.210022: hwptr: pcmC0D0p/sub0: IRQ: pos=128, old=1536, base=1536, period=128, buf=256
60.210202: applptr: pcmC0D0p/sub0: prev=1792, curr=1792, avail=128, period=128, buf=256
60.210344: hwptr: pcmC0D0p/sub0: POS: pos=128, old=1664, base=1536, period=128, buf=256
60.210348: applptr: pcmC0D0p/sub0: prev=1792, curr=1792, avail=128, period=128, buf=256
60.210486: applptr: pcmC0D0p/sub0: prev=1792, curr=1792, avail=128, period=128, buf=256
60.210626: applptr: pcmC0D0p/sub0: prev=1792, curr=1920, avail=0, period=128, buf=256
60.211002: applptr: pcmC0D0p/sub0: prev=1920, curr=1920, avail=0, period=128, buf=256
60.211142: hwptr: pcmC0D0p/sub0: POS: pos=128, old=1664, base=1536, period=128, buf=256
60.211146: applptr: pcmC0D0p/sub0: prev=1920, curr=1920, avail=0, period=128, buf=256
60.211287: applptr: pcmC0D0p/sub0: prev=1920, curr=1920, avail=0, period=128, buf=256
60.212690: hwptr: pcmC0D0p/sub0: IRQ: pos=0, old=1664, base=1536, period=128, buf=256
60.212866: applptr: pcmC0D0p/sub0: prev=1920, curr=1920, avail=128, period=128, buf=256
60.212999: hwptr: pcmC0D0p/sub0: POS: pos=0, old=1792, base=1792, period=128, buf=256
60.213003: applptr: pcmC0D0p/sub0: prev=1920, curr=1920, avail=128, period=128, buf=256
60.213135: applptr: pcmC0D0p/sub0: prev=1920, curr=1920, avail=128, period=128, buf=256
60.213276: applptr: pcmC0D0p/sub0: prev=1920, curr=2048, avail=0, period=128, buf=256
60.213654: applptr: pcmC0D0p/sub0: prev=2048, curr=2048, avail=0, period=128, buf=256
60.213796: hwptr: pcmC0D0p/sub0: POS: pos=0, old=1792, base=1792, period=128, buf=256
60.213800: applptr: pcmC0D0p/sub0: prev=2048, curr=2048, avail=0, period=128, buf=256
60.213937: applptr: pcmC0D0p/sub0: prev=2048, curr=2048, avail=0, period=128, buf=256
60.215356: hwptr: pcmC0D0p/sub0: IRQ: pos=128, old=1792, base=1792, period=128, buf=256
60.215542: applptr: pcmC0D0p/sub0: prev=2048, curr=2048, avail=128, period=128, buf=256
60.215679: hwptr: pcmC0D0p/sub0: POS: pos=128, old=1920, base=1792, period=128, buf=256
60.215683: applptr: pcmC0D0p/sub0: prev=2048, curr=2048, avail=128, period=128, buf=256
60.215813: applptr: pcmC0D0p/sub0: prev=2048, curr=2048, avail=128, period=128, buf=256
60.215947: applptr: pcmC0D0p/sub0: prev=2048, curr=2176, avail=0, period=128, buf=256
...
We can surely see 'applptr' event is probed even if the application run
for mmap programming scenario ('-M' option and 'hw' plugin). Below is a
result of strace:
02:44:15.886382 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.887203 poll([{fd=4, events=POLLOUT|POLLERR|POLLNVAL}], 1, -1) = 1 ([{fd=4, revents=POLLOUT}])
02:44:15.887471 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.887637 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.887805 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.887969 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.888132 read(3, "..."..., 256) = 256
02:44:15.889040 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.889221 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.889431 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.889606 poll([{fd=4, events=POLLOUT|POLLERR|POLLNVAL}], 1, -1) = 1 ([{fd=4, revents=POLLOUT}])
02:44:15.889833 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.889998 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.890164 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.891048 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.891228 read(3, "..."..., 256) = 256
02:44:15.891497 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.891661 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.891829 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
02:44:15.891991 poll([{fd=4, events=POLLOUT|POLLERR|POLLNVAL}], 1, -1) = 1 ([{fd=4, revents=POLLOUT}])
02:44:15.893007 ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x56a32b30) = 0
We can see 7 calls of ioctl(2) with SNDRV_PCM_IOCTL_SYNC_PTR per loop with
call of poll(2). 128 PCM frames are transferred per loop of one poll(2),
because the PCM substream is configured with S16_LE format and 1 channel
(2 byte * 1 * 128 = 256 bytes). This equals to the size of period of PCM
buffer. Comparing to the probed data, one of the 7 calls of ioctl(2) is
actually used to commit the number of copied PCM frames to kernel space.
The other calls are just used to check runtime status of PCM substream;
e.g. XRUN.
The tracepoint event is useful to investigate this case. I note that below
modules are related to the above sample.
* snd-soc-dummy.ko
* snd-soc-imx-sgtl5000.ko
* snd-soc-fsl-ssi.ko
* snd-soc-imx-pcm-dma.ko
* snd-soc-sgtl5000.ko
My additional note is lock acquisition. The event is probed under acquiring
PCM stream lock. This means that calculation in the event is free from
any hardware events.
Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2017-06-12 07:41:45 +07:00
|
|
|
|
|
|
|
trace_applptr(substream, old_appl_ptr, appl_ptr);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
return 0;
|
|
|
|
}
|
2017-06-12 07:41:44 +07:00
|
|
|
|
2017-05-25 03:36:23 +07:00
|
|
|
/* the common loop for read/write data */
|
|
|
|
snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream,
|
|
|
|
void *data, bool interleaved,
|
2017-05-24 23:23:20 +07:00
|
|
|
snd_pcm_uframes_t size, bool in_kernel)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:59:38 +07:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-04-17 05:20:36 +07:00
|
|
|
snd_pcm_uframes_t xfer = 0;
|
|
|
|
snd_pcm_uframes_t offset = 0;
|
2012-05-11 22:50:49 +07:00
|
|
|
snd_pcm_uframes_t avail;
|
2017-05-24 23:15:26 +07:00
|
|
|
pcm_copy_f writer;
|
|
|
|
pcm_transfer_f transfer;
|
2017-05-21 14:35:21 +07:00
|
|
|
bool nonblock;
|
2017-05-25 03:36:23 +07:00
|
|
|
bool is_playback;
|
2008-08-08 22:09:09 +07:00
|
|
|
int err;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2008-08-08 22:09:09 +07:00
|
|
|
err = pcm_sanity_check(substream);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2006-04-28 20:13:40 +07:00
|
|
|
|
2017-05-25 03:36:23 +07:00
|
|
|
is_playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
2017-05-21 14:35:21 +07:00
|
|
|
if (interleaved) {
|
|
|
|
if (runtime->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED &&
|
|
|
|
runtime->channels > 1)
|
|
|
|
return -EINVAL;
|
2017-05-24 23:15:26 +07:00
|
|
|
writer = interleaved_copy;
|
2005-04-17 05:20:36 +07:00
|
|
|
} else {
|
2017-05-21 14:35:21 +07:00
|
|
|
if (runtime->access != SNDRV_PCM_ACCESS_RW_NONINTERLEAVED)
|
|
|
|
return -EINVAL;
|
2017-05-24 23:15:26 +07:00
|
|
|
writer = noninterleaved_copy;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2017-05-24 23:15:26 +07:00
|
|
|
if (!data) {
|
2017-05-25 03:36:23 +07:00
|
|
|
if (is_playback)
|
|
|
|
transfer = fill_silence;
|
|
|
|
else
|
|
|
|
return -EINVAL;
|
2017-05-24 23:23:20 +07:00
|
|
|
} else if (in_kernel) {
|
|
|
|
if (substream->ops->copy_kernel)
|
|
|
|
transfer = substream->ops->copy_kernel;
|
|
|
|
else
|
|
|
|
transfer = is_playback ?
|
|
|
|
default_write_copy_kernel : default_read_copy_kernel;
|
2017-05-24 23:15:26 +07:00
|
|
|
} else {
|
|
|
|
if (substream->ops->copy_user)
|
|
|
|
transfer = (pcm_transfer_f)substream->ops->copy_user;
|
|
|
|
else
|
2017-05-25 03:36:23 +07:00
|
|
|
transfer = is_playback ?
|
|
|
|
default_write_copy : default_read_copy;
|
2017-05-21 14:35:21 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (size == 0)
|
|
|
|
return 0;
|
|
|
|
|
2017-05-21 14:35:21 +07:00
|
|
|
nonblock = !!(substream->f_flags & O_NONBLOCK);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
snd_pcm_stream_lock_irq(substream);
|
2017-05-24 22:51:30 +07:00
|
|
|
err = pcm_accessible_state(runtime);
|
|
|
|
if (err < 0)
|
2005-04-17 05:20:36 +07:00
|
|
|
goto _end_unlock;
|
|
|
|
|
2018-09-08 02:58:54 +07:00
|
|
|
runtime->twake = runtime->control->avail_min ? : 1;
|
|
|
|
if (runtime->status->state == SNDRV_PCM_STATE_RUNNING)
|
|
|
|
snd_pcm_update_hw_ptr(substream);
|
|
|
|
|
2019-02-13 09:57:51 +07:00
|
|
|
/*
|
|
|
|
* If size < start_threshold, wait indefinitely. Another
|
|
|
|
* thread may start capture
|
|
|
|
*/
|
2017-05-25 03:36:23 +07:00
|
|
|
if (!is_playback &&
|
2019-02-06 13:30:44 +07:00
|
|
|
runtime->status->state == SNDRV_PCM_STATE_PREPARED &&
|
|
|
|
size >= runtime->start_threshold) {
|
|
|
|
err = snd_pcm_start(substream);
|
|
|
|
if (err < 0)
|
2017-05-24 22:51:30 +07:00
|
|
|
goto _end_unlock;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2018-04-11 22:56:52 +07:00
|
|
|
avail = snd_pcm_avail(substream);
|
2018-09-08 02:58:54 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
while (size > 0) {
|
|
|
|
snd_pcm_uframes_t frames, appl_ptr, appl_ofs;
|
|
|
|
snd_pcm_uframes_t cont;
|
2008-01-09 00:08:14 +07:00
|
|
|
if (!avail) {
|
2017-05-25 03:36:23 +07:00
|
|
|
if (!is_playback &&
|
|
|
|
runtime->status->state == SNDRV_PCM_STATE_DRAINING) {
|
2008-01-09 00:08:14 +07:00
|
|
|
snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
|
2005-04-17 05:20:36 +07:00
|
|
|
goto _end_unlock;
|
|
|
|
}
|
|
|
|
if (nonblock) {
|
|
|
|
err = -EAGAIN;
|
|
|
|
goto _end_unlock;
|
|
|
|
}
|
2010-06-27 05:13:20 +07:00
|
|
|
runtime->twake = min_t(snd_pcm_uframes_t, size,
|
|
|
|
runtime->control->avail_min ? : 1);
|
|
|
|
err = wait_for_avail(substream, &avail);
|
2008-01-09 00:08:14 +07:00
|
|
|
if (err < 0)
|
2005-08-10 16:18:19 +07:00
|
|
|
goto _end_unlock;
|
2008-01-09 00:08:14 +07:00
|
|
|
if (!avail)
|
|
|
|
continue; /* draining */
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
frames = size > avail ? avail : size;
|
2017-06-17 03:29:55 +07:00
|
|
|
appl_ptr = READ_ONCE(runtime->control->appl_ptr);
|
|
|
|
appl_ofs = appl_ptr % runtime->buffer_size;
|
|
|
|
cont = runtime->buffer_size - appl_ofs;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (frames > cont)
|
|
|
|
frames = cont;
|
2008-08-08 22:09:09 +07:00
|
|
|
if (snd_BUG_ON(!frames)) {
|
2019-01-25 23:31:59 +07:00
|
|
|
err = -EINVAL;
|
|
|
|
goto _end_unlock;
|
2008-08-08 22:09:09 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
2017-05-25 03:36:23 +07:00
|
|
|
err = writer(substream, appl_ofs, data, offset, frames,
|
2017-05-24 23:15:26 +07:00
|
|
|
transfer);
|
2005-04-17 05:20:36 +07:00
|
|
|
snd_pcm_stream_lock_irq(substream);
|
2010-01-07 21:36:31 +07:00
|
|
|
if (err < 0)
|
|
|
|
goto _end_unlock;
|
2017-05-24 22:51:30 +07:00
|
|
|
err = pcm_accessible_state(runtime);
|
|
|
|
if (err < 0)
|
2005-04-17 05:20:36 +07:00
|
|
|
goto _end_unlock;
|
|
|
|
appl_ptr += frames;
|
|
|
|
if (appl_ptr >= runtime->boundary)
|
|
|
|
appl_ptr -= runtime->boundary;
|
2017-06-12 07:41:44 +07:00
|
|
|
err = pcm_lib_apply_appl_ptr(substream, appl_ptr);
|
|
|
|
if (err < 0)
|
|
|
|
goto _end_unlock;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
offset += frames;
|
|
|
|
size -= frames;
|
|
|
|
xfer += frames;
|
2012-05-11 22:50:49 +07:00
|
|
|
avail -= frames;
|
2017-05-25 03:36:23 +07:00
|
|
|
if (is_playback &&
|
|
|
|
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);
|
|
|
|
if (err < 0)
|
|
|
|
goto _end_unlock;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
_end_unlock:
|
2010-01-21 16:32:15 +07:00
|
|
|
runtime->twake = 0;
|
2010-01-07 21:36:31 +07:00
|
|
|
if (xfer > 0 && err >= 0)
|
|
|
|
snd_pcm_update_state(substream, runtime);
|
2005-04-17 05:20:36 +07:00
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
|
|
|
return xfer > 0 ? (snd_pcm_sframes_t)xfer : err;
|
|
|
|
}
|
2017-05-25 03:36:23 +07:00
|
|
|
EXPORT_SYMBOL(__snd_pcm_lib_xfer);
|
2012-07-27 23:27:00 +07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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,
|
2012-09-13 19:48:46 +07:00
|
|
|
.map = { SNDRV_CHMAP_MONO } },
|
2012-07-27 23:27:00 +07:00
|
|
|
{ .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,
|
2012-09-13 19:48:46 +07:00
|
|
|
.map = { SNDRV_CHMAP_MONO } },
|
2012-07-27 23:27:00 +07:00
|
|
|
{ .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;
|
|
|
|
|
2017-06-14 21:20:32 +07:00
|
|
|
if (!info->chmap)
|
2012-07-27 23:27:00 +07:00
|
|
|
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;
|
|
|
|
|
2017-06-14 21:20:32 +07:00
|
|
|
if (!info->chmap)
|
2012-07-27 23:27:00 +07:00
|
|
|
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).
|
2013-03-12 04:05:14 +07:00
|
|
|
* Return: Zero if successful, or a negative error value.
|
2012-07-27 23:27:00 +07:00
|
|
|
*/
|
|
|
|
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;
|
|
|
|
|
2016-05-10 21:07:40 +07:00
|
|
|
if (WARN_ON(pcm->streams[stream].chmap_kctl))
|
|
|
|
return -EBUSY;
|
2012-07-27 23:27:00 +07:00
|
|
|
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);
|